diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs index bfe367790a..c9aebf7ba3 100644 --- a/Content.Benchmarks/ComponentQueryBenchmark.cs +++ b/Content.Benchmarks/ComponentQueryBenchmark.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -44,7 +45,7 @@ public class ComponentQueryBenchmark ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(typeof(QueryBenchSystem).Assembly); - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult(); _entMan = _pair.Server.ResolveDependency(); _itemQuery = _entMan.GetEntityQuery(); diff --git a/Content.Benchmarks/DeltaPressureBenchmark.cs b/Content.Benchmarks/DeltaPressureBenchmark.cs index 8d4929c47f..dac79e0376 100644 --- a/Content.Benchmarks/DeltaPressureBenchmark.cs +++ b/Content.Benchmarks/DeltaPressureBenchmark.cs @@ -1,3 +1,4 @@ +using System.IO; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; @@ -68,7 +69,7 @@ public class DeltaPressureBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); var server = _pair.Server; var mapdata = await _pair.CreateTestMap(); diff --git a/Content.Benchmarks/DestructibleBenchmark.cs b/Content.Benchmarks/DestructibleBenchmark.cs index aa759c35fc..58c834c0ae 100644 --- a/Content.Benchmarks/DestructibleBenchmark.cs +++ b/Content.Benchmarks/DestructibleBenchmark.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; @@ -69,7 +70,7 @@ public class DestructibleBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); var server = _pair.Server; _entMan = server.ResolveDependency(); diff --git a/Content.Benchmarks/DeviceNetworkingBenchmark.cs b/Content.Benchmarks/DeviceNetworkingBenchmark.cs index bb2a22312e..fcde4feb64 100644 --- a/Content.Benchmarks/DeviceNetworkingBenchmark.cs +++ b/Content.Benchmarks/DeviceNetworkingBenchmark.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; @@ -60,7 +61,7 @@ public class DeviceNetworkingBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); var server = _pair.Server; await server.WaitPost(() => diff --git a/Content.Benchmarks/GasReactionBenchmark.cs b/Content.Benchmarks/GasReactionBenchmark.cs index 9ed30373d1..f298869366 100644 --- a/Content.Benchmarks/GasReactionBenchmark.cs +++ b/Content.Benchmarks/GasReactionBenchmark.cs @@ -1,3 +1,4 @@ +using System.IO; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; @@ -51,7 +52,7 @@ public class GasReactionBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); var server = _pair.Server; // Create test map and grid diff --git a/Content.Benchmarks/HeatCapacityBenchmark.cs b/Content.Benchmarks/HeatCapacityBenchmark.cs index cef5bc10c7..18366306d7 100644 --- a/Content.Benchmarks/HeatCapacityBenchmark.cs +++ b/Content.Benchmarks/HeatCapacityBenchmark.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.IO; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; @@ -27,7 +28,7 @@ public class HeatCapacityBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); await _pair.Connect(); _cEntMan = _pair.Client.ResolveDependency(); _sEntMan = _pair.Server.ResolveDependency(); diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 261d408aac..9ea95c84b6 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -29,7 +30,7 @@ public class MapLoadBenchmark ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult(); var server = _pair.Server; Paths = server.ResolveDependency() diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index 51a013539e..af1ec8d9ef 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.IO; using System.Linq; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -50,7 +51,7 @@ public class PvsBenchmark #endif PoolManager.Startup(); - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult(); _entMan = _pair.Server.ResolveDependency(); _pair.Server.CfgMan.SetCVar(CVars.NetPVS, true); _pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0); diff --git a/Content.Benchmarks/RaiseEventBenchmark.cs b/Content.Benchmarks/RaiseEventBenchmark.cs index e3d377ccb3..99e032d0b5 100644 --- a/Content.Benchmarks/RaiseEventBenchmark.cs +++ b/Content.Benchmarks/RaiseEventBenchmark.cs @@ -1,4 +1,5 @@ #nullable enable +using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -21,7 +22,7 @@ public class RaiseEventBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(typeof(BenchSystem).Assembly); - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult(); var entMan = _pair.Server.EntMan; var fact = _pair.Server.ResolveDependency(); var bus = (EntityEventBus)entMan.EventBus; diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index 9b878eac40..b6d5427480 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.IO; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; @@ -36,7 +37,7 @@ public class SpawnEquipDeleteBenchmark { ProgramShared.PathOffset = "../../../../"; PoolManager.Startup(); - _pair = await PoolManager.GetServerClient(); + _pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)); var server = _pair.Server; var mapData = await _pair.CreateTestMap(); diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index 81b1c087d8..31f65dc78d 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -65,7 +65,7 @@ namespace Content.Client.Access.UI _window?.UpdateState(castState); } - public void SubmitData(string newFullName, string newJobTitle, List> newAccessList, ProtoId newJobPrototype) + public void SubmitData(string newFullName, string newJobTitle, List> newAccessList, ProtoId? newJobPrototype) { SendMessage(new WriteToTargetIdMessage( newFullName, diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index bb44ae2615..2169277f0d 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -216,7 +216,7 @@ namespace Content.Client.Access.UI JobTitleLineEdit.Text, // Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair _accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(), - jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty); + jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : null); } } } diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs index f0e4b13356..e192a66fcc 100644 --- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -1,6 +1,7 @@ using Content.Client.Stylesheets; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.Atmos.Monitor; using Content.Shared.FixedPoint; using Content.Shared.Temperature; @@ -22,6 +23,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer private readonly IEntityManager _entManager; private readonly IResourceCache _cache; + private readonly SharedAtmosphereSystem _atmosphere; private Dictionary _alarmStrings = new Dictionary() { @@ -37,6 +39,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer _entManager = IoCManager.Resolve(); _cache = IoCManager.Resolve(); + _atmosphere = _entManager.System(); NetEntity = uid; Coordinates = coordinates; @@ -149,7 +152,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs) { FixedPoint2 gasPercent = percent * 100f; - var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); + var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation); var gasLabel = new Label() { diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs index 0ce0c9c880..cf9a89d1a5 100644 --- a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs @@ -1,6 +1,7 @@ using Content.Client.Stylesheets; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.FixedPoint; using Content.Shared.Temperature; using Robust.Client.AutoGenerated; @@ -19,12 +20,14 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer private readonly IEntityManager _entManager; private readonly IResourceCache _cache; + private readonly SharedAtmosphereSystem _atmosphere; public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data) { RobustXamlLoader.Load(this); _entManager = IoCManager.Resolve(); _cache = IoCManager.Resolve(); + _atmosphere = _entManager.System(); Data = data; @@ -132,7 +135,7 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer var gasPercent = (FixedPoint2)0f; gasPercent = percent * 100f; - var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); + var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation); var gasLabel = new Label() { diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index d950108922..de544fba99 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -51,7 +51,7 @@ public sealed partial class AtmosphereSystem // though this isnt the hottest code path so it should be fine // the gc can eat a little as a treat var tmp = new float[moles.Length]; - NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp); + NumericsHelpers.Multiply(moles, GasMolarHeatCapacities, tmp); // Adjust heat capacity by speedup, because this is primarily what // determines how quickly gases heat up/cool. return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); diff --git a/Content.Client/Atmos/EntitySystems/GasTileHeatBlurOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileHeatBlurOverlaySystem.cs new file mode 100644 index 0000000000..a80208620b --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasTileHeatBlurOverlaySystem.cs @@ -0,0 +1,30 @@ +using Content.Client.Atmos.Overlays; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// System responsible for rendering heat distortion using . +/// +[UsedImplicitly] +public sealed class GasTileHeatBlurOverlaySystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private GasTileHeatBlurOverlay _gasTileHeatBlurOverlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _gasTileHeatBlurOverlay = new GasTileHeatBlurOverlay(); + _overlayMan.AddOverlay(_gasTileHeatBlurOverlay); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMan.RemoveOverlay(); + } +} diff --git a/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs b/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs new file mode 100644 index 0000000000..d849319875 --- /dev/null +++ b/Content.Client/Atmos/Overlays/GasTileHeatBlurOverlay.cs @@ -0,0 +1,263 @@ +using Content.Client.Atmos.EntitySystems; +using Content.Client.Graphics; +using Content.Client.Resources; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; +using Content.Shared.CCVar; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using System.Numerics; +using Color = Robust.Shared.Maths.Color; +using Texture = Robust.Client.Graphics.Texture; + +namespace Content.Client.Atmos.Overlays; + +/// +/// Overlay responsible for rendering heat distortion shader. +/// +public sealed class GasTileHeatBlurOverlay : Overlay +{ + public override bool RequestScreenTexture { get; set; } = true; + private static readonly ProtoId UnshadedShader = "unshaded"; + private static readonly ProtoId HeatOverlayShader = "HeatBlur"; + + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + private readonly SharedTransformSystem _xformSys; + private readonly ShaderInstance _shader; + + private readonly Texture _noiseTexture; + private readonly Texture _heatGradientTexture; + private List> _intersectingGrids = new(); + private readonly OverlayResourceCache _resources = new(); + + // Overlay settings + private const float + ShaderSpilling = 2.5f; // for example 4f - spills shader one tile from hotspot, 2.5f - spills it half tile + + private const float ShaderStrength = 0.04f; // Makes waves stronger + private const float ShaderScale = 1f; // Makes more waves + private const float ShaderSpeed = 0.4f; // Makes waves run faster + + // Overlay settings for reduced motion setting + private const float ShaderStrengthForReducedMotion = 0.01f; + private const float ShaderScaleReducedMotion = 0.5f; + private const float ShaderSpeedReducedMotion = 0.25f; + + private const int MinDistortionTemp = 300; // Distortion starts to show up at this temperature in Kelvins + private const int MaxDistortionTemp = 2000; // Maximum distortion strength at this temperature in Kelvins + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + public GasTileHeatBlurOverlay() + { + IoCManager.InjectDependencies(this); + _xformSys = _entManager.System(); + + _noiseTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/perlin_noise.png"); + _heatGradientTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/soft_circle.png"); + + _shader = _proto.Index(HeatOverlayShader).InstanceUnique(); + _configManager.OnValueChanged(CCVars.ReducedMotion, SetReducedMotion, invokeImmediately: true); + } + + private void SetReducedMotion(bool reducedMotion) + { + _shader.SetParameter("strength_scale", reducedMotion ? ShaderStrengthForReducedMotion : ShaderStrength); + _shader.SetParameter("spatial_scale", reducedMotion ? ShaderScaleReducedMotion : ShaderScale); + _shader.SetParameter("speed_scale", reducedMotion ? ShaderSpeedReducedMotion : ShaderSpeed); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (args.MapId == MapId.Nullspace) + return false; + + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + var target = args.Viewport.RenderTarget; + + // Probably the resolution of the game window changed, remake the textures. + if (res.HeatTarget?.Texture.Size != target.Size) + { + res.HeatTarget?.Dispose(); + res.HeatTarget = _clyde.CreateRenderTarget( + target.Size, + new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), + name: nameof(GasTileHeatBlurOverlaySystem)); + } + + if (res.HeatBlurTarget?.Texture.Size != target.Size) + { + res.HeatBlurTarget?.Dispose(); + res.HeatBlurTarget = _clyde.CreateRenderTarget( + target.Size, + new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), + name: $"{nameof(GasTileHeatBlurOverlaySystem)}-blur"); + } + + var overlayQuery = _entManager.GetEntityQuery(); + + args.WorldHandle.UseShader(_proto.Index(UnshadedShader).Instance()); + + var mapId = args.MapId; + var worldAABB = args.WorldAABB; + var worldBounds = args.WorldBounds; + var worldHandle = args.WorldHandle; + var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix(); + + // If there is no distortion after checking all visible tiles, we can bail early + var anyDistortion = false; + + // We're rendering in the context of the heat target texture, which will encode data as to where and how strong + // the heat distortion will be + args.WorldHandle.RenderInRenderTarget(res.HeatTarget, + () => + { + _intersectingGrids.Clear(); + _mapManager.FindGridsIntersecting(mapId, worldAABB, ref _intersectingGrids); + foreach (var grid in _intersectingGrids) + { + if (!overlayQuery.TryGetComponent(grid.Owner, out var comp)) + continue; + + var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner); + var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal; + + if (!Matrix3x2.Invert(gridEntToViewportLocal, out var viewportLocalToGridEnt)) + continue; + + var uvToUi = Matrix3Helpers.CreateScale(res.HeatTarget.Size.X, -res.HeatTarget.Size.Y); + var uvToGridEnt = uvToUi * viewportLocalToGridEnt; + + // Because we want the actual distortion to be calculated based on the grid coordinates*, we need + // to pass a matrix transformation to go from the viewport coordinates to grid coordinates. + // * (why? because otherwise the effect would shimmer like crazy as you moved around, think + // moving a piece of warped glass above a picture instead of placing the warped glass on the + // paper and moving them together) + _shader.SetParameter("grid_ent_from_viewport_local", uvToGridEnt); + + // Draw commands (like DrawRect) will be using grid coordinates from here + worldHandle.SetTransform(gridEntToViewportLocal); + + // We only care about tiles that fit in these bounds + var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner); + var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize); + + var localBounds = new Box2i( + (int)MathF.Floor(floatBounds.Left), + (int)MathF.Floor(floatBounds.Bottom), + (int)MathF.Ceiling(floatBounds.Right), + (int)MathF.Ceiling(floatBounds.Top)); + + // for each tile and its gas ---> + foreach (var chunk in comp.Chunks.Values) + { + var enumerator = new GasChunkEnumerator(chunk); + + while (enumerator.MoveNext(out var tileGas)) + { + // Check and make sure the tile is within the viewport/screen + var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y); + if (!localBounds.Contains(tilePosition)) + continue; + + // Get the distortion strength from the temperature and bail if it's not hot enough + var strength = GetHeatDistortionStrength(tileGas.ByteGasTemperature); + if (strength <= 0f) + continue; + + anyDistortion = true; + + // Encode the strength in the red channel + // alpha set to 1 as tile is active + worldHandle.DrawTextureRect( + _heatGradientTexture, + Box2.CenteredAround(tilePosition + grid.Comp.TileSizeHalfVector, + grid.Comp.TileSizeVector * ShaderSpilling), + new Color(strength, 0f, 0f)); + } + } + } + }, + // This clears the buffer to all zero first... + new Color(0, 0, 0, 0)); + + // no distortion, no need to render + if (!anyDistortion) + { + args.WorldHandle.UseShader(null); + args.WorldHandle.SetTransform(Matrix3x2.Identity); + return false; + } + + return true; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources()); + + if (ScreenTexture is null || res.HeatTarget is null || res.HeatBlurTarget is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("NOISE_TEXTURE", _noiseTexture); + + args.WorldHandle.UseShader(_shader); + args.WorldHandle.DrawTextureRect(res.HeatTarget.Texture, args.WorldBounds); + + args.WorldHandle.UseShader(null); + args.WorldHandle.SetTransform(Matrix3x2.Identity); + } + + protected override void DisposeBehavior() + { + _resources.Dispose(); + + _configManager.UnsubValueChanged(CCVars.ReducedMotion, SetReducedMotion); + base.DisposeBehavior(); + } + + /// + /// Gets the strength of the heat distortion effect based on the temperature of the tile. + /// The strength is a value between 0 and 1, where 0 means no distortion and 1 means maximum distortion. + /// + /// The temperature of the tile. + /// The strength of the heat distortion effect. + /// + private static float GetHeatDistortionStrength(ThermalByte temp) + { + if (!temp.TryGetTemperature(out var kelvinTemp)) + { + return 0f; + } + + var strength = (kelvinTemp - MinDistortionTemp) / (MaxDistortionTemp - MinDistortionTemp); + + return MathHelper.Clamp01(strength); + } + + internal sealed class CachedResources : IDisposable + { + public IRenderTexture? HeatTarget; + public IRenderTexture? HeatBlurTarget; + + public void Dispose() + { + HeatTarget?.Dispose(); + HeatBlurTarget?.Dispose(); + } + } +} diff --git a/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs index 37298b95fd..4d909c9fc8 100644 --- a/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs @@ -72,17 +72,7 @@ public sealed class GasTileVisibleGasOverlay : Overlay { var gasPrototype = _atmosphereSystem.GetGas(_gasTileOverlaySystem.VisibleGasId[i]); - SpriteSpecifier overlay; - - if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && - !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) - overlay = new SpriteSpecifier.Rsi(new(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState); - else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture)) - overlay = new SpriteSpecifier.Texture(new(gasPrototype.GasOverlayTexture)); - else - continue; - - switch (overlay) + switch (gasPrototype.GasOverlaySprite) { case SpriteSpecifier.Rsi animated: var rsi = _resourceCache.GetResource(animated.RsiPath).RSI; diff --git a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs index 3a5df3f9a8..c8f2091d13 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs @@ -1,41 +1,33 @@ -using Robust.Client.GameObjects; using Robust.Client.UserInterface; -using static Content.Shared.Atmos.Components.GasAnalyzerComponent; +using Content.Shared.Atmos.Components; -namespace Content.Client.Atmos.UI +namespace Content.Client.Atmos.UI; + +public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface { - public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface + [ViewVariables] + private GasAnalyzerWindow? _window; + + public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { - [ViewVariables] - private GasAnalyzerWindow? _window; + } - public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } + protected override void Open() + { + base.Open(); - protected override void Open() - { - base.Open(); + _window = this.CreateWindowCenteredLeft(); + _window.OnClose += Close; + } - _window = this.CreateWindowCenteredLeft(); - _window.OnClose += Close; - } + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + if (_window == null) + return; - protected override void ReceiveMessage(BoundUserInterfaceMessage message) - { - if (_window == null) - return; - if (message is not GasAnalyzerUserMessage cast) - return; - _window.Populate(cast); - } + if (message is not GasAnalyzerUserMessage cast) + return; - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - _window?.Dispose(); - } + _window.Populate(cast); } } diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs index 63b4e6b0c6..94a9f23745 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs @@ -1,6 +1,8 @@ using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.Temperature; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -8,7 +10,6 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; -using static Content.Shared.Atmos.Components.GasAnalyzerComponent; using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.Atmos.UI @@ -16,25 +17,17 @@ namespace Content.Client.Atmos.UI [GenerateTypedNameReferences] public sealed partial class GasAnalyzerWindow : DefaultWindow { + private readonly SharedAtmosphereSystem _atmosphere; private NetEntity _currentEntity = NetEntity.Invalid; public GasAnalyzerWindow() { RobustXamlLoader.Load(this); + _atmosphere = IoCManager.Resolve().System(); } public void Populate(GasAnalyzerUserMessage msg) { - if (msg.Error != null) - { - CTopBox.AddChild(new Label - { - Text = Loc.GetString("gas-analyzer-window-error-text", ("errorText", msg.Error)), - FontColorOverride = Color.Red - }); - return; - } - if (msg.NodeGasMixes.Length == 0) { CTopBox.AddChild(new Label @@ -329,31 +322,31 @@ namespace Content.Client.Atmos.UI for (var j = 0; j < gasMix.Gases.Length; j++) { - var gas = gasMix.Gases[j]; - var color = Color.FromHex($"#{gas.Color}", Color.White); + var gasEntry = gasMix.Gases[j]; + var gasProto = _atmosphere.GetGas(gasEntry.Gas); // Add to the table tableKey.AddChild(new Label { - Text = Loc.GetString(gas.Name) + Text = Loc.GetString(gasProto.Name) }); tableVal.AddChild(new Label { Text = Loc.GetString("gas-analyzer-window-molarity-text", - ("mol", $"{gas.Amount:0.00}")), + ("mol", $"{gasEntry.Amount:0.00}")), Align = Label.AlignMode.Right, }); tablePercent.AddChild(new Label { Text = Loc.GetString("gas-analyzer-window-percentage-text", - ("percentage", $"{(gas.Amount / totalGasAmount * 100):0.0}")), + ("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.0}")), Align = Label.AlignMode.Right }); // Add to the gas bar //TODO: highlight the currently hover one - gasBar.AddEntry(gas.Amount, color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text", - ("gasName", gas.Name), - ("amount", $"{gas.Amount:0.##}"), - ("percentage", $"{(gas.Amount / totalGasAmount * 100):0.#}"))); + gasBar.AddEntry(gasEntry.Amount, gasProto.Color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text", + ("gasName", Loc.GetString(gasProto.Name)), + ("amount", $"{gasEntry.Amount:0.##}"), + ("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.#}"))); } dataContainer.AddChild(gasBar); diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index 9929751b22..92bfc677eb 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -235,8 +235,6 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem /// private void ProcessNearbyAmbience(TransformComponent playerXform) { - var query = GetEntityQuery(); - var metaQuery = GetEntityQuery(); var mapPos = _xformSystem.GetMapCoordinates(playerXform); // Remove out-of-range ambiences @@ -249,9 +247,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem if (comp.Enabled && // Don't keep playing sounds that have changed since. sound.Sound == comp.Sound && - query.TryGetComponent(owner, out var xform) && + TryComp(owner, out TransformComponent? xform) && xform.MapID == playerXform.MapID && - !metaQuery.GetComponent(owner).EntityPaused) + !Paused(owner)) { // TODO: This is just trydistance for coordinates. var distance = (xform.ParentUid == playerXform.ParentUid) @@ -294,7 +292,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem var comp = sourceEntity.Comp; if (_playingSounds.ContainsKey(sourceEntity) || - metaQuery.GetComponent(uid).EntityPaused) + Paused(uid)) continue; var audioParams = _params diff --git a/Content.Client/Audio/AmbientSoundTreeSystem.cs b/Content.Client/Audio/AmbientSoundTreeSystem.cs index 7a9439c9df..2844374452 100644 --- a/Content.Client/Audio/AmbientSoundTreeSystem.cs +++ b/Content.Client/Audio/AmbientSoundTreeSystem.cs @@ -23,8 +23,7 @@ public sealed class AmbientSoundTreeSystem : ComponentTreeSystem()); + entry.Component.TreeUid.Value); return ExtractAabb(in entry, pos, default); } diff --git a/Content.Client/CardboardBox/CardboardBoxSystem.cs b/Content.Client/CardboardBox/CardboardBoxSystem.cs index ecebe16727..a179f14a3b 100644 --- a/Content.Client/CardboardBox/CardboardBoxSystem.cs +++ b/Content.Client/CardboardBox/CardboardBoxSystem.cs @@ -14,15 +14,12 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly SpriteSystem _sprite = default!; - - private EntityQuery _mobStateQuery; + [Dependency] private readonly EntityQuery _mobStateQuery = default!; public override void Initialize() { base.Initialize(); - _mobStateQuery = GetEntityQuery(); - SubscribeNetworkEvent(OnBoxEffect); } @@ -33,11 +30,7 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem if (!TryComp(source, out var box)) return; - var xformQuery = GetEntityQuery(); - - if (!xformQuery.TryGetComponent(source, out var xform)) - return; - + var xform = Transform(source); var sourcePos = _transform.GetMapCoordinates(source, xform); //Any mob that can move should be surprised? @@ -72,7 +65,7 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem var ent = Spawn(box.Effect, mapPos); - if (!xformQuery.TryGetComponent(ent, out var entTransform) || !TryComp(ent, out var sprite)) + if (!TryComp(ent, out TransformComponent? entTransform) || !TryComp(ent, out var sprite)) continue; _sprite.SetOffset((ent, sprite), new Vector2(0, 1)); diff --git a/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs index 64d809c0c5..25cfb1ae82 100644 --- a/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs +++ b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs @@ -4,6 +4,7 @@ using Content.Shared.Changeling.Components; using Content.Shared.Changeling.Systems; using JetBrains.Annotations; using Robust.Client.UserInterface; +using Robust.Shared.Utility; namespace Content.Client.Changeling.UI; @@ -12,7 +13,9 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne { private SimpleRadialMenu? _menu; private static readonly Color SelectedOptionBackground = Palettes.Green.Element.WithAlpha(128); + private static readonly Color DisabledOptionBackground = Palettes.Slate.Element.WithAlpha(128); private static readonly Color SelectedOptionHoverBackground = Palettes.Green.HoveredElement.WithAlpha(128); + private static readonly Color DisabledOptionHoverBackground = Palettes.Slate.HoveredElement.WithAlpha(128); protected override void Open() { @@ -23,7 +26,6 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne _menu.OpenOverMouseScreenPosition(); } - public override void Update() { if (_menu == null) @@ -32,7 +34,7 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne if (!EntMan.TryGetComponent(Owner, out var lingIdentity)) return; - var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity); + var models = ConvertToButtons(lingIdentity.ConsumedIdentities.Keys, lingIdentity?.CurrentIdentity); _menu.SetButtons(models); } @@ -43,21 +45,41 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne ) { var buttons = new List(); + var dropButtons = new List(); + foreach (var identity in identities) { - if (!EntMan.TryGetComponent(identity, out var metadata)) - continue; - + // Options for selecting identities. var option = new RadialMenuActionOption(SendIdentitySelect, EntMan.GetNetEntity(identity)) { IconSpecifier = RadialMenuIconSpecifier.With(identity), - ToolTip = metadata.EntityName, - BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null, + ToolTip = Loc.GetString("changeling-transform-bui-select-entity", ("entity", identity)), + BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null, // mark as selected HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null }; buttons.Add(option); + + // Options for dropping identities. + var dropOption = new RadialMenuActionOption(SendIdentityDrop, EntMan.GetNetEntity(identity)) + { + IconSpecifier = RadialMenuIconSpecifier.With(identity), + ToolTip = (currentIdentity == identity) + ? Loc.GetString("changeling-transform-bui-drop-identity-cannot-drop") + : Loc.GetString("changeling-transform-bui-drop-identity-entity", ("entity", identity)), + BackgroundColor = (currentIdentity == identity) ? DisabledOptionBackground : null, // cannot drop your current identity + HoverBackgroundColor = (currentIdentity == identity) ? DisabledOptionHoverBackground : null + }; + dropButtons.Add(dropOption); } + // Menu category for dropping identities. + var dropMenuButton = new RadialMenuNestedLayerOption(dropButtons) + { + IconSpecifier = RadialMenuIconSpecifier.With(new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/delete.svg.192dpi.png"))), + ToolTip = Loc.GetString("changeling-transform-bui-drop-identity-menu") + }; + buttons.Add(dropMenuButton); + return buttons; } @@ -65,4 +87,9 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne { SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId)); } + + private void SendIdentityDrop(NetEntity identityId) + { + SendPredictedMessage(new ChangelingTransformIdentityDropMessage(identityId)); + } } diff --git a/Content.Client/Clickable/ClickableSystem.cs b/Content.Client/Clickable/ClickableSystem.cs index 8834f023f4..ae3487dc87 100644 --- a/Content.Client/Clickable/ClickableSystem.cs +++ b/Content.Client/Clickable/ClickableSystem.cs @@ -16,17 +16,9 @@ public sealed class ClickableSystem : EntitySystem [Dependency] private readonly SharedTransformSystem _transforms = default!; [Dependency] private readonly SpriteSystem _sprites = default!; - private EntityQuery _clickableQuery; - private EntityQuery _xformQuery; - private EntityQuery _fadingSpriteQuery; - - public override void Initialize() - { - base.Initialize(); - _clickableQuery = GetEntityQuery(); - _xformQuery = GetEntityQuery(); - _fadingSpriteQuery = GetEntityQuery(); - } + [Dependency] private readonly EntityQuery _clickableQuery = default!; + [Dependency] private readonly EntityQuery _xformQuery = default!; + [Dependency] private readonly EntityQuery _fadingSpriteQuery = default!; /// /// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding diff --git a/Content.Client/DisplacementMap/DisplacementMapSystem.cs b/Content.Client/DisplacementMap/DisplacementMapSystem.cs index 6986e1c868..14075caba3 100644 --- a/Content.Client/DisplacementMap/DisplacementMapSystem.cs +++ b/Content.Client/DisplacementMap/DisplacementMapSystem.cs @@ -2,14 +2,18 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.DisplacementMap; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; namespace Content.Client.DisplacementMap; public sealed class DisplacementMapSystem : EntitySystem { - [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly ISerializationManager _serialization = null!; + [Dependency] private readonly SpriteSystem _sprite = null!; + + //needs to be replaced later: see comment on line 48 + private static readonly ProtoId UnshadedID = "unshaded"; private static string? BuildDisplacementLayerKey(object key) { @@ -40,7 +44,16 @@ public sealed class DisplacementMapSystem : EntitySystem EnsureDisplacementIsNotOnSprite(sprite, key); if (data.ShaderOverride is not null) - sprite.Comp.LayerSetShader(index, data.ShaderOverride); + { + //TODO : this is a kinda janky workaround for the fact that the current rendering pipeline does not have + //proper support for multiple shaders on a given layer (or an ubershader to handle stacking all of the effects well) + //should be replaced by an engine-level solution, but this is an adequate temporary solution. + //what's that phrase about temporary solutions? + sprite.Comp.LayerSetShader(index, + (sprite.Comp[index] is SpriteComponent.Layer layer && layer.ShaderPrototype == UnshadedID) + ? data.ShaderOverrideUnshaded + : data.ShaderOverride); + } //allows you not to write it every time in the YML foreach (var pair in data.SizeMaps) diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index 5802059537..407cf3e7c1 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -50,9 +50,7 @@ public sealed class DoAfterSystem : SharedDoAfterSystem var time = GameTiming.CurTime; var comp = Comp(playerEntity.Value); - var xformQuery = GetEntityQuery(); - var handsQuery = GetEntityQuery(); - Update(playerEntity.Value, active, comp, time, xformQuery, handsQuery); + Update(playerEntity.Value, active, comp, time); } /// diff --git a/Content.Client/Drunk/DrunkOverlay.cs b/Content.Client/Drunk/DrunkOverlay.cs index 692232776a..44d82f3794 100644 --- a/Content.Client/Drunk/DrunkOverlay.cs +++ b/Content.Client/Drunk/DrunkOverlay.cs @@ -1,8 +1,8 @@ +using Content.Shared.CCVar; using Content.Shared.Drunk; -using Content.Shared.StatusEffect; -using Content.Shared.StatusEffectNew; using Robust.Client.Graphics; using Robust.Client.Player; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -11,19 +11,23 @@ namespace Content.Client.Drunk; public sealed class DrunkOverlay : Overlay { - private static readonly ProtoId Shader = "Drunk"; + private static readonly ProtoId DrunkShader = "Drunk"; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + private readonly Shared.StatusEffectNew.StatusEffectsSystem _statusEffectsSystem; public override OverlaySpace Space => OverlaySpace.WorldSpace; public override bool RequestScreenTexture => true; private readonly ShaderInstance _drunkShader; public float CurrentBoozePower = 0.0f; + // Starting phase for the rotation effect. + // Needed so it doesn't always look the same for 0 motion. + public float Phase = 0f; private const float VisualThreshold = 10.0f; private const float PowerDivisor = 250.0f; @@ -37,12 +41,22 @@ public sealed class DrunkOverlay : Overlay private const float BoozePowerScale = 8f; - private float _visualScale = 0; + private float _visualScale = 0f; + private float _timeScale = 1f; + private float _distortionScale = 1f; public DrunkOverlay() { IoCManager.InjectDependencies(this); - _drunkShader = _prototypeManager.Index(Shader).InstanceUnique(); + _statusEffectsSystem = _entityManager.System(); + _drunkShader = _prototypeManager.Index(DrunkShader).InstanceUnique(); + _configManager.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true); + } + + private void OnReducedMotionChanged(bool reducedMotion) + { + _timeScale = reducedMotion ? 0.0f : 1.0f; + _distortionScale = reducedMotion ? 4.0f : 1.0f; // Make the offset stronger to compensate the lack of motion. } protected override void FrameUpdate(FrameEventArgs args) @@ -53,15 +67,14 @@ public sealed class DrunkOverlay : Overlay if (playerEntity == null) return; - var statusSys = _sysMan.GetEntitySystem(); - if (!statusSys.TryGetMaxTime(playerEntity.Value, out var status)) + if (!_statusEffectsSystem.TryGetMaxTime(playerEntity.Value, out var status)) return; var time = status.Item2; - var power = time == null ? MaxBoozePower : (float) Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower); + var power = time == null ? MaxBoozePower : (float)Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower); - CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power+1); + CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power + 1); } protected override bool BeforeDraw(in OverlayDrawArgs args) @@ -82,8 +95,12 @@ public sealed class DrunkOverlay : Overlay return; var handle = args.WorldHandle; + _drunkShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); _drunkShader.SetParameter("boozePower", _visualScale); + _drunkShader.SetParameter("timeScale", _timeScale); + _drunkShader.SetParameter("distortionScale", _distortionScale); + _drunkShader.SetParameter("phase", Phase); handle.UseShader(_drunkShader); handle.DrawRect(args.WorldBounds, Color.White); handle.UseShader(null); diff --git a/Content.Client/Drunk/DrunkSystem.cs b/Content.Client/Drunk/DrunkSystem.cs index c5fab75e7d..2e1f8157aa 100644 --- a/Content.Client/Drunk/DrunkSystem.cs +++ b/Content.Client/Drunk/DrunkSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.StatusEffectNew; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Player; +using Robust.Shared.Random; namespace Content.Client.Drunk; @@ -10,6 +11,7 @@ public sealed class DrunkSystem : SharedDrunkSystem { [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IRobustRandom _random = default!; private DrunkOverlay _overlay = default!; @@ -29,7 +31,10 @@ public sealed class DrunkSystem : SharedDrunkSystem private void OnStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) { if (!_overlayMan.HasOverlay()) + { + _overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect _overlayMan.AddOverlay(_overlay); + } } private void OnStatusRemoved(Entity entity, ref StatusEffectRemovedEvent args) @@ -47,6 +52,7 @@ public sealed class DrunkSystem : SharedDrunkSystem private void OnPlayerAttached(Entity entity, ref StatusEffectRelayedEvent args) { _overlayMan.AddOverlay(_overlay); + } private void OnPlayerDetached(Entity entity, ref StatusEffectRelayedEvent args) diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 8226b3eb05..aa1dfaebe2 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -123,7 +123,6 @@ namespace Content.Client.Entry _prototypeManager.RegisterIgnore("noiseChannel"); _prototypeManager.RegisterIgnore("playerConnectionWhitelist"); _prototypeManager.RegisterIgnore("spaceBiome"); - _prototypeManager.RegisterIgnore("worldgenConfig"); _prototypeManager.RegisterIgnore("gameRule"); _prototypeManager.RegisterIgnore("worldSpell"); _prototypeManager.RegisterIgnore("entitySpell"); diff --git a/Content.Client/FeedbackPopup/ClientFeedbackManager.cs b/Content.Client/FeedbackPopup/ClientFeedbackManager.cs index dd390cc2d4..5b2b52b1b7 100644 --- a/Content.Client/FeedbackPopup/ClientFeedbackManager.cs +++ b/Content.Client/FeedbackPopup/ClientFeedbackManager.cs @@ -31,7 +31,7 @@ public sealed class ClientFeedbackManager : SharedFeedbackManager /// public override void Display(List>? prototypes) { - if (prototypes == null || !NetManager.IsClient) + if (prototypes == null) return; var count = _displayedPopups.Count; @@ -42,9 +42,6 @@ public sealed class ClientFeedbackManager : SharedFeedbackManager /// public override void Remove(List>? prototypes) { - if (!NetManager.IsClient) - return; - if (prototypes == null) { _displayedPopups.Clear(); diff --git a/Content.Client/IconSmoothing/IconSmoothSystem.cs b/Content.Client/IconSmoothing/IconSmoothSystem.cs index a1c47ca6be..4b8ec7be79 100644 --- a/Content.Client/IconSmoothing/IconSmoothSystem.cs +++ b/Content.Client/IconSmoothing/IconSmoothSystem.cs @@ -18,6 +18,8 @@ namespace Content.Client.IconSmoothing { [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly EntityQuery _iconSmoothQuery = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; private readonly Queue _dirtyEntities = new(); private readonly Queue _anchorChangedEntities = new(); @@ -106,13 +108,10 @@ namespace Content.Client.IconSmoothing { base.FrameUpdate(frameTime); - var xformQuery = GetEntityQuery(); - var smoothQuery = GetEntityQuery(); - // first process anchor state changes. while (_anchorChangedEntities.TryDequeue(out var uid)) { - if (!xformQuery.TryGetComponent(uid, out var xform)) + if (!TryComp(uid, out TransformComponent? xform)) continue; if (xform.MapID == MapId.Nullspace) @@ -123,7 +122,7 @@ namespace Content.Client.IconSmoothing continue; } - DirtyNeighbours(uid, comp: null, xform, smoothQuery); + DirtyNeighbours(uid, comp: null, xform); } // Next, update actual sprites. @@ -132,19 +131,16 @@ namespace Content.Client.IconSmoothing _generation += 1; - var spriteQuery = GetEntityQuery(); - // Performance: This could be spread over multiple updates, or made parallel. while (_dirtyEntities.TryDequeue(out var uid)) { - CalculateNewSprite(uid, spriteQuery, smoothQuery, xformQuery); + CalculateNewSprite(uid); } } - public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, TransformComponent? transform = null, EntityQuery? smoothQuery = null) + public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, TransformComponent? transform = null) { - smoothQuery ??= GetEntityQuery(); - if (!smoothQuery.Value.Resolve(uid, ref comp) || !comp.Running) + if (!_iconSmoothQuery.Resolve(uid, ref comp) || !comp.Running) return; _dirtyEntities.Enqueue(uid); @@ -206,11 +202,7 @@ namespace Content.Client.IconSmoothing _anchorChangedEntities.Enqueue(uid); } - private void CalculateNewSprite(EntityUid uid, - EntityQuery spriteQuery, - EntityQuery smoothQuery, - EntityQuery xformQuery, - IconSmoothComponent? smooth = null) + private void CalculateNewSprite(EntityUid uid, IconSmoothComponent? smooth = null) { TransformComponent? xform; Entity? gridEntity = null; @@ -218,7 +210,7 @@ namespace Content.Client.IconSmoothing // The generation check prevents updating an entity multiple times per tick. // As it stands now, it's totally possible for something to get queued twice. // Generation on the component is set after an update so we can cull updates that happened this generation. - if (!smoothQuery.Resolve(uid, ref smooth, false) + if (!_iconSmoothQuery.Resolve(uid, ref smooth, false) || smooth.Mode == IconSmoothingMode.NoSprite || smooth.UpdateGeneration == _generation || !smooth.Enabled @@ -226,7 +218,7 @@ namespace Content.Client.IconSmoothing { if (smooth is { Enabled: true } && TryComp(uid, out var edge) && - xformQuery.TryGetComponent(uid, out xform)) + TryComp(uid, out xform)) { var directions = DirectionFlag.None; @@ -237,13 +229,13 @@ namespace Content.Client.IconSmoothing gridEntity = (gridUid, grid); - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)))) directions |= DirectionFlag.North; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)))) directions |= DirectionFlag.South; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)))) directions |= DirectionFlag.East; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)))) directions |= DirectionFlag.West; } @@ -253,10 +245,10 @@ namespace Content.Client.IconSmoothing return; } - xform = xformQuery.GetComponent(uid); + xform = Transform(uid); smooth.UpdateGeneration = _generation; - if (!spriteQuery.TryGetComponent(uid, out var sprite)) + if (!_spriteQuery.TryGetComponent(uid, out var sprite)) { Log.Error($"Encountered a icon-smoothing entity without a sprite: {ToPrettyString(uid)}"); RemCompDeferred(uid, smooth); @@ -281,13 +273,13 @@ namespace Content.Client.IconSmoothing switch (smooth.Mode) { case IconSmoothingMode.Corners: - CalculateNewSpriteCorners(gridEntity, smooth, spriteEnt, xform, smoothQuery); + CalculateNewSpriteCorners(gridEntity, smooth, spriteEnt, xform); break; case IconSmoothingMode.CardinalFlags: - CalculateNewSpriteCardinal(gridEntity, smooth, spriteEnt, xform, smoothQuery); + CalculateNewSpriteCardinal(gridEntity, smooth, spriteEnt, xform); break; case IconSmoothingMode.Diagonal: - CalculateNewSpriteDiagonal(gridEntity, smooth, spriteEnt, xform, smoothQuery); + CalculateNewSpriteDiagonal(gridEntity, smooth, spriteEnt, xform); break; default: throw new ArgumentOutOfRangeException(); @@ -295,7 +287,7 @@ namespace Content.Client.IconSmoothing } private void CalculateNewSpriteDiagonal(Entity? gridEntity, IconSmoothComponent smooth, - Entity sprite, TransformComponent xform, EntityQuery smoothQuery) + Entity sprite, TransformComponent xform) { if (gridEntity == null) { @@ -320,7 +312,7 @@ namespace Content.Client.IconSmoothing for (var i = 0; i < neighbors.Length; i++) { var neighbor = (Vector2i)rotation.RotateVec(neighbors[i]); - matching = matching && MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos + neighbor), smoothQuery); + matching = matching && MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos + neighbor)); } if (matching) @@ -333,7 +325,7 @@ namespace Content.Client.IconSmoothing } } - private void CalculateNewSpriteCardinal(Entity? gridEntity, IconSmoothComponent smooth, Entity sprite, TransformComponent xform, EntityQuery smoothQuery) + private void CalculateNewSpriteCardinal(Entity? gridEntity, IconSmoothComponent smooth, Entity sprite, TransformComponent xform) { var dirs = CardinalConnectDirs.None; @@ -347,13 +339,13 @@ namespace Content.Client.IconSmoothing var grid = gridEntity.Value.Comp; var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates); - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)))) dirs |= CardinalConnectDirs.North; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)))) dirs |= CardinalConnectDirs.South; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)))) dirs |= CardinalConnectDirs.East; - if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery)) + if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)))) dirs |= CardinalConnectDirs.West; _sprite.LayerSetRsiState(sprite.AsNullable(), 0, $"{smooth.StateBase}{(int)dirs}"); @@ -372,11 +364,11 @@ namespace Content.Client.IconSmoothing CalculateEdge(sprite, directions, sprite); } - private bool MatchingEntity(IconSmoothComponent smooth, AnchoredEntitiesEnumerator candidates, EntityQuery smoothQuery) + private bool MatchingEntity(IconSmoothComponent smooth, AnchoredEntitiesEnumerator candidates) { while (candidates.MoveNext(out var entity)) { - if (smoothQuery.TryGetComponent(entity, out var other) && + if (_iconSmoothQuery.TryGetComponent(entity, out var other) && other.SmoothKey != null && (other.SmoothKey == smooth.SmoothKey || smooth.AdditionalKeys.Contains(other.SmoothKey)) && other.Enabled) @@ -388,11 +380,11 @@ namespace Content.Client.IconSmoothing return false; } - private void CalculateNewSpriteCorners(Entity? gridEntity, IconSmoothComponent smooth, Entity spriteEnt, TransformComponent xform, EntityQuery smoothQuery) + private void CalculateNewSpriteCorners(Entity? gridEntity, IconSmoothComponent smooth, Entity spriteEnt, TransformComponent xform) { var (cornerNE, cornerNW, cornerSW, cornerSE) = gridEntity == null ? (CornerFill.None, CornerFill.None, CornerFill.None, CornerFill.None) - : CalculateCornerFill(gridEntity.Value, smooth, xform, smoothQuery); + : CalculateCornerFill(gridEntity.Value, smooth, xform); // TODO figure out a better way to set multiple sprite layers. // This will currently re-calculate the sprite bounding box 4 times. @@ -422,20 +414,20 @@ namespace Content.Client.IconSmoothing CalculateEdge(spriteEnt, directions, sprite); } - private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(Entity gridEntity, IconSmoothComponent smooth, TransformComponent xform, EntityQuery smoothQuery) + private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(Entity gridEntity, IconSmoothComponent smooth, TransformComponent xform) { var gridUid = gridEntity.Owner; var grid = gridEntity.Comp; var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates); - var n = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery); - var ne = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthEast)), smoothQuery); - var e = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery); - var se = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthEast)), smoothQuery); - var s = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery); - var sw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthWest)), smoothQuery); - var w = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery); - var nw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthWest)), smoothQuery); + var n = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North))); + var ne = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthEast))); + var e = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East))); + var se = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthEast))); + var s = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South))); + var sw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthWest))); + var w = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West))); + var nw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthWest))); // ReSharper disable InconsistentNaming var cornerNE = CornerFill.None; diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs index a1af9f9e20..7ff8dd150f 100644 --- a/Content.Client/Interaction/DragDropSystem.cs +++ b/Content.Client/Interaction/DragDropSystem.cs @@ -48,6 +48,7 @@ public sealed class DragDropSystem : SharedDragDropSystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; // how often to recheck possible targets (prevents calling expensive // check logic each update) @@ -433,11 +434,9 @@ public sealed class DragDropSystem : SharedDragDropSystem var bounds = new Box2(mousePos.Position - expansion, mousePos.Position + expansion); var pvsEntities = _lookup.GetEntitiesIntersecting(mousePos.MapId, bounds); - var spriteQuery = GetEntityQuery(); - foreach (var entity in pvsEntities) { - if (!spriteQuery.TryGetComponent(entity, out var inRangeSprite) || + if (!_spriteQuery.TryGetComponent(entity, out var inRangeSprite) || !inRangeSprite.Visible || entity == _draggedEntity) { diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml index a5c8f6a85c..84f1d7835a 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml +++ b/Content.Client/Lathe/UI/LatheMenu.xaml @@ -1,8 +1,9 @@ - @@ -156,4 +157,4 @@ - + diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs index f6688a63af..adb115d352 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Text; using Content.Client.Materials; +using Content.Client.UserInterface.Controls; using Content.Shared.Lathe; using Content.Shared.Lathe.Prototypes; using Content.Shared.Research.Prototypes; @@ -8,7 +9,6 @@ using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -16,7 +16,7 @@ using Robust.Shared.Utility; namespace Content.Client.Lathe.UI; [GenerateTypedNameReferences] -public sealed partial class LatheMenu : DefaultWindow +public sealed partial class LatheMenu : FancyWindow { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -75,6 +75,7 @@ public sealed partial class LatheMenu : DefaultWindow public void SetEntity(EntityUid uid) { Entity = uid; + this.SetInfoFromEntity(_entityManager, Entity); if (_entityManager.TryGetComponent(Entity, out var latheComponent)) { diff --git a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs index 51ff5c1a00..211225119b 100644 --- a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs +++ b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs @@ -2,9 +2,8 @@ using System.Linq; using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Atmos; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Damage.Components; -using Content.Shared.Damage.Systems; using Content.Shared.EntityConditions.Conditions; using Content.Shared.FixedPoint; using Content.Shared.Medical.Cryogenics; @@ -20,6 +19,7 @@ public sealed partial class CryoPodWindow : FancyWindow { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private readonly SharedAtmosphereSystem _atmosphere = default!; public event Action? OnEjectPatientPressed; public event Action? OnEjectBeakerPressed; @@ -29,6 +29,7 @@ public sealed partial class CryoPodWindow : FancyWindow { IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); + _atmosphere = _entityManager.System(); EjectPatientButton.OnPressed += _ => OnEjectPatientPressed?.Invoke(); EjectBeakerButton.OnPressed += _ => OnEjectBeakerPressed?.Invoke(); Inject1.OnPressed += _ => OnInjectPressed?.Invoke(1); @@ -71,16 +72,16 @@ public sealed partial class CryoPodWindow : FancyWindow { var totalGasAmount = msg.GasMix.Gases.Sum(gas => gas.Amount); - foreach (var gas in msg.GasMix.Gases) + foreach (var gasEntry in msg.GasMix.Gases) { - var color = Color.FromHex($"#{gas.Color}", Color.White); - var percent = gas.Amount / totalGasAmount * 100; - var localizedName = Loc.GetString(gas.Name); + var gasProto = _atmosphere.GetGas(gasEntry.Gas); + var percent = gasEntry.Amount / totalGasAmount * 100; + var localizedName = Loc.GetString(gasProto.Name); var tooltip = Loc.GetString("gas-analyzer-window-molarity-percentage-text", ("gasName", localizedName), - ("amount", $"{gas.Amount:0.##}"), + ("amount", $"{gasEntry.Amount:0.##}"), ("percentage", $"{percent:0.#}")); - GasMixChart.AddEntry(gas.Amount, color, tooltip: tooltip); + GasMixChart.AddEntry(gasEntry.Amount, gasProto.Color, tooltip: tooltip); } } diff --git a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs index eb60e4fbb6..c806ae5aed 100644 --- a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs +++ b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs @@ -10,15 +10,12 @@ namespace Content.Client.Movement.Systems; public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem { [Dependency] private readonly SpriteSystem _sprite = default!; - - private EntityQuery _spriteQuery; + [Dependency] private readonly EntityQuery _spriteQuery = default!; public override void Initialize() { base.Initialize(); - _spriteQuery = GetEntityQuery(); - SubscribeLocalEvent(OnAfterAutoHandleState); } diff --git a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs index 6520d98ac9..3e1084bebb 100644 --- a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs +++ b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs @@ -12,14 +12,12 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem [Dependency] private readonly IPrototypeManager _proto = default!; - private EntityQuery _spriteQuery; + [Dependency] private readonly EntityQuery _spriteQuery = default!; public override void Initialize() { base.Initialize(); - _spriteQuery = GetEntityQuery(); - SubscribeLocalEvent(OnOcclusionStartup); SubscribeLocalEvent(OnOcclusionShutdown); SubscribeLocalEvent(OnOcclusionAuto); diff --git a/Content.Client/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Client/Nutrition/EntitySystems/CreamPieSystem.cs new file mode 100644 index 0000000000..96a63cc694 --- /dev/null +++ b/Content.Client/Nutrition/EntitySystems/CreamPieSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Robust.Client.GameObjects; + +namespace Content.Client.Nutrition.EntitySystems; + +public sealed class CreamPieSystem : SharedCreamPieSystem +{ + [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentShutdown); + SubscribeLocalEvent(OnAppearanceChange); + SubscribeLocalEvent(OnAfterAutoHandleState); + } + + private void OnComponentInit(Entity ent, ref ComponentInit args) + { + UpdateAppearance(ent); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + _sprite.RemoveLayer(ent.Owner, CreamPiedVisualLayer.Key); + } + + private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args) + { + UpdateAppearance((ent.Owner, ent.Comp, args.Sprite, args.Component)); + } + + private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + // Update when the sprite datafield is changed so that changelings can transform properly. + UpdateAppearance(ent); + } + + private void UpdateAppearance(Entity ent) + { + if (!Resolve(ent, ref ent.Comp2, false) || !Resolve(ent, ref ent.Comp3, false)) + return; + + var creamPied = ent.Comp1; + var sprite = ent.Comp2; + var appearance = ent.Comp3; + + // If there is no sprite to use, remove the layer. Otherwise ensure that it exists and set the visuals accordingly. + int index; + if (creamPied.Sprite == null) + { + _sprite.RemoveLayer((ent.Owner, sprite), CreamPiedVisualLayer.Key); + return; + } + + index = _sprite.LayerMapReserve((ent.Owner, sprite), CreamPiedVisualLayer.Key); + + _appearance.TryGetData(ent.Owner, CreamPiedVisuals.Creamed, out var isCreamPied, appearance); + _sprite.LayerSetSprite((ent.Owner, sprite), index, creamPied.Sprite); + _sprite.LayerSetVisible((ent.Owner, sprite), index, isCreamPied); + } +} diff --git a/Content.Client/Nutrition/EntitySystems/CreamPiedSystem.cs b/Content.Client/Nutrition/EntitySystems/CreamPiedSystem.cs deleted file mode 100644 index 24632b71fa..0000000000 --- a/Content.Client/Nutrition/EntitySystems/CreamPiedSystem.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Content.Shared.Nutrition.EntitySystems; -using JetBrains.Annotations; - -namespace Content.Client.Nutrition.EntitySystems -{ - [UsedImplicitly] - public sealed class CreamPiedSystem : SharedCreamPieSystem - { - } -} diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs index 73d45a17a9..23752fbb9b 100644 --- a/Content.Client/Outline/TargetOutlineSystem.cs +++ b/Content.Client/Outline/TargetOutlineSystem.cs @@ -27,6 +27,7 @@ public sealed class TargetOutlineSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; private bool _enabled = false; @@ -130,11 +131,10 @@ public sealed class TargetOutlineSystem : EntitySystem var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition).Position; var bounds = new Box2(mousePos - LookupVector, mousePos + LookupVector); var pvsEntities = _lookup.GetEntitiesIntersecting(_eyeManager.CurrentEye.Position.MapId, bounds, LookupFlags.Approximate | LookupFlags.Static); - var spriteQuery = GetEntityQuery(); foreach (var entity in pvsEntities) { - if (!spriteQuery.TryGetComponent(entity, out var sprite) || !sprite.Visible) + if (!_spriteQuery.TryGetComponent(entity, out var sprite) || !sprite.Visible) continue; // Check the predicate diff --git a/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs index 1319a3d74e..f0cc9b2f59 100644 --- a/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs +++ b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Polymorph.Components; using Content.Shared.Polymorph.Systems; using Robust.Client.GameObjects; -using Robust.Shared.Player; namespace Content.Client.Polymorph.Systems; @@ -13,16 +12,13 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SpriteSystem _sprite = default!; - private EntityQuery _appearanceQuery; - private EntityQuery _spriteQuery; + [Dependency] private readonly EntityQuery _appearanceQuery = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; public override void Initialize() { base.Initialize(); - _appearanceQuery = GetEntityQuery(); - _spriteQuery = GetEntityQuery(); - SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnStartup); diff --git a/Content.Client/Radiation/Systems/RadiationSystem.cs b/Content.Client/Radiation/Systems/RadiationSystem.cs index f4f109adc7..f719b7b5b8 100644 --- a/Content.Client/Radiation/Systems/RadiationSystem.cs +++ b/Content.Client/Radiation/Systems/RadiationSystem.cs @@ -5,7 +5,7 @@ using Robust.Client.Graphics; namespace Content.Client.Radiation.Systems; -public sealed class RadiationSystem : EntitySystem +public sealed class RadiationSystem : SharedRadiationSystem { [Dependency] private readonly IOverlayManager _overlayMan = default!; diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs index e7d01713e5..3408a2af26 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs @@ -74,8 +74,7 @@ public sealed partial class ReplaySpectatorSystem if ((Direction & DirectionFlag.East) != 0) effectiveDir &= ~DirectionFlag.West; - var query = GetEntityQuery(); - var xform = query.GetComponent(player); + var xform = Transform(player); var pos = _transform.GetWorldPosition(xform); if (!xform.ParentUid.IsValid()) diff --git a/Content.Client/Silicons/Borgs/BorgSystem.Battery.cs b/Content.Client/Silicons/Borgs/BorgSystem.Battery.cs index 52398921d4..6b946f9cf8 100644 --- a/Content.Client/Silicons/Borgs/BorgSystem.Battery.cs +++ b/Content.Client/Silicons/Borgs/BorgSystem.Battery.cs @@ -13,16 +13,11 @@ public sealed partial class BorgSystem // Don't put this on the component because we only need to track the time for a single entity // and we don't want to TryComp it every single tick. private TimeSpan _nextAlertUpdate = TimeSpan.Zero; - private EntityQuery _chassisQuery; - private EntityQuery _slotQuery; public void InitializeBattery() { SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); - - _chassisQuery = GetEntityQuery(); - _slotQuery = GetEntityQuery(); } private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args) diff --git a/Content.Client/Silicons/Borgs/BorgSystem.cs b/Content.Client/Silicons/Borgs/BorgSystem.cs index 517027d082..09e6e387f7 100644 --- a/Content.Client/Silicons/Borgs/BorgSystem.cs +++ b/Content.Client/Silicons/Borgs/BorgSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Mobs; using Content.Shared.Power.EntitySystems; using Content.Shared.PowerCell; +using Content.Shared.PowerCell.Components; using Content.Shared.Silicons.Borgs; using Content.Shared.Silicons.Borgs.Components; using Robust.Client.GameObjects; @@ -22,6 +23,8 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly EntityQuery _chassisQuery = default!; + [Dependency] private readonly EntityQuery _slotQuery = default!; public override void Initialize() { diff --git a/Content.Client/Singularity/SingularityOverlay.cs b/Content.Client/Singularity/SingularityOverlay.cs index 39b0758bf1..fd888fb6dd 100644 --- a/Content.Client/Singularity/SingularityOverlay.cs +++ b/Content.Client/Singularity/SingularityOverlay.cs @@ -1,9 +1,11 @@ +using System.Numerics; +using Content.Shared.CCVar; using Content.Shared.Singularity.Components; using Robust.Client.Graphics; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Prototypes; -using System.Numerics; namespace Content.Client.Singularity { @@ -13,6 +15,7 @@ namespace Content.Client.Singularity [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; private SharedTransformSystem? _xformSystem = null; /// @@ -28,6 +31,8 @@ namespace Content.Client.Singularity private readonly ShaderInstance _shader; + private bool _reducedMotion; + public SingularityOverlay() { IoCManager.InjectDependencies(this); @@ -35,6 +40,8 @@ namespace Content.Client.Singularity _shader.SetParameter("maxDistance", MaxDistance * EyeManager.PixelsPerMeter); _entMan.EventBus.SubscribeEvent(EventSource.Local, this, OnProjectFromScreenToMap); ZIndex = 101; // Should be drawn after the placement overlay so admins placing items near the singularity can tell where they're going. + + _configManager.OnValueChanged(CCVars.ReducedMotion, (b) => { _reducedMotion = b; }, invokeImmediately: true); } private readonly Vector2[] _positions = new Vector2[MaxCount]; @@ -44,6 +51,8 @@ namespace Content.Client.Singularity protected override bool BeforeDraw(in OverlayDrawArgs args) { + if (_reducedMotion) + return false; if (args.Viewport.Eye == null) return false; if (_xformSystem is null && !_entMan.TrySystem(out _xformSystem)) @@ -102,6 +111,8 @@ namespace Content.Client.Singularity /// private void OnProjectFromScreenToMap(ref PixelToMapEvent args) { // Mostly copypasta from the singularity shader. + if (_reducedMotion) + return; if (args.Viewport.Eye == null) return; var maxDistance = MaxDistance * EyeManager.PixelsPerMeter; diff --git a/Content.Client/Sprite/SpriteFadeSystem.cs b/Content.Client/Sprite/SpriteFadeSystem.cs index b347411040..bdc49bd25f 100644 --- a/Content.Client/Sprite/SpriteFadeSystem.cs +++ b/Content.Client/Sprite/SpriteFadeSystem.cs @@ -27,16 +27,15 @@ public sealed class SpriteFadeSystem : EntitySystem [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; + [Dependency] private readonly EntityQuery _fadeQuery = default!; + [Dependency] private readonly EntityQuery _fadingQuery = default!; + [Dependency] private readonly EntityQuery _fixturesQuery = default!; private List<(MapCoordinates Point, bool ExcludeBoundingBox)> _points = new(); private readonly HashSet _comps = new(); - private EntityQuery _spriteQuery; - private EntityQuery _fadeQuery; - private EntityQuery _fadingQuery; - private EntityQuery _fixturesQuery; - private const float TargetAlpha = 0.4f; private const float ChangeRate = 1f; @@ -44,11 +43,6 @@ public sealed class SpriteFadeSystem : EntitySystem { base.Initialize(); - _spriteQuery = GetEntityQuery(); - _fadeQuery = GetEntityQuery(); - _fadingQuery = GetEntityQuery(); - _fixturesQuery = GetEntityQuery(); - SubscribeLocalEvent(OnFadingShutdown); } diff --git a/Content.Client/Sticky/Visualizers/StickyVisualizerSystem.cs b/Content.Client/Sticky/Visualizers/StickyVisualizerSystem.cs index 85c6b8e066..4f58919d78 100644 --- a/Content.Client/Sticky/Visualizers/StickyVisualizerSystem.cs +++ b/Content.Client/Sticky/Visualizers/StickyVisualizerSystem.cs @@ -5,14 +5,12 @@ namespace Content.Client.Sticky.Visualizers; public sealed class StickyVisualizerSystem : VisualizerSystem { - private EntityQuery _spriteQuery; + [Dependency] private readonly EntityQuery _spriteQuery = default!; public override void Initialize() { base.Initialize(); - _spriteQuery = GetEntityQuery(); - SubscribeLocalEvent(OnInit); } diff --git a/Content.Client/Store/StoreSystem.cs b/Content.Client/Store/StoreSystem.cs new file mode 100644 index 0000000000..cb6e42a0d7 --- /dev/null +++ b/Content.Client/Store/StoreSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Store; + +namespace Content.Client.Store; + +public sealed class StoreSystem : SharedStoreSystem; diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs index d8236604bf..36eec4a671 100644 --- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs +++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs @@ -1,7 +1,6 @@ +using System.Linq; using Content.Shared.Store; using JetBrains.Annotations; -using System.Linq; -using Content.Shared.Store.Components; using Robust.Client.UserInterface; using Robust.Shared.Prototypes; @@ -11,6 +10,7 @@ namespace Content.Client.Store.Ui; public sealed class StoreBoundUserInterface : BoundUserInterface { private IPrototypeManager _prototypeManager = default!; + private readonly StoreSystem _storeSystem = default!; [ViewVariables] private StoreMenu? _menu; @@ -23,6 +23,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { + _storeSystem = EntMan.System(); } protected override void Open() @@ -30,12 +31,12 @@ public sealed class StoreBoundUserInterface : BoundUserInterface base.Open(); _menu = this.CreateWindow(); - if (EntMan.TryGetComponent(Owner, out var store)) - _menu.Title = Loc.GetString(store.Name); + if (_storeSystem.TryGetStore(Owner, out var store)) + _menu.Title = Loc.GetString(store.Value.Comp.Name); _menu.OnListingButtonPressed += (_, listing) => { - SendMessage(new StoreBuyListingMessage(listing.ID)); + SendMessage(new StoreBuyListingMessage(listing.ID, EntMan.GetNetEntity(Owner))); }; _menu.OnCategoryButtonPressed += (_, category) => diff --git a/Content.Client/Stylesheets/Stylesheets/NanotrasenStylesheet.Palettes.cs b/Content.Client/Stylesheets/Stylesheets/NanotrasenStylesheet.Palettes.cs index df190154f8..097d54d581 100644 --- a/Content.Client/Stylesheets/Stylesheets/NanotrasenStylesheet.Palettes.cs +++ b/Content.Client/Stylesheets/Stylesheets/NanotrasenStylesheet.Palettes.cs @@ -2,7 +2,7 @@ namespace Content.Client.Stylesheets.Stylesheets; -public sealed partial class NanotrasenStylesheet +public partial class NanotrasenStylesheet { public override ColorPalette PrimaryPalette => Palettes.Navy; public override ColorPalette SecondaryPalette => Palettes.Slate; diff --git a/Content.Client/SubFloor/TrayScannerSystem.cs b/Content.Client/SubFloor/TrayScannerSystem.cs index 4c67890f6a..a23d67a98b 100644 --- a/Content.Client/SubFloor/TrayScannerSystem.cs +++ b/Content.Client/SubFloor/TrayScannerSystem.cs @@ -20,6 +20,8 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly TrayScanRevealSystem _trayScanReveal = default!; + [Dependency] private readonly EntityQuery _trayScannerQuery = default!; + [Dependency] private readonly EntityQuery _subFloorHideQuery = default!; private const string TRayAnimationKey = "trays"; private const double AnimationLength = 0.3; @@ -35,16 +37,14 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem // TODO: Multiple viewports or w/e var player = _player.LocalEntity; - var xformQuery = GetEntityQuery(); - if (!xformQuery.TryGetComponent(player, out var playerXform)) + if (!TryComp(player, out TransformComponent? playerXform)) return; - var playerPos = _transform.GetWorldPosition(playerXform, xformQuery); + var playerPos = _transform.GetWorldPosition(playerXform); var playerMap = playerXform.MapID; var range = 0f; HashSet> inRange; - var scannerQuery = GetEntityQuery(); // TODO: Should probably sub to player attached changes / inventory changes but inventory's // API is extremely skrungly. If this ever shows up on dottrace ping me and laugh. @@ -57,7 +57,7 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem { foreach (var ent in slot.ContainedEntities) { - if (!scannerQuery.TryGetComponent(ent, out var sneakScanner) || !sneakScanner.Enabled) + if (!_trayScannerQuery.TryGetComponent(ent, out var sneakScanner) || !sneakScanner.Enabled) continue; canSee = true; @@ -71,7 +71,7 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity)) continue; - if (!scannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled) + if (!_trayScannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled) continue; range = MathF.Max(heldScanner.Range, range); @@ -93,13 +93,12 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem } var revealedQuery = AllEntityQuery(); - var subfloorQuery = GetEntityQuery(); while (revealedQuery.MoveNext(out var uid, out _, out var sprite)) { // Revealing // Add buffer range to avoid flickers. - if (subfloorQuery.TryGetComponent(uid, out var subfloor) && + if (_subFloorHideQuery.TryGetComponent(uid, out var subfloor) && inRange.Contains((uid, subfloor))) { // Due to the fact client is predicting this server states will reset it constantly diff --git a/Content.Client/UserInterface/BuiPreTickUpdateSystem.cs b/Content.Client/UserInterface/BuiPreTickUpdateSystem.cs index 330cb51dcc..a25d89543d 100644 --- a/Content.Client/UserInterface/BuiPreTickUpdateSystem.cs +++ b/Content.Client/UserInterface/BuiPreTickUpdateSystem.cs @@ -33,15 +33,7 @@ public sealed class BuiPreTickUpdateSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = null!; [Dependency] private readonly UserInterfaceSystem _uiSystem = null!; [Dependency] private readonly IGameTiming _gameTiming = null!; - - private EntityQuery _userQuery; - - public override void Initialize() - { - base.Initialize(); - - _userQuery = GetEntityQuery(); - } + [Dependency] private readonly EntityQuery _userQuery = default!; public void RunUpdates() { diff --git a/Content.Client/UserInterface/Controls/RadialMenu.cs b/Content.Client/UserInterface/Controls/RadialMenu.cs index 959a60ef4f..0cc207dd89 100644 --- a/Content.Client/UserInterface/Controls/RadialMenu.cs +++ b/Content.Client/UserInterface/Controls/RadialMenu.cs @@ -228,7 +228,6 @@ public class RadialMenu : BaseWindow /// Base class for radial menu buttons. Excludes all actions except clicks and alt-clicks /// from interactions. /// -[Virtual] public abstract class RadialMenuButtonBase : BaseButton { /// diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs index 2b43f2397d..d55b4acf55 100644 --- a/Content.Client/UserInterface/Controls/SlotControl.cs +++ b/Content.Client/UserInterface/Controls/SlotControl.cs @@ -9,7 +9,6 @@ using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Controls { - [Virtual] public abstract class SlotControl : Control, IEntityControl { public static int DefaultButtonSize = 64; diff --git a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotUIContainer.cs b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotUIContainer.cs index 8094a77b6b..cd18c3e696 100644 --- a/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotUIContainer.cs +++ b/Content.Client/UserInterface/Systems/Inventory/Controls/ItemSlotUIContainer.cs @@ -11,7 +11,6 @@ public interface IItemslotUIContainer public bool TryAddButton(SlotControl control); } -[Virtual] public abstract class ItemSlotUIContainer : GridContainer, IItemslotUIContainer where T : SlotControl { private readonly Dictionary _buttons = new(); diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index 77b8531888..e0bf99d1cb 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -34,6 +34,7 @@ namespace Content.Client.Verbs [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly EntityQuery _spriteQuery = default!; private float _lookupSize; @@ -159,10 +160,9 @@ namespace Content.Client.Verbs if (container == null && (visibility & MenuVisibility.InContainer) == 0) return entities.Count != 0; - var spriteQuery = GetEntityQuery(); for (var i = entities.Count - 1; i >= 0; i--) { - if (!spriteQuery.TryGetComponent(entities[i], out var spriteComponent) || !spriteComponent.Visible) + if (!_spriteQuery.TryGetComponent(entities[i], out var spriteComponent) || !spriteComponent.Visible) entities.RemoveSwap(i); } diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index 8971508969..c792910fcf 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -32,7 +32,7 @@ public sealed partial class MeleeWeaponSystem if (localPos == Vector2.Zero || animation == null) return; - if (!_xformQuery.TryGetComponent(user, out var userXform) || userXform.MapID == MapId.Nullspace) + if (!TryComp(user, out TransformComponent? userXform) || userXform.MapID == MapId.Nullspace) return; var animationUid = Spawn(animation, userXform.Coordinates); @@ -64,7 +64,7 @@ public sealed partial class MeleeWeaponSystem } _sprite.SetRotation((animationUid, sprite), localPos.ToWorldAngle()); - var xform = _xformQuery.GetComponent(animationUid); + var xform = Transform(animationUid); TrackUserComponent track; switch (arcComponent.Animation) diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 420e18748f..5ecbaf63b8 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -35,14 +35,12 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - private EntityQuery _xformQuery; - private const string MeleeLungeKey = "melee-lunge"; public override void Initialize() { base.Initialize(); - _xformQuery = GetEntityQuery(); + SubscribeNetworkEvent(OnMeleeLunge); UpdatesOutsidePrediction = true; } @@ -177,7 +175,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem private void ClientHeavyAttack(EntityUid user, EntityCoordinates coordinates, EntityUid meleeUid, MeleeWeaponComponent component) { // Only run on first prediction to avoid the potential raycast entities changing. - if (!_xformQuery.TryGetComponent(user, out var userXform) || + if (!TryComp(user, out TransformComponent? userXform) || !Timing.IsFirstTimePredicted) { return; diff --git a/Content.Client/Weather/WeatherSystem.cs b/Content.Client/Weather/WeatherSystem.cs index 4b63c05991..a7d8343fb2 100644 --- a/Content.Client/Weather/WeatherSystem.cs +++ b/Content.Client/Weather/WeatherSystem.cs @@ -20,19 +20,15 @@ public sealed class WeatherSystem : SharedWeatherSystem [Dependency] private readonly MapSystem _mapSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - private EntityQuery _audioQuery; - private EntityQuery _gridQuery; - private EntityQuery _roofQuery; + [Dependency] private readonly EntityQuery _audioQuery = default!; + [Dependency] private readonly EntityQuery _gridQuery = default!; + [Dependency] private readonly EntityQuery _roofQuery = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentShutdown); - - _audioQuery = GetEntityQuery(); - _gridQuery = GetEntityQuery(); - _roofQuery = GetEntityQuery(); } private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) diff --git a/Content.IntegrationTests/Fixtures/Attributes/TrackingIssueAttribute.cs b/Content.IntegrationTests/Fixtures/Attributes/TrackingIssueAttribute.cs index 02f9e4f7e4..4acbe8e87b 100644 --- a/Content.IntegrationTests/Fixtures/Attributes/TrackingIssueAttribute.cs +++ b/Content.IntegrationTests/Fixtures/Attributes/TrackingIssueAttribute.cs @@ -28,7 +28,7 @@ public sealed class TrackingIssueAttribute : PropertyAttribute "github.com" ]; - private static readonly Regex GithubStyleIssueMatch = new(@"^\/[a-z\-\$\#]*\/[a-z\-\$\#]*\/(issues|pulls)\/\d*$", + private static readonly Regex GithubStyleIssueMatch = new(@"^\/[a-z\d\-\$\#]*\/[a-z\d\-\$\#]*\/(issues|pulls)\/\d*$", RegexOptions.Compiled | RegexOptions.NonBacktracking | RegexOptions.IgnoreCase); public TrackingIssueAttribute([StringSyntax(StringSyntaxAttribute.Uri)] string url) : base(url) diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index b457d4a40b..189e37fa39 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -25,7 +25,6 @@ public static partial class PoolManager (CCVars.ArrivalsShuttles.Name, "false"), (CCVars.EmergencyShuttleEnabled.Name, "false"), (CCVars.ProcgenPreload.Name, "false"), - (CCVars.WorldgenEnabled.Name, "false"), (CCVars.GatewayGeneratorEnabled.Name, "false"), (CCVars.GameDummyTicker.Name, "true"), (CCVars.GameLobbyEnabled.Name, "false"), diff --git a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs index a0c8c775b1..0279673910 100644 --- a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs +++ b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs @@ -1,16 +1,17 @@ +#nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Access { - [TestFixture] [TestOf(typeof(AccessReaderComponent))] - public sealed class AccessReaderTest + public sealed class AccessReaderTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -21,91 +22,85 @@ namespace Content.IntegrationTests.Tests.Access - type: AccessReader "; + [SidedDependency(Side.Server)] private readonly AccessReaderSystem _system = null!; + [Test] + [RunOnSide(Side.Server)] public async Task TestTags() { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - var entityManager = server.ResolveDependency(); + var ent = SSpawn("TestAccessReader"); + var reader = new Entity(ent, SComp(ent)); - await server.WaitAssertion(() => + // test empty + Assert.Multiple(() => { - var system = entityManager.System(); - var ent = entityManager.SpawnEntity("TestAccessReader", MapCoordinates.Nullspace); - var reader = new Entity(ent, entityManager.GetComponent(ent)); - - // test empty - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "Bar" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.True); - }); - - // test deny - system.AddDenyTag(reader, "A"); - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "A", "Foo" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.True); - }); - system.ClearDenyTags(reader); - - // test one list - system.TryAddAccess(reader, "A"); - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); - }); - system.TryClearAccesses(reader); - - // test one list - two items - system.TryAddAccess(reader, new HashSet> { "A", "B" }); - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); - }); - system.TryClearAccesses(reader); - - // test two list - var accesses = new List>>() { - new HashSet> () { "A" }, - new HashSet> () { "B", "C" } - }; - system.TryAddAccesses(reader, accesses); - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "C", "B" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "C", "B", "A" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); - }); - system.TryClearAccesses(reader); - - // test deny list - system.TryAddAccess(reader, new HashSet> { "A" }); - system.AddDenyTag(reader, "B"); - Assert.Multiple(() => - { - Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); - Assert.That(system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.False); - Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); - }); - system.TryClearAccesses(reader); - system.ClearDenyTags(reader); + Assert.That(_system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "Bar" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.True); }); - await pair.CleanReturnAsync(); + + // test deny + _system.AddDenyTag(reader, "A"); + Assert.Multiple(() => + { + Assert.That(_system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A", "Foo" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.True); + }); + _system.ClearDenyTags(reader); + + // test one list + _system.TryAddAccess(reader, "A"); + Assert.Multiple(() => + { + Assert.That(_system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); + }); + _system.TryClearAccesses(reader); + + // test one list - two items + _system.TryAddAccess(reader, new HashSet> { "A", "B" }); + Assert.Multiple(() => + { + Assert.That(_system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); + }); + _system.TryClearAccesses(reader); + + // test two list + var accesses = new List>>() { + new HashSet> () { "A" }, + new HashSet> () { "B", "C" } + }; + _system.TryAddAccesses(reader, accesses); + Assert.Multiple(() => + { + Assert.That(_system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "C", "B" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "C", "B", "A" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); + }); + _system.TryClearAccesses(reader); + + // test deny list + _system.TryAddAccess(reader, new HashSet> { "A" }); + _system.AddDenyTag(reader, "B"); + Assert.Multiple(() => + { + Assert.That(_system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); + Assert.That(_system.AreAccessTagsAllowed(new List> { "B" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.False); + Assert.That(_system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); + }); + _system.TryClearAccesses(reader); + _system.ClearDenyTags(reader); } } diff --git a/Content.IntegrationTests/Tests/Actions/ActionPvsDetachTest.cs b/Content.IntegrationTests/Tests/Actions/ActionPvsDetachTest.cs index 45addff00b..9680e48939 100644 --- a/Content.IntegrationTests/Tests/Actions/ActionPvsDetachTest.cs +++ b/Content.IntegrationTests/Tests/Actions/ActionPvsDetachTest.cs @@ -1,4 +1,7 @@ +#nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; using Content.Shared.Actions; using Content.Shared.Eye; using Robust.Server.GameObjects; @@ -7,15 +10,18 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Actions; [TestFixture] -public sealed class ActionPvsDetachTest +public sealed class ActionPvsDetachTest : GameTest { + [SidedDependency(Side.Server)] private readonly SharedActionsSystem _sActionsSys = null!; + [SidedDependency(Side.Client)] private readonly SharedActionsSystem _cActionsSys = null!; + [Test] public async Task TestActionDetach() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); - var (server, client) = pair; - var sys = server.System(); - var cSys = client.System(); + var pair = Pair; + var (server, client) = (Server, Client); + var sys = _sActionsSys; + var cSys = _cActionsSys; // Spawn mob that has some actions EntityUid ent = default; @@ -60,6 +66,5 @@ public sealed class ActionPvsDetachTest Assert.That(cSys.GetActions(cEnt).Count(), Is.EqualTo(initActions)); await server.WaitPost(() => server.EntMan.DeleteEntity(map.MapUid)); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs index 0dbec0c83a..1fa005496e 100644 --- a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs +++ b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs @@ -1,4 +1,6 @@ +#nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Actions; using Content.Shared.Actions.Components; using Content.Shared.CombatMode; @@ -11,15 +13,17 @@ namespace Content.IntegrationTests.Tests.Actions; /// This tests checks that actions properly get added to an entity's actions component.. /// [TestFixture] -public sealed class ActionsAddedTest +public sealed class ActionsAddedTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings { Connected = true, DummyTicker = false }; + // TODO add magboot test (inventory action) // TODO add ghost toggle-fov test (client-side action) [Test] public async Task TestCombatActionsAdded() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false }); + var pair = Pair; var server = pair.Server; var client = pair.Client; var sEntMan = server.ResolveDependency(); @@ -67,7 +71,5 @@ public sealed class ActionsAddedTest // required, because integration tests do not respect the [NonSerialized] attribute and will simply events by reference. Assert.That(ReferenceEquals(sAct.Comp, cAct.Comp), Is.False); Assert.That(ReferenceEquals(sQuery.GetComponent(sAct).Event, cQuery.GetComponent(cAct).Event), Is.False); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs index 772af337a1..71cb0fd7ed 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +#nullable enable +using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Administration.Logs; using Content.Server.Database; using Content.Server.GameTicking; @@ -12,9 +14,9 @@ namespace Content.IntegrationTests.Tests.Administration.Logs; [TestFixture] [TestOf(typeof(AdminLogSystem))] -public sealed class AddTests +public sealed class AddTests : GameTest { - public static PoolSettings LogTestSettings = new() + public override PoolSettings PoolSettings => new() { AdminLogsEnabled = true, DummyTicker = false, @@ -24,7 +26,7 @@ public sealed class AddTests [Test] public async Task AddAndGetSingleLog() { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -33,7 +35,7 @@ public sealed class AddTests var guid = Guid.NewGuid(); await pair.CreateTestMap(); - var coordinates = pair.TestMap.GridCoords; + var coordinates = pair.TestMap!.GridCoords; await server.WaitPost(() => { var entity = sEntities.SpawnEntity(null, coordinates); @@ -62,14 +64,12 @@ public sealed class AddTests return false; }); - - await pair.CleanReturnAsync(); } [Test] public async Task AddAndGetUnformattedLog() { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); + var pair = Pair; var server = pair.Server; var sDatabase = server.ResolveDependency(); @@ -127,15 +127,13 @@ public sealed class AddTests json.Dispose(); } - - await pair.CleanReturnAsync(); } [Test] [TestCase(500)] public async Task BulkAddLogs(int amount) { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -158,14 +156,12 @@ public sealed class AddTests var messages = await sAdminLogSystem.CurrentRoundLogs(); return messages.Count >= amount; }); - - await pair.CleanReturnAsync(); } [Test] public async Task AddPlayerSessionLog() { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); + var pair = Pair; var server = pair.Server; var sPlayers = server.ResolveDependency(); @@ -195,20 +191,91 @@ public sealed class AddTests Assert.That(logs.First().Players, Does.Contain(playerGuid)); return true; }); - await pair.CleanReturnAsync(); } + [Test] + public async Task DuplicatePlayerDoesNotThrowTest() + { + var pair = Pair; + var server = pair.Server; + + var sPlayers = server.ResolveDependency(); + var sAdminLogSystem = server.ResolveDependency(); + + var guid = Guid.NewGuid(); + + await server.WaitPost(() => + { + var player = sPlayers.Sessions.Single(); + + sAdminLogSystem.Add(LogType.Unknown, $"{player} {player} test log: {guid}"); + }); + + await PoolManager.WaitUntil(server, async () => + { + var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter + { + Search = guid.ToString() + }); + + if (logs.Count == 0) + { + return false; + } + + return true; + }); + } + + [Test] + public async Task DuplicatePlayerIdDoesNotThrowTest() + { + var pair = Pair; + var server = pair.Server; + + var sPlayers = server.ResolveDependency(); + + var sAdminLogSystem = server.ResolveDependency(); + + var guid = Guid.NewGuid(); + + await server.WaitPost(() => + { + var player = sPlayers.Sessions.Single(); + + sAdminLogSystem.Add(LogType.Unknown, $"{player:first} {player:second} test log: {guid}"); + }); + + await PoolManager.WaitUntil(server, async () => + { + var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter + { + Search = guid.ToString() + }); + + if (logs.Count == 0) + { + return false; + } + + return true; + }); + } +} + +public sealed class PreRoundAddTests : GameTest +{ + public override PoolSettings PoolSettings => new PoolSettings + { + Dirty = true, + InLobby = true, + AdminLogsEnabled = true + }; + [Test] public async Task PreRoundAddAndGetSingle() { - var setting = new PoolSettings - { - Dirty = true, - InLobby = true, - AdminLogsEnabled = true - }; - - await using var pair = await PoolManager.GetServerClient(setting); + var pair = Pair; var server = pair.Server; var sDatabase = server.ResolveDependency(); @@ -262,81 +329,6 @@ public sealed class AddTests json.Dispose(); } - await pair.CleanReturnAsync(); } - [Test] - public async Task DuplicatePlayerDoesNotThrowTest() - { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); - var server = pair.Server; - - var sPlayers = server.ResolveDependency(); - var sAdminLogSystem = server.ResolveDependency(); - - var guid = Guid.NewGuid(); - - await server.WaitPost(() => - { - var player = sPlayers.Sessions.Single(); - - sAdminLogSystem.Add(LogType.Unknown, $"{player} {player} test log: {guid}"); - }); - - await PoolManager.WaitUntil(server, async () => - { - var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter - { - Search = guid.ToString() - }); - - if (logs.Count == 0) - { - return false; - } - - return true; - }); - - await pair.CleanReturnAsync(); - Assert.Pass(); - } - - [Test] - public async Task DuplicatePlayerIdDoesNotThrowTest() - { - await using var pair = await PoolManager.GetServerClient(LogTestSettings); - var server = pair.Server; - - var sPlayers = server.ResolveDependency(); - - var sAdminLogSystem = server.ResolveDependency(); - - var guid = Guid.NewGuid(); - - await server.WaitPost(() => - { - var player = sPlayers.Sessions.Single(); - - sAdminLogSystem.Add(LogType.Unknown, $"{player:first} {player:second} test log: {guid}"); - }); - - await PoolManager.WaitUntil(server, async () => - { - var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter - { - Search = guid.ToString() - }); - - if (logs.Count == 0) - { - return false; - } - - return true; - }); - - await pair.CleanReturnAsync(); - Assert.Pass(); - } } diff --git a/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs index 6f907f425e..2517defe96 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Administration.Logs; using Content.Shared.Administration.Logs; using Content.Shared.Database; @@ -7,14 +8,21 @@ namespace Content.IntegrationTests.Tests.Administration.Logs; [TestFixture] [TestOf(typeof(AdminLogSystem))] -public sealed class FilterTests +public sealed class FilterTests : GameTest { + public override PoolSettings PoolSettings => new() + { + AdminLogsEnabled = true, + DummyTicker = false, + Connected = true + }; + [Test] [TestCase(DateOrder.Ascending)] [TestCase(DateOrder.Descending)] public async Task Date(DateOrder order) { - await using var pair = await PoolManager.GetServerClient(AddTests.LogTestSettings); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -96,6 +104,5 @@ public sealed class FilterTests return firstFound && secondFound; }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs index 5a58757d53..55b36ebae1 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Administration.Logs; using Content.Server.GameTicking; using Content.Shared.Database; @@ -11,12 +12,19 @@ namespace Content.IntegrationTests.Tests.Administration.Logs; [TestFixture] [TestOf(typeof(AdminLogSystem))] -public sealed class QueryTests +public sealed class QueryTests : GameTest { + public override PoolSettings PoolSettings => new() + { + AdminLogsEnabled = true, + DummyTicker = false, + Connected = true + }; + [Test] public async Task QuerySingleLog() { - await using var pair = await PoolManager.GetServerClient(AddTests.LogTestSettings); + var pair = Pair; var server = pair.Server; var sSystems = server.ResolveDependency(); @@ -55,7 +63,5 @@ public sealed class QueryTests return false; }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs b/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs index b74c35ba11..2eaa073819 100644 --- a/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Atmos.Monitor; using Robust.Shared.Prototypes; @@ -5,7 +6,7 @@ namespace Content.IntegrationTests.Tests.Atmos { [TestFixture] [TestOf(typeof(AtmosAlarmThreshold))] - public sealed class AlarmThresholdTest + public sealed class AlarmThresholdTest : GameTest { private const string AlarmThresholdTestDummyId = "AlarmThresholdTestDummy"; @@ -26,7 +27,7 @@ namespace Content.IntegrationTests.Tests.Atmos [Test] public async Task TestAlarmThreshold() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var prototypeManager = server.ResolveDependency(); @@ -136,7 +137,6 @@ namespace Content.IntegrationTests.Tests.Atmos Assert.That(alarmType, Is.EqualTo(AtmosAlarmType.Normal)); } }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs index 6481e377c9..d44bfe7ae4 100644 --- a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Atmos.Prototypes; @@ -6,12 +7,12 @@ using Content.Shared.Atmos.Prototypes; namespace Content.IntegrationTests.Tests.Atmos; [TestOf(typeof(Atmospherics))] -public sealed class ConstantsTest +public sealed class ConstantsTest : GameTest { [Test] public async Task TotalGasesTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.EntMan; var protoManager = server.ProtoMan; @@ -34,9 +35,6 @@ public sealed class ConstantsTest // enum mapping gases to their Id Assert.That(Enum.GetValues(), Has.Length.EqualTo(Atmospherics.TotalNumberOfGases), $"Gas enum size is not equal to TotalNumberOfGases."); - // localized abbreviations for UI purposes - Assert.That(Atmospherics.GasAbbreviations, Has.Count.EqualTo(Atmospherics.TotalNumberOfGases), - $"GasAbbreviations size is not equal to TotalNumberOfGases."); // the ID for each gas has to correspond to a value in the Gas enum (converted to a string) foreach (var gas in gasProtos) @@ -45,7 +43,6 @@ public sealed class ConstantsTest } }); }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs b/Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs index 07caf447bd..eda9061281 100644 --- a/Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/GasArrayTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Robust.Shared.GameObjects; @@ -8,7 +9,7 @@ namespace Content.IntegrationTests.Tests.Atmos; [TestFixture] [TestOf(typeof(Atmospherics))] -public sealed class GasArrayTest +public sealed class GasArrayTest : GameTest { private const string GasTankTestDummyId = "GasTankTestDummy"; @@ -42,7 +43,7 @@ public sealed class GasArrayTest [Test] public async Task TestGasArrayDeserialization() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var compFactory = server.ResolveDependency(); @@ -80,6 +81,5 @@ public sealed class GasArrayTest } }); }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs b/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs index 1cb8fd8b6f..70bbd0ac0c 100644 --- a/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/GasMixtureTest.cs @@ -1,4 +1,5 @@ -using Content.Server.Atmos; +using Content.IntegrationTests.Fixtures; +using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Robust.Shared.GameObjects; @@ -7,12 +8,12 @@ namespace Content.IntegrationTests.Tests.Atmos { [TestFixture] [TestOf(typeof(GasMixture))] - public sealed class GasMixtureTest + public sealed class GasMixtureTest : GameTest { [Test] public async Task TestMerge() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var atmosphereSystem = server.ResolveDependency().GetEntitySystem(); @@ -56,8 +57,6 @@ namespace Content.IntegrationTests.Tests.Atmos Assert.That(a.GetMoles(Gas.Oxygen), Is.EqualTo(50)); }); }); - - await pair.CleanReturnAsync(); } [Test] @@ -69,7 +68,7 @@ namespace Content.IntegrationTests.Tests.Atmos [TestCase(Atmospherics.BreathPercentage)] public async Task RemoveRatio(float ratio) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitAssertion(() => @@ -103,8 +102,6 @@ namespace Content.IntegrationTests.Tests.Atmos Assert.That(a.GetMoles(Gas.Nitrogen), Is.EqualTo(100 - b.GetMoles(Gas.Nitrogen))); }); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs index 45ccddfad9..9b43e85396 100644 --- a/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/GridJoinTest.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.IntegrationTests.Fixtures; using Content.Server.Atmos.Piping.EntitySystems; using Content.Shared.Atmos.Components; using Robust.Shared.GameObjects; @@ -6,14 +7,14 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Atmos; [TestFixture] -public sealed class GridJoinTest +public sealed class GridJoinTest : GameTest { private const string CanisterProtoId = "AirCanister"; [Test] public async Task TestGridJoinAtmosphere() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; @@ -46,7 +47,5 @@ public sealed class GridJoinTest // Make sure that the canister is now properly tracked as on-grid Assert.That(atmosDeviceSystem.IsJoinedOffGrid(canisterEnt), Is.False); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Atmos/SharedGasSpecificHeatsTest.cs b/Content.IntegrationTests/Tests/Atmos/SharedGasSpecificHeatsTest.cs index ac80f4a105..7269b1b473 100644 --- a/Content.IntegrationTests/Tests/Atmos/SharedGasSpecificHeatsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/SharedGasSpecificHeatsTest.cs @@ -37,7 +37,7 @@ public sealed class SharedGasSpecificHeatsTest { Connected = true, }; - _pair = await PoolManager.GetServerClient(poolSettings); + _pair = await PoolManager.GetServerClient(poolSettings, new NUnitTestContextWrap(TestContext.CurrentContext, TestContext.Out)); _sEntMan = Server.ResolveDependency(); _cEntMan = Client.ResolveDependency(); @@ -62,12 +62,12 @@ public sealed class SharedGasSpecificHeatsTest var clientSpecificHeats = Array.Empty(); await Server.WaitPost(delegate { - serverSpecificHeats = _sAtmos.GasSpecificHeats; + serverSpecificHeats = _sAtmos.GasMolarHeatCapacities; }); await Client.WaitPost(delegate { - clientSpecificHeats = _cAtmos.GasSpecificHeats; + clientSpecificHeats = _cAtmos.GasMolarHeatCapacities; }); Assert.That(serverSpecificHeats, diff --git a/Content.IntegrationTests/Tests/Body/GibbingTest.cs b/Content.IntegrationTests/Tests/Body/GibbingTest.cs index a727487940..a3f3c1bcc3 100644 --- a/Content.IntegrationTests/Tests/Body/GibbingTest.cs +++ b/Content.IntegrationTests/Tests/Body/GibbingTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Body; using Content.Shared.Gibbing; using Robust.Shared.GameObjects; @@ -6,7 +7,7 @@ namespace Content.IntegrationTests.Tests.Body; [TestFixture] [TestOf(typeof(GibbableOrganSystem))] -public sealed class GibletTest +public sealed class GibletTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -33,7 +34,7 @@ public sealed class GibletTest [Test] public async Task GibletCountTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -54,7 +55,5 @@ public sealed class GibletTest Assert.That(entityManager.HasComponent(giblet), Is.True); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Body/HandOrganTest.cs b/Content.IntegrationTests/Tests/Body/HandOrganTest.cs index 560dfbf64a..3d885186f1 100644 --- a/Content.IntegrationTests/Tests/Body/HandOrganTest.cs +++ b/Content.IntegrationTests/Tests/Body/HandOrganTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Body; using Content.Shared.Hands.Components; using Robust.Shared.Containers; @@ -9,7 +10,7 @@ namespace Content.IntegrationTests.Tests.Body; [TestFixture] [TestOf(typeof(HandOrganSystem))] -public sealed class HandOrganTest +public sealed class HandOrganTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -46,7 +47,7 @@ public sealed class HandOrganTest [Test] public async Task HandInsertionAndRemovalTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -81,7 +82,5 @@ public sealed class HandOrganTest Assert.That(hands.Count, Is.EqualTo(expectedCount)); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs index d9cce764ab..444bd159f3 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs @@ -12,7 +12,7 @@ public sealed partial class BuckleTest [Test] public async Task BuckleInteractUnbuckleOther() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -55,14 +55,12 @@ public sealed partial class BuckleTest Assert.That(strap.BuckledEntities, Does.Not.Contain(victim)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task BuckleInteractBuckleUnbuckleSelf() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -102,7 +100,5 @@ public sealed partial class BuckleTest Assert.That(strap.BuckledEntities, Does.Not.Contain(user)); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index b42f42922a..9c12da3fe8 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Shared.Buckle; using Content.Shared.ActionBlocker; using Content.Shared.Buckle.Components; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.Buckle [TestFixture] [TestOf(typeof(BuckleComponent))] [TestOf(typeof(StrapComponent))] - public sealed partial class BuckleTest + public sealed partial class BuckleTest : GameTest { private const string BuckleDummyId = "BuckleDummy"; private const string StrapDummyId = "StrapDummy"; @@ -50,7 +51,7 @@ namespace Content.IntegrationTests.Tests.Buckle [Test] public async Task BuckleUnbuckleCooldownRangeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -228,14 +229,12 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.That(strap.BuckledEntities, Is.Empty); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task BuckledDyingDropItemsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -298,14 +297,12 @@ namespace Content.IntegrationTests.Tests.Buckle buckleSystem.Unbuckle(human, human); Assert.That(buckle.Buckled, Is.False); }); - - await pair.CleanReturnAsync(); } [Test] public async Task ForceUnbuckleBuckleTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -373,7 +370,6 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.That(buckle.Buckled); }); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index df85e61550..0c8fc2f0fb 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.Nutrition.Components; @@ -17,7 +18,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class CargoTest +public sealed class CargoTest : GameTest { private static readonly HashSet> Ignored = [ @@ -28,7 +29,7 @@ public sealed class CargoTest [Test] public async Task NoCargoOrderArbitrage() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -54,13 +55,11 @@ public sealed class CargoTest } }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task NoCargoBountyArbitrageTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -94,14 +93,12 @@ public sealed class CargoTest mapSystem.DeleteMap(mapId); }); - - await pair.CleanReturnAsync(); } [Test] public async Task NoStaticPriceAndStackPrice() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoManager = server.ProtoMan; @@ -133,8 +130,6 @@ public sealed class CargoTest } } }); - - await pair.CleanReturnAsync(); } /// @@ -144,7 +139,7 @@ public sealed class CargoTest [Test] public async Task NoSliceableBountyArbitrageTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -209,8 +204,6 @@ public sealed class CargoTest } mapSystem.DeleteMap(mapId); }); - - await pair.CleanReturnAsync(); } [TestPrototypes] @@ -233,7 +226,7 @@ public sealed class CargoTest [Test] public async Task StackPrice() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -245,14 +238,12 @@ public sealed class CargoTest var price = priceSystem.GetPrice(ent); Assert.That(price, Is.EqualTo(100.0)); }); - - await pair.CleanReturnAsync(); } [Test] public async Task MobPrice() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var componentFactory = pair.Server.ResolveDependency(); @@ -266,7 +257,5 @@ public sealed class CargoTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Chemistry/ReagentDataTest.cs b/Content.IntegrationTests/Tests/Chemistry/ReagentDataTest.cs index 59948c8b17..9ac45549a2 100644 --- a/Content.IntegrationTests/Tests/Chemistry/ReagentDataTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/ReagentDataTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Tests.Interaction; using Content.Shared.Chemistry.Reagent; using Robust.Shared.Reflection; @@ -8,12 +9,12 @@ namespace Content.IntegrationTests.Tests.Chemistry; [TestFixture] [TestOf(typeof(ReagentData))] -public sealed class ReagentDataTest +public sealed class ReagentDataTest : GameTest { [Test] public async Task ReagentDataIsSerializable() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var reflection = pair.Server.ResolveDependency(); Assert.Multiple(() => @@ -24,7 +25,5 @@ public sealed class ReagentDataTest Assert.That(instance.HasCustomAttribute(), $"{instance} must have the serializable attribute."); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs index 5b5829d386..f188fd0f66 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reaction; @@ -9,7 +10,7 @@ namespace Content.IntegrationTests.Tests.Chemistry; [TestFixture] [TestOf(typeof(ChemicalReactionSystem))] -public sealed class SolutionRoundingTest +public sealed class SolutionRoundingTest : GameTest { // This test tests two things: // * A rounding error in reaction code while I was making chloral hydrate @@ -72,7 +73,7 @@ public sealed class SolutionRoundingTest [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -121,7 +122,5 @@ public sealed class SolutionRoundingTest Is.EqualTo((FixedPoint2) 30)); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs index 6f50f54103..ffaca012a3 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.FixedPoint; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.Chemistry; // reactions can change this assumption [TestFixture] [TestOf(typeof(SharedSolutionContainerSystem))] -public sealed class SolutionSystemTests +public sealed class SolutionSystemTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -53,7 +54,7 @@ public sealed class SolutionSystemTests [Test] public async Task TryAddTwoNonReactiveReagent() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -88,8 +89,6 @@ public sealed class SolutionSystemTests Assert.That(oil, Is.EqualTo(oilQuantity)); }); }); - - await pair.CleanReturnAsync(); } // This test mimics current behavior @@ -97,7 +96,7 @@ public sealed class SolutionSystemTests [Test] public async Task TryAddTooMuchNonReactiveReagent() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -133,15 +132,13 @@ public sealed class SolutionSystemTests Assert.That(oil, Is.EqualTo(FixedPoint2.Zero)); }); }); - - await pair.CleanReturnAsync(); } // Unlike TryAddSolution this adds and two solution without then splits leaving only threshold in original [Test] public async Task TryMixAndOverflowTooMuchReagent() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; @@ -188,15 +185,13 @@ public sealed class SolutionSystemTests Assert.That(oilOverFlow, Is.EqualTo(oilQuantity - oilMix)); }); }); - - await pair.CleanReturnAsync(); } // TryMixAndOverflow will fail if Threshold larger than MaxVolume [Test] public async Task TryMixAndOverflowTooBigOverflow() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -226,14 +221,12 @@ public sealed class SolutionSystemTests .TryMixAndOverflow(solutionEnt.Value, oilAdded, threshold, out _), Is.False); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestTemperatureCalculations() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); const float temp = 100.0f; @@ -264,7 +257,5 @@ public sealed class SolutionSystemTests solutionOne.AddSolution(solutionTwo, protoMan); Assert.That(solutionOne.GetHeatCapacity(protoMan) * solutionOne.Temperature, Is.EqualTo(thermalEnergyOne + thermalEnergyTwo)); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs index 0037670556..e720387425 100644 --- a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs @@ -5,6 +5,7 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Shared.Chemistry.EntitySystems; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.Chemistry { [TestFixture] [TestOf(typeof(ReactionPrototype))] - public sealed class TryAllReactionsTest + public sealed class TryAllReactionsTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -33,7 +34,7 @@ namespace Content.IntegrationTests.Tests.Chemistry [Description("Tries an individual reaction to see if it succeeds.")] public async Task TryReaction(string reaction) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -134,8 +135,6 @@ namespace Content.IntegrationTests.Tests.Chemistry server.EntMan.DeleteEntity(beaker); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Cleanup/EuiManagerTest.cs b/Content.IntegrationTests/Tests/Cleanup/EuiManagerTest.cs index e2bff03501..037224fec5 100644 --- a/Content.IntegrationTests/Tests/Cleanup/EuiManagerTest.cs +++ b/Content.IntegrationTests/Tests/Cleanup/EuiManagerTest.cs @@ -1,35 +1,37 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Administration.UI; using Content.Server.EUI; using Robust.Server.Player; namespace Content.IntegrationTests.Tests.Cleanup; -public sealed class EuiManagerTest +public sealed class EuiManagerTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + Connected = true, + Dirty = true + }; + [Test] + [Retry(2)] + // Even though we are using the server EUI here, we actually want to see if the client EUIManager crashes public async Task EuiManagerRecycleWithOpenWindowTest() { - // Even though we are using the server EUI here, we actually want to see if the client EUIManager crashes - for (var i = 0; i < 2; i++) + var pair = Pair; + var server = pair.Server; + + var sPlayerManager = server.ResolveDependency(); + var eui = server.ResolveDependency(); + + await server.WaitAssertion(() => { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true - }); - var server = pair.Server; + var clientSession = sPlayerManager.Sessions.Single(); + var ui = new AdminAnnounceEui(); + eui.OpenEui(ui, clientSession); + }); - var sPlayerManager = server.ResolveDependency(); - var eui = server.ResolveDependency(); - - await server.WaitAssertion(() => - { - var clientSession = sPlayerManager.Sessions.Single(); - var ui = new AdminAnnounceEui(); - eui.OpenEui(ui, clientSession); - }); - await pair.CleanReturnAsync(); - } + await RunUntilSynced(); } } diff --git a/Content.IntegrationTests/Tests/ClickableTest.cs b/Content.IntegrationTests/Tests/ClickableTest.cs index aaac421ed1..fb20cd9038 100644 --- a/Content.IntegrationTests/Tests/ClickableTest.cs +++ b/Content.IntegrationTests/Tests/ClickableTest.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Client.Clickable; +using Content.IntegrationTests.Fixtures; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class ClickableTest + public sealed class ClickableTest : GameTest { private const double DirSouth = 0; private const double DirNorth = Math.PI; @@ -44,7 +45,7 @@ namespace Content.IntegrationTests.Tests [TestCase("ClickTestRotatingCornerInvisibleNoRot", 0.25f, 0.25f, DirSouthEastJustShy, 1, ExpectedResult = true)] public async Task Test(string prototype, float clickPosX, float clickPosY, double angle, float scale) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -66,7 +67,7 @@ namespace Content.IntegrationTests.Tests }); // Let client sync up. - await pair.RunTicksSync(5); + await RunUntilSynced(); var hit = false; var clientEnt = clientEntManager.GetEntity(serverEntManager.GetNetEntity(serverEnt)); @@ -89,8 +90,6 @@ namespace Content.IntegrationTests.Tests serverEntManager.DeleteEntity(serverEnt); }); - await pair.CleanReturnAsync(); - return hit; } } diff --git a/Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs b/Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs index 9edc115eee..c21bcf922a 100644 --- a/Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs @@ -1,8 +1,9 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Cloning; namespace Content.IntegrationTests.Tests.Cloning; -public sealed class CloningSettingsPrototypeTest +public sealed class CloningSettingsPrototypeTest : GameTest { /// /// Checks that the components named in every are valid components known to the server. @@ -12,7 +13,7 @@ public sealed class CloningSettingsPrototypeTest [Test] public async Task ValidatePrototypes() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; var compFactory = server.EntMan.ComponentFactory; @@ -40,7 +41,5 @@ public sealed class CloningSettingsPrototypeTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs index 3fa7e64f1a..566edd47ca 100644 --- a/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs +++ b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Maps; using Content.Shared.CCVar; using Robust.Shared.Configuration; @@ -6,7 +7,7 @@ using Robust.Shared.Console; namespace Content.IntegrationTests.Tests.Commands; [TestFixture] -public sealed class ForceMapTest +public sealed class ForceMapTest : GameTest { private const string DefaultMapName = "Empty"; private const string BadMapName = "asdf_asd-fa__sdfAsd_f"; // Hopefully no one ever names a map this... @@ -44,7 +45,7 @@ public sealed class ForceMapTest [Test] public async Task TestForceMapCommand() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; @@ -82,7 +83,5 @@ public sealed class ForceMapTest // Cleanup configManager.SetCVar(CCVars.GameMap, DefaultMapName); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs index d430325e31..eb18d8e1ae 100644 --- a/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs +++ b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Objectives; using Content.Shared.Mind; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.Player; namespace Content.IntegrationTests.Tests.Commands; -public sealed class ObjectiveCommandsTest +public sealed class ObjectiveCommandsTest : GameTest { private const string ObjectiveProtoId = "MindCommandsTestObjective"; @@ -27,6 +28,11 @@ public sealed class ObjectiveCommandsTest - type: DieCondition """; + public override PoolSettings PoolSettings => new () + { + Connected = false + }; + /// /// Creates a dummy session, and assigns it a mind, then /// tests using addobjective, lsobjectives, @@ -35,7 +41,7 @@ public sealed class ObjectiveCommandsTest [Test] public async Task AddListRemoveObjectiveTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; var playerMan = server.ResolveDependency(); @@ -66,7 +72,5 @@ public sealed class ObjectiveCommandsTest await pair.WaitCommand($"rmobjective {playerSession.Name} 0"); Assert.That(mindComp.Objectives, Is.Empty, "rmobjective failed to remove objective"); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs index 5f77af1b10..2d84eebf96 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Database; using Robust.Server.Console; using Robust.Server.Player; @@ -8,14 +9,14 @@ namespace Content.IntegrationTests.Tests.Commands { [TestFixture] [TestOf(typeof(PardonCommand))] - public sealed class PardonCommand + public sealed class PardonCommand : GameTest { private static readonly TimeSpan MarginOfError = TimeSpan.FromMinutes(1); [Test] public async Task PardonTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -148,8 +149,6 @@ namespace Content.IntegrationTests.Tests.Commands await client.WaitPost(() => netMan.ClientConnect(null!, 0, null!)); await pair.RunTicksSync(5); Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1)); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index 308f2797c4..15d93c40ae 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -1,4 +1,5 @@ -using Content.Shared.Administration.Systems; +using Content.IntegrationTests.Fixtures; +using Content.Shared.Administration.Systems; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; @@ -14,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Commands { [TestFixture] [TestOf(typeof(RejuvenateSystem))] - public sealed class RejuvenateTest + public sealed class RejuvenateTest : GameTest { private static readonly ProtoId TestDamageGroup = "Toxin"; @@ -36,7 +37,7 @@ namespace Content.IntegrationTests.Tests.Commands [Test] public async Task RejuvenateDeadTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); var prototypeManager = server.ResolveDependency(); @@ -92,7 +93,6 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(damSystem.GetTotalDamage((human, damageable)), Is.EqualTo(FixedPoint2.Zero)); }); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs index 72a05b5246..bcacc8011b 100644 --- a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.GameTicking.Commands; using Content.Shared.CCVar; @@ -10,25 +11,27 @@ namespace Content.IntegrationTests.Tests.Commands { [TestFixture] [TestOf(typeof(RestartRoundNowCommand))] - public sealed class RestartRoundNowTest + public sealed class RestartRoundNowTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + DummyTicker = false, + Dirty = true + }; + [Test] [TestCase(true)] [TestCase(false)] public async Task RestartRoundAfterStart(bool lobbyEnabled) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Dirty = true - }); + var pair = Pair; var server = pair.Server; var configManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var gameTicker = entityManager.System(); - await pair.RunTicksSync(5); + await pair.RunUntilSynced(); GameTick tickBeforeRestart = default; @@ -58,8 +61,7 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(tickBeforeRestart, Is.LessThan(tickAfterRestart)); }); - await pair.RunTicksSync(5); - await pair.CleanReturnAsync(); + await pair.RunUntilSynced(); } } } diff --git a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs index 32f1f6128e..f78bd4949e 100644 --- a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs +++ b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; @@ -21,7 +22,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Commands; [TestFixture] -public sealed class SuicideCommandTests +public sealed class SuicideCommandTests : GameTest { [TestPrototypes] @@ -57,6 +58,13 @@ public sealed class SuicideCommandTests private static readonly ProtoId CannotSuicideTag = "CannotSuicide"; private static readonly ProtoId DamageType = "Slash"; + public override PoolSettings PoolSettings => new PoolSettings + { + Connected = true, + Dirty = true, + DummyTicker = false + }; + /// /// Run the suicide command in the console /// Should successfully kill the player and ghost them @@ -64,12 +72,7 @@ public sealed class SuicideCommandTests [Test] public async Task TestSuicide() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); @@ -104,8 +107,6 @@ public sealed class SuicideCommandTests !ghostComp.CanReturnToBody); }); }); - - await pair.CleanReturnAsync(); } /// @@ -115,12 +116,7 @@ public sealed class SuicideCommandTests [Test] public async Task TestSuicideWhileDamaged() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); @@ -166,8 +162,6 @@ public sealed class SuicideCommandTests Assert.That(damageableSystem.GetTotalDamage(player), Is.EqualTo(lethalDamageThreshold)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -177,12 +171,7 @@ public sealed class SuicideCommandTests [Test] public async Task TestSuicideWhenCannotSuicide() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); @@ -217,8 +206,6 @@ public sealed class SuicideCommandTests !ghostComp.CanReturnToBody); }); }); - - await pair.CleanReturnAsync(); } @@ -228,12 +215,7 @@ public sealed class SuicideCommandTests [Test] public async Task TestSuicideByHeldItem() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); @@ -292,8 +274,6 @@ public sealed class SuicideCommandTests Assert.That(damageableSystem.GetAllDamage((player, damageableComp)).DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -303,12 +283,7 @@ public sealed class SuicideCommandTests [Test] public async Task TestSuicideByHeldItemSpreadDamage() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); @@ -367,7 +342,5 @@ public sealed class SuicideCommandTests Assert.That(damageableSystem.GetAllDamage((player, damageableComp)).DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold / 2)); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/ConfigPresetTests.cs b/Content.IntegrationTests/Tests/ConfigPresetTests.cs index ebeea7f391..224a5677c0 100644 --- a/Content.IntegrationTests/Tests/ConfigPresetTests.cs +++ b/Content.IntegrationTests/Tests/ConfigPresetTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using Content.IntegrationTests.Fixtures; using Content.Server.Entry; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; @@ -7,12 +8,12 @@ using Robust.Shared.ContentPack; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class ConfigPresetTests +public sealed class ConfigPresetTests : GameTest { [Test] public async Task TestLoadAll() { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var resources = server.ResolveDependency(); @@ -70,7 +71,5 @@ public sealed class ConfigPresetTests Assert.Fail($"CVar {name} was not reset to its original value."); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Construction/ConstructionActionValid.cs b/Content.IntegrationTests/Tests/Construction/ConstructionActionValid.cs index c59b42d638..8c68e5b192 100644 --- a/Content.IntegrationTests/Tests/Construction/ConstructionActionValid.cs +++ b/Content.IntegrationTests/Tests/Construction/ConstructionActionValid.cs @@ -1,4 +1,5 @@ using System.Text; +using Content.IntegrationTests.Fixtures; using Content.Server.Construction.Completions; using Content.Shared.Construction; using Content.Shared.Construction.Prototypes; @@ -7,7 +8,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Construction { [TestFixture] - public sealed class ConstructionActionValid + public sealed class ConstructionActionValid : GameTest { private bool IsValid(IGraphAction action, IPrototypeManager protoMan, out string prototype) { @@ -47,7 +48,7 @@ namespace Content.IntegrationTests.Tests.Construction [Test] public async Task ConstructionGraphSpawnPrototypeValid() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -84,13 +85,12 @@ namespace Content.IntegrationTests.Tests.Construction }); Assert.That(valid, Is.True, $"One or more SpawnPrototype actions specified invalid entity prototypes!\n{message}"); - await pair.CleanReturnAsync(); } [Test] public async Task ConstructionGraphEdgeValid() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -118,7 +118,6 @@ namespace Content.IntegrationTests.Tests.Construction }); Assert.That(valid, Is.True, $"One or more edges specified invalid node targets!\n{message}"); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Construction/ConstructionPrototypeTest.cs b/Content.IntegrationTests/Tests/Construction/ConstructionPrototypeTest.cs index 9441443b22..49cc76e945 100644 --- a/Content.IntegrationTests/Tests/Construction/ConstructionPrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Construction/ConstructionPrototypeTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Server.Construction.Components; using Content.Shared.Construction.Prototypes; @@ -7,7 +8,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Construction { [TestFixture] - public sealed class ConstructionPrototypeTest + public sealed class ConstructionPrototypeTest : GameTest { // discount linter for construction graphs // TODO: Create serialization validators for these? @@ -25,7 +26,7 @@ namespace Content.IntegrationTests.Tests.Construction [Description("Tests that a given entity specifies a valid node for construction, and optionally a valid one for deconstruction.")] public async Task ConstructionComponentValid(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -49,8 +50,6 @@ namespace Content.IntegrationTests.Tests.Construction $"Invalid deconstruction node \"{target}\" on graph \"{graph.ID}\" for construction entity \"{proto.ID}\"!"); } }); - - await pair.CleanReturnAsync(); } [Test] @@ -59,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Construction [Description("Tests that a given construction prototype has a valid starting and target node, and a valid path between them.")] public async Task ConstructionFormsValidGraph(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -95,7 +94,6 @@ namespace Content.IntegrationTests.Tests.Construction $"The next node ({next.Name}) in the path from the start node ({start}) to the target node ({target}) specified an entity prototype ({next.Entity}) without a ConstructionComponent."); #pragma warning restore NUnit2045 }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs index 37c4b0c9b5..cb7e401601 100644 --- a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs +++ b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Storage.EntitySystems; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests { - public sealed class ContainerOcclusionTest + public sealed class ContainerOcclusionTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -34,7 +35,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task TestA() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -69,14 +70,12 @@ namespace Content.IntegrationTests.Tests Assert.That(light.ContainerOccluded); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestB() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -112,14 +111,12 @@ namespace Content.IntegrationTests.Tests Assert.That(light.ContainerOccluded, Is.False); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestAb() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -157,8 +154,6 @@ namespace Content.IntegrationTests.Tests Assert.That(light.ContainerOccluded); }); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/ContrabandTest.cs b/Content.IntegrationTests/Tests/ContrabandTest.cs index c52ef293e1..9f0fb1d499 100644 --- a/Content.IntegrationTests/Tests/ContrabandTest.cs +++ b/Content.IntegrationTests/Tests/ContrabandTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Contraband; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -5,12 +6,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class ContrabandTest +public sealed class ContrabandTest : GameTest { [Test] public async Task EntityShowDepartmentsAndJobs() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var client = pair.Client; var protoMan = client.ResolveDependency(); var componentFactory = client.ResolveDependency(); @@ -41,7 +42,5 @@ public sealed class ContrabandTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs index 9777054625..758bbe0a50 100644 --- a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.Damageable [TestFixture] [TestOf(typeof(DamageableComponent))] [TestOf(typeof(DamageableSystem))] - public sealed class DamageableTest + public sealed class DamageableTest : GameTest { private const string TestDamageableEntityId = "TestDamageableEntityId"; private const string TestGroup1 = "TestGroup1"; @@ -95,7 +96,7 @@ namespace Content.IntegrationTests.Tests.Damageable [Test] public async Task TestDamageableComponents() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntityManager = server.ResolveDependency(); @@ -254,7 +255,6 @@ namespace Content.IntegrationTests.Tests.Damageable sDamageableSystem.ChangeDamage(uid, new DamageSpecifier(group3, -100)); Assert.That(sDamageableSystem.GetTotalDamage(ent), Is.EqualTo(FixedPoint2.Zero)); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Damageable/MobThresholdsTest.cs b/Content.IntegrationTests/Tests/Damageable/MobThresholdsTest.cs index b359f05569..c4179135cd 100644 --- a/Content.IntegrationTests/Tests/Damageable/MobThresholdsTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/MobThresholdsTest.cs @@ -1,10 +1,11 @@ +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Shared.Alert; using Content.Shared.Mobs.Components; namespace Content.IntegrationTests.Tests.Damageable; -public sealed class MobThresholdsTest +public sealed class MobThresholdsTest : GameTest { private static string[] _entitiesWithThresholds = GameDataScrounger.EntitiesWithComponent("MobThresholds"); @@ -14,7 +15,7 @@ public sealed class MobThresholdsTest [Description("Ensures every entity with mob thresholds has valid mob state configuration corresponding to some AlertPrototype.")] public async Task ValidateMobThresholds(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; @@ -33,7 +34,5 @@ public sealed class MobThresholdsTest Assert.That(alertStates, Does.Contain(state), $"{proto.ID} does not have an alert state for mob state {state}"); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs b/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs index 09be373a4c..b4a278529d 100644 --- a/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/StaminaComponentTest.cs @@ -1,11 +1,12 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Shared.Damage.Components; using Content.Shared.FixedPoint; namespace Content.IntegrationTests.Tests.Damageable; -public sealed class StaminaComponentTest +public sealed class StaminaComponentTest : GameTest { private static string[] _entitiesWithStamina = GameDataScrounger.EntitiesWithComponent("Stamina"); @@ -15,7 +16,7 @@ public sealed class StaminaComponentTest [Description("Ensures every entity with Stamina has a valid stamina configuration.")] public async Task ValidateStamina(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; @@ -46,7 +47,5 @@ public sealed class StaminaComponentTest #pragma warning restore NUnit2041 } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/DeleteInventoryTest.cs b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs index 49d54bbecf..bbaf11a052 100644 --- a/Content.IntegrationTests/Tests/DeleteInventoryTest.cs +++ b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Clothing.Components; using Content.Shared.Clothing.EntitySystems; using Content.Shared.Inventory; @@ -7,14 +8,14 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class DeleteInventoryTest + public sealed class DeleteInventoryTest : GameTest { // Test that when deleting an entity with an InventoryComponent, // any equipped items also get deleted. [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); var entMgr = server.ResolveDependency(); @@ -44,7 +45,6 @@ namespace Content.IntegrationTests.Tests // Assert that child item was also deleted. Assert.That(item.Deleted, Is.True); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs index f5010eefdc..a83891b8fc 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; @@ -13,12 +14,12 @@ namespace Content.IntegrationTests.Tests.Destructible [TestFixture] [TestOf(typeof(DamageGroupTrigger))] [TestOf(typeof(AndTrigger))] - public sealed class DestructibleDamageGroupTest + public sealed class DestructibleDamageGroupTest : GameTest { [Test] public async Task AndTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -193,7 +194,6 @@ namespace Content.IntegrationTests.Tests.Destructible // No new thresholds reached as triggers once is set to true and it already triggered before Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs index 70baaea95a..47f44ad496 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; @@ -12,12 +13,12 @@ namespace Content.IntegrationTests.Tests.Destructible [TestFixture] [TestOf(typeof(DamageTypeTrigger))] [TestOf(typeof(AndTrigger))] - public sealed class DestructibleDamageTypeTest + public sealed class DestructibleDamageTypeTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -188,7 +189,6 @@ namespace Content.IntegrationTests.Tests.Destructible // No new thresholds reached as triggers once is set to true and it already triggered before Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs index df98294ee9..0aa0449682 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Destructible.Thresholds.Behaviors; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -10,12 +11,12 @@ using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototy namespace Content.IntegrationTests.Tests.Destructible { - public sealed class DestructibleDestructionTest + public sealed class DestructibleDestructionTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -89,7 +90,6 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(found, Is.True, $"Unable to find {SpawnedEntityId} nearby for destructible test; found {entitiesInRange.Count} entities."); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs index 4460affedf..da56a43a4a 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Destructible; using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; @@ -19,12 +20,12 @@ namespace Content.IntegrationTests.Tests.Destructible [TestFixture] [TestOf(typeof(DestructibleComponent))] [TestOf(typeof(DamageThreshold))] - public sealed class DestructibleThresholdActivationTest + public sealed class DestructibleThresholdActivationTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntityManager = server.ResolveDependency(); @@ -289,7 +290,6 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); }); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs b/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs index c62b46ab6d..7a2a880bae 100644 --- a/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs +++ b/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Server.DeviceLinking.Systems; using Content.Shared.DeviceLinking; @@ -7,7 +8,7 @@ using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests.DeviceLinking; -public sealed class DeviceLinkingTest +public sealed class DeviceLinkingTest : GameTest { private const string PortTesterProtoId = "DeviceLinkingSinkPortTester"; @@ -29,7 +30,7 @@ public sealed class DeviceLinkingTest [Description("Ensures all devices that can sink signals will not cause exceptions when signaled.")] public async Task DeviceLinkSinkAllPortsTest(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; var compFact = server.ResolveDependency(); @@ -77,7 +78,5 @@ public sealed class DeviceLinkingTest } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs index fdc0e1a4d4..6ecdffc58c 100644 --- a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs +++ b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Shared.DeviceNetwork; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork [TestOf(typeof(DeviceNetworkComponent))] [TestOf(typeof(WiredNetworkComponent))] [TestOf(typeof(WirelessNetworkComponent))] - public sealed class DeviceNetworkTest + public sealed class DeviceNetworkTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -50,7 +51,7 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork [Test] public async Task NetworkDeviceSendAndReceive() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -104,13 +105,12 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork { Assert.That(payload, Is.EquivalentTo(deviceNetTestSystem.LastPayload)); }); - await pair.CleanReturnAsync(); } [Test] public async Task WirelessNetworkDeviceSendAndReceive() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; @@ -188,14 +188,12 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork { Assert.That(payload, Is.Not.EqualTo(deviceNetTestSystem.LastPayload).AsCollection); }); - - await pair.CleanReturnAsync(); } [Test] public async Task WiredNetworkDeviceSendAndReceive() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; @@ -271,8 +269,6 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork { Assert.That(payload, Is.EqualTo(deviceNetTestSystem.LastPayload).AsCollection); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index 52b669b09d..f7b20819c2 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -1,6 +1,7 @@ #nullable enable annotations using System.Linq; using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Disposal.Unit; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -16,7 +17,7 @@ namespace Content.IntegrationTests.Tests.Disposal [TestOf(typeof(DisposalHolderComponent))] [TestOf(typeof(DisposalEntryComponent))] [TestOf(typeof(DisposalUnitComponent))] - public sealed class DisposalUnitTest + public sealed class DisposalUnitTest : GameTest { [Reflect(false)] private sealed class DisposalUnitTestSystem : EntitySystem @@ -145,7 +146,7 @@ namespace Content.IntegrationTests.Tests.Disposal [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -240,8 +241,6 @@ namespace Content.IntegrationTests.Tests.Disposal // Re-pressurizing Flush(disposalUnit, unitComponent, false, disposalSystem); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs index 32f8b58542..d9619076e0 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Robust.Shared.GameObjects; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.DoAfter { [TestFixture] [TestOf(typeof(DoAfterComponent))] - public sealed partial class DoAfterServerTest + public sealed partial class DoAfterServerTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -35,7 +36,7 @@ namespace Content.IntegrationTests.Tests.DoAfter [Test] public async Task TestSerializable() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); var refMan = server.ResolveDependency(); @@ -55,14 +56,12 @@ namespace Content.IntegrationTests.Tests.DoAfter } }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestFinished() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -84,14 +83,12 @@ namespace Content.IntegrationTests.Tests.DoAfter await server.WaitRunTicks(1); Assert.That(ev.Cancelled, Is.False); - - await pair.CleanReturnAsync(); } [Test] public async Task TestCancelled() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.EntMan; var timing = server.ResolveDependency(); @@ -113,8 +110,6 @@ namespace Content.IntegrationTests.Tests.DoAfter await server.WaitRunTicks(3); Assert.That(ev.Cancelled); - - await pair.CleanReturnAsync(); } /// @@ -124,7 +119,7 @@ namespace Content.IntegrationTests.Tests.DoAfter [Test] public async Task TestGetInteractingEntities() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.EntMan; var timing = server.ResolveDependency(); @@ -175,8 +170,6 @@ namespace Content.IntegrationTests.Tests.DoAfter entityManager.DeleteEntity(target); entityManager.DeleteEntity(target2); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs index 69fe66039b..af03de169f 100644 --- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Doors.Systems; using Content.Shared.Doors.Components; using Robust.Shared.GameObjects; @@ -11,7 +12,7 @@ namespace Content.IntegrationTests.Tests.Doors { [TestFixture] [TestOf(typeof(AirlockComponent))] - public sealed class AirlockTest + public sealed class AirlockTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -54,7 +55,7 @@ namespace Content.IntegrationTests.Tests.Doors [Test] public async Task OpenCloseDestroyTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -104,16 +105,12 @@ namespace Content.IntegrationTests.Tests.Doors entityManager.DeleteEntity(airlock); }); }); - - server.RunTicks(5); - - await pair.CleanReturnAsync(); } [Test] public async Task AirlockBlockTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -179,7 +176,6 @@ namespace Content.IntegrationTests.Tests.Doors { Assert.That(Math.Abs(xformSystem.GetWorldPosition(airlockPhysicsDummy).X - 1), Is.GreaterThan(0.01f)); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/DummyIconTest.cs b/Content.IntegrationTests/Tests/DummyIconTest.cs index 62197bb319..dcb5d315dc 100644 --- a/Content.IntegrationTests/Tests/DummyIconTest.cs +++ b/Content.IntegrationTests/Tests/DummyIconTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; using Robust.Client.GameObjects; using Robust.Client.ResourceManagement; using Robust.Shared.Prototypes; @@ -7,12 +8,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class DummyIconTest + public sealed class DummyIconTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var client = pair.Client; var prototypeManager = client.ResolveDependency(); var resourceCache = client.ResolveDependency(); @@ -32,7 +33,6 @@ namespace Content.IntegrationTests.Tests proto.ID); } }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 9b0e7729f5..13293b7d46 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; using Robust.Shared; using Robust.Shared.Audio.Components; using Robust.Shared.Configuration; @@ -16,17 +18,28 @@ namespace Content.IntegrationTests.Tests { [TestFixture] [TestOf(typeof(EntityUid))] - public sealed class EntityTest + public sealed class EntityTest : GameTest { private static readonly ProtoId SpawnerCategory = "Spawner"; + public override PoolSettings PoolSettings => new() + { + Connected = true, + Dirty = true + }; + + public static PoolSettings Disconnected => new() + { + Dirty = true, + }; + [Test] + [PairConfig(nameof(Disconnected))] public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps() { // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round // is minimal relative to the rest of the test. - var settings = new PoolSettings { Dirty = true }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; var server = pair.Server; var entityMan = server.ResolveDependency(); @@ -79,17 +92,14 @@ namespace Content.IntegrationTests.Tests Assert.That(entityMan.EntityCount, Is.Zero); }); - - await pair.CleanReturnAsync(); } [Test] + [PairConfig(nameof(Disconnected))] public async Task SpawnAndDeleteAllEntitiesInTheSameSpot() { - // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round - // is minimal relative to the rest of the test. - var settings = new PoolSettings { Dirty = true }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; + Assert.That(pair.Client.Session, Is.Null); var server = pair.Server; var map = await pair.CreateTestMap(); @@ -134,8 +144,6 @@ namespace Content.IntegrationTests.Tests Assert.That(entityMan.EntityCount, Is.Zero); }); - - await pair.CleanReturnAsync(); } /// @@ -145,10 +153,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task SpawnAndDirtyAllEntities() { - // This test dirties the pair as it simply deletes ALL entities when done. Overhead of restarting the round - // is minimal relative to the rest of the test. - var settings = new PoolSettings { Connected = true, Dirty = true }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -182,7 +187,7 @@ namespace Content.IntegrationTests.Tests } }); - await pair.RunTicksSync(15); + await pair.RunUntilSynced(); // Make sure the client actually received the entities // 500 is completely arbitrary. Note that the client & sever entity counts aren't expected to match. @@ -209,8 +214,6 @@ namespace Content.IntegrationTests.Tests Assert.That(sEntMan.EntityCount, Is.Zero); }); - - await pair.CleanReturnAsync(); } /// @@ -230,8 +233,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task SpawnAndDeleteEntityCountTest() { - var settings = new PoolSettings { Connected = true, Dirty = true }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; var mapSys = pair.Server.System(); var server = pair.Server; var client = pair.Client; @@ -317,8 +319,6 @@ namespace Content.IntegrationTests.Tests BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan)); } }); - - await pair.CleanReturnAsync(); } private static string BuildDiffString(IEnumerable oldEnts, IEnumerable newEnts, IEntityManager entMan) @@ -385,14 +385,11 @@ namespace Content.IntegrationTests.Tests "StationData", // errors when removed mid-round "StationJobs", "Actor", // We aren't testing actor components, those need their player session set. - "BlobFloorPlanBuilder", // Implodes if unconfigured. - "DebrisFeaturePlacerController", // Above. - "LoadedChunk", // Worldgen chunk loading malding. "BiomeSelection", // Whaddya know, requires config. "ActivatableUI", // Requires enum key }; - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); var componentFactory = server.ResolveDependency(); @@ -445,8 +442,6 @@ namespace Content.IntegrationTests.Tests } }); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Explosion/ExplosionPrototypeTest.cs b/Content.IntegrationTests/Tests/Explosion/ExplosionPrototypeTest.cs index 01f52f7d83..7a486e6839 100644 --- a/Content.IntegrationTests/Tests/Explosion/ExplosionPrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Explosion/ExplosionPrototypeTest.cs @@ -1,9 +1,10 @@ +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Utility; using Content.Shared.Explosion; namespace Content.IntegrationTests.Tests.Explosion; -public sealed class ExplosionPrototypeTest +public sealed class ExplosionPrototypeTest : GameTest { private static string[] _explosionKinds = GameDataScrounger.PrototypesOfKind(); @@ -13,7 +14,7 @@ public sealed class ExplosionPrototypeTest [Description("Ensures various properties of ExplosionPrototype are correctly configured.")] public async Task Validate(string protoKey) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; @@ -40,7 +41,5 @@ public sealed class ExplosionPrototypeTest Assert.That(proto.IntensityPerState, Is.Positive); Assert.That(proto.FireStates, Is.Positive); } - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs index 99354e16c1..142ec21ddb 100644 --- a/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs +++ b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Prototypes; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests; /// Tests to see if any entity prototypes specify solution fill level sprites that don't exist. /// [TestFixture] -public sealed class FillLevelSpriteTest +public sealed class FillLevelSpriteTest : GameTest { private static readonly string[] HandStateNames = ["left", "right"]; private static readonly string[] EquipStateNames = ["back", "suitstorage"]; @@ -20,7 +21,7 @@ public sealed class FillLevelSpriteTest [Test] public async Task FillLevelSpritesExist() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var client = pair.Client; var protoMan = client.ResolveDependency(); var componentFactory = client.ResolveDependency(); @@ -101,7 +102,5 @@ public sealed class FillLevelSpriteTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs index 1afed38966..3d9ff61a2d 100644 --- a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs @@ -7,12 +7,13 @@ using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; namespace Content.IntegrationTests.Tests.Fluids; [TestFixture] [TestOf(typeof(AbsorbentComponent))] -public sealed class AbsorbentTest +public sealed class AbsorbentTest : GameTest { private const string UserDummyId = "UserDummy"; private const string AbsorbentDummyId = "AbsorbentDummy"; @@ -73,7 +74,7 @@ public sealed class AbsorbentTest [TestCaseSource(nameof(TestCasesToRun))] public async Task AbsorbentOnRefillableTest(TestSolutionCase testCase) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -123,15 +124,12 @@ public sealed class AbsorbentTest Assert.That(VolumeOfPrototypeInComposition(refillableComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfNonEvaporable)); }); }); - await pair.RunTicksSync(5); - - await pair.CleanReturnAsync(); } [TestCaseSource(nameof(TestCasesToRunOnSmallRefillable))] public async Task AbsorbentOnSmallRefillableTest(TestSolutionCase testCase) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -180,9 +178,6 @@ public sealed class AbsorbentTest Assert.That(VolumeOfPrototypeInComposition(refillableComposition, NonEvaporablePrototypeId), Is.EqualTo(testCase.ExpectedRefillableSolution.VolumeOfNonEvaporable)); }); }); - await pair.RunTicksSync(5); - - await pair.CleanReturnAsync(); } private static FixedPoint2 VolumeOfPrototypeInComposition(Dictionary composition, string prototypeId) diff --git a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs index d6f9bf3598..c073020e9c 100644 --- a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Server.Fluids.EntitySystems; using Content.Server.Spreader; using Content.Shared.Chemistry.Components; @@ -14,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Fluids; [TestFixture] [TestOf(typeof(SpreaderSystem))] -public sealed class FluidSpill +public sealed class FluidSpill : GameTest { private static PuddleComponent? GetPuddle(IEntityManager entityManager, Entity mapGrid, Vector2i pos) { @@ -36,7 +37,7 @@ public sealed class FluidSpill [Test] public async Task SpillCorner() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -110,7 +111,5 @@ public sealed class FluidSpill } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs index ee2d0cb1f7..71fbefa241 100644 --- a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Coordinates; @@ -10,12 +11,12 @@ namespace Content.IntegrationTests.Tests.Fluids { [TestFixture] [TestOf(typeof(PuddleComponent))] - public sealed class PuddleTest + public sealed class PuddleTest : GameTest { [Test] public async Task TilePuddleTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -32,15 +33,12 @@ namespace Content.IntegrationTests.Tests.Fluids Assert.That(spillSystem.TrySpillAt(coordinates, solution, out _), Is.True); }); - await pair.RunTicksSync(5); - - await pair.CleanReturnAsync(); } [Test] public async Task SpaceNoPuddleTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -69,8 +67,6 @@ namespace Content.IntegrationTests.Tests.Fluids Assert.That(spillSystem.TrySpillAt(coordinates, solution, out _), Is.False); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/FollowerSystemTest.cs b/Content.IntegrationTests/Tests/FollowerSystemTest.cs index f4447426c7..464b3306f2 100644 --- a/Content.IntegrationTests/Tests/FollowerSystemTest.cs +++ b/Content.IntegrationTests/Tests/FollowerSystemTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Shared.Follower; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.Map; namespace Content.IntegrationTests.Tests; [TestFixture, TestOf(typeof(FollowerSystem))] -public sealed class FollowerSystemTest +public sealed class FollowerSystemTest : GameTest { /// /// This test ensures that deleting a map while an entity follows another doesn't throw any exceptions. @@ -15,7 +16,7 @@ public sealed class FollowerSystemTest [Test] public async Task FollowerMapDeleteTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -44,6 +45,5 @@ public sealed class FollowerSystemTest entMan.DeleteEntity(mapSys.GetMap(map)); }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs index dae3203f9f..97681ad469 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Server.Cuffs; using Content.Shared.Cuffs.Components; using Content.Shared.Hands.Components; @@ -10,7 +11,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking [TestFixture] [TestOf(typeof(CuffableComponent))] [TestOf(typeof(HandcuffComponent))] - public sealed class HandCuffTest + public sealed class HandCuffTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -40,7 +41,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; EntityUid human; @@ -98,8 +99,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking cuffableSys.TryAddNewCuffs(human, human, secondCuffs, cuffed); Assert.That(cuffed.CuffedHandCount, Is.EqualTo(4), "Player doesn't have correct amount of hands cuffed"); }); - - await pair.CleanReturnAsync(); } private static void AddHand(NetEntity to, IServerConsoleHost host) diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/EntityPrototypeComponentsTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/EntityPrototypeComponentsTest.cs index ef94cf0f00..3473f1bc9c 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/EntityPrototypeComponentsTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/EntityPrototypeComponentsTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Content.IntegrationTests.Fixtures; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.Utility; @@ -11,12 +12,12 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components { [TestFixture] [TestOf(typeof(Server.Entry.IgnoredComponents))] - public sealed class EntityPrototypeComponentsTest + public sealed class EntityPrototypeComponentsTest : GameTest { [Test] public async Task PrototypesHaveKnownComponents() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -100,7 +101,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components if (unknownComponentsClient.Count + unknownComponentsServer.Count + doubleIgnoredComponents.Count == 0) { - await pair.CleanReturnAsync(); Assert.Pass($"Validated {entitiesValidated} entities with {componentsValidated} components in {paths.Length} files."); return; } @@ -131,11 +131,11 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components [Test] public async Task IgnoredComponentsExistInTheCorrectPlaces() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var client = pair.Client; var serverComponents = server.ResolveDependency(); - var ignoredServerNames = Server.Entry.IgnoredComponents.List; + var ignoredServerNames = Content.Server.Entry.IgnoredComponents.List; var clientComponents = client.ResolveDependency(); var failureMessages = ""; @@ -151,7 +151,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components } } Assert.That(failureMessages, Is.Empty); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index 1e307e9447..3db194eb9e 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Client.UserInterface.Systems.Alerts.Controls; using Content.Client.UserInterface.Systems.Alerts.Widgets; +using Content.IntegrationTests.Fixtures; using Content.Shared.Alert; using Robust.Client.UserInterface; using Robust.Server.Player; @@ -10,16 +11,18 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs { [TestFixture] [TestOf(typeof(AlertsComponent))] - public sealed class AlertsComponentTests + public sealed class AlertsComponentTests : GameTest { + public override PoolSettings PoolSettings => new() + { + Connected = true, + DummyTicker = false + }; + [Test] public async Task AlertsTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -109,8 +112,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs if (entManager.GetComponent(playerUid).EntityPrototype.ID != "MobIpc") // Corvax-IPC Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index b215584c57..4a059eb1cc 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Antag; using Content.Server.Antag.Components; using Content.Server.GameTicking; @@ -14,17 +15,19 @@ namespace Content.IntegrationTests.Tests.GameRules; // Once upon a time, players in the lobby weren't ever considered eligible for antag roles. // Lets not let that happen again. [TestFixture] -public sealed class AntagPreferenceTest +public sealed class AntagPreferenceTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }; + [Test] public async Task TestLobbyPlayersValid() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -71,6 +74,5 @@ public sealed class AntagPreferenceTest Assert.That(pool.Count, Is.EqualTo(0)); await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs index b9a02339fb..b8efb64b8b 100644 --- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.GameTicking.Presets; using Content.Shared.CCVar; @@ -9,7 +10,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] -public sealed class FailAndStartPresetTest +public sealed class FailAndStartPresetTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -52,19 +53,21 @@ public sealed class FailAndStartPresetTest - type: TestRule "; + public override PoolSettings PoolSettings => new() + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true + }; + /// /// Test that a nuke ops gamemode can start after failing to start once. /// [Test] public async Task FailAndStartTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -115,7 +118,6 @@ public sealed class FailAndStartPresetTest server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, true); server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "secret"); server.System().Run = false; - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 53165adca0..f9074f7dc6 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Body.Components; using Content.Server.GameTicking; using Content.Server.GameTicking.Presets; @@ -30,24 +31,27 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] -public sealed class NukeOpsTest +public sealed class NukeOpsTest : GameTest { private static readonly ProtoId SyndicateFaction = "Syndicate"; private static readonly ProtoId NanotrasenFaction = "NanoTrasen"; + public override PoolSettings PoolSettings => new() + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true + }; + + /// /// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded. /// [Test] public async Task TryStopNukeOpsFromConstantlyFailing() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -260,6 +264,5 @@ public sealed class NukeOpsTest }); ticker.SetGamePreset((GamePresetPrototype?) null); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs index c805d04a71..0bbf2fae4f 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; @@ -9,12 +10,14 @@ namespace Content.IntegrationTests.Tests.GameRules { [TestFixture] [TestOf(typeof(MaxTimeRestartRuleSystem))] - public sealed class RuleMaxTimeRestartTest + public sealed class RuleMaxTimeRestartTest : GameTest { + public override PoolSettings PoolSettings => new() { InLobby = true }; + [Test] public async Task RestartTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { InLobby = true }); + var pair = Pair; var server = pair.Server; Assert.That(server.EntMan.Count(), Is.Zero); @@ -64,8 +67,6 @@ namespace Content.IntegrationTests.Tests.GameRules { Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs index 5d7ae8efbf..521b043c68 100644 --- a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs @@ -1,19 +1,22 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] -public sealed class SecretStartsTest +public sealed class SecretStartsTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings { Dirty = true }; + /// /// Tests that when secret is started, all of the game rules it successfully adds are also started. /// [Test] public async Task TestSecretStarts() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Dirty = true }); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -38,7 +41,5 @@ public sealed class SecretStartsTest // End all rules gameTicker.ClearGameRules(); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameRules/StartEndGameRulesTest.cs b/Content.IntegrationTests/Tests/GameRules/StartEndGameRulesTest.cs index bda931397b..1eef1ab0a1 100644 --- a/Content.IntegrationTests/Tests/GameRules/StartEndGameRulesTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/StartEndGameRulesTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Shared.CCVar; using Robust.Shared.Configuration; @@ -7,19 +8,21 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] -public sealed class StartEndGameRulesTest +public sealed class StartEndGameRulesTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + Dirty = true, + DummyTicker = false + }; + /// /// Tests that all game rules can be added/started/ended at the same time without exceptions. /// [Test] public async Task TestAllConcurrent() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); var gameTicker = server.ResolveDependency().GetEntitySystem(); @@ -47,7 +50,5 @@ public sealed class StartEndGameRulesTest gameTicker.ClearGameRules(); Assert.That(!gameTicker.GetAddedGameRules().Any()); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs b/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs index 97fe1c8762..318c4b6b42 100644 --- a/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/TraitorRuleTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Antag.Components; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; @@ -17,23 +18,25 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] -public sealed class TraitorRuleTest +public sealed class TraitorRuleTest : GameTest { private const string TraitorGameRuleProtoId = "Traitor"; private const string TraitorAntagRoleName = "Traitor"; private static readonly ProtoId SyndicateFaction = "Syndicate"; private static readonly ProtoId NanotrasenFaction = "NanoTrasen"; + public override PoolSettings PoolSettings => new() + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true, + }; + [Test] public async Task TestTraitorObjectives() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings() - { - Dirty = true, - DummyTicker = false, - Connected = true, - InLobby = true, - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; var entMan = server.EntMan; @@ -123,9 +126,6 @@ public sealed class TraitorRuleTest $"MaxDifficulty exceeded! Objectives: {string.Join(", ", mindComp.Objectives.Select(o => FormatObjective(o, entMan)))}"); Assert.That(mindComp.Objectives, Is.Not.Empty, $"No objectives assigned!"); - - - await pair.CleanReturnAsync(); } private static string FormatObjective(Entity entity, IEntityManager entMan) diff --git a/Content.IntegrationTests/Tests/Gibbing/GibTest.cs b/Content.IntegrationTests/Tests/Gibbing/GibTest.cs index ee0f7a742d..23be8f028a 100644 --- a/Content.IntegrationTests/Tests/Gibbing/GibTest.cs +++ b/Content.IntegrationTests/Tests/Gibbing/GibTest.cs @@ -1,16 +1,17 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Shared.Gibbing; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Body; [TestFixture] -public sealed class GibTest +public sealed class GibTest : GameTest { [Test] public async Task TestGib() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var (server, client) = (pair.Server, pair.Client); var map = await pair.CreateTestMap(); @@ -30,7 +31,5 @@ public sealed class GibTest await pair.RunTicksSync(5); Assert.That(!client.EntMan.EntityExists(nuid)); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 0951e7e260..0ef52a12a6 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Gravity; using Content.Shared.Alert; using Content.Shared.Gravity; @@ -8,7 +9,7 @@ namespace Content.IntegrationTests.Tests.Gravity [TestFixture] [TestOf(typeof(GravitySystem))] [TestOf(typeof(GravityGeneratorComponent))] - public sealed class WeightlessStatusTests + public sealed class WeightlessStatusTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -38,7 +39,7 @@ namespace Content.IntegrationTests.Tests.Gravity [Test] public async Task WeightlessStatusTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -86,8 +87,6 @@ namespace Content.IntegrationTests.Tests.Gravity }); await pair.RunTicksSync(10); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs index 047ec0259a..10804ea201 100644 --- a/Content.IntegrationTests/Tests/GravityGridTest.cs +++ b/Content.IntegrationTests/Tests/GravityGridTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Power.Components; using Content.Shared.Gravity; using Robust.Shared.GameObjects; @@ -11,7 +12,7 @@ namespace Content.IntegrationTests.Tests /// making sure that gravity is applied to the correct grids. [TestFixture] [TestOf(typeof(GravityGeneratorComponent))] - public sealed class GravityGridTest + public sealed class GravityGridTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -31,7 +32,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -96,8 +97,6 @@ namespace Content.IntegrationTests.Tests Assert.That(entityMan.GetComponent(grid2).Enabled, Is.False); }); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Guidebook/DocumentParsingTest.cs b/Content.IntegrationTests/Tests/Guidebook/DocumentParsingTest.cs index dec2c40c0a..98b7056d00 100644 --- a/Content.IntegrationTests/Tests/Guidebook/DocumentParsingTest.cs +++ b/Content.IntegrationTests/Tests/Guidebook/DocumentParsingTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Content.Client.Guidebook; using Content.Client.Guidebook.Richtext; +using Content.IntegrationTests.Fixtures; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -13,7 +14,7 @@ namespace Content.IntegrationTests.Tests.Guidebook; /// [TestFixture] [TestOf(typeof(DocumentParsingManager))] -public sealed class DocumentParsingTest +public sealed class DocumentParsingTest : GameTest { public string TestDocument = @"multiple @@ -45,7 +46,7 @@ whitespace before newlines are ignored. [Test] public async Task ParseTestDocument() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var client = pair.Client; await client.WaitIdleAsync(); var parser = client.ResolveDependency(); @@ -133,8 +134,6 @@ whitespace before newlines are ignored. subTest2.Params.TryGetValue("k", out val); Assert.That(val, Is.EqualTo(@"<>\>=""=<-_?*3.0//")); - - await pair.CleanReturnAsync(); } public sealed class TestControl : Control, IDocumentTag diff --git a/Content.IntegrationTests/Tests/Guidebook/GuideEntryPrototypeTests.cs b/Content.IntegrationTests/Tests/Guidebook/GuideEntryPrototypeTests.cs index 13d0ad1497..016b06abe8 100644 --- a/Content.IntegrationTests/Tests/Guidebook/GuideEntryPrototypeTests.cs +++ b/Content.IntegrationTests/Tests/Guidebook/GuideEntryPrototypeTests.cs @@ -1,5 +1,6 @@ using Content.Client.Guidebook; using Content.Client.Guidebook.Richtext; +using Content.IntegrationTests.Fixtures; using Robust.Shared.ContentPack; using Robust.Shared.Prototypes; using Content.IntegrationTests.Utility; @@ -12,7 +13,7 @@ namespace Content.IntegrationTests.Tests.Guidebook; [TestOf(typeof(GuidebookSystem))] [TestOf(typeof(GuideEntryPrototype))] [TestOf(typeof(DocumentParsingManager))] -public sealed class GuideEntryPrototypeTests +public sealed class GuideEntryPrototypeTests : GameTest { private static string[] _guideEntries = GameDataScrounger.PrototypesOfKind(); @@ -21,7 +22,7 @@ public sealed class GuideEntryPrototypeTests [Description("Ensures a given guidebook entry is valid, checking the document/etc.")] public async Task Validate(string protoKey) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var client = pair.Client; await client.WaitIdleAsync(); var protoMan = client.ResolveDependency(); @@ -36,7 +37,5 @@ public sealed class GuideEntryPrototypeTests Assert.That(parser.TryAddMarkup(new Document(), text), $"Failed to parse the guide entry's document."); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Hands/HandTests.cs b/Content.IntegrationTests/Tests/Hands/HandTests.cs index d5cf75c463..95f053a1cb 100644 --- a/Content.IntegrationTests/Tests/Hands/HandTests.cs +++ b/Content.IntegrationTests/Tests/Hands/HandTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Storage.EntitySystems; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -10,7 +11,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Hands; [TestFixture] -public sealed class HandTests +public sealed class HandTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -25,14 +26,16 @@ public sealed class HandTests "; + public override PoolSettings PoolSettings => new() + { + Connected = true, + DummyTicker = false + }; + [Test] public async Task TestPickupDrop() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -69,17 +72,12 @@ public sealed class HandTests Assert.That(sys.GetActiveItem((player, hands)), Is.Null); await server.WaitPost(() => mapSystem.DeleteMap(data.MapId)); - await pair.CleanReturnAsync(); } [Test] public async Task TestPickUpThenDropInContainer() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var map = await pair.CreateTestMap(); await pair.RunTicksSync(5); @@ -134,6 +132,5 @@ public sealed class HandTests Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform))); await server.WaitPost(() => mapSystem.DeleteMap(map.MapId)); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs b/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs index 929a231159..accdd4667c 100644 --- a/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs +++ b/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Inventory; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ namespace Content.IntegrationTests.Tests // i.e. the interaction between uniforms and the pocket/ID slots. // and also how big items don't fit in pockets. [TestFixture] - public sealed class HumanInventoryUniformSlotsTest + public sealed class HumanInventoryUniformSlotsTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -55,7 +56,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; @@ -130,8 +131,6 @@ namespace Content.IntegrationTests.Tests mapSystem.DeleteMap(testMap.MapId); }); - - await pair.CleanReturnAsync(); } private static bool IsDescendant(EntityUid descendant, EntityUid parent, IEntityManager entManager) diff --git a/Content.IntegrationTests/Tests/Humanoid/HideablePrototypeValidation.cs b/Content.IntegrationTests/Tests/Humanoid/HideablePrototypeValidation.cs index d95992bda2..4ee65c9c32 100644 --- a/Content.IntegrationTests/Tests/Humanoid/HideablePrototypeValidation.cs +++ b/Content.IntegrationTests/Tests/Humanoid/HideablePrototypeValidation.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Body; using Content.Shared.Clothing.Components; using Content.Shared.Humanoid; @@ -8,13 +9,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Humanoid; [TestFixture] -public sealed class HideablePrototypeValidation +public sealed class HideablePrototypeValidation : GameTest { [Test] public async Task NoOrgansWithoutClothing() { - await using var pair = await PoolManager.GetServerClient(); - + var pair = Pair; var requirements = new Dictionary>(); foreach (var (proto, component) in pair.GetPrototypesWithComponent()) { @@ -42,14 +42,12 @@ public sealed class HideablePrototypeValidation { Assert.That(provided, Does.Contain(key), $"No clothing will hide {key} that can be hidden on {string.Join(", ", requirement.Select(it => it.Id))}"); } - - await pair.CleanReturnAsync(); } [Test] public async Task NoClothingWithoutOrgans() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var requirements = new Dictionary>(); foreach (var (proto, component) in pair.GetPrototypesWithComponent()) @@ -74,7 +72,5 @@ public sealed class HideablePrototypeValidation { Assert.That(provided, Does.Contain(key), $"No organ will hide {key} that can be hidden by {string.Join(", ", requirement.Select(it => it.Id))}"); } - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Humanoid/HumanoidProfileTests.cs b/Content.IntegrationTests/Tests/Humanoid/HumanoidProfileTests.cs index 1e4a094b79..48eb521c5e 100644 --- a/Content.IntegrationTests/Tests/Humanoid/HumanoidProfileTests.cs +++ b/Content.IntegrationTests/Tests/Humanoid/HumanoidProfileTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; @@ -10,14 +11,14 @@ namespace Content.IntegrationTests.Tests.Humanoid; [TestFixture] [TestOf(typeof(HumanoidProfileSystem))] -public sealed class HumanoidProfileTests +public sealed class HumanoidProfileTests : GameTest { private static readonly ProtoId Vox = "Vox"; [Test] public async Task EnsureValidLoading() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -43,7 +44,5 @@ public sealed class HumanoidProfileTests Assert.That(voiceComponent.Sounds, Is.Not.Null, message: "the MobHuman spawned by this test needs to have sex-specific sound set"); Assert.That(voiceComponent.Sounds![Sex.Female], Is.EqualTo(voiceComponent.EmoteSounds)); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 6ac40e92a1..e4bd4615c5 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -1,5 +1,6 @@ #nullable enable annotations using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Interaction; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -16,7 +17,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click { [TestFixture] [TestOf(typeof(InteractionSystem))] - public sealed class InteractionSystemTests + public sealed class InteractionSystemTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -40,7 +41,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click [Test] public async Task InteractionTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -101,13 +102,12 @@ namespace Content.IntegrationTests.Tests.Interaction.Click }); testInteractionSystem.ClearHandlers(); - await pair.CleanReturnAsync(); } [Test] public async Task InteractionObstructionTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -168,13 +168,12 @@ namespace Content.IntegrationTests.Tests.Interaction.Click }); testInteractionSystem.ClearHandlers(); - await pair.CleanReturnAsync(); } [Test] public async Task InteractionInRangeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -234,14 +233,13 @@ namespace Content.IntegrationTests.Tests.Interaction.Click }); testInteractionSystem.ClearHandlers(); - await pair.CleanReturnAsync(); } [Test] public async Task InteractionOutOfRangeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -300,13 +298,12 @@ namespace Content.IntegrationTests.Tests.Interaction.Click }); testInteractionSystem.ClearHandlers(); - await pair.CleanReturnAsync(); } [Test] public async Task InsideContainerInteractionBlockTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -388,7 +385,6 @@ namespace Content.IntegrationTests.Tests.Interaction.Click }); testInteractionSystem.ClearHandlers(); - await pair.CleanReturnAsync(); } public sealed class TestInteractionSystem : EntitySystem diff --git a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs index 801433ae72..966f13675d 100644 --- a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs +++ b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Shared.Interaction; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -10,7 +11,7 @@ namespace Content.IntegrationTests.Tests.Interaction { [TestFixture] [TestOf(typeof(SharedInteractionSystem))] - public sealed class InRangeUnobstructed + public sealed class InRangeUnobstructed : GameTest { private const string HumanId = "MobHuman"; @@ -27,7 +28,7 @@ namespace Content.IntegrationTests.Tests.Interaction [Test] public async Task EntityEntityTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -109,8 +110,6 @@ namespace Content.IntegrationTests.Tests.Interaction Assert.That(interactionSys.InRangeUnobstructed(mapCoordinates, origin, InteractionRangeDivided15Times3)); }); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index 62a03b0abe..5231055c69 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -175,7 +175,7 @@ public abstract partial class InteractionTest [SetUp] public virtual async Task Setup() { - Pair = await PoolManager.GetServerClient(Settings); + Pair = await PoolManager.GetServerClient(Settings, new NUnitTestContextWrap(TestContext.CurrentContext, TestContext.Out)); // server dependencies SEntMan = Server.ResolveDependency(); diff --git a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs index d153536873..d2f67e9830 100644 --- a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs +++ b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Systems; using Content.Server.Station.Systems; @@ -7,12 +8,12 @@ namespace Content.IntegrationTests.Tests.Internals; [TestFixture] [TestOf(typeof(InternalsSystem))] -public sealed class AutoInternalsTests +public sealed class AutoInternalsTests : GameTest { [Test] public async Task TestInternalsAutoActivateInSpaceForStationSpawn() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -31,14 +32,12 @@ public sealed class AutoInternalsTests server.EntMan.DeleteEntity(dummy); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestInternalsAutoActivateInSpaceForEntitySpawn() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -55,8 +54,6 @@ public sealed class AutoInternalsTests server.EntMan.DeleteEntity(dummy); }); - - await pair.CleanReturnAsync(); } [TestPrototypes] diff --git a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs index 39761ad089..c841e23365 100644 --- a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs +++ b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Stunnable; using Content.Shared.Inventory; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.Map; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class InventoryHelpersTest + public sealed class InventoryHelpersTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -39,7 +40,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task SpawnItemInSlotTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntities = server.ResolveDependency(); @@ -87,8 +88,6 @@ namespace Content.IntegrationTests.Tests #pragma warning restore NUnit2045 sEntities.DeleteEntity(human); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Lathe/LatheTest.cs b/Content.IntegrationTests/Tests/Lathe/LatheTest.cs index c335f8d6c8..f0a5c04237 100644 --- a/Content.IntegrationTests/Tests/Lathe/LatheTest.cs +++ b/Content.IntegrationTests/Tests/Lathe/LatheTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Lathe; using Content.Shared.Materials; using Content.Shared.Prototypes; @@ -11,12 +12,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Lathe; [TestFixture] -public sealed class LatheTest +public sealed class LatheTest : GameTest { [Test] public async Task TestLatheRecipeIngredientsFitLathe() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapData = await pair.CreateTestMap(); @@ -111,14 +112,12 @@ public sealed class LatheTest } }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task AllLatheRecipesValidTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var proto = server.ProtoMan; @@ -131,7 +130,5 @@ public sealed class LatheTest Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents."); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs index b75b81ab3c..bd35c1f08e 100644 --- a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs +++ b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -11,12 +12,12 @@ namespace Content.IntegrationTests.Tests.Linter; /// Verify that the yaml linter successfully validates static fields /// [TestFixture] -public sealed class StaticFieldValidationTest +public sealed class StaticFieldValidationTest : GameTest { [Test] public async Task TestStaticFieldValidation() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var protoMan = pair.Server.ProtoMan; var protos = new Dictionary>(); @@ -49,8 +50,6 @@ public sealed class StaticFieldValidationTest Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdSetInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(PrivateProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); - - await pair.CleanReturnAsync(); } [TestPrototypes] diff --git a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs index 2fa3c9961c..fb48797616 100644 --- a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs +++ b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs @@ -1,4 +1,5 @@ using Content.Client.Lobby; +using Content.IntegrationTests.Fixtures; using Content.Server.Preferences.Managers; using Content.Shared.Humanoid; using Content.Shared.Preferences; @@ -9,12 +10,14 @@ namespace Content.IntegrationTests.Tests.Lobby; [TestFixture] [TestOf(typeof(ClientPreferencesManager))] [TestOf(typeof(ServerPreferencesManager))] -public sealed class CharacterCreationTest +public sealed class CharacterCreationTest : GameTest { + public override PoolSettings PoolSettings => new() { InLobby = true }; + [Test] public async Task CreateDeleteCreateTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { InLobby = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; var user = pair.Client.User!.Value; @@ -72,7 +75,6 @@ public sealed class CharacterCreationTest serverCharacters = serverPrefManager.GetPreferences(user).Characters; Assert.That(serverCharacters, Has.Count.EqualTo(2)); AssertEqual(serverCharacters[1], profile); - await pair.CleanReturnAsync(); } private void AssertEqual(HumanoidCharacterProfile a, HumanoidCharacterProfile b) diff --git a/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs b/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs index 3dc2075887..e59bc269ad 100644 --- a/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs +++ b/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs @@ -1,20 +1,23 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.CCVar; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; namespace Content.IntegrationTests.Tests.Lobby; -public sealed class ServerReloginTest +public sealed class ServerReloginTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + Connected = true, + DummyTicker = false + }; + [Test] public async Task Relogin() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; var originalMaxPlayers = 0; @@ -62,7 +65,5 @@ public sealed class ServerReloginTest //Put the cvar back, so other tests can still use this server serverConfig.SetCVar(CCVars.SoftMaxPlayers, originalMaxPlayers); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Localization/EntityPrototypeLocalizationTest.cs b/Content.IntegrationTests/Tests/Localization/EntityPrototypeLocalizationTest.cs index 69d44fd08b..1b8fa16543 100644 --- a/Content.IntegrationTests/Tests/Localization/EntityPrototypeLocalizationTest.cs +++ b/Content.IntegrationTests/Tests/Localization/EntityPrototypeLocalizationTest.cs @@ -1,9 +1,10 @@ +using Content.IntegrationTests.Fixtures; using Robust.Shared.Localization; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Localization; -public sealed class EntityPrototypeLocalizationTest +public sealed class EntityPrototypeLocalizationTest : GameTest { /// /// An explanation of why LocIds should not be used for entity prototype names/descriptions. @@ -18,7 +19,7 @@ public sealed class EntityPrototypeLocalizationTest [Test] public async Task TestNoManualEntityLocStrings() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; var locMan = server.ResolveDependency(); @@ -44,7 +45,5 @@ public sealed class EntityPrototypeLocalizationTest } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs index 05f98c3d19..14aeaf648c 100644 --- a/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Dataset; using Robust.Shared.Localization; using Robust.Shared.Prototypes; @@ -6,12 +7,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Localization; [TestFixture] -public sealed class LocalizedDatasetPrototypeTest +public sealed class LocalizedDatasetPrototypeTest : GameTest { [Test] public async Task ValidProtoIdsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -36,7 +37,5 @@ public sealed class LocalizedDatasetPrototypeTest Assert.That(localizationMan.HasString(nextId), Is.False, $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but a localized string exists with ID {nextId}! Does count need to be raised?"); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/MachineBoardTest.cs b/Content.IntegrationTests/Tests/MachineBoardTest.cs index e1533bbb8d..9554357ece 100644 --- a/Content.IntegrationTests/Tests/MachineBoardTest.cs +++ b/Content.IntegrationTests/Tests/MachineBoardTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Construction.Components; using Content.Shared.Construction.Components; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; -public sealed class MachineBoardTest +public sealed class MachineBoardTest : GameTest { /// /// A list of machine boards that can be ignored by this test. @@ -32,7 +33,7 @@ public sealed class MachineBoardTest [Test] public async Task TestMachineBoardHasValidMachine() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -60,8 +61,6 @@ public sealed class MachineBoardTest }); } }); - - await pair.CleanReturnAsync(); } /// @@ -71,7 +70,7 @@ public sealed class MachineBoardTest [Test] public async Task TestComputerBoardHasValidComputer() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -100,8 +99,6 @@ public sealed class MachineBoardTest }); } }); - - await pair.CleanReturnAsync(); } /// @@ -111,7 +108,7 @@ public sealed class MachineBoardTest [Test] public async Task TestValidateBoardComponentRequirements() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -136,7 +133,5 @@ public sealed class MachineBoardTest }); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs b/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs index 6d48a668a5..43fea6d6c0 100644 --- a/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs +++ b/Content.IntegrationTests/Tests/MagazineVisualsSpriteTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Client.Weapons.Ranged.Components; +using Content.IntegrationTests.Fixtures; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; @@ -9,12 +10,12 @@ namespace Content.IntegrationTests.Tests; /// Tests all entity prototypes with the MagazineVisualsComponent. /// [TestFixture] -public sealed class MagazineVisualsSpriteTest +public sealed class MagazineVisualsSpriteTest : GameTest { [Test] public async Task MagazineVisualsSpritesExist() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var client = pair.Client; var toTest = new List<(int, string)>(); var protos = pair.GetPrototypesWithComponent(); @@ -67,7 +68,5 @@ public sealed class MagazineVisualsSpriteTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Mapping/MappingTests.cs b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs index be8bad229b..0bf637cf19 100644 --- a/Content.IntegrationTests/Tests/Mapping/MappingTests.cs +++ b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -5,15 +6,18 @@ using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.Mapping; [TestFixture] -public sealed class MappingTests +public sealed class MappingTests : GameTest { + public override PoolSettings PoolSettings => + new() { Dirty = true, Connected = true, DummyTicker = false }; + /// /// Checks that the mapping command creates paused & uninitialized maps. /// [Test] public async Task MappingTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Dirty = true, Connected = true, DummyTicker = false }); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; @@ -97,6 +101,5 @@ public sealed class MappingTests Assert.That(server.MetaData(ent).EntityPaused, Is.True); await server.WaitPost(() => entMan.DeleteEntity(map)); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/MappingEditorTest.cs b/Content.IntegrationTests/Tests/MappingEditorTest.cs index bd930aef9e..987fe7ad73 100644 --- a/Content.IntegrationTests/Tests/MappingEditorTest.cs +++ b/Content.IntegrationTests/Tests/MappingEditorTest.cs @@ -1,19 +1,17 @@ using Content.Client.Gameplay; using Content.Client.Mapping; +using Content.IntegrationTests.Fixtures; using Robust.Client.State; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class MappingEditorTest +public sealed class MappingEditorTest : GameTest { [Test] public async Task StopHardCodingWidgetsJesusChristTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true - }); + var pair = Pair; var client = pair.Client; var state = client.ResolveDependency(); @@ -35,7 +33,5 @@ public sealed class MappingEditorTest state.RequestStateChange(); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Markings/MarkingManagerTests.cs b/Content.IntegrationTests/Tests/Markings/MarkingManagerTests.cs index c81ff6a698..af5c6c2018 100644 --- a/Content.IntegrationTests/Tests/Markings/MarkingManagerTests.cs +++ b/Content.IntegrationTests/Tests/Markings/MarkingManagerTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Shared.Body; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -9,7 +10,7 @@ namespace Content.IntegrationTests.Tests.Markings; [TestFixture] [TestOf(typeof(MarkingManager))] -public sealed class MarkingManagerTests +public sealed class MarkingManagerTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -76,7 +77,7 @@ public sealed class MarkingManagerTests [Test] public async Task HairConvesion() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -96,14 +97,12 @@ public sealed class MarkingManagerTests Assert.That(hairMarkings[0].MarkingId, Is.EqualTo("HumanHairLongBedhead2")); Assert.That(hairMarkings[0].MarkingColors[0], Is.EqualTo(Color.Red)); }); - - await pair.CleanReturnAsync(); } [Test] public async Task LimitsFilling() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -118,14 +117,12 @@ public sealed class MarkingManagerTests Assert.That(dict[HumanoidVisualLayers.Eyes], Has.Count.EqualTo(1)); Assert.That(dict[HumanoidVisualLayers.Eyes][0].MarkingId, Is.EqualTo("EyesMarking")); }); - - await pair.CleanReturnAsync(); } [Test] public async Task LimitsTruncations() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -146,14 +143,12 @@ public sealed class MarkingManagerTests Assert.That(dict[HumanoidVisualLayers.Eyes], Has.Count.EqualTo(1)); Assert.That(dict[HumanoidVisualLayers.Eyes][0].MarkingId, Is.EqualTo("MenOnlyMarking")); }); - - await pair.CleanReturnAsync(); } [Test] public async Task EnsureValidGroupAndSex() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -191,14 +186,12 @@ public sealed class MarkingManagerTests Assert.That(testingMenMarkings[HumanoidVisualLayers.Eyes][1].MarkingId, Is.EqualTo("TestingOnlyMarking")); Assert.That(testingMenMarkings[HumanoidVisualLayers.Eyes][2].MarkingId, Is.EqualTo("TestingMenOnlyMarking")); }); - - await pair.CleanReturnAsync(); } [Test] public async Task EnsureValidColors() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -232,7 +225,5 @@ public sealed class MarkingManagerTests Assert.That(eyeMarkings[1].MarkingColors[0], Is.EqualTo(Color.Red)); Assert.That(eyeMarkings[3].MarkingColors[0], Is.EqualTo(Color.Green)); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Markings/MarkingsViewModelTests.cs b/Content.IntegrationTests/Tests/Markings/MarkingsViewModelTests.cs index f69d3356c2..026d2ecf3b 100644 --- a/Content.IntegrationTests/Tests/Markings/MarkingsViewModelTests.cs +++ b/Content.IntegrationTests/Tests/Markings/MarkingsViewModelTests.cs @@ -59,7 +59,7 @@ public sealed class MarkingsViewModelTests [SetUp] public async Task SetUp() { - Pair = await PoolManager.GetServerClient(); + Pair = await PoolManager.GetServerClient(testContext: new NUnitTestContextWrap(TestContext.CurrentContext, TestContext.Out)); await Client.WaitPost(() => { Model = new MarkingsViewModel(); diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index 3c6c372b75..460146357c 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Server.Cargo.Systems; using Content.Server.Construction.Completions; using Content.Server.Construction.Components; @@ -26,7 +27,7 @@ namespace Content.IntegrationTests.Tests; /// create them. /// [TestFixture] -public sealed class MaterialArbitrageTest +public sealed class MaterialArbitrageTest : GameTest { // These sets are for selectively excluding recipes from arbitrage. // You should NOT be adding to these. They exist here for downstreams and potential future issues. @@ -36,7 +37,7 @@ public sealed class MaterialArbitrageTest [Test] public async Task NoMaterialArbitrage() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -439,7 +440,6 @@ public sealed class MaterialArbitrageTest }); await server.WaitPost(() => mapSystem.DeleteMap(testMap.MapId)); - await pair.CleanReturnAsync(); async Task GetSpawnedPrice(Dictionary ents) { diff --git a/Content.IntegrationTests/Tests/Materials/MaterialTests.cs b/Content.IntegrationTests/Tests/Materials/MaterialTests.cs index a177869e7f..43c7dab2b0 100644 --- a/Content.IntegrationTests/Tests/Materials/MaterialTests.cs +++ b/Content.IntegrationTests/Tests/Materials/MaterialTests.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Server.Stack; using Content.Shared.Stacks; using Content.Shared.Materials; @@ -14,12 +15,12 @@ namespace Content.IntegrationTests.Tests.Materials [TestFixture] [TestOf(typeof(StackSystem))] [TestOf(typeof(MaterialPrototype))] - public sealed class MaterialPrototypeSpawnsStackMaterialTest + public sealed class MaterialPrototypeSpawnsStackMaterialTest : GameTest { [Test] public async Task MaterialPrototypeSpawnsStackMaterial() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -60,8 +61,6 @@ namespace Content.IntegrationTests.Tests.Materials mapSystem.DeleteMap(testMap.MapId); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs index 32fcf9c1ad..7369fece35 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Shared.Ghost; @@ -12,7 +13,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Minds; [TestFixture] -public sealed class GhostRoleTests +public sealed class GhostRoleTests : GameTest { private const string GhostRoleProtoId = "GhostRoleTestEntity"; private const string TestMobProtoId = "GhostRoleTestMob"; @@ -33,6 +34,13 @@ public sealed class GhostRoleTests - type: MobState # MobState is required for correct determination of if the player can return to body or not """; + public override PoolSettings PoolSettings => new() + { + Dirty = true, + DummyTicker = false, + Connected = true + }; + /// /// This is a simple test that just checks if a player can take a ghost role and then regain control of their /// original entity without encountering errors. @@ -43,12 +51,7 @@ public sealed class GhostRoleTests { var ghostCommand = adminGhost ? "aghost" : "ghost"; - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - DummyTicker = false, - Connected = true - }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -210,7 +213,6 @@ public sealed class GhostRoleTests if (!adminGhost) { // End of the normal player ghost role test - await pair.CleanReturnAsync(); return; } @@ -242,7 +244,5 @@ public sealed class GhostRoleTests // Check that there is are no lingereing ghosts Assert.That(entMan.Count(), Is.Zero); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/GhostTests.cs b/Content.IntegrationTests/Tests/Minds/GhostTests.cs index 3a860267e5..26991c08ad 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostTests.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Pair; using Content.Shared.Ghost; using Content.Shared.Mind; @@ -12,7 +13,7 @@ using Robust.UnitTesting; namespace Content.IntegrationTests.Tests.Minds; [TestFixture] -public sealed class GhostTests +public sealed class GhostTests : GameTest { private struct GhostTestData { @@ -45,17 +46,20 @@ public sealed class GhostTests } } + // Client is needed to create a session for the ghost system. Creating a dummy session was too difficult. + public override PoolSettings PoolSettings => new() + { + DummyTicker = false, + Connected = true, + Dirty = true + }; + private async Task SetupData() { var data = new GhostTestData { - // Client is needed to create a session for the ghost system. Creating a dummy session was too difficult. - Pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = true - }) + // ..Just use the gametest pair, please. + Pair = Pair, }; data.SEntMan = data.Pair.Server.ResolveDependency(); @@ -126,8 +130,6 @@ public sealed class GhostTests // Ensure the position is the same var ghostPosition = data.SEntMan.GetComponent(ghost).Coordinates; Assert.That(ghostPosition, Is.EqualTo(oldPosition)); - - await data.Pair.CleanReturnAsync(); } /// @@ -154,8 +156,6 @@ public sealed class GhostTests // Ensure the position is the same var ghostPosition = data.SEntMan.GetComponent(ghost).Coordinates; Assert.That(ghostPosition, Is.EqualTo(oldPosition)); - - await data.Pair.CleanReturnAsync(); } [Test] @@ -168,10 +168,6 @@ public sealed class GhostTests // Delete the grid await data.Server.WaitPost(() => data.SEntMan.DeleteEntity(data.MapData.Grid.Owner)); }); - - await data.Pair.RunTicksSync(5); - - await data.Pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs index ad4ddc2612..c207e7b489 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs @@ -11,13 +11,7 @@ public sealed partial class MindTests [Test] public async Task DeleteAllThenGhost() { - var settings = new PoolSettings - { - Dirty = true, - DummyTicker = false, - Connected = true - }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; // Client is connected with a valid entity & mind Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity)); @@ -56,7 +50,5 @@ public sealed partial class MindTests Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind)); var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value); Assert.That(xform.MapID, Is.EqualTo(mapId)); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs index 513049bcad..cf87b3ea76 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs @@ -23,7 +23,7 @@ public sealed partial class MindTests [Test] public async Task TestDeleteVisiting() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var server = pair.Server; var entMan = server.ResolveDependency(); @@ -67,15 +67,13 @@ public sealed partial class MindTests // This used to throw so make sure it doesn't. await server.WaitPost(() => entMan.DeleteEntity(mind.OwnedEntity!.Value)); await pair.RunTicksSync(5); - - await pair.CleanReturnAsync(); } // this is a variant of TestGhostOnDelete that just deletes the whole map. [Test] public async Task TestGhostOnDeleteMap() { - await using var pair = await SetupPair(dirty: true); + var pair = await SetupPair(dirty: true); var server = pair.Server; var testMap = await pair.CreateTestMap(); var testMap2 = await pair.CreateTestMap(); @@ -117,8 +115,6 @@ public sealed partial class MindTests Assert.That(transform.MapID, Is.Not.EqualTo(testMap.MapId)); #pragma warning restore NUnit2045 }); - - await pair.CleanReturnAsync(); } /// @@ -130,7 +126,7 @@ public sealed partial class MindTests public async Task TestGhostOnDelete() { // Client is needed to spawn session - await using var pair = await SetupPair(dirty: true); + var pair = await SetupPair(dirty: true); var server = pair.Server; var entMan = server.ResolveDependency(); @@ -145,8 +141,6 @@ public sealed partial class MindTests await pair.RunTicksSync(5); Assert.That(entMan.HasComponent(player.AttachedEntity), "Player did not become a ghost"); - - await pair.CleanReturnAsync(); } /// @@ -162,7 +156,7 @@ public sealed partial class MindTests public async Task TestOriginalDeletedWhileGhostingKeepsGhost() { // Client is needed to spawn session - await using var pair = await SetupPair(); + var pair = await SetupPair(); var server = pair.Server; var entMan = server.ResolveDependency(); @@ -207,8 +201,6 @@ public sealed partial class MindTests Assert.That(mind.Comp.VisitingEntity, Is.Null); Assert.That(mind.Comp.OwnedEntity, Is.EqualTo(ghost)); }); - - await pair.CleanReturnAsync(); } /// @@ -220,7 +212,7 @@ public sealed partial class MindTests [Test] public async Task TestGhostToAghost() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var server = pair.Server; var entMan = server.ResolveDependency(); var playerMan = server.ResolveDependency(); @@ -250,8 +242,6 @@ public sealed partial class MindTests var mind = entMan.GetComponent(mindId!.Value); Assert.That(mind.VisitingEntity, Is.Null); - - await pair.CleanReturnAsync(); } /// @@ -264,7 +254,7 @@ public sealed partial class MindTests public async Task TestGhostDeletedSpawnsNewGhost() { // Client is needed to spawn session - await using var pair = await SetupPair(); + var pair = await SetupPair(); var server = pair.Server; var entMan = server.ResolveDependency(); @@ -308,7 +298,5 @@ public sealed partial class MindTests Assert.That(entMan.HasComponent(player.AttachedEntity!.Value)); #pragma warning restore NUnit2045 }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs b/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs index cebbed8ee8..e22f6c1e4d 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs @@ -18,6 +18,14 @@ namespace Content.IntegrationTests.Tests.Minds; // This partial class contains misc helper functions for other tests. public sealed partial class MindTests { + // TODO GAMETEST: Rewrite this test to use improved GameTest pair management when I have an API for that figured out. + public override PoolSettings PoolSettings => new() + { + DummyTicker = false, + Connected = true, + Dirty = true, + }; + /// /// Gets a server-client pair and ensures that the client is attached to a simple mind test entity. /// @@ -26,15 +34,9 @@ public sealed partial class MindTests /// the player's mind's current entity, likely because some previous test directly changed the players attached /// entity. /// - private static async Task SetupPair(bool dirty = false) + private async Task SetupPair(bool dirty = false) { - var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = dirty - }); - + var pair = Pair; var entMan = pair.Server.ResolveDependency(); var playerMan = pair.Server.ResolveDependency(); var mindSys = entMan.System(); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.ReconnectTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.ReconnectTests.cs index db87797553..fbe79fe044 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.ReconnectTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.ReconnectTests.cs @@ -17,7 +17,7 @@ public sealed partial class MindTests [Test] public async Task TestGhostsCanReconnect() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var entMan = pair.Server.ResolveDependency(); var mind = GetMind(pair); @@ -32,8 +32,6 @@ public sealed partial class MindTests Assert.That(entMan.HasComponent(mind.Comp.OwnedEntity)); Assert.That(mind.Comp.VisitingEntity, Is.Null); }); - - await pair.CleanReturnAsync(); } // This test will do the following: @@ -44,7 +42,7 @@ public sealed partial class MindTests [Test] public async Task TestDeletedCanReconnect() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var entMan = pair.Server.ResolveDependency(); var mind = GetMind(pair); @@ -82,8 +80,6 @@ public sealed partial class MindTests Assert.That(mind.Comp.OwnedEntity, Is.Not.EqualTo(entity)); Assert.That(entMan.HasComponent(mind.Comp.OwnedEntity)); }); - - await pair.CleanReturnAsync(); } // This test will do the following: @@ -94,7 +90,7 @@ public sealed partial class MindTests [Test] public async Task TestVisitingGhostReconnect() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var entMan = pair.Server.ResolveDependency(); var mind = GetMind(pair); @@ -110,8 +106,6 @@ public sealed partial class MindTests Assert.That(entMan.Deleted(original), Is.False); Assert.That(entMan.Deleted(ghost)); }); - - await pair.CleanReturnAsync(); } // This test will do the following: @@ -122,7 +116,7 @@ public sealed partial class MindTests [Test] public async Task TestVisitingReconnect() { - await using var pair = await SetupPair(true); + var pair = await SetupPair(true); var entMan = pair.Server.ResolveDependency(); var mindSys = entMan.System(); var mind = GetMind(pair); @@ -150,8 +144,6 @@ public sealed partial class MindTests Assert.That(entMan.Deleted(visiting), Is.False); Assert.That(mind.Comp.CurrentEntity, Is.EqualTo(visiting)); }); - - await pair.CleanReturnAsync(); } // This test will do the following @@ -162,7 +154,7 @@ public sealed partial class MindTests [Test] public async Task TestReconnect() { - await using var pair = await SetupPair(); + var pair = await SetupPair(); var mind = GetMind(pair); Assert.That(mind.Comp.VisitingEntity, Is.Null); @@ -178,7 +170,5 @@ public sealed partial class MindTests Assert.That(newMind.Comp.VisitingEntity, Is.Null); Assert.That(newMind.Comp.OwnedEntity, Is.EqualTo(entity)); Assert.That(newMind.Id, Is.EqualTo(mind.Id)); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 35069339ba..08cfbaed5e 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; @@ -23,7 +24,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Minds; [TestFixture] -public sealed partial class MindTests +public sealed partial class MindTests : GameTest { private static readonly ProtoId BluntDamageType = "Blunt"; @@ -56,7 +57,7 @@ public sealed partial class MindTests [Test] public async Task TestCreateAndTransferMindToNewEntity() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -75,14 +76,12 @@ public sealed partial class MindTests mindSystem.TransferTo(mind, entity, mind: mind); Assert.That(mindSystem.GetMind(entity, mindComp), Is.EqualTo(mind.Owner)); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestReplaceMind() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -107,14 +106,12 @@ public sealed partial class MindTests Assert.That(mind.OwnedEntity, Is.Not.EqualTo(entity)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestEntityDeadWhenGibbed() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -160,14 +157,12 @@ public sealed partial class MindTests var mind = entMan.GetComponent(mindId); Assert.That(mindSystem.IsCharacterDeadPhysically(mind)); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestMindTransfersToOtherEntity() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -194,18 +189,12 @@ public sealed partial class MindTests Assert.That(mindSystem.GetMind(targetEntity), Is.EqualTo(mind)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestOwningPlayerCanBeChanged() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Connected = true, - DummyTicker = false - }); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -253,14 +242,12 @@ public sealed partial class MindTests }); await pair.RunTicksSync(5); - - await pair.CleanReturnAsync(); } [Test] public async Task TestAddRemoveHasRoles() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -323,15 +310,13 @@ public sealed partial class MindTests Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestPlayerCanGhost() { // Client is needed to spawn session - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false }); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -399,19 +384,12 @@ public sealed partial class MindTests Assert.That(mId, Is.Not.EqualTo(mindId)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestGhostDoesNotInfiniteLoop() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = true - }); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -482,7 +460,5 @@ public sealed partial class MindTests Assert.That(player.AttachedEntity, Is.Not.Null); Assert.That(player.AttachedEntity!.Value, Is.EqualTo(ghost)); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs index f0a7268a3d..3204dc1c2f 100644 --- a/Content.IntegrationTests/Tests/Minds/RoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Roles.Components; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; @@ -6,7 +7,7 @@ using Robust.Shared.Reflection; namespace Content.IntegrationTests.Tests.Minds; [TestFixture] -public sealed class RoleTests +public sealed class RoleTests : GameTest { /// /// Check that any prototype with a is properly configured @@ -14,7 +15,7 @@ public sealed class RoleTests [Test] public async Task ValidateRolePrototypes() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var jobComp = pair.Server.ResolveDependency().GetComponentName(); @@ -35,7 +36,6 @@ public sealed class RoleTests } }); - await pair.CleanReturnAsync(); } /// @@ -45,7 +45,7 @@ public sealed class RoleTests [Test] public async Task ValidateJobPrototypes() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var mindCompId = pair.Server.ResolveDependency().GetComponentName(); @@ -57,8 +57,6 @@ public sealed class RoleTests Assert.That(((MindRoleComponent)mindComp).JobPrototype, Is.Not.Null); } }); - - await pair.CleanReturnAsync(); } /// @@ -68,7 +66,7 @@ public sealed class RoleTests [Test] public async Task ValidateRolesHaveMindRoleComp() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var refMan = pair.Server.ResolveDependency(); var mindCompId = pair.Server.ResolveDependency().GetComponentName(); @@ -87,7 +85,5 @@ public sealed class RoleTests } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/NPC/NPCTest.cs b/Content.IntegrationTests/Tests/NPC/NPCTest.cs index 064fd6c5bf..1568f360e7 100644 --- a/Content.IntegrationTests/Tests/NPC/NPCTest.cs +++ b/Content.IntegrationTests/Tests/NPC/NPCTest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Server.NPC.HTN; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -7,12 +8,12 @@ using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.NPC; [TestFixture] -public sealed class NPCTest +public sealed class NPCTest : GameTest { [Test] public async Task CompoundRecursion() { - var pool = await PoolManager.GetServerClient(); + var pool = Pair; var server = pool.Server; await server.WaitIdleAsync(); @@ -30,8 +31,6 @@ public sealed class NPCTest counts.Clear(); } }); - - await pool.CleanReturnAsync(); } private static void Count(HTNCompoundPrototype compound, Dictionary counts, HTNSystem htnSystem, IPrototypeManager protoManager) diff --git a/Content.IntegrationTests/Tests/Networking/NetworkIdsMatchTest.cs b/Content.IntegrationTests/Tests/Networking/NetworkIdsMatchTest.cs index c72be944fd..7b4f048939 100644 --- a/Content.IntegrationTests/Tests/Networking/NetworkIdsMatchTest.cs +++ b/Content.IntegrationTests/Tests/Networking/NetworkIdsMatchTest.cs @@ -1,14 +1,15 @@ +using Content.IntegrationTests.Fixtures; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Networking { [TestFixture] - public sealed class NetworkIdsMatchTest + public sealed class NetworkIdsMatchTest : GameTest { [Test] public async Task TestConnect() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -38,7 +39,6 @@ namespace Content.IntegrationTests.Tests.Networking Assert.That(clientNetComps[netId].Name, Is.EqualTo(serverNetComps[netId].Name)); } }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs b/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs index b395569848..786e14ae42 100644 --- a/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs +++ b/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Robust.Shared.GameObjects; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; @@ -5,16 +6,17 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Networking; [TestFixture] -public sealed class PvsCommandTest +public sealed class PvsCommandTest : GameTest { private static readonly EntProtoId TestEnt = "MobHuman"; + public override PoolSettings PoolSettings => new() { Connected = true, DummyTicker = false }; + [Test] public async Task TestPvsCommands() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false }); + var pair = Pair; var (server, client) = pair; - await pair.RunTicksSync(5); // Spawn a complex entity. EntityUid entity = default; @@ -46,6 +48,5 @@ public sealed class PvsCommandTest Assert.That(meta.LastStateApplied, Is.GreaterThan(lastApplied)); await server.WaitPost(() => server.EntMan.DeleteEntity(entity)); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs b/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs index 4539ca81a5..26e76376ce 100644 --- a/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs +++ b/Content.IntegrationTests/Tests/Networking/ReconnectTest.cs @@ -1,15 +1,16 @@ +using Content.IntegrationTests.Fixtures; using Robust.Client.Console; using Robust.Shared.Network; namespace Content.IntegrationTests.Tests.Networking { [TestFixture] - public sealed class ReconnectTest + public sealed class ReconnectTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -32,7 +33,6 @@ namespace Content.IntegrationTests.Tests.Networking await pair.RunTicksSync(10); await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs b/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs index 3e49499845..15c7373b63 100644 --- a/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs +++ b/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Numerics; +using Content.IntegrationTests.Fixtures; using Robust.Client.GameStates; using Robust.Client.Timing; using Robust.Shared; @@ -27,12 +28,12 @@ namespace Content.IntegrationTests.Tests.Networking // the tick where the server *should* have, but did not, acknowledge the state change. // Finally, we run two events inside the prediction area to ensure reconciling does for incremental stuff. [TestFixture] - public sealed class SimplePredictReconcileTest + public sealed class SimplePredictReconcileTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var pair = Pair; var server = pair.Server; var client = pair.Client; @@ -386,7 +387,6 @@ namespace Content.IntegrationTests.Tests.Networking } cfg.SetCVar(CVars.NetLogging, log); - await pair.CleanReturnAsync(); } public sealed class PredictionTestEntitySystem : EntitySystem diff --git a/Content.IntegrationTests/Tests/Physics/AnchorPrototypeTest.cs b/Content.IntegrationTests/Tests/Physics/AnchorPrototypeTest.cs index a65e7d1fd6..f65bd324a0 100644 --- a/Content.IntegrationTests/Tests/Physics/AnchorPrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Physics/AnchorPrototypeTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Robust.Shared.GameObjects; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -6,7 +7,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Physics; [TestFixture] -public sealed class AnchorPrototypeTest +public sealed class AnchorPrototypeTest : GameTest { /// /// Asserts that entityprototypes marked as anchored are also static physics bodies. @@ -14,7 +15,7 @@ public sealed class AnchorPrototypeTest [Test] public async Task TestStaticAnchorPrototypes() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var protoManager = pair.Server.ResolveDependency(); @@ -37,7 +38,5 @@ public sealed class AnchorPrototypeTest Assert.That(physics.BodyType, Is.EqualTo(BodyType.Static), $"Found entity prototype {ent} marked as anchored but not static for physics."); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index e0588f789c..5473595963 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; using Content.IntegrationTests.Utility; using YamlDotNet.RepresentationModel; using Content.Server.Administration.Systems; @@ -28,8 +30,14 @@ using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class PostMapInitTest + public sealed class PostMapInitTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings() + { + Connected = true, + Dirty = true, + }; + private const bool SkipTestMaps = true; private const string TestMapsPath = "/Maps/Test/"; @@ -95,16 +103,16 @@ namespace Content.IntegrationTests.Tests /// Asserts that specific files have been saved as grids and not maps. /// [Test, TestCaseSource(nameof(Grids))] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task GridsLoadableTest(string mapFile) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); var mapSystem = entManager.System(); var cfg = server.ResolveDependency(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); var path = new ResPath(mapFile); await server.WaitPost(() => @@ -121,9 +129,6 @@ namespace Content.IntegrationTests.Tests mapSystem.DeleteMap(mapId); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } /// @@ -131,16 +136,16 @@ namespace Content.IntegrationTests.Tests /// [Test] [TestCaseSource(nameof(ShuttleMapFiles))] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task ShuttlesLoadableTest(ResPath path) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); var mapSystem = entManager.System(); var cfg = server.ResolveDependency(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { @@ -160,17 +165,13 @@ namespace Content.IntegrationTests.Tests mapSystem.DeleteMap(mapId); }); }); - - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } [Test] [TestCaseSource(nameof(AllMapFiles))] public async Task NoSavedPostMapInitTest(ResPath map) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var resourceManager = server.ResolveDependency(); @@ -184,7 +185,6 @@ namespace Content.IntegrationTests.Tests // ReSharper disable once RedundantLogicalConditionalExpressionOperand if (SkipTestMaps && rootedPath.ToString().StartsWith(TestMapsPath, StringComparison.Ordinal)) { - await pair.CleanReturnAsync(); return; // We just pass immediately. } @@ -241,8 +241,6 @@ namespace Content.IntegrationTests.Tests await server.WaitPost(() => mapSys.InitializeMap(id)); Assert.That(loader.TrySaveMap(id, path)); Assert.That(IsPreInit(path, loader, deps, ev.RenamedPrototypes, ev.DeletedPrototypes), Is.False); - - await pair.CleanReturnAsync(); } private bool IsWhitelistedForMap(EntProtoId protoId, ResPath map) @@ -327,12 +325,10 @@ namespace Content.IntegrationTests.Tests } [Test, TestCaseSource(nameof(GameMaps))] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task GameMapsLoadableTest(string mapProto) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true // Stations spawn a bunch of nullspace entities and maps like centcomm. - }); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -343,7 +339,6 @@ namespace Content.IntegrationTests.Tests var ticker = entManager.EntitySysManager.GetEntitySystem(); var shuttleSystem = entManager.EntitySysManager.GetEntitySystem(); var cfg = server.ResolveDependency(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { @@ -442,9 +437,6 @@ namespace Content.IntegrationTests.Tests throw new Exception($"Failed to delete map {mapProto}", ex); } }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } @@ -473,37 +465,18 @@ namespace Content.IntegrationTests.Tests return resultCount; } - [Test] - public async Task AllMapsTested() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - var protoMan = server.ResolveDependency(); - - var gameMaps = protoMan.EnumeratePrototypes() - .Where(x => !pair.IsTestPrototype(x)) - .Select(x => x.ID) - .ToHashSet(); - - Assert.That(gameMaps.Remove(PoolManager.TestMap)); - - Assert.That(gameMaps, Is.EquivalentTo(GameMaps.ToHashSet()), "Game map prototype missing from test cases."); - - await pair.CleanReturnAsync(); - } - [Test] [TestCaseSource(nameof(AllMapFiles))] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task NonGameMapsLoadableTest(ResPath mapPath) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var resourceManager = server.ResolveDependency(); var protoManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); var gameMaps = protoManager.EnumeratePrototypes().Select(o => o.MapPath).ToHashSet(); @@ -512,7 +485,6 @@ namespace Content.IntegrationTests.Tests { // TODO: You might be able to save like, 1-2 seconds of test time if you eliminate these before // actually needing a pair. - await pair.CleanReturnAsync(); return; } @@ -520,7 +492,6 @@ namespace Content.IntegrationTests.Tests if (SkipTestMaps && rootedPath.ToString().StartsWith(TestMapsPath, StringComparison.Ordinal)) { - await pair.CleanReturnAsync(); return; } @@ -563,9 +534,6 @@ namespace Content.IntegrationTests.Tests } }); }); - - await server.WaitRunTicks(1); - await pair.CleanReturnAsync(); } /// diff --git a/Content.IntegrationTests/Tests/Power/PowerStatePrototypeTest.cs b/Content.IntegrationTests/Tests/Power/PowerStatePrototypeTest.cs index 288e976e9b..e991801330 100644 --- a/Content.IntegrationTests/Tests/Power/PowerStatePrototypeTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerStatePrototypeTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Power.Components; using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; @@ -8,7 +9,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Power; [TestFixture, TestOf(typeof(SharedPowerStateSystem))] -public sealed class PowerStatePrototypeTest +public sealed class PowerStatePrototypeTest : GameTest { /// /// Asserts that the 's load is the same @@ -18,7 +19,7 @@ public sealed class PowerStatePrototypeTest [Test] public async Task AssertApcPowerMatchesPowerState() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ResolveDependency(); @@ -53,7 +54,5 @@ public sealed class PowerStatePrototypeTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Power/PowerStateTest.cs b/Content.IntegrationTests/Tests/Power/PowerStateTest.cs index edec6f3d21..a265de532f 100644 --- a/Content.IntegrationTests/Tests/Power/PowerStateTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerStateTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Coordinates; using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; @@ -8,7 +9,7 @@ using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests.Power; [TestFixture] -public sealed class PowerStateTest +public sealed class PowerStateTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -31,7 +32,7 @@ public sealed class PowerStateTest [Test] public async Task SetWorkingState_IdleToWorking_UpdatesLoad() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -65,8 +66,6 @@ public sealed class PowerStateTest Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -75,7 +74,7 @@ public sealed class PowerStateTest [Test] public async Task SetWorkingState_WorkingToIdle_UpdatesLoad() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -118,8 +117,6 @@ public sealed class PowerStateTest Assert.That(receiver.Load, Is.EqualTo(powerState.IdlePowerDraw).Within(0.01f)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -128,7 +125,7 @@ public sealed class PowerStateTest [Test] public async Task SetWorkingState_AlreadyInState_NoChange() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -179,8 +176,6 @@ public sealed class PowerStateTest Assert.That(receiver.Load, Is.EqualTo(powerState.WorkingPowerDraw).Within(0.01f)); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index a28f646ef8..db6c0b7c89 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Server.NodeContainer.EntitySystems; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -14,7 +15,7 @@ using Robust.Shared.Timing; namespace Content.IntegrationTests.Tests.Power { [TestFixture] - public sealed class PowerTest + public sealed class PowerTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -167,7 +168,7 @@ namespace Content.IntegrationTests.Tests.Power [Test] public async Task TestSimpleSurplus() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -218,8 +219,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(supplier.CurrentSupply, Is.EqualTo(loadPower * 2).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } @@ -229,7 +228,7 @@ namespace Content.IntegrationTests.Tests.Power [Test] public async Task TestSimpleDeficit() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -280,14 +279,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestSupplyRamp() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -368,14 +365,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(consumer.ReceivedPower, Is.EqualTo(400).Within(tickDev)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestBatteryRamp() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -472,8 +467,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(currentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev)); }); }); - - await pair.CleanReturnAsync(); } [Test] @@ -481,7 +474,7 @@ namespace Content.IntegrationTests.Tests.Power { // checks that batteries and supplies properly ramp down if the load is disconnected/disabled. - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -571,14 +564,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(consumer.ReceivedPower, Is.EqualTo(0).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestSimpleBatteryChargeDeficit() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var gameTiming = server.ResolveDependency(); @@ -631,14 +622,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(supplier.CurrentSupply, Is.EqualTo(500).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestFullBattery() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -713,14 +702,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(currentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestFullBatteryEfficiencyPassThrough() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -795,14 +782,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(currentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestFullBatteryEfficiencyDemandPassThrough() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -888,8 +873,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -899,7 +882,7 @@ namespace Content.IntegrationTests.Tests.Power [Test] public async Task TestSupplyPrioritized() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -988,8 +971,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(netBattery2.SupplyRampPosition, Is.EqualTo(500).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -998,7 +979,7 @@ namespace Content.IntegrationTests.Tests.Power [Test] public async Task TestBatteriesProportional() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -1079,14 +1060,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestBatteryEngineCut() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -1161,8 +1140,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(netBattery.CurrentSupply, Is.GreaterThan(0)); }); }); - - await pair.CleanReturnAsync(); } /// @@ -1171,7 +1148,7 @@ namespace Content.IntegrationTests.Tests.Power [Test] public async Task TestTerminalNodeGroups() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -1230,14 +1207,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(leftNode.NodeGroup, Is.Not.EqualTo(rightNode.NodeGroup)); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task ApcChargingTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -1290,14 +1265,12 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(currentCharge, Is.GreaterThan(0)); //apc battery should have gained charge }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task ApcNetTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); @@ -1355,8 +1328,6 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(apcNetBattery.CurrentSupply, Is.EqualTo(1).Within(0.1)); }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs index 9d79abf480..763cec328e 100644 --- a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs +++ b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -13,7 +14,7 @@ using Robust.Shared.EntitySerialization; namespace Content.IntegrationTests.Tests.Power; -public sealed class StationPowerTests +public sealed class StationPowerTests : GameTest { /// /// How long the station should be able to survive on stored power if nothing is changed from round start. @@ -36,14 +37,16 @@ public sealed class StationPowerTests "Exo", ]; + public override PoolSettings PoolSettings => new () + { + Dirty = true, + }; + [Explicit] [Test, TestCaseSource(nameof(GameMaps))] public async Task TestStationStartingPowerWindow(string mapProtoId) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - }); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; @@ -96,17 +99,12 @@ public sealed class StationPowerTests Assert.That(totalStartingCharge, Is.GreaterThanOrEqualTo(requiredStoredPower), $"Needs at least {requiredStoredPower - totalStartingCharge} more stored power!"); }); - - await pair.CleanReturnAsync(); } [Test, TestCaseSource(nameof(GameMaps))] public async Task TestApcLoad(string mapProtoId) { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - Dirty = true, - }); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; @@ -145,7 +143,5 @@ public sealed class StationPowerTests } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs index 267b3637e0..62c9f1a460 100644 --- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Server.Station.Systems; using Content.Shared.Inventory; using Content.Shared.Preferences; @@ -9,7 +10,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Preferences; [TestFixture] -public sealed class LoadoutTests +public sealed class LoadoutTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -42,16 +43,18 @@ public sealed class LoadoutTests ["jumpsuit"] = "ClothingUniformJumpsuitColorGrey" }; + public override PoolSettings PoolSettings => new() + { + Dirty = true, + }; + /// /// Checks that an empty loadout still spawns with default gear and not naked. /// [Test] public async Task TestEmptyLoadout() { - var pair = await PoolManager.GetServerClient(new PoolSettings() - { - Dirty = true, - }); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -87,7 +90,5 @@ public sealed class LoadoutTests entManager.DeleteEntity(tester); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs index 8209e6c61e..b62f5627db 100644 --- a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading; +using Content.IntegrationTests.Fixtures; using Content.Server.Database; using Content.Server.Preferences.Managers; using Content.Shared.Body; @@ -20,7 +21,7 @@ using Robust.UnitTesting; namespace Content.IntegrationTests.Tests.Preferences { [TestFixture] - public sealed class ServerDbSqliteTests + public sealed class ServerDbSqliteTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -75,12 +76,10 @@ namespace Content.IntegrationTests.Tests.Preferences [Test] public async Task TestUserDoesNotExist() { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var db = GetDb(pair.Server); // Database should be empty so a new GUID should do it. Assert.That(await db.GetPlayerPreferencesAsync(NewUserId()), Is.Null); - - await pair.CleanReturnAsync(); } [Test] @@ -119,7 +118,7 @@ namespace Content.IntegrationTests.Tests.Preferences [Test] public async Task TestInitPrefs() { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var db = GetDb(pair.Server); var preferences = (ServerPreferencesManager)pair.Server.ResolveDependency(); var username = new NetUserId(new Guid("640bd619-fc8d-4fe2-bf3c-4a5fb17d6ddd")); @@ -129,13 +128,12 @@ namespace Content.IntegrationTests.Tests.Preferences var prefs = await db.GetPlayerPreferencesAsync(username); var profile = preferences.ConvertProfiles(prefs!.Profiles.Find(p => p.Slot == slot)); Assert.That(profile.MemberwiseEquals(originalProfile)); - await pair.CleanReturnAsync(); } [Test] public async Task TestDeleteCharacter() { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var db = GetDb(server); var username = new NetUserId(new Guid("640bd619-fc8d-4fe2-bf3c-4a5fb17d6ddd")); @@ -145,18 +143,16 @@ namespace Content.IntegrationTests.Tests.Preferences await db.SaveCharacterSlotAsync(username, null, 1); var prefs = await db.GetPlayerPreferencesAsync(username); Assert.That(prefs!.Profiles, Has.Count.EqualTo(1)); - await pair.CleanReturnAsync(); } [Test] public async Task TestNoPendingDatabaseChanges() { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var db = GetDb(server); Assert.That(async () => await db.HasPendingModelChanges(), Is.False, "The database has pending model changes. Add a new migration to apply them. See https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations"); - await pair.CleanReturnAsync(); } private static NetUserId NewUserId() @@ -172,7 +168,7 @@ namespace Content.IntegrationTests.Tests.Preferences [TestCaseSource(nameof(_trueFalse))] public async Task InvalidSpeciesConversion(bool legacy) { - var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var db = GetDb(pair.Server); var preferences = (ServerPreferencesManager)pair.Server.ResolveDependency(); @@ -204,8 +200,6 @@ namespace Content.IntegrationTests.Tests.Preferences Assert.That(converted.Characters[0].Species, Is.Not.EqualTo(InvalidSpecies)); Assert.That(converted.Characters[0].Species, Is.EqualTo(HumanoidCharacterProfile.DefaultSpecies)); }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Procedural/DungeonTests.cs b/Content.IntegrationTests/Tests/Procedural/DungeonTests.cs index 6bede9660a..d3afcc7253 100644 --- a/Content.IntegrationTests/Tests/Procedural/DungeonTests.cs +++ b/Content.IntegrationTests/Tests/Procedural/DungeonTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Server.Procedural; using Content.Shared.Procedural; using Robust.Shared.Maths; @@ -7,12 +8,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Procedural; [TestOf(typeof(DungeonSystem))] -public sealed class DungeonTests +public sealed class DungeonTests : GameTest { [Test] public async Task TestDungeonRoomPackBounds() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var protoManager = pair.Server.ResolveDependency(); await pair.Server.WaitAssertion(() => @@ -55,14 +56,12 @@ public sealed class DungeonTests } } }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestDungeonPresets() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var protoManager = pair.Server.ResolveDependency(); await pair.Server.WaitAssertion(() => @@ -92,7 +91,5 @@ public sealed class DungeonTests } } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs index 9ff8ca2900..5977e7deee 100644 --- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Coordinates; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -27,12 +28,12 @@ namespace Content.IntegrationTests.Tests; /// spawn it into a new empty map and seeing what the map yml looks like. /// [TestFixture] -public sealed class PrototypeSaveTest +public sealed class PrototypeSaveTest : GameTest { [Test] public async Task UninitializedSaveTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entityMan = server.ResolveDependency(); @@ -156,7 +157,6 @@ public sealed class PrototypeSaveTest } }); }); - await pair.CleanReturnAsync(); } public sealed class TestEntityUidContext : ISerializationContext, diff --git a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeTests.cs b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeTests.cs index 440d9e636e..3b4bed882d 100644 --- a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeTests.cs +++ b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeTests.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; @@ -8,7 +9,7 @@ using Robust.UnitTesting; namespace Content.IntegrationTests.Tests.PrototypeTests; -public sealed class PrototypeTests +public sealed class PrototypeTests : GameTest { /// /// This test writes all known server prototypes as yaml files, then validates that the result is valid yaml. @@ -17,10 +18,9 @@ public sealed class PrototypeTests [Test] public async Task TestAllServerPrototypesAreSerializable() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveThenValidatePrototype(pair.Server, "server", context); - await pair.CleanReturnAsync(); } /// @@ -30,10 +30,9 @@ public sealed class PrototypeTests [Test] public async Task TestAllClientPrototypesAreSerializable() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveThenValidatePrototype(pair.Client, "client", context); - await pair.CleanReturnAsync(); } public async Task SaveThenValidatePrototype(RobustIntegrationTest.IntegrationInstance instance, string instanceId, @@ -69,10 +68,9 @@ public sealed class PrototypeTests [Test] public async Task ServerPrototypeSaveLoadSaveTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveLoadSavePrototype(pair.Server, context); - await pair.CleanReturnAsync(); } /// @@ -81,10 +79,9 @@ public sealed class PrototypeTests [Test] public async Task ClientPrototypeSaveLoadSaveTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var context = new PrototypeSaveTest.TestEntityUidContext(); await SaveLoadSavePrototype(pair.Client, context); - await pair.CleanReturnAsync(); } private async Task SaveLoadSavePrototype( diff --git a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs index c641cd9bd1..ac7bdcd24d 100644 --- a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Tag; using Robust.Client.Upload.Commands; using Robust.Shared.GameObjects; @@ -6,7 +7,7 @@ using Robust.Shared.Upload; namespace Content.IntegrationTests.Tests.PrototypeTests; -public sealed class PrototypeUploadTest +public sealed class PrototypeUploadTest : GameTest { public const string IdA = "UploadTestPrototype"; public const string IdB = $"{IdA}NoParent"; @@ -36,7 +37,7 @@ public sealed class PrototypeUploadTest [TestOf(typeof(LoadPrototypeCommand))] public async Task TestFileUpload() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings {Connected = true}); + var pair = Pair; var sCompFact = pair.Server.ResolveDependency(); var cCompFact = pair.Client.ResolveDependency(); @@ -79,7 +80,5 @@ public sealed class PrototypeUploadTest Assert.That(cProtoB!.TryGetComponent(out _, cCompFact), Is.False); Assert.That(cProtoD!.TryGetComponent(out _, cCompFact), Is.True); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Puller/PullerTest.cs b/Content.IntegrationTests/Tests/Puller/PullerTest.cs index a4fde86dbf..541417354a 100644 --- a/Content.IntegrationTests/Tests/Puller/PullerTest.cs +++ b/Content.IntegrationTests/Tests/Puller/PullerTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Hands.Components; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Prototypes; @@ -9,7 +10,7 @@ namespace Content.IntegrationTests.Tests.Puller; #nullable enable [TestFixture] -public sealed class PullerTest +public sealed class PullerTest : GameTest { /// /// Checks that needsHands on PullerComponent is not set on mobs that don't even have hands. @@ -17,7 +18,7 @@ public sealed class PullerTest [Test] public async Task PullerSanityTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var compFactory = server.ResolveDependency(); @@ -39,7 +40,5 @@ public sealed class PullerTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Replays/ReplayTests.cs b/Content.IntegrationTests/Tests/Replays/ReplayTests.cs index dcff6c3b45..4bd5f62e78 100644 --- a/Content.IntegrationTests/Tests/Replays/ReplayTests.cs +++ b/Content.IntegrationTests/Tests/Replays/ReplayTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Shared.CCVar; using Robust.Shared; @@ -6,20 +7,21 @@ using Robust.Shared.Replays; namespace Content.IntegrationTests.Tests.Replays; [TestFixture] -public sealed class ReplayTests +public sealed class ReplayTests : GameTest { + public override PoolSettings PoolSettings => new() + { + DummyTicker = false, + Dirty = true + }; + /// /// Simple test that just makes sure that automatic replay recording on round restarts works without any issues. /// [Test] public async Task AutoRecordReplayTest() { - var settings = new PoolSettings - { - DummyTicker = false, - Dirty = true - }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; var server = pair.Server; Assert.That(server.CfgMan.GetCVar(CVars.ReplayServerRecordingEnabled), Is.False); @@ -54,7 +56,5 @@ public sealed class ReplayTests await server.WaitPost(() => ticker.RestartRound()); await pair.RunTicksSync(25); Assert.That(recordMan.IsRecording, Is.False); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/ResearchTest.cs b/Content.IntegrationTests/Tests/ResearchTest.cs index 4661a1ea9e..c95d68d9e2 100644 --- a/Content.IntegrationTests/Tests/ResearchTest.cs +++ b/Content.IntegrationTests/Tests/ResearchTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Lathe; using Content.Shared.Research.Prototypes; using Robust.Shared.GameObjects; @@ -8,12 +9,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class ResearchTest +public sealed class ResearchTest : GameTest { [Test] public async Task DisciplineValidTierPrerequesitesTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoManager = server.ResolveDependency(); @@ -42,14 +43,12 @@ public sealed class ResearchTest } }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task AllTechPrintableTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -99,7 +98,5 @@ public sealed class ResearchTest } }); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs index 40457f5488..13bb3cfd74 100644 --- a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs +++ b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking; +using Content.IntegrationTests.Fixtures; +using Content.Server.GameTicking; using Content.Shared.GameTicking; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; @@ -7,7 +8,7 @@ namespace Content.IntegrationTests.Tests { [TestFixture] [TestOf(typeof(RoundRestartCleanupEvent))] - public sealed class ResettingEntitySystemTests + public sealed class ResettingEntitySystemTests : GameTest { public sealed class TestRoundRestartCleanupEvent : EntitySystem { @@ -26,15 +27,17 @@ namespace Content.IntegrationTests.Tests } } + public override PoolSettings PoolSettings => new PoolSettings + { + DummyTicker = false, + Connected = true, + Dirty = true + }; + [Test] public async Task ResettingEntitySystemResetTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = true - }); + var pair = Pair; var server = pair.Server; var entitySystemManager = server.ResolveDependency(); @@ -52,7 +55,6 @@ namespace Content.IntegrationTests.Tests Assert.That(system.HasBeenReset); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Respirator/LungTest.cs b/Content.IntegrationTests/Tests/Respirator/LungTest.cs index ae6b50ff0f..097fe5e3a0 100644 --- a/Content.IntegrationTests/Tests/Respirator/LungTest.cs +++ b/Content.IntegrationTests/Tests/Respirator/LungTest.cs @@ -6,6 +6,7 @@ using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Map; using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Shared.Atmos.Components; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Utility; @@ -14,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Respirator; [TestFixture] [TestOf(typeof(LungSystem))] -public sealed class LungTest +public sealed class LungTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -54,7 +55,7 @@ public sealed class LungTest public async Task AirConsistencyTest() { // --- Setup - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -123,14 +124,12 @@ public sealed class LungTest "Did not exhale as much gas as was inhaled" ); } - - await pair.CleanReturnAsync(); } [Test] public async Task NoSuffocationTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); @@ -183,7 +182,5 @@ public sealed class LungTest $"Entity {entityManager.GetComponent(human).EntityName} is suffocating on tick {tick}"); }); } - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/RestartRoundTest.cs b/Content.IntegrationTests/Tests/RestartRoundTest.cs index 69c9a7dedf..1ed43b0951 100644 --- a/Content.IntegrationTests/Tests/RestartRoundTest.cs +++ b/Content.IntegrationTests/Tests/RestartRoundTest.cs @@ -1,20 +1,23 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class RestartRoundTest + public sealed class RestartRoundTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings + { + DummyTicker = false, + Connected = true, + Dirty = true + }; + [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = true - }); + var pair = Pair; var server = pair.Server; var sysManager = server.ResolveDependency(); @@ -23,8 +26,7 @@ namespace Content.IntegrationTests.Tests sysManager.GetEntitySystem().RestartRound(); }); - await pair.RunTicksSync(10); - await pair.CleanReturnAsync(); + await pair.RunUntilSynced(); } } } diff --git a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs index de89f16be7..e4f80eebb7 100644 --- a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs +++ b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Roles; using Content.Server.Storage.EntitySystems; using Robust.Shared.GameObjects; @@ -7,16 +8,17 @@ using Robust.Shared.Collections; namespace Content.IntegrationTests.Tests.Roles; [TestFixture] -public sealed class StartingGearPrototypeStorageTest +public sealed class StartingGearPrototypeStorageTest : GameTest { + public override PoolSettings PoolSettings => new() { Connected = true, Dirty = true }; + /// /// Checks that a storage fill on a StartingGearPrototype will properly fill /// [Test] public async Task TestStartingGearStorage() { - var settings = new PoolSettings { Connected = true, Dirty = true }; - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; var server = pair.Server; var mapSystem = server.System(); var storageSystem = server.System(); @@ -65,7 +67,5 @@ public sealed class StartingGearPrototypeStorageTest mapSystem.DeleteMap(testMap.MapId); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Round/JobTest.cs b/Content.IntegrationTests/Tests/Round/JobTest.cs index 215890791d..927f34a9ef 100644 --- a/Content.IntegrationTests/Tests/Round/JobTest.cs +++ b/Content.IntegrationTests/Tests/Round/JobTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.IntegrationTests.Pair; using Content.Server.GameTicking; using Content.Server.Mind; @@ -16,7 +17,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Round; [TestFixture] -public sealed class JobTest +public sealed class JobTest : GameTest { private static readonly ProtoId Passenger = "Passenger"; private static readonly ProtoId Engineer = "StationEngineer"; @@ -44,6 +45,13 @@ public sealed class JobTest {Captain}: [ 1, 1 ] "; + public override PoolSettings PoolSettings => new() + { + DummyTicker = false, + Connected = true, + InLobby = true + }; + private void AssertJob(TestPair pair, ProtoId job, NetUserId? user = null, bool isAntag = false) { var jobSys = pair.Server.System(); @@ -71,12 +79,7 @@ public sealed class JobTest [Test] public async Task StartRoundTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); var ticker = pair.Server.System(); @@ -95,7 +98,6 @@ public sealed class JobTest AssertJob(pair, Passenger); await pair.Server.WaitPost(() => ticker.RestartRound()); - await pair.CleanReturnAsync(); } /// @@ -104,12 +106,7 @@ public sealed class JobTest [Test] public async Task JobPreferenceTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); var ticker = pair.Server.System(); @@ -133,7 +130,6 @@ public sealed class JobTest AssertJob(pair, Passenger); await pair.Server.WaitPost(() => ticker.RestartRound()); - await pair.CleanReturnAsync(); } /// @@ -143,12 +139,7 @@ public sealed class JobTest [Test] public async Task JobWeightTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); var ticker = pair.Server.System(); @@ -169,7 +160,6 @@ public sealed class JobTest AssertJob(pair, Captain); await pair.Server.WaitPost(() => ticker.RestartRound()); - await pair.CleanReturnAsync(); } /// @@ -178,12 +168,7 @@ public sealed class JobTest [Test] public async Task JobPriorityTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - InLobby = true - }); + var pair = Pair; pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); var ticker = pair.Server.System(); @@ -217,6 +202,5 @@ public sealed class JobTest }); await pair.Server.WaitPost(() => ticker.RestartRound()); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/RoundEndTest.cs b/Content.IntegrationTests/Tests/RoundEndTest.cs index 5de6de381d..16c460c3da 100644 --- a/Content.IntegrationTests/Tests/RoundEndTest.cs +++ b/Content.IntegrationTests/Tests/RoundEndTest.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.RoundEnd; using Content.Shared.CCVar; @@ -7,7 +8,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class RoundEndTest + public sealed class RoundEndTest : GameTest { private sealed class RoundEndTestSystem : EntitySystem { @@ -25,15 +26,18 @@ namespace Content.IntegrationTests.Tests } } + + public override PoolSettings PoolSettings => new PoolSettings + { + DummyTicker = false, + Connected = true, + Dirty = true + }; + [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings - { - DummyTicker = false, - Connected = true, - Dirty = true - }); + var pair = Pair; var server = pair.Server; @@ -151,7 +155,6 @@ namespace Content.IntegrationTests.Tests roundEndSystem.DefaultCountdownDuration = TimeSpan.FromMinutes(4); ticker.RestartRound(); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/SalvageTest.cs b/Content.IntegrationTests/Tests/SalvageTest.cs index 0059db6292..68bdf4726c 100644 --- a/Content.IntegrationTests/Tests/SalvageTest.cs +++ b/Content.IntegrationTests/Tests/SalvageTest.cs @@ -1,4 +1,6 @@ -using Content.Shared.CCVar; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; +using Content.Shared.CCVar; using Content.Shared.Salvage; using Robust.Shared.Configuration; using Robust.Shared.EntitySerialization.Systems; @@ -8,15 +10,16 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class SalvageTest +public sealed class SalvageTest : GameTest { /// /// Asserts that all salvage maps have been saved as grids and are loadable. /// [Test] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task AllSalvageMapsLoadableTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -24,7 +27,6 @@ public sealed class SalvageTest var prototypeManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); var mapSystem = entManager.System(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { @@ -50,8 +52,6 @@ public sealed class SalvageTest } } }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); + await RunUntilSynced(); } } diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs index eb3dc14720..914a1e21c2 100644 --- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs @@ -1,4 +1,6 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; using Content.Shared.CCVar; using Robust.Server.GameObjects; using Robust.Shared.Configuration; @@ -12,14 +14,15 @@ using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class SaveLoadMapTest + public sealed class SaveLoadMapTest : GameTest { [Test] + [EnsureCVar(Side.Server, typeof(CCVars), nameof(CCVars.GridFill), false)] public async Task SaveLoadMultiGridMap() { var mapPath = new ResPath("/Maps/Test/TestMap.yml"); - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapManager = server.ResolveDependency(); var sEntities = server.ResolveDependency(); @@ -27,8 +30,6 @@ namespace Content.IntegrationTests.Tests var mapSystem = sEntities.System(); var xformSystem = sEntities.EntitySysManager.GetEntitySystem(); var resManager = server.ResolveDependency(); - var cfg = server.ResolveDependency(); - Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitAssertion(() => { @@ -94,8 +95,6 @@ namespace Content.IntegrationTests.Tests }); } }); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index b41aa0bf2f..9339d61270 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.CCVar; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; @@ -16,12 +17,12 @@ namespace Content.IntegrationTests.Tests /// Tests that a grid's yaml does not change when saved consecutively. /// [TestFixture] - public sealed class SaveLoadSaveTest + public sealed class SaveLoadSaveTest : GameTest { [Test] public async Task CreateSaveLoadSaveGrid() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); @@ -85,10 +86,9 @@ namespace Content.IntegrationTests.Tests } }); testSystem.Enabled = false; - await pair.CleanReturnAsync(); } - private const string TestMap = "Maps/bagel.yml"; + private new const string TestMap = "Maps/bagel.yml"; /// /// Loads the default map, runs it for 5 ticks, then assert that it did not change. @@ -96,7 +96,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task LoadSaveTicksSaveBagel() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var mapSys = server.System(); @@ -167,7 +167,6 @@ namespace Content.IntegrationTests.Tests testSystem.Enabled = false; await server.WaitPost(() => mapSys.DeleteMap(mapId)); - await pair.CleanReturnAsync(); } /// @@ -183,7 +182,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task LoadTickLoadBagel() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var mapLoader = server.System(); @@ -241,7 +240,6 @@ namespace Content.IntegrationTests.Tests testSystem.Enabled = false; await server.WaitPost(() => mapSys.DeleteMap(mapId1)); await server.WaitPost(() => mapSys.DeleteMap(mapId2)); - await pair.CleanReturnAsync(); } /// diff --git a/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs b/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs index 339420362c..daf14e42a0 100644 --- a/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs +++ b/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; @@ -8,7 +9,7 @@ using Robust.Shared.Serialization.Markdown.Value; namespace Content.IntegrationTests.Tests.Serialization; [TestFixture] -public sealed partial class SerializationTest +public sealed partial class SerializationTest : GameTest { /// /// Check that serializing generic enums works as intended. This should really be in engine, but engine @@ -17,7 +18,7 @@ public sealed partial class SerializationTest [Test] public async Task SerializeGenericEnums() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var seriMan = server.ResolveDependency(); var refMan = server.ResolveDependency(); @@ -67,8 +68,6 @@ public sealed partial class SerializationTest Assert.That(seriMan.ValidateNode(genericNode).GetErrors().Any(), Is.True); Assert.That(seriMan.ValidateNode(typedNode).GetErrors().Any(), Is.True); Assert.That(seriMan.ValidateNode(typedNode).GetErrors().Any(), Is.False); - - await pair.CleanReturnAsync(); } private enum TestEnum : byte { Aa, Bb, Cc, Dd } diff --git a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs index ab82a3d2f9..927f973472 100644 --- a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs +++ b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Shuttles.Systems; using Content.Tests; using Robust.Server.GameObjects; @@ -13,7 +14,7 @@ using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Shuttle; -public sealed class DockTest : ContentUnitTest +public sealed class DockTest : GameTest { private static IEnumerable TestSource() { @@ -26,7 +27,7 @@ public sealed class DockTest : ContentUnitTest [TestCaseSource(nameof(TestSource))] public async Task TestDockingConfig(Vector2 dock1Pos, Vector2 dock2Pos, Angle dock1Angle, Angle dock2Angle, bool result) { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var map = await pair.CreateTestMap(); @@ -83,14 +84,12 @@ public sealed class DockTest : ContentUnitTest Assert.That(result, Is.EqualTo(config != null)); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestPlanetDock() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var map = await pair.CreateTestMap(); @@ -126,7 +125,5 @@ public sealed class DockTest : ContentUnitTest var dockingConfig = dockingSystem.GetDockingConfig(shuttle, map.MapUid); Assert.That(dockingConfig, Is.Not.EqualTo(null)); }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/ShuttleTest.cs b/Content.IntegrationTests/Tests/ShuttleTest.cs index da5b82d91e..1c447db871 100644 --- a/Content.IntegrationTests/Tests/ShuttleTest.cs +++ b/Content.IntegrationTests/Tests/ShuttleTest.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.IntegrationTests.Fixtures; using Content.Server.Shuttles.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -9,12 +10,12 @@ using Robust.Shared.Physics.Systems; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class ShuttleTest + public sealed class ShuttleTest : GameTest { [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -50,7 +51,6 @@ namespace Content.IntegrationTests.Tests { Assert.That(entManager.GetComponent(map.Grid).LocalPosition, Is.Not.EqualTo(Vector2.Zero)); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index da7e1e8e9b..ca667f2e7a 100644 --- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs +++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Shared.Item; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; @@ -22,7 +23,7 @@ namespace Content.IntegrationTests.Tests.Sprite; /// /// [TestFixture] -public sealed class PrototypeSaveTest +public sealed class PrototypeSaveTest : GameTest { private static readonly HashSet Ignored = new() { @@ -34,8 +35,7 @@ public sealed class PrototypeSaveTest [Test] public async Task AllItemsHaveSpritesTest() { - var settings = new PoolSettings() { Connected = true }; // client needs to be in-game - await using var pair = await PoolManager.GetServerClient(settings); + var pair = Pair; List badPrototypes = []; await pair.Client.WaitPost(() => @@ -58,7 +58,5 @@ public sealed class PrototypeSaveTest Assert.Fail($"Item prototype has no sprite: {proto.ID}. It should probably either be marked as abstract, not be an item, or have a valid sprite"); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/StartTest.cs b/Content.IntegrationTests/Tests/StartTest.cs index e2bf5e8ff1..223f4676b0 100644 --- a/Content.IntegrationTests/Tests/StartTest.cs +++ b/Content.IntegrationTests/Tests/StartTest.cs @@ -1,9 +1,10 @@ +using Content.IntegrationTests.Fixtures; using Robust.Shared.Exceptions; namespace Content.IntegrationTests.Tests { [TestFixture] - public sealed class StartTest + public sealed class StartTest : GameTest { /// /// Test that the server, and client start, and stop. @@ -11,7 +12,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task TestClientStart() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var client = pair.Client; Assert.That(client.IsAlive); await client.WaitRunTicks(5); @@ -27,8 +28,6 @@ namespace Content.IntegrationTests.Tests Assert.That(sRuntimeLog.ExceptionCount, Is.EqualTo(0), "No exceptions must be logged on server."); await server.WaitIdleAsync(); Assert.That(server.IsAlive); - - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs index 02552669f7..8da97cbab7 100644 --- a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs +++ b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.GameTicking; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; @@ -12,15 +13,21 @@ namespace Content.IntegrationTests.Tests.Station; [TestFixture] [TestOf(typeof(EmergencyShuttleSystem))] -public sealed class EvacShuttleTest +public sealed class EvacShuttleTest : GameTest { + public override PoolSettings PoolSettings => new PoolSettings() + { + DummyTicker = true, + Dirty = true, + }; + /// /// Ensure that the emergency shuttle can be called, and that it will travel to centcomm /// [Test] public async Task EmergencyEvacTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { DummyTicker = true, Dirty = true }); + var pair = Pair; var server = pair.Server; var entMan = server.EntMan; var ticker = server.System(); @@ -122,6 +129,5 @@ public sealed class EvacShuttleTest server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, dockTime); pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, false); pair.Server.CfgMan.SetCVar(CCVars.GameMap, gameMap); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Station/JobTests.cs b/Content.IntegrationTests/Tests/Station/JobTests.cs index 5172049a99..0961db1c67 100644 --- a/Content.IntegrationTests/Tests/Station/JobTests.cs +++ b/Content.IntegrationTests/Tests/Station/JobTests.cs @@ -2,12 +2,13 @@ using Content.Shared.Roles; using Content.Shared.Roles.Jobs; using Robust.Shared.Prototypes; using System.Linq; +using Content.IntegrationTests.Fixtures; namespace Content.IntegrationTests.Tests.Station; [TestFixture] [TestOf(typeof(SharedJobSystem))] -public sealed class JobTest +public sealed class JobTest : GameTest { /// /// Ensures that every job belongs to at most 1 primary department. @@ -16,7 +17,7 @@ public sealed class JobTest [Test] public async Task PrimaryDepartmentsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var prototypeManager = server.ResolveDependency(); @@ -43,6 +44,5 @@ public sealed class JobTest } } }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs index 4abd32bda0..c0f0b19b19 100644 --- a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs +++ b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared.Maps; @@ -15,7 +16,7 @@ namespace Content.IntegrationTests.Tests.Station; [TestFixture] [TestOf(typeof(StationJobsSystem))] -public sealed class StationJobsTest +public sealed class StationJobsTest : GameTest { private const string StationMapId = "FooStation"; @@ -85,7 +86,7 @@ public sealed class StationJobsTest [Test] public async Task AssignJobsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var prototypeManager = server.ResolveDependency(); @@ -153,13 +154,12 @@ public sealed class StationJobsTest Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Contain("TCaptain")); }); }); - await pair.CleanReturnAsync(); } [Test] public async Task AdjustJobsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var prototypeManager = server.ResolveDependency(); @@ -203,13 +203,12 @@ public sealed class StationJobsTest Assert.That(stationJobs.IsJobUnlimited(station, "TChaplain"), "Could not make TChaplain unlimited."); }); }); - await pair.CleanReturnAsync(); } [Test] public async Task InvalidRoundstartJobsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var prototypeManager = server.ResolveDependency(); @@ -247,7 +246,6 @@ public sealed class StationJobsTest } }); }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs b/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs index f80cc089de..6b100f89fd 100644 --- a/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs +++ b/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Server.Storage.EntitySystems; using Content.Shared.Damage; using Content.Shared.Damage.Systems; @@ -7,7 +8,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Storage; [TestFixture] -public sealed class EntityStorageTests +public sealed class EntityStorageTests : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -31,7 +32,7 @@ public sealed class EntityStorageTests [Test] public async Task TestContainerDestruction() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var map = await pair.CreateTestMap(); @@ -76,7 +77,5 @@ public sealed class EntityStorageTests await server.WaitRunTicks(5); Assert.That(server.EntMan.Deleted(box)); Assert.That(server.EntMan.Deleted(crowbar), Is.False); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Storage/StorageTest.cs b/Content.IntegrationTests/Tests/Storage/StorageTest.cs index 95d94906bb..c0ef6a3691 100644 --- a/Content.IntegrationTests/Tests/Storage/StorageTest.cs +++ b/Content.IntegrationTests/Tests/Storage/StorageTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Containers; using Content.Shared.Item; using Content.Shared.Prototypes; @@ -12,7 +13,7 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Storage; -public sealed class StorageTest +public sealed class StorageTest : GameTest { /// /// Can an item store more than itself weighs. @@ -21,7 +22,7 @@ public sealed class StorageTest [Test] public async Task StorageSizeArbitrageTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoManager = server.ResolveDependency(); @@ -44,13 +45,12 @@ public sealed class StorageTest $"Found storage arbitrage on {proto.ID}"); } }); - await pair.CleanReturnAsync(); } [Test] public async Task TestStorageFillPrototypes() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoManager = server.ResolveDependency(); @@ -72,13 +72,12 @@ public sealed class StorageTest } }); }); - await pair.CleanReturnAsync(); } [Test] public async Task TestSufficientSpaceForFill() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -159,14 +158,12 @@ public sealed class StorageTest } } }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestSufficientSpaceForEntityStorageFill() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entMan = server.ResolveDependency(); @@ -194,7 +191,6 @@ public sealed class StorageTest $"{proto.ID} storage fill is too large."); }); } - await pair.CleanReturnAsync(); } private int GetEntrySize(EntitySpawnEntry entry, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem) @@ -242,7 +238,7 @@ public sealed class StorageTest [Test] public async Task NoMultipleContainerFillsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var compFact = pair.Server.ResolveDependency(); Assert.Multiple(() => @@ -258,6 +254,5 @@ public sealed class StorageTest Assert.That(!proto.HasComponent(compFact), $"Prototype {proto.ID} has both {nameof(ContainerFillComponent)} and {nameof(StorageFillComponent)}."); } }); - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/StoreTests.cs b/Content.IntegrationTests/Tests/StoreTests.cs index 39df0fc8cc..5a63dfa563 100644 --- a/Content.IntegrationTests/Tests/StoreTests.cs +++ b/Content.IntegrationTests/Tests/StoreTests.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; using System.Linq; -using System.Threading; -using Content.Server.Store.Systems; +using Content.IntegrationTests.Fixtures; +using Content.IntegrationTests.Fixtures.Attributes; +using Content.Server.PDA.Ringer; using Content.Server.Traitor.Uplink; using Content.Shared.FixedPoint; using Content.Shared.Inventory; @@ -10,13 +10,12 @@ using Content.Shared.Store; using Content.Shared.Store.Components; using Content.Shared.StoreDiscount.Components; using Robust.Shared.GameObjects; -using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class StoreTests +public sealed class StoreTests : GameTest { [TestPrototypes] @@ -32,10 +31,23 @@ public sealed class StoreTests - idcard - type: Pda "; + [Test] + [Ignore(""" + This currently causes the client to crash, failing the test. + When this is fixed, this test should be removed and StoreDiscountAndRefund + should just use the default pair config. + """)] + public async Task StoreDiscountAndRefundWithClient() + { + await StoreDiscountAndRefund(); + } + + [Test] + [PairConfig(nameof(PsDisconnected))] public async Task StoreDiscountAndRefund() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -57,6 +69,7 @@ public sealed class StoreTests EntityUid pda = default; var uplinkSystem = entManager.System(); + var ringerSystem = entManager.System(); var listingPrototypes = prototypeManager.EnumeratePrototypes() .ToArray(); @@ -78,10 +91,13 @@ public sealed class StoreTests mindSystem.TransferTo(mind, human, mind: mind); FixedPoint2 originalBalance = 20; - uplinkSystem.AddUplink(human, originalBalance, null, true); + uplinkSystem.AddUplink(human, originalBalance, out var notes, pda, true); - var storeComponent = entManager.GetComponent(pda); - var discountComponent = entManager.GetComponent(pda); + Assert.That(notes != null); + ringerSystem.TryMatchRingtoneToStore(notes, out var storeEnt); + Assert.That(storeEnt.HasValue); + var storeComponent = entManager.GetComponent(storeEnt.Value); + var discountComponent = entManager.GetComponent(storeEnt.Value); Assert.That( discountComponent.Discounts, Has.Exactly(6).Items, @@ -127,8 +143,8 @@ public sealed class StoreTests Assert.That(plainDiscountedCost.Value, Is.LessThan(prototypeCost.Value), "Expected discounted cost to be lower then prototype cost."); - var buyMsg = new StoreBuyListingMessage(discountedListingItem.ID){Actor = human}; - server.EntMan.EventBus.RaiseLocalEvent(pda, buyMsg); + var buyMsg = new StoreBuyListingMessage(discountedListingItem.ID, null){Actor = human}; + server.EntMan.EventBus.RaiseLocalEvent(storeEnt.Value, buyMsg); var newBalance = storeComponent.Balance[UplinkSystem.TelecrystalCurrencyPrototype]; Assert.That(newBalance.Value, Is.EqualTo((originalBalance - plainDiscountedCost).Value), "Expected to have balance reduced by discounted cost"); @@ -141,7 +157,7 @@ public sealed class StoreTests Assert.That(costAfterBuy.Value, Is.EqualTo(prototypeCost.Value), "Expected cost after discount refund to be equal to prototype cost."); var refundMsg = new StoreRequestRefundMessage { Actor = human }; - server.EntMan.EventBus.RaiseLocalEvent(pda, refundMsg); + server.EntMan.EventBus.RaiseLocalEvent(storeEnt.Value, refundMsg); // get refreshed item after refund re-generated items discountedListingItem = storeComponent.FullListingsCatalog.First(x => x.ID == itemId); @@ -168,7 +184,5 @@ public sealed class StoreTests } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs index e6cd2accaf..4632e3ea2e 100644 --- a/Content.IntegrationTests/Tests/Tag/TagTest.cs +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -10,7 +11,7 @@ namespace Content.IntegrationTests.Tests.Tag { [TestFixture] [TestOf(typeof(TagComponent))] - public sealed class TagTest + public sealed class TagTest : GameTest { private const string TagEntityId = "TagTestDummy"; @@ -44,7 +45,7 @@ namespace Content.IntegrationTests.Tests.Tag [Test] public async Task TagComponentTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var sEntityManager = server.ResolveDependency(); @@ -295,7 +296,6 @@ namespace Content.IntegrationTests.Tests.Tag Assert.Throws(() => { tagSystem.AddTags(sTagEntity, new HashSet> { UnregisteredTag }); }); #endif }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs b/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs index 52c5b03265..d1c26e9bec 100644 --- a/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs +++ b/Content.IntegrationTests/Tests/Tiles/TileStackRecursionTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.CCVar; using Content.Shared.Maps; using Robust.Shared.Configuration; @@ -7,12 +8,12 @@ using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Tiles; -public sealed class TileStackRecursionTest +public sealed class TileStackRecursionTest : GameTest { [Test] public async Task TestBaseTurfRecursion() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var protoMan = pair.Server.ResolveDependency(); var cfg = pair.Server.ResolveDependency(); var maxTileHistoryLength = cfg.GetCVar(CCVars.TileStackLimit); @@ -40,7 +41,6 @@ public sealed class TileStackRecursionTest (possibleTurf, new ProtoId(ctdef.ID)))); } Bfs(nodes, edges, maxTileHistoryLength); - await pair.CleanReturnAsync(); } private void Bfs(List<(ProtoId, int)> nodes, List<(ProtoId, ProtoId)> edges, int depthLimit) diff --git a/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs b/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs index 5efa009ca7..2aea2986ec 100644 --- a/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs +++ b/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Client.LateJoin; +using Content.IntegrationTests.Fixtures; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.ContentPack; using Robust.Shared.IoC; @@ -8,7 +9,7 @@ using Robust.Shared.Reflection; namespace Content.IntegrationTests.Tests.UserInterface; [TestFixture] -public sealed class UiControlTest +public sealed class UiControlTest : GameTest { // You should not be adding to this. private Type[] _ignored = new Type[] @@ -22,10 +23,7 @@ public sealed class UiControlTest [Test] public async Task TestWindows() { - var pair = await PoolManager.GetServerClient(new PoolSettings() - { - Connected = true, - }); + var pair = Pair; var activator = pair.Client.ResolveDependency(); var refManager = pair.Client.ResolveDependency(); var loader = pair.Client.ResolveDependency(); @@ -50,7 +48,5 @@ public sealed class UiControlTest activator.CreateInstance(type, oneOff: true, inject: false); } }); - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs index d460fd354f..6ab5ca1691 100644 --- a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs @@ -1,4 +1,5 @@ #nullable enable +using Content.IntegrationTests.Fixtures; using Content.Shared.Physics; using Content.Shared.Spawning; using Robust.Shared.GameObjects; @@ -8,7 +9,7 @@ namespace Content.IntegrationTests.Tests.Utility { [TestFixture] [TestOf(typeof(EntitySystemExtensions))] - public sealed class EntitySystemExtensionsTest + public sealed class EntitySystemExtensionsTest : GameTest { private const string BlockerDummyId = "BlockerDummy"; @@ -32,7 +33,7 @@ namespace Content.IntegrationTests.Tests.Utility [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -95,7 +96,6 @@ namespace Content.IntegrationTests.Tests.Utility Assert.That(entity, Is.Not.Null); }); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs index 19b25816fa..d7e1239603 100644 --- a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Containers.ItemSlots; using Content.Shared.Whitelist; using Robust.Shared.GameObjects; @@ -7,7 +8,7 @@ namespace Content.IntegrationTests.Tests.Utility { [TestFixture] [TestOf(typeof(EntityWhitelist))] - public sealed class EntityWhitelistTest + public sealed class EntityWhitelistTest : GameTest { private const string InvalidComponent = "Sprite"; private const string ValidComponent = "Physics"; @@ -58,7 +59,7 @@ namespace Content.IntegrationTests.Tests.Utility [Test] public async Task Test() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -111,7 +112,6 @@ namespace Content.IntegrationTests.Tests.Utility Assert.That(sys.IsValid(whitelistSer, WhitelistTestInvalidTag), Is.False); }); }); - await pair.CleanReturnAsync(); } } } diff --git a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs index 7058cfab6a..dfaaae05ad 100644 --- a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs +++ b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Generic; +using Content.IntegrationTests.Fixtures; using Content.Server.VendingMachines; using Content.Server.Wires; using Content.Shared.Cargo.Prototypes; @@ -21,7 +22,7 @@ namespace Content.IntegrationTests.Tests [TestFixture] [TestOf(typeof(VendingMachineRestockComponent))] [TestOf(typeof(VendingMachineSystem))] - public sealed class VendingMachineRestockTest : EntitySystem + public sealed class VendingMachineRestockTest : GameTest { private static readonly ProtoId TestDamageType = "Blunt"; @@ -111,7 +112,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task TestAllRestocksAreAvailableToBuy() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -194,14 +195,12 @@ namespace Content.IntegrationTests.Tests $"Some entities with {restockCompName} are unavailable for purchase: \n - {string.Join("\n - ", restockEntities)}"); }); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestCompleteRestockProcess() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -280,14 +279,12 @@ namespace Content.IntegrationTests.Tests mapSystem.DeleteMap(testMap.MapId); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestRestockBreaksOpen() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -342,14 +339,12 @@ namespace Content.IntegrationTests.Tests mapSystem.DeleteMap(testMap.MapId); }); - - await pair.CleanReturnAsync(); } [Test] public async Task TestRestockInventoryBounds() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; await server.WaitIdleAsync(); @@ -388,10 +383,6 @@ namespace Content.IntegrationTests.Tests Assert.That(vendingMachineSystem.GetAvailableInventory(machine)[0].Amount, Is.EqualTo(3), "Machine's available inventory did not stay the same after a third restock."); }); - - await pair.CleanReturnAsync(); } } } - -#nullable disable diff --git a/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs b/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs index 920dc08818..d9801275b9 100644 --- a/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs +++ b/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs @@ -1,4 +1,5 @@ -using Content.Server.Doors; +using Content.IntegrationTests.Fixtures; +using Content.Server.Doors; using Content.Server.Power; using Content.Server.Wires; using Robust.Shared.GameObjects; @@ -10,7 +11,7 @@ namespace Content.IntegrationTests.Tests.Wires; [TestFixture] [Parallelizable(ParallelScope.All)] [TestOf(typeof(WiresSystem))] -public sealed class WireLayoutTest +public sealed class WireLayoutTest : GameTest { [TestPrototypes] public const string Prototypes = """ @@ -53,7 +54,7 @@ public sealed class WireLayoutTest [Test] public async Task TestLayoutInheritance() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var testMap = await pair.CreateTestMap(); @@ -89,8 +90,6 @@ public sealed class WireLayoutTest Assert.That(ent3.Comp.WiresList, Has.One.With.Property("Action").InstanceOf(), "1 door bolt wire"); }); }); - - await pair.CleanReturnAsync(); } private static Entity SpawnWithComp(IEntityManager entityManager, string prototype, MapCoordinates coords) diff --git a/Content.IntegrationTests/Tests/WizdenContentFreeze/WizdenContentFreeze.cs b/Content.IntegrationTests/Tests/WizdenContentFreeze/WizdenContentFreeze.cs index 69ca794baf..6d93d290f1 100644 --- a/Content.IntegrationTests/Tests/WizdenContentFreeze/WizdenContentFreeze.cs +++ b/Content.IntegrationTests/Tests/WizdenContentFreeze/WizdenContentFreeze.cs @@ -1,3 +1,4 @@ +using Content.IntegrationTests.Fixtures; using Content.Shared.Kitchen; namespace Content.IntegrationTests.Tests.WizdenContentFreeze; @@ -5,7 +6,7 @@ namespace Content.IntegrationTests.Tests.WizdenContentFreeze; /// /// These tests are limited to adding a specific type of content, essentially freezing it. If you are a fork developer, you may want to disable these tests. /// -public sealed class WizdenContentFreeze +public sealed class WizdenContentFreeze : GameTest { /// /// This freeze prohibits the addition of new microwave recipes. @@ -18,7 +19,7 @@ public sealed class WizdenContentFreeze [Test] public async Task MicrowaveRecipesFreezeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var protoMan = server.ProtoMan; @@ -35,7 +36,5 @@ public sealed class WizdenContentFreeze { Assert.Fail($"Oh, you deleted the microwave recipes? YOU ARE SO COOL! Please lower the number of recipes in MicrowaveRecipesFreezeTest from {recipesLimit} to {recipesCount} so that future contributors cannot add new recipes back."); } - - await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/XenoArtifactTest.cs b/Content.IntegrationTests/Tests/XenoArtifactTest.cs index ac4c58c52c..35b03470e9 100644 --- a/Content.IntegrationTests/Tests/XenoArtifactTest.cs +++ b/Content.IntegrationTests/Tests/XenoArtifactTest.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.IntegrationTests.Fixtures; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.Components; using Robust.Shared.GameObjects; @@ -6,7 +7,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests; [TestFixture] -public sealed class XenoArtifactTest +public sealed class XenoArtifactTest : GameTest { [TestPrototypes] private const string Prototypes = @" @@ -90,7 +91,7 @@ public sealed class XenoArtifactTest [Test] public async Task XenoArtifactAddNodeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -133,9 +134,6 @@ public sealed class XenoArtifactTest Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(1)); Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(2)); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } /// @@ -144,7 +142,7 @@ public sealed class XenoArtifactTest [Test] public async Task XenoArtifactRemoveNodeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -182,9 +180,6 @@ public sealed class XenoArtifactTest Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Is.Empty); Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } /// @@ -193,7 +188,7 @@ public sealed class XenoArtifactTest [Test] public async Task XenoArtifactResizeTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -245,9 +240,6 @@ public sealed class XenoArtifactTest Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty); Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } /// @@ -256,7 +248,7 @@ public sealed class XenoArtifactTest [Test] public async Task XenoArtifactReplaceTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -304,9 +296,6 @@ public sealed class XenoArtifactTest Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } /// @@ -315,7 +304,7 @@ public sealed class XenoArtifactTest [Test] public async Task XenoArtifactBuildActiveNodesTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -367,15 +356,12 @@ public sealed class XenoArtifactTest Assert.That(artifactEnt.Comp.CachedActiveNodes, Has.Count.EqualTo(expectedActiveNodes.Length)); }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } [Test] public async Task XenoArtifactGenerateSegmentsTest() { - await using var pair = await PoolManager.GetServerClient(); + var pair = Pair; var server = pair.Server; var entManager = server.ResolveDependency(); @@ -412,8 +398,5 @@ public sealed class XenoArtifactTest Assert.That(grouped[2].Count(), Is.LessThanOrEqualTo(2)); // maintain same width or, if we used 3 nodes on previous layer - we only have 1 left! }); - await server.WaitRunTicks(1); - - await pair.CleanReturnAsync(); } } diff --git a/Content.MapRenderer/Program.cs b/Content.MapRenderer/Program.cs index 90f97a5786..668e04f63d 100644 --- a/Content.MapRenderer/Program.cs +++ b/Content.MapRenderer/Program.cs @@ -145,7 +145,7 @@ namespace Content.MapRenderer { Console.Write($"Following map files did not exist on disk directly, searching through prototypes: {string.Join(", ", lookupPrototypeFiles)}"); - await using var pair = await PoolManager.GetServerClient(); + await using var pair = await PoolManager.GetServerClient(testContext: testContext); var mapPrototypes = pair.Server .ResolveDependency() .EnumeratePrototypes() diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index 0ea55eee91..1e4a044901 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -135,7 +135,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem string newFullName, string newJobTitle, List> newAccessList, - ProtoId newJobProto, + ProtoId? newJobProto, EntityUid player, IdCardConsoleComponent? component = null) { @@ -158,7 +158,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem _idCard.TryChangeFullName(targetId, newFullName, player: player); _idCard.TryChangeJobTitle(targetId, newJobTitle, player: player); - if (_prototype.Resolve(newJobProto, out var job) + if (_prototype.TryIndex(newJobProto, out var job) && _prototype.Resolve(job.Icon, out var jobIcon)) { _idCard.TryChangeJobIcon(targetId, jobIcon, player: player); diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 540bd7d089..63b4b5646b 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -279,7 +279,7 @@ public sealed partial class AdminVerbSystem Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"), Act = () => { - _creamPieSystem.SetCreamPied(args.Target, creamPied, true); + _creamPieSystem.SetCreamPied((args.Target, creamPied), true); }, Impact = LogImpact.Extreme, Message = string.Join(": ", creamPieName, Loc.GetString("admin-smite-creampie-description")) diff --git a/Content.Server/Anomaly/Effects/GravityAnomalySystem.cs b/Content.Server/Anomaly/Effects/GravityAnomalySystem.cs index 02cd0aafc6..e053377d57 100644 --- a/Content.Server/Anomaly/Effects/GravityAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/GravityAnomalySystem.cs @@ -1,9 +1,9 @@ using Content.Server.Physics.Components; +using Content.Server.Radiation.Systems; using Content.Server.Singularity.Components; using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Effects; using Content.Shared.Anomaly.Effects.Components; -using Content.Shared.Radiation.Components; namespace Content.Server.Anomaly.Effects; @@ -12,6 +12,8 @@ namespace Content.Server.Anomaly.Effects; /// public sealed class GravityAnomalySystem : SharedGravityAnomalySystem { + [Dependency] private readonly RadiationSystem _radiation = default!; + /// public override void Initialize() { @@ -22,8 +24,7 @@ public sealed class GravityAnomalySystem : SharedGravityAnomalySystem private void OnSeverityChanged(Entity anomaly, ref AnomalySeverityChangedEvent args) { - if (TryComp(anomaly, out var radSource)) - radSource.Intensity = anomaly.Comp.MaxRadiationIntensity * args.Severity; + _radiation.SetIntensity(anomaly.Owner, anomaly.Comp.MaxRadiationIntensity * args.Severity); if (TryComp(anomaly, out var gravityWell)) { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index e68bf7b2ec..8c3681d543 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Diagnostics; using Content.Server.NodeContainer.NodeGroups; using Content.Shared.Atmos; @@ -289,6 +290,95 @@ public partial class AtmosphereSystem GetTileMixture(entity, excite)?.AdjustMoles(gas, mols); } + /// + /// Retrieves the pressures of all gas mixtures + /// in the given array of s, and stores the results in the + /// provided span. + /// + /// The tiles span to find the pressures of. + /// The span to store the pressures to - this should be the same length + /// as the tile array. + /// Thrown when the length of the provided spans do not match. + /// Note that for or s that are null, + /// this method will return a value close to zero but not exactly zero. + [PublicAPI] + public static void GetBulkTileAtmospherePressures(Span tiles, Span pressures) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(tiles.Length, pressures.Length); + + var len = tiles.Length; + var arr1 = ArrayPool.Shared.Rent(len); + + try + { + var mixtSpan = arr1.AsSpan(0, len); + for (var i = 0; i < tiles.Length; i++) + { + mixtSpan[i] = tiles[i]?.Air; + } + + GetBulkGasMixturePressures(mixtSpan, pressures); + } + finally + { + ArrayPool.Shared.Return(arr1); + } + } + + /// + /// Gets the pressures of a of s. + /// + /// The to get the pressures of. + /// The to store the pressures to - this should be the same length + /// as the mixtures array. + /// Thrown when the length of the provided spans do not match. + /// Note that for GasMixtures that are null, this method will return a value close to zero but not exactly zero. + [PublicAPI] + public static void GetBulkGasMixturePressures(Span mixtures, Span pressures) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(mixtures.Length, pressures.Length); + + var len = mixtures.Length; + + var arr1 = ArrayPool.Shared.Rent(len); + var arr2 = ArrayPool.Shared.Rent(len); + var arr3 = ArrayPool.Shared.Rent(len); + try + { + var mixtVol = arr1.AsSpan(0, len); + var mixtTemp = arr2.AsSpan(0, len); + var mixtMoles = arr3.AsSpan(0, len); + + for (var i = 0; i < len; i++) + { + if (mixtures[i] is not { } mixture) + { + // To prevent any NaN/Div/0 errors, we just bite the bullet + // and set everything to the lowest possible value. + mixtVol[i] = 1; + mixtTemp[i] = 1; + mixtMoles[i] = float.Epsilon; + continue; + } + + mixtVol[i] = mixture.Volume; + mixtTemp[i] = mixture.Temperature; + mixtMoles[i] = mixture.TotalMoles; + } + + // TODO NumericsHelpers need a method that substitutes NaNs with zeros. AVX-512 has one iirc but for 256/128 we need to do some masking bs + NumericsHelpers.Multiply(mixtMoles, Atmospherics.R); + NumericsHelpers.Multiply(mixtMoles, mixtTemp); + NumericsHelpers.Divide(mixtMoles, mixtVol, pressures); + } + finally + { + ArrayPool.Shared.Return(arr1); + ArrayPool.Shared.Return(arr2); + ArrayPool.Shared.Return(arr3); + } + } + /// /// Triggers a tile's to react. /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.DeltaPressure.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.DeltaPressure.cs index 9457c5689c..59ccbbb8c8 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.DeltaPressure.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.DeltaPressure.cs @@ -200,71 +200,6 @@ public sealed partial class AtmosphereSystem } } - /// - /// A DeltaPressure helper method that retrieves the pressures of all gas mixtures - /// in the given array of s, and stores the results in the - /// provided span. - /// - /// The tiles span to find the pressures of. - /// The span to store the pressures to - this should be the same length - /// as the tile array. - /// Thrown when the length of the provided spans do not match. - private static void GetBulkTileAtmospherePressures(Span tiles, Span pressures) - { - // this shit is internal because I don't even trust myself - if (tiles.Length != pressures.Length) - throw new ArgumentException("Length of Tiles and Pressures span must be the same!"); - - var len = pressures.Length; - - // Once again, ArrayPool might return arrays that are longer than the length. - // We really need them to be all the same length, so slice them here. - var arr1 = ArrayPool.Shared.Rent(len); - var arr2 = ArrayPool.Shared.Rent(len); - var arr3 = ArrayPool.Shared.Rent(len); - - var mixtVol = arr1.AsSpan(0, len); - var mixtTemp = arr2.AsSpan(0, len); - var mixtMoles = arr3.AsSpan(0, len); - - try - { - for (var i = 0; i < len; i++) - { - if (tiles[i] is not { Air: { } mixture }) - { - // To prevent any NaN/Div/0 errors, we just bite the bullet - // and set everything to the lowest possible value. - mixtVol[i] = 1; - mixtTemp[i] = 1; - mixtMoles[i] = float.Epsilon; - continue; - } - - mixtVol[i] = mixture.Volume; - mixtTemp[i] = mixture.Temperature; - mixtMoles[i] = mixture.TotalMoles; - } - - /* - Retrieval of single tile pressures requires calling a get method for each tile, - which does a bunch of scalar operations. - - So we go ahead and batch-retrieve the pressures of all tiles - and process them in bulk. - */ - NumericsHelpers.Multiply(mixtMoles, Atmospherics.R); - NumericsHelpers.Multiply(mixtMoles, mixtTemp); - NumericsHelpers.Divide(mixtMoles, mixtVol, pressures); - } - finally - { - ArrayPool.Shared.Return(arr1); - ArrayPool.Shared.Return(arr2); - ArrayPool.Shared.Return(arr3); - } - } - /// /// Packs data into a data struct and enqueues it /// into the queue for diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index 639a71ec60..1fae93ef94 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -38,7 +38,7 @@ namespace Content.Server.Atmos.EntitySystems } Span tmp = stackalloc float[moles.Length]; - NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp); + NumericsHelpers.Multiply(moles, GasMolarHeatCapacities, tmp); // Adjust heat capacity by speedup, because this is primarily what // determines how quickly gases heat up/cool. return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index ad19770bfe..d4a70dfbc0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -117,8 +117,11 @@ namespace Content.Server.Atmos.EntitySystems private void Archive(TileAtmosphere tile, int fireCount) { if (tile.Air != null) + { + // TODO ATMOS: This is an extremely large hotspot in LINDA, accounting for 1/5th of its time. + // Please make GasMixture a struct or use a FauxGasMixture with an InlineArray to handle copying this sanely. tile.AirArchived = new GasMixture(tile.Air); - + } tile.ArchivedCycle = fireCount; } @@ -197,12 +200,25 @@ namespace Content.Server.Atmos.EntitySystems } /// - /// Shares gas between two tiles. Part of LINDA. + /// Performs a share operation between two tiles, sharing both physical gas and temperature. /// + /// The receiving the share. + /// The sharing its air. + /// The number of s next to the receiver that air can flow to. + /// The pressure difference between the two tiles after sharing. + /// LINDA is an FEA-like solver and this method is basically the core of it. + /// In FEA we divide the problem into infinitesimal parts and try to step towards the desired end state: + /// a steady state where all air is equalized between tiles. + /// To do this we share the tiles air between other tiles over time (as well as the temperature). + /// Note that the timestep is actually a cyclestep, so running the cycles faster leads to a faster equalization. + /// Hilarious, I know. public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs) { - if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer || - tileReceiver.AirArchived == null || tileSharer.AirArchived == null) + // TODO ATMOS: Method needs to timestep over deltaTime instead of per cycle + // TODO ATMOS: Method needs to account for adjacent turfs in the situation where air is moving from receiver to sharer. + // See https://github.com/tgstation/tgstation/pull/63785 + if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer || + tileReceiver.AirArchived == null || tileSharer.AirArchived == null) return 0f; var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature; @@ -221,15 +237,16 @@ namespace Content.Server.Atmos.EntitySystems var movedMoles = 0f; var absMovedMoles = 0f; - for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++) + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var thisValue = receiver.Moles[i]; var sharerValue = sharer.Moles[i]; var delta = (thisValue - sharerValue) / (atmosAdjacentTurfs + 1); - if (!(MathF.Abs(delta) >= Atmospherics.GasMinMoles)) continue; + if (!(MathF.Abs(delta) >= Atmospherics.GasMinMoles)) + continue; if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider) { - var gasHeatCapacity = delta * GasSpecificHeats[i]; + var gasHeatCapacity = delta * GasMolarHeatCapacities[i]; if (delta > 0) { heatCapacityToSharer += gasHeatCapacity; @@ -240,8 +257,10 @@ namespace Content.Server.Atmos.EntitySystems } } - if (!receiver.Immutable) receiver.Moles[i] -= delta; - if (!sharer.Immutable) sharer.Moles[i] += delta; + if (!receiver.Immutable) + receiver.Moles[i] -= delta; + if (!sharer.Immutable) + sharer.Moles[i] += delta; movedMoles += delta; absMovedMoles += MathF.Abs(delta); } @@ -256,16 +275,21 @@ namespace Content.Server.Atmos.EntitySystems // Transfer of thermal energy (via changed heat capacity) between self and sharer. if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity) { - receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.AirArchived.Temperature) + (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature)) / newHeatCapacity; + receiver.Temperature = + (oldHeatCapacity * receiver.Temperature - + heatCapacityToSharer * tileReceiver.AirArchived.Temperature + + heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) / newHeatCapacity; } if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity) { - sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) + (heatCapacityToSharer * tileReceiver.AirArchived.Temperature)) / newSharerHeatCapacity; + sharer.Temperature = + (oldSharerHeatCapacity * sharer.Temperature - + heatCapacitySharerToThis * tileSharer.AirArchived.Temperature + + heatCapacityToSharer * tileReceiver.AirArchived.Temperature) / newSharerHeatCapacity; } // Thermal energy of the system (self and sharer) is unchanged. - if (MathF.Abs(oldSharerHeatCapacity) > Atmospherics.MinimumHeatCapacity) { if (MathF.Abs(newSharerHeatCapacity / oldSharerHeatCapacity - 1) < 0.1) @@ -275,12 +299,25 @@ namespace Content.Server.Atmos.EntitySystems } } - if (!(temperatureDelta > Atmospherics.MinimumTemperatureToMove) && - !(MathF.Abs(movedMoles) > Atmospherics.MinimumMolesDeltaToMove)) return 0f; + // If we didn't move enough air or if the temperature difference is too small, + // we don't consider there to be a pressure difference. + // TODO ATMOS: This is a very weird early return, please figure out why this exists because this logic seems to be double checked + // in a lot of other places (ex. HighPressureDelta). + if (!(absTemperatureDelta > Atmospherics.MinimumTemperatureToMove) && + !(MathF.Abs(movedMoles) > Atmospherics.MinimumMolesDeltaToMove)) + return 0f; + var moles = receiver.TotalMoles; var theirMoles = sharer.TotalMoles; - return (tileReceiver.AirArchived.Temperature * (moles + movedMoles)) - (tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; + /* + To get the pressure delta: + PV = nRT + P = nRT / V + \Delta P = ((n_1 * T_1) - (n_2 * T_2)) * R / V + */ + return (tileReceiver.AirArchived.Temperature * (moles + movedMoles) - + tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; } /// diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index a3fe2400d0..9dcb4d13a2 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.Interaction; using Content.Shared.NodeContainer; using JetBrains.Annotations; using Robust.Server.GameObjects; -using static Content.Shared.Atmos.Components.GasAnalyzerComponent; namespace Content.Server.Atmos.EntitySystems; @@ -101,6 +100,7 @@ public sealed class GasAnalyzerSystem : EntitySystem _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), user.Value, user.Value); entity.Comp.Enabled = false; + entity.Comp.User = null; Dirty(entity); _appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled); RemCompDeferred(entity.Owner); @@ -139,15 +139,15 @@ public sealed class GasAnalyzerSystem : EntitySystem return false; // check if the user has walked away from what they scanned - if (component.Target.HasValue) + if (component.Target.HasValue && component.User.HasValue) { // Listen! Even if you don't want the Gas Analyzer to work on moving targets, you should use // this code to determine if the object is still generally in range so that the check is consistent with the code // in OnAfterInteract() and also consistent with interaction code in general. - if (!_interactionSystem.InRangeUnobstructed((component.User, null), (component.Target.Value, null))) + if (!_interactionSystem.InRangeUnobstructed((component.User.Value, null), (component.Target.Value, null))) { - if (component.User is { } userId && component.Enabled) - _popup.PopupEntity(Loc.GetString("gas-analyzer-object-out-of-range"), userId, userId); + if (component.Enabled) + _popup.PopupEntity(Loc.GetString("gas-analyzer-object-out-of-range"), component.User.Value, component.User.Value); component.Target = null; } @@ -256,18 +256,17 @@ public sealed class GasAnalyzerSystem : EntitySystem { var gases = new List(); + if (mixture == null) + return []; + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var gas = _atmo.GetGas(i); + var gas = (Gas)i; - if (mixture?[i] <= UIMinMoles) + if (mixture[i] <= UIMinMoles) continue; - if (mixture != null) - { - var gasName = Loc.GetString(gas.Name); - gases.Add(new GasEntry(gasName, mixture[i], gas.Color)); - } + gases.Add(new GasEntry(gas, mixture[i])); } var gasesOrdered = gases.OrderByDescending(gas => gas.Amount); diff --git a/Content.Server/Cargo/Components/RandomPriceComponent.cs b/Content.Server/Cargo/Components/RandomPriceComponent.cs new file mode 100644 index 0000000000..3ece8334e6 --- /dev/null +++ b/Content.Server/Cargo/Components/RandomPriceComponent.cs @@ -0,0 +1,38 @@ +using Content.Server.Cargo.Systems; + +namespace Content.Server.Cargo.Components; + +/// +/// Adds a random value between 0 and X to an entity's sell value. +/// +[RegisterComponent, Access(typeof(PricingSystem))] +public sealed partial class RandomPriceComponent : Component +{ + /// + /// The max random price the entity may be priced at. Non-inclusive. + /// + [DataField(required: true)] + public double MaxRandomPrice; + + /// + /// How the random pricing modifier (0.0 - 1.0) should be distributed. + /// + [DataField] + public RandomPricingCurve PricingCurve = RandomPricingCurve.Cubed; + + /// + /// The generated price for the specific entity. + /// + [DataField] + public double? RandomPrice = null; +} + +/// +/// The random distribution used when generating a random price. +/// +public enum RandomPricingCurve +{ + Linear, + Squared, + Cubed, +} diff --git a/Content.Server/Cargo/Components/TradeStationComponent.cs b/Content.Server/Cargo/Components/TradeStationComponent.cs deleted file mode 100644 index 0422470cc9..0000000000 --- a/Content.Server/Cargo/Components/TradeStationComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Server.Cargo.Components; - -/// -/// Target for approved orders to spawn at. -/// -[RegisterComponent] -public sealed partial class TradeStationComponent : Component -{ - -} diff --git a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs index 351451123c..635eadea1d 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs @@ -6,6 +6,7 @@ using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Events; using Content.Shared.Cargo.Prototypes; using Content.Shared.CCVar; +using Content.Shared.HijackBeacon; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -14,7 +15,7 @@ namespace Content.Server.Cargo.Systems; public sealed partial class CargoSystem { /* - * Handles cargo shuttle / trade mechanics. + * Handles automated trade station / trade mechanics. */ private static readonly SoundPathSpecifier ApproveSound = new("/Audio/Effects/Cargo/ping.ogg"); diff --git a/Content.Server/Cargo/Systems/CargoSystem.cs b/Content.Server/Cargo/Systems/CargoSystem.cs index 9f3a4d5bf3..a35f1e7ea3 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Access.Systems; using Content.Shared.Administration.Logs; using Content.Server.Radio.EntitySystems; using Content.Shared.Cargo; +using Content.Shared.Cargo.Components; using Content.Shared.Containers.ItemSlots; using Content.Shared.Mobs.Components; using Content.Shared.Paper; diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index 0101b913d6..bbf8176035 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -8,13 +8,14 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Materials; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Research.Prototypes; using Content.Shared.Stacks; using Robust.Shared.Console; using Robust.Shared.Containers; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Utility; -using Content.Shared.Research.Prototypes; namespace Content.Server.Cargo.Systems; @@ -24,6 +25,7 @@ namespace Content.Server.Cargo.Systems; public sealed class PricingSystem : EntitySystem { [Dependency] private readonly IConsoleHost _consoleHost = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; @@ -32,6 +34,8 @@ public sealed class PricingSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(CalculateMobPrice); + SubscribeLocalEvent(SetRandomPrice); + SubscribeLocalEvent(CalculateRandomPrice); _consoleHost.RegisterCommand("appraisegrid", "Calculates the total value of the given grids.", @@ -95,6 +99,37 @@ public sealed class PricingSystem : EntitySystem args.Price += component.Price * (_mobStateSystem.IsAlive(uid, state) ? 1.0 : component.DeathPenalty); } + private void SetRandomPrice(Entity entity, ref MapInitEvent args) + { + if (entity.Comp.RandomPrice == null) + { + var modifier = _random.NextDouble(); + switch (entity.Comp.PricingCurve) + { + default: + case RandomPricingCurve.Linear: + break; + case RandomPricingCurve.Squared: + modifier = modifier * modifier; + break; + case RandomPricingCurve.Cubed: + modifier = modifier * modifier * modifier; + break; + } + + entity.Comp.RandomPrice = modifier * entity.Comp.MaxRandomPrice; + } + } + + private void CalculateRandomPrice(Entity entity, ref PriceCalculationEvent args) + { + // TODO: Estimated pricing. + if (args.Handled) + return; + + args.Price += entity.Comp.RandomPrice ?? 0; + } + private double GetSolutionPrice(Entity entity) { if (Comp(entity).EntityLifeStage < EntityLifeStage.MapInitialized) diff --git a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs index b1b23b2732..e7687ec3df 100644 --- a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs +++ b/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Station.Systems; using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.CCVar; +using Content.Shared.CrewManifest; using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.Prototypes; @@ -60,7 +61,13 @@ public sealed class CrewManifestCartridgeSystem : EntitySystem var owningStation = _stationSystem.GetOwningStation(uid); if (owningStation is null) + { + // Display "loading failed" message + var failureMessage = Loc.GetString("crew-manifest-cartridge-loading-failed"); + var failureState = new CrewManifestUiState(failureMessage, new CrewManifestEntries()); + _cartridgeLoader.UpdateCartridgeUiState(loaderUid, failureState); return; + } var (stationName, entries) = _crewManifest.GetCrewManifest(owningStation.Value); diff --git a/Content.Server/Cloning/CloningSystem.Subscriptions.cs b/Content.Server/Cloning/CloningSystem.Subscriptions.cs index a05c7069f0..10ac8bc9ed 100644 --- a/Content.Server/Cloning/CloningSystem.Subscriptions.cs +++ b/Content.Server/Cloning/CloningSystem.Subscriptions.cs @@ -53,7 +53,7 @@ public sealed partial class CloningSystem SubscribeLocalEvent(OnCloneVocal); SubscribeLocalEvent(OnCloneStorage); SubscribeLocalEvent(OnCloneInventory); - SubscribeLocalEvent(OnCloneInventory); + SubscribeLocalEvent(OnCloneMovementSpeedModifier); } private void OnCloneItemStack(Entity ent, ref CloningItemEvent args) @@ -120,7 +120,7 @@ public sealed partial class CloningSystem _inventory.CopyComponent(ent.AsNullable(), args.CloneUid); } - private void OnCloneInventory(Entity ent, ref CloningEvent args) + private void OnCloneMovementSpeedModifier(Entity ent, ref CloningEvent args) { if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) return; diff --git a/Content.Server/Cloning/RandomCloneSpawnerSystem.cs b/Content.Server/Cloning/RandomCloneSpawnerSystem.cs index a645a10890..a18ccd2e71 100644 --- a/Content.Server/Cloning/RandomCloneSpawnerSystem.cs +++ b/Content.Server/Cloning/RandomCloneSpawnerSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Cloning.Components; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; +using Content.Shared.Objectives.Systems; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -15,7 +16,7 @@ public sealed class RandomCloneSpawnerSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly TargetSystem _target = default!; public override void Initialize() { @@ -34,7 +35,7 @@ public sealed class RandomCloneSpawnerSystem : EntitySystem return; } - var allHumans = _mind.GetAliveHumans(); + var allHumans = _target.GetAliveHumans(); if (allHumans.Count == 0) return; diff --git a/Content.Server/Clothing/Systems/OutfitSystem.cs b/Content.Server/Clothing/Systems/OutfitSystem.cs index b1efbffd60..c1187657fd 100644 --- a/Content.Server/Clothing/Systems/OutfitSystem.cs +++ b/Content.Server/Clothing/Systems/OutfitSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Hands.Systems; using Content.Server.Preferences.Managers; +using Content.Server.Storage.EntitySystems; using Content.Shared.Access.Components; using Content.Shared.Clothing; using Content.Shared.Hands.Components; @@ -11,6 +12,8 @@ using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Content.Shared.Station; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Storage; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -23,6 +26,8 @@ public sealed class OutfitSystem : EntitySystem [Dependency] private readonly HandsSystem _handSystem = default!; [Dependency] private readonly InventorySystem _invSystem = default!; [Dependency] private readonly SharedStationSpawningSystem _spawningSystem = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] private readonly StorageSystem _storageSystem = default!; public bool SetOutfit(EntityUid target, string gear, Action? onEquipped = null, bool unremovable = false) { @@ -68,9 +73,35 @@ public sealed class OutfitSystem : EntitySystem } } + var coords = Transform(target).Coordinates; + foreach (var (slotName, storageContainers) in startingGear.Storage) + { + if (storageContainers.Count == 0) + continue; + + if (!_invSystem.TryGetSlotEntity(target, slotName, out var slotEnt)) + continue; + + if (TryComp(slotEnt, out var storage)) + { + foreach (var entProto in storageContainers) + { + var spawnedEntity = SpawnAtPosition(entProto, coords); + _storageSystem.Insert(slotEnt.Value, spawnedEntity, out _, user: null, storageComp: storage, playSound: false); + } + } + else if (TryComp(slotEnt, out var itemSlots)) + { + foreach (var entProto in storageContainers) + { + var spawnedEntity = SpawnAtPosition(entProto, coords); + _itemSlotsSystem.TryInsertEmpty((slotEnt.Value, itemSlots), spawnedEntity, null, excludeUserAudio: true); + } + } + } + if (TryComp(target, out HandsComponent? handsComponent)) { - var coords = Comp(target).Coordinates; foreach (var prototype in startingGear.Inhand) { var inhandEntity = Spawn(prototype, coords); diff --git a/Content.Server/Dragon/Components/DragonComponent.cs b/Content.Server/Dragon/Components/DragonComponent.cs index 80461e156a..c634836b5a 100644 --- a/Content.Server/Dragon/Components/DragonComponent.cs +++ b/Content.Server/Dragon/Components/DragonComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Chemistry.Components; using Content.Shared.NPC.Prototypes; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -65,5 +66,17 @@ namespace Content.Server.Dragon /// [DataField] public ProtoId Faction = "Dragon"; + + /// + /// The smoke to spawn upon rift timeout death. + /// + [DataField] + public EntProtoId SmokePrototype = "BloodSmoke"; + + /// + /// The solution to place into the smoke (mostly just needed for color) + /// + [DataField] + public Solution SmokeSolution = new ([new("Blood", 1)]); } } diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index 1c838939ec..8bc5fd83b1 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -1,8 +1,11 @@ +using Content.Server.Fluids.EntitySystems; using Content.Server.Objectives.Components; using Content.Server.Objectives.Systems; using Content.Server.Popups; using Content.Shared.Actions; +using Content.Shared.Chemistry.Components; using Content.Shared.Dragon; +using Content.Shared.Gibbing; using Content.Shared.Maps; using Content.Shared.Mind; using Content.Shared.Mind.Components; @@ -12,6 +15,7 @@ using Content.Shared.Movement.Systems; using Content.Shared.NPC.Systems; using Content.Shared.Zombies; using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; using Robust.Shared.Map.Components; namespace Content.Server.Dragon; @@ -29,6 +33,8 @@ public sealed partial class DragonSystem : EntitySystem [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly TurfSystem _turf = default!; + [Dependency] private readonly GibbingSystem _gibbing = default!; + [Dependency] private readonly SmokeSystem _smoke = default!; private EntityQuery _objQuery; @@ -96,11 +102,14 @@ public sealed partial class DragonSystem : EntitySystem if (!_mobState.IsDead(uid)) comp.RiftAccumulator += frameTime; - // Delete it, naughty dragon! + // Gib it, naughty dragon! if (comp.RiftAccumulator >= comp.RiftMaxAccumulator) { - Roar(uid, comp); - QueueDel(uid); + Roar(uid, comp, Transform(uid).Coordinates); + var smoke = Spawn(comp.SmokePrototype, Transform(uid).Coordinates); + if (TryComp(smoke, out var smokeComp)) + _smoke.StartSmoke(smoke, comp.SmokeSolution, smokeComp.Duration, smokeComp.SpreadAmount, smokeComp); + _gibbing.Gib(uid); } } } @@ -200,10 +209,15 @@ public sealed partial class DragonSystem : EntitySystem _faction.AddFaction(ent.Owner, ent.Comp.Faction); } - private void Roar(EntityUid uid, DragonComponent comp) + private void Roar(EntityUid uid, DragonComponent comp, EntityCoordinates? coords = null) { if (comp.SoundRoar != null) - _audio.PlayPvs(comp.SoundRoar, uid); + { + if (coords != null) + _audio.PlayPvs(comp.SoundRoar, coords.Value); + else + _audio.PlayPvs(comp.SoundRoar, uid); + } } /// diff --git a/Content.Server/FeedbackSystem/ServerFeedbackManager.cs b/Content.Server/FeedbackSystem/ServerFeedbackManager.cs index 09edd8eefe..4ecb22bcc2 100644 --- a/Content.Server/FeedbackSystem/ServerFeedbackManager.cs +++ b/Content.Server/FeedbackSystem/ServerFeedbackManager.cs @@ -29,9 +29,6 @@ public sealed class ServerFeedbackManager : SharedFeedbackManager /// public override void SendToSession(ICommonSession session, List> popupPrototypes, bool remove = false) { - if (!NetManager.IsServer) - return; - var msg = new FeedbackPopupMessage { FeedbackPrototypes = popupPrototypes, @@ -44,9 +41,6 @@ public sealed class ServerFeedbackManager : SharedFeedbackManager /// public override void SendToAllSessions(List> popupPrototypes, bool remove = false) { - if (!NetManager.IsServer) - return; - var msg = new FeedbackPopupMessage { FeedbackPrototypes = popupPrototypes, @@ -59,9 +53,6 @@ public sealed class ServerFeedbackManager : SharedFeedbackManager /// public override void OpenForSession(ICommonSession session) { - if (!NetManager.IsServer) - return; - var msg = new OpenFeedbackPopupMessage(); NetManager.ServerSendMessage(msg, session.Channel); } @@ -69,9 +60,6 @@ public sealed class ServerFeedbackManager : SharedFeedbackManager /// public override void OpenForAllSessions() { - if (!NetManager.IsServer) - return; - var msg = new OpenFeedbackPopupMessage(); NetManager.ServerSendToAll(msg); } diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 16699acfd5..7d370767e4 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -352,17 +352,17 @@ namespace Content.Server.GameTicking DebugTools.AssertNotNull(data); - var newMind = _mind.CreateMind(data!.UserId, character.Name); - _mind.SetUserId(newMind, data.UserId); - jobPrototype = _prototypeManager.Index(jobId); - _playTimeTrackings.PlayerRolesChanged(player); - var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character); DebugTools.AssertNotNull(mobMaybe); mob = mobMaybe!.Value; + var newMind = _mind.CreateMind(data.UserId, Name(mob)); + _mind.SetUserId(newMind, data.UserId); + + _playTimeTrackings.PlayerRolesChanged(player); + _mind.TransferTo(newMind, mob); _roles.MindAddJobRole(newMind, silent: silent, jobPrototype: jobId); diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 00169f53cd..7a5ac41638 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -3,28 +3,45 @@ using Content.Server.Communications; using Content.Server.GameTicking.Rules.Components; using Content.Server.Nuke; using Content.Server.NukeOps; +using Content.Server.Pinpointer; using Content.Server.Popups; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; +using Content.Server.StationRecords.Systems; using Content.Server.Store.Systems; +using Content.Shared.Access.Systems; using Content.Shared.GameTicking.Components; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Nuke; using Content.Shared.NukeOps; +using Content.Shared.Roles; using Content.Shared.Roles.Components; +using Content.Shared.Roles.Jobs; +using Content.Shared.Station; +using Content.Shared.Station.Components; +using Content.Shared.StationRecords; using Content.Shared.Store; +using Content.Shared.Store.Components; using Content.Shared.Tag; using Content.Shared.Zombies; +using Robust.Server.Player; +using Robust.Shared.Containers; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; +using System.Data; using System.Linq; +using System.Text; using Content.Shared.CombatMode.Pacification; using Content.Shared.Station.Components; using Content.Shared.Store.Components; @@ -36,9 +53,18 @@ public sealed class NukeopsRuleSystem : GameRuleSystem { [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; + [Dependency] private readonly SharedIdCardSystem _idCard = default!; + [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly NavMapSystem _navMap = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly SharedStationSystem _station = default!; + [Dependency] private readonly StationRecordsSystem _records = default!; [Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly TagSystem _tag = default!; @@ -106,6 +132,63 @@ public sealed class NukeopsRuleSystem : GameRuleSystem args.AddLine(text); } + // Print disk location if nuke didn't explode and is not armed + List diskWinConditions = [WinCondition.NukeDiskOnCentCom, WinCondition.NukeDiskNotOnCentCom]; + if (component.WinConditions.Any(diskWinConditions.Contains)) + { + var diskQuery = AllEntityQuery(); + while (diskQuery.MoveNext(out var diskUid, out _, out var transform)) + { + StringBuilder text = new StringBuilder(Loc.GetString("nukeops-disk-location-title")); + + List containers = new List(); + bool carriedByMob = false; + + var tempParent = diskUid; + while (_containers.TryGetContainingContainer((tempParent, null), out var container) && !carriedByMob) + { + if (HasComp(container.Owner)) + { + carriedByMob = true; + } + var containermeta = MetaData(container.Owner); + containers.Add(containermeta.EntityName); + tempParent = container.Owner; + } + + string location = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((diskUid, transform))); + + if (carriedByMob) + { + GetDiskCarrierData(tempParent, out var name, out var job, out var username); + text.Append(Loc.GetString("nukeops-disk-carried-by", + ("name", name), + ("job", job), + ("user", username), + ("location", location))); + } + else + { + if (containers.Count > 0) + { + string hierarchy = string.Empty; + for (var i = 0; i < containers.Count; i++) + { + hierarchy = (Loc.GetString( + "storage-hierarchy-list", + ("item", containers[i]), + ("existing-text", hierarchy), + ("items-left", containers.Count - i - 1))); + } + text.Append(hierarchy); + } + text.Append(" "); + text.Append(location); + } + args.AddLine(text.ToString()); + } + } + args.AddLine(Loc.GetString("nukeops-list-start")); var antags = _antag.GetAntagIdentifiers(uid); @@ -552,6 +635,74 @@ public sealed class NukeopsRuleSystem : GameRuleSystem return null; } + + private void GetDiskCarrierData(EntityUid carrier, + out string name, + out string job, + out string username) + { + name = Name(carrier); + job = Loc.GetString("job-name-unknown"); + username = "unknown"; // magic word in Fluent selector + + Entity? mind = null; + + if (_mind.TryGetMind(carrier, out _, out var mindComp)) + { + mind = (carrier, mindComp); + } + else + { + var allMinds = EntityQueryEnumerator(); + while (allMinds.MoveNext(out _, out mindComp)) + { + if (mindComp.CharacterName != name) + continue; + + mind = (carrier, mindComp); + break; + } + } + + if (mind is not null) + { + NetUserId? userId = mind.Value.Comp.UserId; + if (userId is not null && _player.TryGetPlayerData(userId.Value, out var sessionData)) + username = sessionData.UserName; + + // Role/job is the trickiest since it can be unknown in some cases + // For example, after "make ghost role" verb + var roles = _roles.MindGetAllRoleInfo(mind.Value.Owner); + if (roles.Count > 0) + { + job = Loc.GetString(roles.First().Name); + return; + } + + if (_jobs.MindTryGetJobName(mind, out var jobName)) + { + job = jobName; + return; + } + } + + // Try station records + var xform = Transform(carrier); + var station = _station.GetStationInMap(xform.MapID); + if (station != null && _records.GetRecordByName(station.Value, name) is { } id) + { + var key = new StationRecordKey(id, station.Value); + if (_records.TryGetRecord(key, out var record)) + { + job = record.JobTitle; + return; + } + } + + // Fallback to ID + if (_idCard.TryFindIdCard(carrier, out var idCard)) + job = idCard.Comp.LocalizedJobTitle ?? job; + } } /// diff --git a/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs b/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs index ab8864caaa..7eb076d0d5 100644 --- a/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs @@ -7,17 +7,20 @@ using Content.Shared.GameTicking.Components; using Content.Shared.Gibbing.Components; using Content.Shared.Medical.SuitSensor; using Content.Shared.Mind; +using Content.Shared.Objectives.Systems; +using Content.Shared.Random.Helpers; using Robust.Shared.Random; namespace Content.Server.GameTicking.Rules; public sealed class ParadoxCloneRuleSystem : GameRuleSystem { - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly CloningSystem _cloning = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SuitSensorSystem _sensor = default!; + [Dependency] private readonly TargetSystem _target = default!; public override void Initialize() { @@ -32,7 +35,7 @@ public sealed class ParadoxCloneRuleSystem : GameRuleSystem(ev.Target) && !alwaysConvertible || !_mobState.IsAlive(ev.Target) || - HasComp(ev.Target)) + HasComp(ev.Target) || + !HasComp(ev.Used)) { return; } diff --git a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs index 046ecedad6..b520a3f642 100644 --- a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs @@ -1,11 +1,11 @@ using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Mind; using Content.Server.Roles; using Content.Server.Shuttles.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; +using Content.Shared.Objectives.Systems; using Content.Shared.Roles.Components; using Content.Shared.Survivor.Components; using Content.Shared.Tag; @@ -16,13 +16,13 @@ namespace Content.Server.GameTicking.Rules; public sealed class SurvivorRuleSystem : GameRuleSystem { - [Dependency] private readonly RoleSystem _role = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; - [Dependency] private readonly TransformSystem _xform = default!; [Dependency] private readonly EmergencyShuttleSystem _eShuttle = default!; - [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly RoleSystem _role = default!; + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly TargetSystem _target = default!; + [Dependency] private readonly TransformSystem _xform = default!; private static readonly ProtoId InvalidForSurvivorAntagTag = "InvalidForSurvivorAntag"; @@ -38,7 +38,7 @@ public sealed class SurvivorRuleSystem : GameRuleSystem { base.Started(uid, component, gameRule, args); - var allAliveHumanMinds = _mind.GetAliveHumans(); + var allAliveHumanMinds = _target.GetAliveHumans(); foreach (var humanMind in allAliveHumanMinds) { diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 3568f17306..bc053c80ba 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -18,6 +18,7 @@ using Robust.Shared.Random; using System.Linq; using System.Text; using Content.Server.Codewords; +using Robust.Shared.Map; namespace Content.Server.GameTicking.Rules; @@ -148,32 +149,22 @@ public sealed class TraitorRuleSystem : GameRuleSystem private (Note[]?, string) RequestUplink(EntityUid traitor, FixedPoint2 startingBalance, string briefing) { var pda = _uplink.FindUplinkTarget(traitor); - Note[]? code = null; Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink add"); - var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true); + var uplinked = _uplink.AddUplink(traitor, startingBalance, out var code, pda, giveDiscounts: true, bindToPda: false); - if (pda is not null && uplinked) + if (code != null && uplinked == AddUplinkResult.Pda) { Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is PDA"); - // Codes are only generated if the uplink is a PDA - var ev = new GenerateUplinkCodeEvent(); - RaiseLocalEvent(pda.Value, ref ev); - if (ev.Code is { } generatedCode) - { - code = generatedCode; - - // If giveUplink is false the uplink code part is omitted - briefing = string.Format("{0}\n{1}", - briefing, - Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); - return (code, briefing); - } - - Log.Error($"MakeTraitor {ToPrettyString(traitor)} failed to generate an uplink code on {ToPrettyString(pda)}."); + // If giveUplink is false the uplink code part is omitted + briefing = string.Format("{0}\n{1}", + briefing, + Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); + return (code, briefing); } - else if (pda is null && uplinked) + + if (uplinked == AddUplinkResult.Implant) { Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is implant"); briefing += "\n" + Loc.GetString("traitor-role-uplink-implant-short"); @@ -183,6 +174,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem Log.Error($"MakeTraitor failed on {ToPrettyString(traitor)} - No uplink could be added"); } + return (null, briefing); } diff --git a/Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs b/Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs index 0b6a700d11..932774e9a1 100644 --- a/Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Destructible; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; +using Content.Shared.Objectives.Systems; using Content.Shared.Xenoborgs.Components; using Robust.Shared.Timing; @@ -14,13 +15,14 @@ namespace Content.Server.GameTicking.Rules; public sealed class XenoborgsRuleSystem : GameRuleSystem { + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly TargetSystem _target = default!; private static readonly Color AnnouncmentColor = Color.Gold; @@ -53,7 +55,7 @@ public sealed class XenoborgsRuleSystem : GameRuleSystem base.AppendRoundEndText(uid, component, gameRule, ref args); var numXenoborgs = GetNumberXenoborgs(); - var numHumans = _mindSystem.GetAliveHumans().Count; + var numHumans = _target.GetAliveHumans().Count; if (numXenoborgs < 5) args.AddLine(Loc.GetString("xenoborgs-crewmajor")); @@ -96,7 +98,7 @@ public sealed class XenoborgsRuleSystem : GameRuleSystem private void CheckRoundEnd(XenoborgsRuleComponent xenoborgsRuleComponent) { var numXenoborgs = GetNumberXenoborgs(); - var numHumans = _mindSystem.GetAliveHumans().Count; + var numHumans = _target.GetAliveHumans().Count; xenoborgsRuleComponent.MaxNumberXenoborgs = Math.Max(xenoborgsRuleComponent.MaxNumberXenoborgs, numXenoborgs); diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 9d3d05c607..d986c83a54 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -12,7 +12,6 @@ using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Systems; using Content.Shared.Database; -using Content.Shared.Examine; using Content.Shared.Eye; using Content.Shared.FixedPoint; using Content.Shared.Follower; @@ -38,7 +37,6 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Timing; namespace Content.Server.Ghost { @@ -48,7 +46,6 @@ namespace Content.Server.Ghost [Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] private readonly FollowerSystem _followerSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly JobSystem _jobs = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly MindSystem _minds = default!; @@ -88,8 +85,6 @@ namespace Content.Server.Ghost SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnGhostShutdown); - SubscribeLocalEvent(OnGhostExamine); - SubscribeLocalEvent(OnMindRemovedMessage); SubscribeLocalEvent(OnMindUnvisitedMessage); SubscribeLocalEvent(OnPlayerDetached); @@ -205,8 +200,10 @@ namespace Content.Server.Ghost } _eye.RefreshVisibilityMask(uid); - var time = _gameTiming.CurTime; + var time = _gameTiming.RealTime; component.TimeOfDeath = time; + + Dirty(uid, component); } private void OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentShutdown args) @@ -237,16 +234,6 @@ namespace Content.Server.Ghost _actions.AddAction(uid, ref component.ToggleGhostsActionEntity, component.ToggleGhostsAction); } - private void OnGhostExamine(EntityUid uid, GhostComponent component, ExaminedEvent args) - { - var timeSinceDeath = _gameTiming.RealTime.Subtract(component.TimeOfDeath); - var deathTimeInfo = timeSinceDeath.Minutes > 0 - ? Loc.GetString("comp-ghost-examine-time-minutes", ("minutes", timeSinceDeath.Minutes)) - : Loc.GetString("comp-ghost-examine-time-seconds", ("seconds", timeSinceDeath.Seconds)); - - args.PushMarkup(deathTimeInfo); - } - #region Ghost Deletion private void OnMindRemovedMessage(EntityUid uid, GhostComponent component, MindRemovedMessage args) diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index 582b9cb2ac..08fe5b54cf 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -1,44 +1,5 @@ -using Content.Server.Store.Components; -using Content.Server.Store.Systems; using Content.Shared.Implants; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using Content.Shared.Store.Components; namespace Content.Server.Implants; -public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem -{ - [Dependency] private readonly StoreSystem _store = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(OnStoreRelay); - } - - // TODO: This shouldn't be in the SubdermalImplantSystem - private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent implantRelay) - { - var args = implantRelay.Event; - - if (args.Handled) - return; - - // can only insert into yourself to prevent uplink checking with renault - if (args.Target != args.User) - return; - - if (!TryComp(args.Used, out var currency)) - return; - - // same as store code, but message is only shown to yourself - if (!_store.TryAddCurrency((args.Used, currency), (uid, store))) - return; - - args.Handled = true; - var msg = Loc.GetString("store-currency-inserted-implant", ("used", args.Used)); - _popup.PopupEntity(msg, args.User, args.User); - } -} +public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem; diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index c8bba7dac3..7d6d86e379 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -24,7 +24,6 @@ using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; using Content.Server.ServerUpdates; using Content.Server.Voting.Managers; -using Content.Server.Worldgen.Tools; using Content.Shared.Administration.Logs; using Content.Shared.Administration.Managers; using Content.Shared.Chat; @@ -67,7 +66,6 @@ internal static class ServerContentIoC deps.Register(); deps.Register(); deps.Register(); - deps.Register(); deps.Register(); deps.Register(); deps.Register(); diff --git a/Content.Server/Mind/Filters/TargetObjectiveMindFilter.cs b/Content.Server/Mind/Filters/TargetObjectiveMindFilter.cs index 6fc031d7c1..975f4c19bd 100644 --- a/Content.Server/Mind/Filters/TargetObjectiveMindFilter.cs +++ b/Content.Server/Mind/Filters/TargetObjectiveMindFilter.cs @@ -20,7 +20,7 @@ public sealed partial class TargetObjectiveMindFilter : MindFilter [DataField] public EntityWhitelist? Blacklist; - protected override bool ShouldRemove(Entity mind, EntityUid? excluded, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity mind, EntityUid? excluded, IEntityManager entMan) { // ignore this filter if there is no user to check if (!entMan.TryGetComponent(excluded, out var excludedMind)) diff --git a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs index 33b619732d..202d03c6ea 100644 --- a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs @@ -1,103 +1,5 @@ -using Content.Server.Fluids.EntitySystems; -using Content.Server.Nutrition.Components; -using Content.Server.Popups; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.IdentityManagement; -using Content.Shared.Nutrition; -using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; -using Content.Shared.Rejuvenate; -using Content.Shared.Throwing; -using Content.Shared.Trigger.Components; -using Content.Shared.Trigger.Systems; -using Content.Shared.Chemistry.EntitySystems; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; -namespace Content.Server.Nutrition.EntitySystems -{ - [UsedImplicitly] - public sealed class CreamPieSystem : SharedCreamPieSystem - { - [Dependency] private readonly IngestionSystem _ingestion = default!; - [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly PuddleSystem _puddle = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutions = default!; - [Dependency] private readonly TriggerSystem _trigger = default!; +namespace Content.Server.Nutrition.EntitySystems; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnSlice); - - SubscribeLocalEvent(OnRejuvenate); - } - - protected override void SplattedCreamPie(Entity entity) - { - // The entity is deleted, so play the sound at its position rather than parenting - var coordinates = Transform(entity).Coordinates; - _audio.PlayPvs(_audio.ResolveSound(entity.Comp1.Sound), coordinates, AudioParams.Default.WithVariation(0.125f)); - - if (Resolve(entity, ref entity.Comp2, false)) - { - if (_solutions.TryGetSolution(entity.Owner, entity.Comp2.Solution, out _, out var solution)) - _puddle.TrySpillAt(entity.Owner, solution, out _, false); - - _ingestion.SpawnTrash((entity, entity.Comp2)); - } - - ActivatePayload(entity); - - QueueDel(entity); - } - - // TODO - // A regression occured here. Previously creampies would activate their hidden payload if you tried to eat them. - // However, the refactor to IngestionSystem caused the event to not be reached, - // because eating is blocked if an item is inside the food. - - private void OnSlice(Entity entity, ref SliceFoodEvent args) - { - ActivatePayload(entity); - } - - private void ActivatePayload(EntityUid uid) - { - if (_itemSlots.TryGetSlot(uid, CreamPieComponent.PayloadSlotName, out var itemSlot)) - { - if (_itemSlots.TryEject(uid, itemSlot, user: null, out var item)) - { - if (TryComp(item.Value, out var timerTrigger)) - { - _trigger.ActivateTimerTrigger((item.Value, timerTrigger)); - } - } - } - } - - protected override void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) - { - _popup.PopupEntity(Loc.GetString("cream-pied-component-on-hit-by-message", - ("thrown", Identity.Entity(args.Thrown, EntityManager))), - uid, args.Target); - - var otherPlayers = Filter.PvsExcept(uid); - - _popup.PopupEntity(Loc.GetString("cream-pied-component-on-hit-by-message-others", - ("owner", Identity.Entity(uid, EntityManager)), - ("thrown", Identity.Entity(args.Thrown, EntityManager))), - uid, otherPlayers, false); - } - - private void OnRejuvenate(Entity entity, ref RejuvenateEvent args) - { - SetCreamPied(entity, entity.Comp, false); - } - } -} +public sealed class CreamPieSystem : SharedCreamPieSystem; diff --git a/Content.Server/Objectives/Components/HijackTradeStationConditionComponent.cs b/Content.Server/Objectives/Components/HijackTradeStationConditionComponent.cs new file mode 100644 index 0000000000..416c422837 --- /dev/null +++ b/Content.Server/Objectives/Components/HijackTradeStationConditionComponent.cs @@ -0,0 +1,11 @@ +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +/// +/// Objective condition that requires the player to hijack the trade station. +/// +[RegisterComponent, Access(typeof(HijackTradeStationConditionSystem))] +public sealed partial class HijackTradeStationConditionComponent : Component +{ +} diff --git a/Content.Server/Objectives/Components/PickRandomPersonComponent.cs b/Content.Server/Objectives/Components/PickRandomPersonComponent.cs index 2c864a80d4..16e539f320 100644 --- a/Content.Server/Objectives/Components/PickRandomPersonComponent.cs +++ b/Content.Server/Objectives/Components/PickRandomPersonComponent.cs @@ -16,7 +16,7 @@ public sealed partial class PickRandomPersonComponent : Component /// A pool to pick potential targets from. /// [DataField] - public IMindPool Pool = new AliveHumansPool(); + public MindPool Pool = new AliveHumansPool(); /// /// Filters to apply to . diff --git a/Content.Server/Objectives/Components/SupercriticalAnomaliesConditionComponent.cs b/Content.Server/Objectives/Components/SupercriticalAnomaliesConditionComponent.cs new file mode 100644 index 0000000000..038150d90c --- /dev/null +++ b/Content.Server/Objectives/Components/SupercriticalAnomaliesConditionComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Objectives.Components; + +/// +/// Objective condition that requires a certain number of anomalies (defined by ) to go supercritical while the objective is in play. +/// +[RegisterComponent] +public sealed partial class SupercriticalAnomaliesConditionComponent : Component +{ + /// + /// The number of anomalies that have gone supercritical since this objective was added. + /// + [DataField] + public int SupercriticalAnomalies = 0; +} diff --git a/Content.Server/Objectives/Systems/HijackTradeStationConditionSystem.cs b/Content.Server/Objectives/Systems/HijackTradeStationConditionSystem.cs new file mode 100644 index 0000000000..e5f88a28dd --- /dev/null +++ b/Content.Server/Objectives/Systems/HijackTradeStationConditionSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Objectives.Components; +using Content.Shared.Cargo.Components; +using Content.Shared.Mind; +using Content.Shared.Objectives.Components; +using NetCord; + +namespace Content.Server.Objectives.Systems; + +/// +/// Handles the Hijack Trade Station objective. +/// +public sealed class HijackTradeStationConditionSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetProgress); + } + + private void OnGetProgress(Entity ent, ref ObjectiveGetProgressEvent args) + { + var enumerator = EntityQueryEnumerator(); + args.Progress = 0f; + // If there's any hacked trade station, succeed. + while (enumerator.MoveNext(out var comp)) + { + if (!comp.Hacked) + continue; + + args.Progress = 1f; + return; + } + } +} diff --git a/Content.Server/Objectives/Systems/PickObjectiveTargetSystem.cs b/Content.Server/Objectives/Systems/PickObjectiveTargetSystem.cs index b3075b2274..5b48b25ad4 100644 --- a/Content.Server/Objectives/Systems/PickObjectiveTargetSystem.cs +++ b/Content.Server/Objectives/Systems/PickObjectiveTargetSystem.cs @@ -5,6 +5,7 @@ using Content.Server.GameTicking.Rules; using Content.Server.Revolutionary.Components; using Robust.Shared.Random; using System.Linq; +using Content.Shared.Objectives.Systems; namespace Content.Server.Objectives.Systems; @@ -14,8 +15,8 @@ namespace Content.Server.Objectives.Systems; /// public sealed class PickObjectiveTargetSystem : EntitySystem { - [Dependency] private readonly TargetObjectiveSystem _target = default!; - [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly TargetObjectiveSystem _objective = default!; + [Dependency] private readonly TargetSystem _target = default!; public override void Initialize() { @@ -51,7 +52,7 @@ public sealed class PickObjectiveTargetSystem : EntitySystem return; } - _target.SetTarget(ent.Owner, targetComp.Target.Value); + _objective.SetTarget(ent.Owner, targetComp.Target.Value); } private void OnRandomPersonAssigned(Entity ent, ref ObjectiveAssignedEvent args) @@ -68,12 +69,12 @@ public sealed class PickObjectiveTargetSystem : EntitySystem return; // couldn't find a target :( - if (_mind.PickFromPool(ent.Comp.Pool, ent.Comp.Filters, args.MindId) is not {} picked) + if (_target.PickFromPool(ent.Comp.Pool, ent.Comp.Filters, args.MindId) is not {} picked) { args.Cancelled = true; return; } - _target.SetTarget(ent, picked, target); + _objective.SetTarget(ent, picked, target); } } diff --git a/Content.Server/Objectives/Systems/SupercriticalAnomaliesConditionSystem.cs b/Content.Server/Objectives/Systems/SupercriticalAnomaliesConditionSystem.cs new file mode 100644 index 0000000000..93e649def2 --- /dev/null +++ b/Content.Server/Objectives/Systems/SupercriticalAnomaliesConditionSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Objectives.Components; +using Content.Shared.Anomaly.Components; +using Content.Shared.Objectives.Components; + +namespace Content.Server.Objectives.Systems; + +public sealed partial class SupercriticalAnomaliesConditionSystem : EntitySystem +{ + [Dependency] private readonly NumberObjectiveSystem _numberObjective = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAnomalySupercrit); + SubscribeLocalEvent(OnGetProgress); + } + + private void OnAnomalySupercrit(ref AnomalyShutdownEvent args) + { + if (!args.Supercritical) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var comp)) + { + comp.SupercriticalAnomalies += 1; + } + } + + private void OnGetProgress(Entity ent, ref ObjectiveGetProgressEvent args) + { + var target = _numberObjective.GetTarget(ent); + if (target == 0) + { + args.Progress = 0f; + return; + } + args.Progress = MathF.Min((float)ent.Comp.SupercriticalAnomalies / target, 1f); + } +} diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index 9122c96964..724c9dc491 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Server.Access.Systems; using Content.Server.AlertLevel; using Content.Server.CartridgeLoader; @@ -17,6 +18,7 @@ using Content.Shared.Light; using Content.Shared.Light.EntitySystems; using Content.Shared.PDA; using Content.Shared.PDA.Ringer; +using Content.Shared.Store.Components; using Content.Shared.VoiceMask; using Robust.Server.Containers; using Robust.Server.GameObjects; @@ -189,7 +191,7 @@ namespace Content.Server.PDA var address = GetDeviceNetAddress(uid); var hasInstrument = HasComp(uid); - var showUplink = HasComp(uid) && IsUnlocked(uid); + var showUplink = TryGetUnlockedStore(uid, out _); UpdateStationName(uid, pda); UpdateAlertLevel(uid, pda); @@ -274,8 +276,19 @@ namespace Content.Server.PDA return; // check if its locked again to prevent malicious clients opening locked uplinks - if (HasComp(uid) && IsUnlocked(uid)) - _store.ToggleUi(msg.Actor, uid); + if (TryGetUnlockedStore(uid, out var store)) + { + if (store != uid) + { + if (TryComp(uid, out var remoteStore)) + remoteStore.Store = store; + _store.ToggleUi(msg.Actor, store.Value, remoteAccess: uid); + } + else + { + _store.ToggleUi(msg.Actor, store.Value); + } + } } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) @@ -285,14 +298,24 @@ namespace Content.Server.PDA if (TryComp(uid, out var uplink)) { + if (TryComp(uid, out var remoteStore)) + remoteStore.Store = null; _ringer.LockUplink((uid, uplink)); UpdatePdaUi(uid, pda); } } - private bool IsUnlocked(EntityUid uid) + /// + /// Returns the currently unlocked store, if there is one. + /// + private bool TryGetUnlockedStore(EntityUid uid, [NotNullWhen(true)] out EntityUid? store) { - return !TryComp(uid, out var uplink) || uplink.Unlocked; + store = null; + if (!TryComp(uid, out var uplink) || !uplink.Unlocked) + return false; + + store = _store.GetStore(uid); + return store != null; } private void UpdateStationName(EntityUid uid, PdaComponent pda) diff --git a/Content.Server/PDA/Ringer/RingerAccessUplinkComponent.cs b/Content.Server/PDA/Ringer/RingerAccessUplinkComponent.cs new file mode 100644 index 0000000000..f7d124adef --- /dev/null +++ b/Content.Server/PDA/Ringer/RingerAccessUplinkComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.PDA; + +namespace Content.Server.PDA.Ringer; + +/// +/// Opens the store UI when a PDA's ringtone is set to the secret code. +/// Traitors are told the code when greeted. +/// +[RegisterComponent, Access(typeof(RingerSystem))] +public sealed partial class RingerAccessUplinkComponent : Component +{ + /// + /// Notes to set ringtone to in order to lock or unlock the uplink. + /// Set via GenerateUplinkCodeEvent. + /// + [DataField] + public Note[]? Code; + + /// + /// If set, the uplink store can only be opened with the given entity. + /// + [DataField] + public EntityUid? BoundEntity; +} diff --git a/Content.Server/PDA/Ringer/RingerSystem.cs b/Content.Server/PDA/Ringer/RingerSystem.cs index b47ca0fde3..a86b44d389 100644 --- a/Content.Server/PDA/Ringer/RingerSystem.cs +++ b/Content.Server/PDA/Ringer/RingerSystem.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Server.Store.Systems; +using Content.Shared.GameTicking; using Content.Shared.PDA; using Content.Shared.PDA.Ringer; -using Content.Shared.Store.Components; +using Content.Shared.Store; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.PDA.Ringer; @@ -14,15 +16,35 @@ public sealed class RingerSystem : SharedRingerSystem { [Dependency] private readonly IRobustRandom _random = default!; + public static Note[] AllowedNotes = + { + Note.C, + Note.D, + Note.E, + Note.F, + Note.G, + Note.A, + Note.B + }; + + /// + /// Stores the serialized version of any ringtone that can be excluded from new ringtone generations. + /// + [ViewVariables] + public readonly HashSet ReservedSerializedRingtones = new(); + /// public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnCurrencyInsert); - SubscribeLocalEvent(OnGenerateUplinkCode); + SubscribeLocalEvent(OnGenerateUplinkCode); + + SubscribeLocalEvent(CleanupReserved); + + InitialSetup(); } /// @@ -30,7 +52,11 @@ public sealed class RingerSystem : SharedRingerSystem /// private void OnMapInit(Entity ent, ref MapInitEvent args) { - UpdateRingerRingtone(ent, GenerateRingtone()); + var ringtone = GenerateRingtone(); + + ringtone ??= new Note[RingtoneLength] { Note.A, Note.A, Note.A, Note.A, Note.A, Note.A }; // Fallback + + UpdateRingerRingtone(ent, ringtone); } /// @@ -53,71 +79,211 @@ public sealed class RingerSystem : SharedRingerSystem /// /// Handles the for generating an uplink code. /// - private void OnGenerateUplinkCode(Entity ent, ref GenerateUplinkCodeEvent ev) + private void OnGenerateUplinkCode(Entity ent, ref GenerateUplinkCodeEvent ev) { - var code = GenerateRingtone(); + var code = GenerateRingtone(true, true); // Set the code on the component ent.Comp.Code = code; - // Return the code via the event ev.Code = code; } - /// - public override bool TryToggleUplink(EntityUid uid, Note[] ringtone, EntityUid? user = null) + private void InitialSetup() { - if (!TryComp(uid, out var uplink)) - return false; + ReservedSerializedRingtones.Clear(); + } - if (!HasComp(uid)) - return false; - - // Wasn't generated yet - if (uplink.Code is null) + /// + public override bool TryToggleUplink(Entity entity, Note[] ringtone, EntityUid? user = null) + { + if (!Resolve(entity, ref entity.Comp)) return false; // On the server, we always check if the code matches - if (!uplink.Code.SequenceEqual(ringtone)) + if (!TryMatchRingtoneToStore(ringtone, out var store, entity)) return false; - return ToggleUplinkInternal((uid, uplink)); + // If the store is not this entity, we make sure to properly set the remote store. + if (store != entity.Owner) + Store.SetRemoteStore(entity.Owner, store); + + return ToggleUplinkInternal((entity, entity.Comp)); } /// - /// Generates a random ringtone using the C pentatonic scale. + /// Generates a random ringtone using the C major scale. /// + /// Exclude any ringtone registered to ReservedSerializedRingtones. + /// Add the generated ringtone to ReservedSerializedRingtones. Requires ExcludeReserved to be true. /// An array of Notes representing the ringtone. /// The logic for this is on the Server so that we don't get a different result on the Client every time. - private Note[] GenerateRingtone() + private Note[]? GenerateRingtone(bool excludeReserved = false, bool reserveRingtone = false) { - // Default to using C pentatonic so it at least sounds not terrible. - return GenerateRingtone(new[] - { - Note.C, - Note.D, - Note.E, - Note.G, - Note.A - }); + // Default to using C major so it at least sounds not terrible. + return GenerateRingtone(AllowedNotes, excludeReserved, reserveRingtone); } /// /// Generates a random ringtone using the specified notes. /// /// The notes to choose from when generating the ringtone. + /// Exclude any ringtone registered to ReservedSerializedRingtones. + /// Add the generated ringtone to ReservedSerializedRingtones. Requires ExcludeReserved to be true. /// An array of Notes representing the ringtone. /// The logic for this is on the Server so that we don't get a different result on the Client every time. - private Note[] GenerateRingtone(Note[] notes) + private Note[]? GenerateRingtone(Note[] notes, bool excludeReserved = false, bool reserveRingtone = false) { - var ringtone = new Note[RingtoneLength]; + var excludedRingtones = excludeReserved ? ReservedSerializedRingtones.ToArray() : null; + + var maxPow = Math.Pow(notes.Length, RingtoneLength); + if (maxPow > int.MaxValue) + { + return null; + } + + var generatedRingtone = NextIntInRangeButExclude(0, Convert.ToInt32(maxPow) - 1, excludedRingtones); + + if (!TryDeserializeRingtone(notes, generatedRingtone, out var ringtone)) + return null; + + if (excludeReserved && reserveRingtone) + ReservedSerializedRingtones.Add(generatedRingtone); + + return ringtone; + } + + /// + /// Serialize a ringtone, representing it as an Int32. + /// + /// The array of notes used to generate the ringtone. + /// The ringtone which needs to be serialized. + /// The ringtone in a serialized format. + /// Whether the ringtone could be serialized or not. + private bool TrySerializeRingtone(Note[] allowedNotes, Note[] ringtone, [NotNullWhen(true)] out int? serializedRingtone) + { + var noteLength = allowedNotes.Length; + + // The serialization stores as an Int32, and therefore using Pow risks overshooting the max value, so we check for if that's a risk. + // If using 12 possible notes, you can have a ringtone sequence of 7 notes safely without overshooting. + var maxPow = Math.Pow(noteLength, ringtone.Length); + if (maxPow > int.MaxValue) + { + serializedRingtone = null; + return false; + } + + var serializationValue = 0; + + for (var i = 0; i < ringtone.Length; i++) + { + var pow = Math.Pow(noteLength, i); + var index = Array.IndexOf(allowedNotes, ringtone[i]); + if (index == -1) + { + serializedRingtone = null; + return false; + } + + serializationValue += Convert.ToInt32(pow) * index; + } + + serializedRingtone = serializationValue; + return true; + } + + /// + /// Deserialize a serialized ringtone into a Note array. + /// + /// The array of notes used to generate the ringtone. + /// The ringtone in a serialized format. + /// The ringtone resulting from the deserialization. + /// Whether the ringtone could be deserialized or not. + private bool TryDeserializeRingtone(Note[] allowedNotes, int serializedRingtone, [NotNullWhen(true)] out Note[]? ringtone) + { + var noteLength = allowedNotes.Length; + ringtone = new Note[RingtoneLength]; + + // The serialization stores as an Int32, and therefore using Pow risks overshooting the max value, so we check for if that's a risk. + // If using 12 possible notes, you can have a ringtone sequence of 7 notes safely without overshooting. + var maxPow = Math.Pow(noteLength, RingtoneLength); + if (maxPow > int.MaxValue) + { + ringtone = null; + return false; + } for (var i = 0; i < RingtoneLength; i++) { - ringtone[i] = _random.Pick(notes); + var pow = Math.Pow(noteLength, RingtoneLength - 1 - i); + var powInt = Convert.ToInt32(pow); + var val = serializedRingtone / powInt; + if (!AllowedNotes.TryGetValue(val, out var note)) + { + ringtone = null; + return false; + } + + ringtone[RingtoneLength - 1 - i] = note; + serializedRingtone -= val * powInt; } - return ringtone; + return true; + } + + /// + /// Try to get the store entity that has the matching ringer access. + /// + /// Notes from the ringer. + /// The store entity, if there is one. + /// The entity providing the code. + public bool TryMatchRingtoneToStore(Note[] notes, [NotNullWhen(true)] out EntityUid? store, EntityUid? ringer = null) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.Code != null && notes.SequenceEqual(comp.Code)) + { + if (comp.BoundEntity != null && comp.BoundEntity != ringer) + break; + + store = uid; + return true; + } + } + + store = null; + return false; + } + + private void CleanupReserved(RoundRestartCleanupEvent ev) + { + ReservedSerializedRingtones.Clear(); + } + + private int NextIntInRangeButExclude(int start, int end, int[]? excludes) + { + excludes ??= new int[0]; + Array.Sort(excludes); + var rangeLength = end - start - excludes.Length; + var randomInt = _random.Next(rangeLength) + start; + + for (var i = 0; i < excludes.Length; i++) + { + if (excludes[i] > randomInt) + { + return randomInt; + } + + randomInt++; + } + + return randomInt; + } + + public void SetBoundUplinkEntity(Entity entity, EntityUid? targetEntity) + { + entity.Comp.BoundEntity = targetEntity; } } diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index e5b0981b3d..0854fbb172 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -7,6 +7,7 @@ using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Utility; +using Robust.Shared.Random; namespace Content.Server.Procedural; @@ -19,7 +20,7 @@ public sealed partial class DungeonSystem /// /// Gets a random dungeon room matching the specified area, whitelist and size. /// - public DungeonRoomPrototype? GetRoomPrototype(Vector2i size, Random random, EntityWhitelist? whitelist = null) + public DungeonRoomPrototype? GetRoomPrototype(Vector2i size, IRobustRandom random, EntityWhitelist? whitelist = null) { return GetRoomPrototype(random, whitelist, minSize: size, maxSize: size); } @@ -27,7 +28,7 @@ public sealed partial class DungeonSystem /// /// Gets a random dungeon room matching the specified area and whitelist and size range /// - public DungeonRoomPrototype? GetRoomPrototype(Random random, + public DungeonRoomPrototype? GetRoomPrototype(IRobustRandom random, EntityWhitelist? whitelist = null, Vector2i? minSize = null, Vector2i? maxSize = null) @@ -77,7 +78,7 @@ public sealed partial class DungeonSystem MapGridComponent grid, Vector2i origin, DungeonRoomPrototype room, - Random random, + IRobustRandom random, HashSet? reservedTiles, bool clearExisting = false, bool rotation = false) @@ -96,7 +97,7 @@ public sealed partial class DungeonSystem SpawnRoom(gridUid, grid, finalTransform, room, reservedTiles, clearExisting); } - public Angle GetRoomRotation(DungeonRoomPrototype room, Random random) + public Angle GetRoomRotation(DungeonRoomPrototype room, IRobustRandom random) { var roomRotation = Angle.Zero; diff --git a/Content.Server/Procedural/RoomFillSystem.cs b/Content.Server/Procedural/RoomFillSystem.cs index f4ccab2367..96adb8dec3 100644 --- a/Content.Server/Procedural/RoomFillSystem.cs +++ b/Content.Server/Procedural/RoomFillSystem.cs @@ -1,4 +1,5 @@ using Robust.Shared.Map.Components; +using Robust.Shared.Random; namespace Content.Server.Procedural; @@ -6,6 +7,7 @@ public sealed class RoomFillSystem : EntitySystem { [Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly IRobustRandom _random = default!; public override void Initialize() { @@ -19,8 +21,7 @@ public sealed class RoomFillSystem : EntitySystem if (xform.GridUid != null) { - var random = new Random(); - var room = _dungeon.GetRoomPrototype(random, component.RoomWhitelist, component.MinSize, component.MaxSize); + var room = _dungeon.GetRoomPrototype(_random, component.RoomWhitelist, component.MinSize, component.MaxSize); if (room != null) { @@ -30,7 +31,7 @@ public sealed class RoomFillSystem : EntitySystem mapGrid, _maps.LocalToTile(xform.GridUid.Value, mapGrid, xform.Coordinates) - new Vector2i(room.Size.X/2,room.Size.Y/2), room, - random, + _random, null, clearExisting: component.ClearExisting, rotation: component.Rotation); diff --git a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs index 796c1e1fce..8bbfa7ad41 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs @@ -1,11 +1,15 @@ +using System.Buffers; +using System.Collections.Concurrent; +using System.Linq; using System.Numerics; using Content.Server.Radiation.Components; using Content.Server.Radiation.Events; using Content.Shared.Radiation.Components; using Content.Shared.Radiation.Systems; -using Robust.Shared.Collections; +using JetBrains.Annotations; using Robust.Shared.Map.Components; -using Robust.Shared.Timing; +using Robust.Shared.Physics; +using Robust.Shared.Threading; using Robust.Shared.Utility; namespace Content.Server.Radiation.Systems; @@ -13,113 +17,62 @@ namespace Content.Server.Radiation.Systems; // main algorithm that fire radiation rays to target public partial class RadiationSystem { - private List> _grids = new(); - private readonly record struct SourceData( float Intensity, + float Slope, + float MaxRange, Entity Entity, Vector2 WorldPosition) { - public EntityUid? GridUid => Entity.Comp2.GridUid; - public float Slope => Entity.Comp1.Slope; + public EntityUid Uid => Entity.Owner; public TransformComponent Transform => Entity.Comp2; } private void UpdateGridcast() { - // should we save debug information into rays? - // if there is no debug sessions connected - just ignore it var debug = _debugSessions.Count > 0; - - var stopwatch = new Stopwatch(); + var stopwatch = new Robust.Shared.Timing.Stopwatch(); stopwatch.Start(); - _sources.Clear(); - _sources.EnsureCapacity(Count()); - - var sources = EntityQueryEnumerator(); - var destinations = EntityQueryEnumerator(); - - while (sources.MoveNext(out var uid, out var source, out var xform)) + var sourcesCount = _sourceDataMap.Count; + if (_activeReceivers.Count == 0 || sourcesCount == 0) { - if (!source.Enabled) - continue; - - var worldPos = _transform.GetWorldPosition(xform); - - // Intensity is scaled by stack size. - var intensity = source.Intensity * _stack.GetCount(uid); - - // Apply rad modifier if the source is enclosed within a radiation blocking container - // Note that this also applies to receivers, and it doesn't bother to check if the container sits between them. - // I.e., a source & receiver in the same blocking container will get double-blocked, when no blocking should be applied. - intensity = GetAdjustedRadiationIntensity(uid, intensity); - - _sources.Add(new(intensity, (uid, source, xform), worldPos)); + RaiseLocalEvent(new RadiationSystemUpdatedEvent()); + return; } - var debugRays = debug ? new List() : null; - var receiversTotalRads = new ValueList<(Entity, float)>(); + var results = new float[_activeReceivers.Count]; + var debugRays = debug ? new ConcurrentBag() : null; - // TODO RADIATION Parallelize - // Would need to give receiversTotalRads a fixed size. - // Also the _grids list needs to be local to a job. (or better yet cached in SourceData) - // And I guess disable parallelization when debugging to make populating the debug List easier. - // Or just make it threadsafe? - while (destinations.MoveNext(out var destUid, out var dest, out var destTrs)) + var job = new RadiationJob { - var destWorld = _transform.GetWorldPosition(destTrs); + System = this, + SourceTree = _sourceTree, + SourceDataMap = _sourceDataMap, + Destinations = _activeReceivers, + Results = results, + DebugRays = debugRays, + Debug = debug + }; - var rads = 0f; - foreach (var source in _sources) + _parallel.ProcessNow(job, _activeReceivers.Count); + + for (var i = 0; i < _activeReceivers.Count; i++) + { + var uid = _activeReceivers[i]; + var rads = results[i]; + + if (_receiverQuery.TryComp(uid, out var receiver)) { - // send ray towards destination entity - if (Irradiate(source, destUid, destTrs, destWorld, debug) is not { } ray) - continue; - - // add rads to total rad exposure - if (ray.ReachedDestination) - rads += ray.Rads; - - if (!debug) - continue; - - debugRays!.Add(new DebugRadiationRay( - ray.MapId, - GetNetEntity(ray.SourceUid), - ray.Source, - GetNetEntity(ray.DestinationUid), - ray.Destination, - ray.Rads, - ray.Blockers ?? new()) - ); + receiver.CurrentRadiation = rads; + if (rads > 0) + IrradiateEntity(uid, rads, GridcastUpdateRate); } - - // Apply modifier if the destination entity is hidden within a radiation blocking container - rads = GetAdjustedRadiationIntensity(destUid, rads); - - receiversTotalRads.Add(((destUid, dest), rads)); } - // update information for debug overlay - var elapsedTime = stopwatch.Elapsed.TotalMilliseconds; - var totalSources = _sources.Count; - var totalReceivers = receiversTotalRads.Count; - UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays); + if (debugRays is not null) + UpdateGridcastDebugOverlay(stopwatch.Elapsed.TotalMilliseconds, sourcesCount, _activeReceivers.Count, debugRays.ToList()); - // send rads to each entity - foreach (var (receiver, rads) in receiversTotalRads) - { - // update radiation value of receiver - // if no radiation rays reached target, that will set it to 0 - receiver.Comp.CurrentRadiation = rads; - - // also send an event with combination of total rad - if (rads > 0) - IrradiateEntity(receiver, rads, GridcastUpdateRate); - } - - // raise broadcast event that radiation system has updated RaiseLocalEvent(new RadiationSystemUpdatedEvent()); } @@ -127,68 +80,28 @@ public partial class RadiationSystem EntityUid destUid, TransformComponent destTrs, Vector2 destWorld, - bool saveVisitedTiles) + bool saveVisitedTiles, + List> gridList) { - // lets first check that source and destination on the same map - if (source.Transform.MapID != destTrs.MapID) - return null; - var mapId = destTrs.MapID; - - // get direction from rad source to destination and its distance - var dir = destWorld - source.WorldPosition; - var dist = dir.Length(); - - // check if receiver is too far away - if (dist > GridcastMaxDistance) - return null; - - // will it even reach destination considering distance penalty + var dist = (destWorld - source.WorldPosition).Length(); var rads = source.Intensity - source.Slope * dist; if (rads < MinIntensity) return null; - // create a new radiation ray from source to destination - // at first we assume that it doesn't hit any radiation blockers - // and has only distance penalty var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads); - // if source and destination on the same grid it's possible that - // between them can be another grid (ie. shuttle in center of donut station) - // however we can do simplification and ignore that case - if (GridcastSimplifiedSameGrid && destTrs.GridUid is { } gridUid && source.GridUid == gridUid) - { - if (!_gridQuery.TryGetComponent(gridUid, out var gridComponent)) - return ray; - return Gridcast((gridUid, gridComponent, Transform(gridUid)), ref ray, saveVisitedTiles, source.Transform, destTrs); - } - - // lets check how many grids are between source and destination - // do a box intersection test between target and destination - // it's not very precise, but really cheap - - // TODO RADIATION - // Consider caching this in SourceData? - // I.e., make the lookup for grids as large as the sources's max distance and store the result in SourceData. - // Avoids having to do a lookup per source*receiver. var box = Box2.FromTwoPoints(source.WorldPosition, destWorld); - _grids.Clear(); - _mapManager.FindGridsIntersecting(mapId, box, ref _grids, true); + gridList.Clear(); + _mapManager.FindGridsIntersecting(mapId, box, ref gridList, true); - // gridcast through each grid and try to hit some radiation blockers - // the ray will be updated with each grid that has some blockers - foreach (var grid in _grids) + foreach (var grid in gridList) { ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs); - - // looks like last grid blocked all radiation - // we can return right now if (ray.Rads <= 0) return ray; } - _grids.Clear(); - return ray; } @@ -200,19 +113,12 @@ public partial class RadiationSystem TransformComponent destTrs) { var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null; - - // if grid doesn't have resistance map just apply distance penalty var gridUid = grid.Owner; if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance)) return ray; + var resistanceMap = resistance.ResistancePerTile; - // get coordinate of source and destination in grid coordinates - - // TODO Grid overlap. This currently assumes the grid is always parented directly to the map (local matrix == world matrix). - // If ever grids are allowed to overlap, this might no longer be true. In that case, this should precompute and cache - // inverse world matrices. - Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner ? sourceTrs.LocalPosition : Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix); @@ -221,28 +127,20 @@ public partial class RadiationSystem ? destTrs.LocalPosition : Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix); - Vector2i sourceGrid = new( - (int)Math.Floor(srcLocal.X / grid.Comp1.TileSize), - (int)Math.Floor(srcLocal.Y / grid.Comp1.TileSize)); + Vector2i sourceGrid = new((int)Math.Floor(srcLocal.X / grid.Comp1.TileSize), (int)Math.Floor(srcLocal.Y / grid.Comp1.TileSize)); + Vector2i destGrid = new((int)Math.Floor(dstLocal.X / grid.Comp1.TileSize), (int)Math.Floor(dstLocal.Y / grid.Comp1.TileSize)); - Vector2i destGrid = new( - (int)Math.Floor(dstLocal.X / grid.Comp1.TileSize), - (int)Math.Floor(dstLocal.Y / grid.Comp1.TileSize)); - - // iterate tiles in grid line from source to destination var line = new GridLineEnumerator(sourceGrid, destGrid); while (line.MoveNext()) { var point = line.Current; if (!resistanceMap.TryGetValue(point, out var resData)) continue; + ray.Rads -= resData; + if (saveVisitedTiles && blockers is not null) + blockers.Add((point, ray.Rads)); - // save data for debug - if (saveVisitedTiles) - blockers!.Add((point, ray.Rads)); - - // no intensity left after blocker if (ray.Rads <= MinIntensity) { ray.Rads = 0; @@ -250,13 +148,11 @@ public partial class RadiationSystem } } - if (!saveVisitedTiles || blockers!.Count <= 0) + if (blockers is null || blockers.Count == 0) return ray; - // save data for debug if needed ray.Blockers ??= new(); ray.Blockers.Add(GetNetEntity(gridUid), blockers); - return ray; } @@ -291,4 +187,92 @@ public partial class RadiationSystem return rads; } + + [UsedImplicitly] + private readonly record struct RadiationJob : IParallelRobustJob + { + public int BatchSize => 5; + public required RadiationSystem System { get; init; } + public required B2DynamicTree SourceTree { get; init; } + public required Dictionary SourceDataMap { get; init; } + public required List Destinations { get; init; } + public required float[] Results { get; init; } + public required ConcurrentBag? DebugRays { get; init; } + public required bool Debug { get; init; } + + public void Execute(int index) + { + var destUid = Destinations[index]; + if (System.Deleted(destUid) || !System.TryComp(destUid, out TransformComponent? destTrs)) + { + Results[index] = 0; + return; + } + + var nearbySourcesArray = ArrayPool.Shared.Rent(256); + + var gridList = new List>(8); + + try + { + var destWorld = System._transform.GetWorldPosition(destTrs); + var rads = 0f; + var destMapId = destTrs.MapID; + + var queryAabb = new Box2(destWorld, destWorld); + + var state = (nearbySourcesArray, 0, SourceTree); + SourceTree.Query(ref state, + static (ref (EntityUid[] arr, int count, B2DynamicTree tree) tuple, + DynamicTree.Proxy proxy) => + { + if (tuple.count >= tuple.arr.Length) + return true; + + var uid = tuple.tree.GetUserData(proxy); + tuple.arr[tuple.count++] = uid; + return true; + }, + in queryAabb); + + var nearbySourcesSpan = nearbySourcesArray.AsSpan(0, state.Item2); + + foreach (var sourceUid in nearbySourcesSpan) + { + if (!SourceDataMap.TryGetValue(sourceUid, out var source) + || source.Transform.MapID != destMapId) + continue; + var delta = source.WorldPosition - destWorld; + if (delta.LengthSquared() > source.MaxRange * source.MaxRange) + continue; + var dist = delta.Length(); + var radsAfterDist = source.Intensity - source.Slope * dist; + if (radsAfterDist < System.MinIntensity) + continue; + if (System.Irradiate(source, destUid, destTrs, destWorld, Debug, gridList) is not { } ray) + continue; + + if (ray.ReachedDestination) + rads += ray.Rads; + + DebugRays?.Add(new DebugRadiationRay( + ray.MapId, + System.GetNetEntity(ray.SourceUid), + ray.Source, + System.GetNetEntity(ray.DestinationUid), + ray.Destination, + ray.Rads, + ray.Blockers ?? new Dictionary>()) + ); + } + + rads = System.GetAdjustedRadiationIntensity(destUid, rads); + Results[index] = rads; + } + finally + { + ArrayPool.Shared.Return(nearbySourcesArray); + } + } + } } diff --git a/Content.Server/Radiation/Systems/RadiationSystem.cs b/Content.Server/Radiation/Systems/RadiationSystem.cs index 4a4d0f8930..fd1a60710d 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.cs @@ -4,25 +4,31 @@ using Content.Shared.Radiation.Events; using Content.Shared.Stacks; using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Threading; +using System.Numerics; +using Content.Shared.Radiation.Systems; namespace Content.Server.Radiation.Systems; -public sealed partial class RadiationSystem : EntitySystem +public sealed partial class RadiationSystem : SharedRadiationSystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedStackSystem _stack = default!; [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly IParallelManager _parallel = default!; - private EntityQuery _blockerQuery; - private EntityQuery _resistanceQuery; - private EntityQuery _gridQuery; - private EntityQuery _stackQuery; + [Dependency] private readonly EntityQuery _receiverQuery = default!; + [Dependency] private EntityQuery _blockerQuery = default; + [Dependency] private EntityQuery _resistanceQuery = default; + + private readonly B2DynamicTree _sourceTree = new(); + private readonly Dictionary _sourceDataMap = new(); + private readonly List _activeReceivers = new(); private float _accumulator; - private List _sources = new(); public override void Initialize() { @@ -30,10 +36,100 @@ public sealed partial class RadiationSystem : EntitySystem SubscribeCvars(); InitRadBlocking(); - _blockerQuery = GetEntityQuery(); - _resistanceQuery = GetEntityQuery(); - _gridQuery = GetEntityQuery(); - _stackQuery = GetEntityQuery(); + SubscribeLocalEvent(OnSourceStartup); + SubscribeLocalEvent(OnSourceShutdown); + SubscribeLocalEvent(OnSourceMove); + SubscribeLocalEvent(OnSourceStackChanged); + + SubscribeLocalEvent(OnReceiverStartup); + SubscribeLocalEvent(OnReceiverShutdown); + } + + private void OnSourceStartup(Entity entity, ref ComponentStartup args) + { + UpdateSource(entity); + } + + private void OnSourceShutdown(EntityUid uid, RadiationSourceComponent component, ComponentShutdown args) + { + if (component.Proxy != DynamicTree.Proxy.Free) + { + _sourceTree.DestroyProxy(component.Proxy); + component.Proxy = DynamicTree.Proxy.Free; + } + _sourceDataMap.Remove(uid); + } + + private void OnSourceMove(Entity entity, ref MoveEvent args) + { + if (args.NewPosition.EntityId == args.OldPosition.EntityId && + args.NewPosition.Position.EqualsApprox(args.OldPosition.Position)) + return; + + UpdateSource(entity); + } + + private void OnSourceStackChanged(Entity entity, ref StackCountChangedEvent args) + { + UpdateSource(entity); + } + + private void OnReceiverStartup(EntityUid uid, RadiationReceiverComponent component, ComponentStartup args) + { + _activeReceivers.Add(uid); + } + + private void OnReceiverShutdown(EntityUid uid, RadiationReceiverComponent component, ComponentShutdown args) + { + _activeReceivers.Remove(uid); + } + + protected override void UpdateSource(Entity entity) + { + var (uid, component) = entity; + var xform = Transform(uid); + + if (!component.Enabled || Terminating(uid)) + { + if (component.Proxy != DynamicTree.Proxy.Free) + { + _sourceTree.DestroyProxy(component.Proxy); + component.Proxy = DynamicTree.Proxy.Free; + } + _sourceDataMap.Remove(uid); + return; + } + + var worldPos = _transform.GetWorldPosition(xform); + var intensity = component.Intensity * _stack.GetCount(uid); + intensity = GetAdjustedRadiationIntensity(uid, intensity); + + if (intensity <= 0) + { + if (component.Proxy != DynamicTree.Proxy.Free) + { + _sourceTree.DestroyProxy(component.Proxy); + component.Proxy = DynamicTree.Proxy.Free; + } + _sourceDataMap.Remove(uid); + return; + } + + // Avoid division by 0 + var maxRange = component.Slope >= float.Epsilon ? intensity / component.Slope : GridcastMaxDistance; + maxRange = Math.Min(maxRange, GridcastMaxDistance); + + _sourceDataMap[uid] = new SourceData(intensity, component.Slope, maxRange, (uid, component, xform), worldPos); + var aabb = Box2.CenteredAround(worldPos, new Vector2(maxRange * 2, maxRange * 2)); + + if (component.Proxy != DynamicTree.Proxy.Free) + { + _sourceTree.MoveProxy(component.Proxy, in aabb); + } + else + { + component.Proxy = _sourceTree.CreateProxy(in aabb, uint.MaxValue, uid); + } } public override void Update(float frameTime) @@ -59,8 +155,11 @@ public sealed partial class RadiationSystem : EntitySystem { if (!Resolve(entity, ref entity.Comp, false)) return; + if (entity.Comp.Enabled == val) + return; entity.Comp.Enabled = val; + UpdateSource((entity.Owner, entity.Comp)); } /// diff --git a/Content.Server/Revolutionary/Components/RevolutionaryConverterComponent.cs b/Content.Server/Revolutionary/Components/RevolutionaryConverterComponent.cs new file mode 100644 index 0000000000..61acf26d5b --- /dev/null +++ b/Content.Server/Revolutionary/Components/RevolutionaryConverterComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.Revolutionary.Components; + +/// +/// This is a marker component that indicates that a flash can be used to convert someone into a revolutionary. +/// Viva la revolution! +/// +[RegisterComponent] +public sealed partial class RevolutionaryConverterComponent : Component; diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 76b4131ca2..6307469b41 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -315,7 +315,7 @@ namespace Content.Server.RoundEnd Loc.GetString( "round-end-system-round-restart-eta-announcement", ("time", time), - ("units", Loc.GetString(unitsLocString)))); + ("units", Loc.GetString(unitsLocString, ("amount", time))))); Timer.Spawn(countdownTime.Value, AfterEndRoundRestart, _countdownTokenSource.Token); } diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index cc2f7a4e2f..b16c98f56b 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -67,6 +67,7 @@ public sealed class StationAiSystem : SharedStationAiSystem private readonly ProtoId _aiWireSnippedChatNotificationPrototype = "AiWireSnipped"; private readonly ProtoId _aiLosingPowerChatNotificationPrototype = "AiLosingPower"; private readonly ProtoId _aiCriticalPowerChatNotificationPrototype = "AiCriticalPower"; + private readonly ProtoId _aiTakingDamageChatNotificationPrototype = "AiTakingDamage"; private readonly ProtoId _stationAiJob = "StationAi"; private readonly EntProtoId _stationAiBrain = "StationAiBrain"; @@ -235,7 +236,7 @@ public sealed class StationAiSystem : SharedStationAiSystem private void OnDamageChanged(Entity entity, ref DamageChangedEvent args) { - UpdateCoreIntegrityAlert(entity); + UpdateCoreIntegrityAlert(entity, args.DamageIncreased); UpdateDamagedAccent(entity); } @@ -285,7 +286,7 @@ public sealed class StationAiSystem : SharedStationAiSystem } } - private void UpdateCoreIntegrityAlert(Entity ent) + private void UpdateCoreIntegrityAlert(Entity ent, bool damageIncreased = false) { if (!TryComp(ent, out var damageable)) return; @@ -303,6 +304,12 @@ public sealed class StationAiSystem : SharedStationAiSystem var damageLevel = Math.Round(damagePercent.Float() * proto.MaxSeverity); _alerts.ShowAlert(held.Value, _damageAlert, (short)Math.Clamp(damageLevel, 0, proto.MaxSeverity)); + + if (damageIncreased) + { + var ev = new ChatNotificationEvent(_aiTakingDamageChatNotificationPrototype, ent); + RaiseLocalEvent(held.Value, ref ev); + } } private void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent args) diff --git a/Content.Server/Sound/EmitSoundSystem.cs b/Content.Server/Sound/EmitSoundSystem.cs index 1720d67d02..38878b147f 100644 --- a/Content.Server/Sound/EmitSoundSystem.cs +++ b/Content.Server/Sound/EmitSoundSystem.cs @@ -1,14 +1,12 @@ using Content.Shared.Sound; using Content.Shared.Sound.Components; using Robust.Shared.Timing; -using Robust.Shared.Network; namespace Content.Server.Sound; public sealed class EmitSoundSystem : SharedEmitSoundSystem { [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly INetManager _net = default!; public override void Update(float frameTime) { @@ -49,9 +47,6 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem private void SpamEmitSoundReset(Entity entity) { - if (_net.IsClient) - return; - entity.Comp.NextSound = _timing.CurTime + ((entity.Comp.MinInterval < entity.Comp.MaxInterval) ? Random.Next(entity.Comp.MinInterval, entity.Comp.MaxInterval) : entity.Comp.MaxInterval); diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index a0d923dd1e..a6b75f953c 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -21,7 +21,9 @@ namespace Content.Server.Stack /// Spawns a new entity and moves an amount to it from the stack. /// Moves nothing if amount is greater than ent's stack count. /// - /// How much to move to the new entity. + /// Entity to split in a new stack. + /// How much to move to the new entity. + /// Where to spawn the new stack /// Null if StackComponent doesn't resolve, or amount to move is greater than ent has available. [PublicAPI] public EntityUid? Split(Entity ent, int amount, EntityCoordinates spawnPosition) diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index cb92a42c10..f4ecd87040 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -4,11 +4,14 @@ using Content.Shared.Interaction.Events; using Content.Shared.Store.Components; using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Containers; +using Robust.Shared.Timing; namespace Content.Server.Store.Systems; public sealed partial class StoreSystem { + [Dependency] private readonly IGameTiming _timing = default!; + private void InitializeRefund() { SubscribeLocalEvent(OnStoreTerminating); diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 6b67ff5d60..7a87f8b162 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -7,32 +7,25 @@ using Content.Shared.Actions; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; -using Content.Shared.Mind; using Content.Shared.Mindshield.Components; using Content.Shared.NPC.Systems; -using Content.Shared.PDA.Ringer; using Content.Shared.Store; using Content.Shared.Store.Components; using Content.Shared.UserInterface; -using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; namespace Content.Server.Store.Systems; public sealed partial class StoreSystem { [Dependency] private readonly IAdminLogManager _admin = default!; - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly ActionUpgradeSystem _actionUpgrade = default!; - [Dependency] private readonly SharedMindSystem _mind = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly StackSystem _stack = default!; - [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly StackSystem _stack = default!; private void InitializeUi() { @@ -41,6 +34,16 @@ public sealed partial class StoreSystem SubscribeLocalEvent(OnRequestWithdraw); SubscribeLocalEvent(OnRequestRefund); SubscribeLocalEvent(OnRefundEntityDeleted); + SubscribeLocalEvent((e, c, ev) => + RemoteStoreRelay((e, c), ev)); + SubscribeLocalEvent((e, c, ev) => + RemoteStoreRelay((e, c), ev)); + SubscribeLocalEvent((e, c, ev) => + RemoteStoreRelay((e, c), ev)); + SubscribeLocalEvent((e, c, ev) => + RemoteStoreRelay((e, c), ev)); + SubscribeLocalEvent((e, c, ev) => + RemoteStoreRelay((e, c), ev)); } private void OnRefundEntityDeleted(Entity ent, ref RefundEntityDeletedEvent args) @@ -48,73 +51,12 @@ public sealed partial class StoreSystem ent.Comp.BoughtEntities.Remove(args.Uid); } - /// - /// Toggles the store Ui open and closed - /// - /// the person doing the toggling - /// the store being toggled - /// - public void ToggleUi(EntityUid user, EntityUid storeEnt, StoreComponent? component = null) + private void RemoteStoreRelay(Entity entity, object ev) { - if (!Resolve(storeEnt, ref component)) + if (entity.Comp.Store == null || !TryComp(entity.Comp.Store, out var store)) return; - if (!TryComp(user, out var actor)) - return; - - if (!_ui.TryToggleUi(storeEnt, StoreUiKey.Key, actor.PlayerSession)) - return; - - UpdateUserInterface(user, storeEnt, component); - } - - /// - /// Closes the store UI for everyone, if it's open - /// - public void CloseUi(EntityUid uid, StoreComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - _ui.CloseUi(uid, StoreUiKey.Key); - } - - /// - /// Updates the user interface for a store and refreshes the listings - /// - /// The person who if opening the store ui. Listings are filtered based on this. - /// The store entity itself - /// The store component being refreshed. - public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) - { - if (!Resolve(store, ref component)) - return; - - //this is the person who will be passed into logic for all listing filtering. - if (user != null) //if we have no "buyer" for this update, then don't update the listings - { - component.LastAvailableListings = GetAvailableListings(component.AccountOwner ?? user.Value, store, component) - .ToHashSet(); - } - - //dictionary for all currencies, including 0 values for currencies on the whitelist - Dictionary, FixedPoint2> allCurrency = new(); - foreach (var supported in component.CurrencyWhitelist) - { - allCurrency.Add(supported, FixedPoint2.Zero); - - if (component.Balance.TryGetValue(supported, out var value)) - allCurrency[supported] = value; - } - - // TODO: if multiple users are supposed to be able to interact with a single BUI & see different - // stores/listings, this needs to use session specific BUI states. - - // only tell operatives to lock their uplink if it can be locked - var showFooter = HasComp(store); - - var state = new StoreUpdateState(component.LastAvailableListings, allCurrency, showFooter, component.RefundAllowed); - _ui.SetUiState(store, StoreUiKey.Key, state); + RaiseLocalEvent(entity.Comp.Store.Value, ev); } private void OnRequestUpdate(EntityUid uid, StoreComponent component, StoreRequestUpdateInterfaceMessage args) @@ -205,7 +147,7 @@ public sealed partial class StoreSystem EntityUid? actionId; // I guess we just allow duplicate actions? // Allow duplicate actions and just have a single list buy for the buy-once ones. - if (listing.ApplyToMob || !_mind.TryGetMind(buyer, out var mind, out _)) + if (listing.ApplyToMob || !Mind.TryGetMind(buyer, out var mind, out _)) actionId = _actions.AddAction(buyer, listing.ProductAction); else actionId = _actionContainer.AddAction(mind, listing.ProductAction); @@ -281,10 +223,11 @@ public sealed partial class StoreSystem _admin.Add(LogType.StorePurchase, logImpact, - $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _proto)}\" from {ToPrettyString(uid)}{logExtraInfo}."); + $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, Proto)}\" from {ToPrettyString(uid)}{logExtraInfo}."); listing.PurchaseAmount++; //track how many times something has been purchased - _audio.PlayEntity(component.BuySuccessSound, msg.Actor, uid); //cha-ching! + if (msg.SoundSource != null && GetEntity(msg.SoundSource) != null) + _audio.PlayEntity(component.BuySuccessSound, msg.Actor, GetEntity(msg.SoundSource.Value)); //cha-ching! var buyFinished = new StoreBuyFinishedEvent { @@ -313,7 +256,7 @@ public sealed partial class StoreSystem return; //make sure a malicious client didn't send us random shit - if (!_proto.TryIndex(msg.Currency, out var proto)) + if (!Proto.TryIndex(msg.Currency, out var proto)) return; //we need an actually valid entity to spawn. This check has been done earlier, but just in case. diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index 7c5c99b5b4..b8e92f10bf 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -1,42 +1,25 @@ -using System.Linq; -using Content.Server.Store.Components; -using Content.Shared.FixedPoint; using Content.Shared.Implants.Components; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using Content.Shared.Stacks; +using Content.Shared.Store; using Content.Shared.Store.Components; -using Content.Shared.Store.Events; using Content.Shared.UserInterface; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; -/// -/// Manages general interactions with a store and different entities, -/// getting listings for stores, and interfacing with the store UI. -/// -public sealed partial class StoreSystem : EntitySystem +public sealed partial class StoreSystem : SharedStoreSystem { - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly IGameTiming _timing = default!; - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStoreOpenAttempt); - SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(BeforeActivatableUiOpen); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnImplantActivate); - SubscribeLocalEvent(OnIntrinsicStoreAction); + + SubscribeLocalEvent(OnImplantActivate); InitializeUi(); InitializeCommand(); @@ -47,6 +30,10 @@ public sealed partial class StoreSystem : EntitySystem { RefreshAllListings(component); component.StartingMap = Transform(uid).MapUid; + + // Add the bui key if it does not exist already (the check is needed to make sure that we don't overwrite existing InterfaceData). + if (!UI.HasUi(uid, StoreUiKey.Key)) + UI.SetUi(uid, StoreUiKey.Key, new InterfaceData("StoreBoundUserInterface")); } private void OnStartup(EntityUid uid, StoreComponent component, ComponentStartup args) @@ -72,7 +59,7 @@ public sealed partial class StoreSystem : EntitySystem if (!component.OwnerOnly) return; - if (!_mind.TryGetMind(args.User, out var mind, out _)) + if (!Mind.TryGetMind(args.User, out var mind, out _)) return; component.AccountOwner ??= mind; @@ -82,133 +69,16 @@ public sealed partial class StoreSystem : EntitySystem return; if (!args.Silent) - _popup.PopupEntity(Loc.GetString("store-not-account-owner", ("store", uid)), uid, args.User); + Popup.PopupEntity(Loc.GetString("store-not-account-owner", ("store", uid)), uid, args.User); args.Cancel(); } - private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args) + private void OnImplantActivate(Entity entity, ref OpenUplinkImplantEvent args) { - if (args.Handled || !args.CanReach) + if (GetRemoteStore(entity.AsNullable()) is not { } store) return; - if (!TryComp(args.Target, out var store)) - return; - - var ev = new CurrencyInsertAttemptEvent(args.User, args.Target.Value, args.Used, store); - RaiseLocalEvent(args.Target.Value, ev); - if (ev.Cancelled) - return; - - if (!TryAddCurrency((uid, component), (args.Target.Value, store))) - return; - - args.Handled = true; - var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target)); - _popup.PopupEntity(msg, args.Target.Value, args.User); - } - - private void OnImplantActivate(EntityUid uid, StoreComponent component, OpenUplinkImplantEvent args) - { - ToggleUi(args.Performer, uid, component); - } - - /// - /// Gets the value from an entity's currency component. - /// Scales with stacks. - /// - /// - /// If this result is intended to be used with , - /// consider using instead to ensure that the currency is consumed in the process. - /// - /// - /// - /// The value of the currency - public Dictionary GetCurrencyValue(EntityUid uid, CurrencyComponent component) - { - var amount = EntityManager.GetComponentOrNull(uid)?.Count ?? 1; - return component.Price.ToDictionary(v => v.Key, p => p.Value * amount); - } - - /// - /// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process. - /// - public bool TryAddCurrency(Entity currency, Entity store) - { - if (!Resolve(currency.Owner, ref currency.Comp)) - return false; - - if (!Resolve(store.Owner, ref store.Comp)) - return false; - - var value = currency.Comp.Price; - if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1) - { - value = currency.Comp.Price - .ToDictionary(v => v.Key, p => p.Value * stack.Count); - } - - if (!TryAddCurrency(value, store, store.Comp)) - return false; - - // Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the - // same tick - currency.Comp.Price.Clear(); - if (stack != null) - _stack.SetCount((currency.Owner, stack), 0); - - QueueDel(currency); - return true; - } - - /// - /// Tries to add a currency to a store's balance - /// - /// The value to add to the store - /// - /// The store to add it to - /// Whether or not the currency was succesfully added - public bool TryAddCurrency(Dictionary currency, EntityUid uid, StoreComponent? store = null) - { - if (!Resolve(uid, ref store)) - return false; - - //verify these before values are modified - foreach (var type in currency) - { - if (!store.CurrencyWhitelist.Contains(type.Key)) - return false; - } - - foreach (var type in currency) - { - if (!store.Balance.TryAdd(type.Key, type.Value)) - store.Balance[type.Key] += type.Value; - } - - UpdateUserInterface(null, uid, store); - return true; - } - - private void OnIntrinsicStoreAction(Entity ent, ref IntrinsicStoreActionEvent args) - { - ToggleUi(args.Performer, ent.Owner, ent.Comp); - } - -} - -public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs -{ - public readonly EntityUid User; - public readonly EntityUid Target; - public readonly EntityUid Used; - public readonly StoreComponent Store; - - public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid used, StoreComponent store) - { - User = user; - Target = target; - Used = used; - Store = store; + ToggleUi(args.Performer, store, store.Comp, entity, entity.Comp); } } diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index e8c53de9eb..723ddbf0ff 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -30,7 +30,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem // This function ensures that chat popups appear on camera views that have connected microphones. foreach (var (_, __, camera, xform) in EntityQuery()) { - if (camera.ActiveViewers.Count == 0) + if (camera.ActivePvsViewers.Count == 0) continue; // get range to camera. This way wispers will still appear as obfuscated if they are too far from the camera's microphone @@ -41,7 +41,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem if (range < 0 || range > ev.VoiceRange) continue; - foreach (var viewer in camera.ActiveViewers) + foreach (var viewer in camera.ActivePvsViewers) { // if the player has not already received the chat message, send it to them but don't log it to the chat // window. This is simply so that it appears in camera. diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.Collide.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.Collide.cs new file mode 100644 index 0000000000..4015a65cc9 --- /dev/null +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.Collide.cs @@ -0,0 +1,101 @@ +using Content.Shared.Power.EntitySystems; +using Content.Shared.SurveillanceCamera; +using Content.Shared.SurveillanceCamera.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; + +namespace Content.Server.SurveillanceCamera; + +public partial class SurveillanceCameraSystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedPowerReceiverSystem _power = default!; + [Dependency] private readonly EntityQuery _cameraQuery = default!; + + public void InitializeCollide() + { + SubscribeLocalEvent(OnPreventCollide); + SubscribeLocalEvent(OnStart); + SubscribeLocalEvent(OnEnd); + + SubscribeLocalEvent(OnCollideShutdown); + SubscribeLocalEvent(OnOverrideState); + } + + private void OnCollideShutdown(Entity ent, ref ComponentShutdown args) + { + // TODO: Check this on the event. + if (TerminatingOrDeleted(ent.Owner)) + return; + + // Regenerate contacts for everything we were colliding with. + var contacts = _physics.GetContacts(ent.Owner); + + while (contacts.MoveNext(out var contact)) + { + if (!contact.IsTouching) + continue; + + var other = contact.OtherEnt(ent.Owner); + + if (_cameraQuery.HasComp(other)) + { + _physics.RegenerateContacts(other); + } + } + } + + // You may be wondering what de fok this is doing here. + // At the moment there's no easy way to do collision whitelists based on components. + private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) + { + if (!_cameraQuery.HasComp(args.OtherEntity)) + { + args.Cancelled = true; + } + } + + private void OnEnd(Entity ent, ref EndCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!_cameraQuery.TryComp(args.OtherEntity, out var cameraCollider)) + return; + + // TODO: Engine bug IsTouching box2d yay. + var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1; + + if (contacts > 0) + return; + + cameraCollider.Enabled = false; + Dirty(args.OtherEntity, cameraCollider); + UpdateVisuals(args.OtherEntity); + } + + private void OnStart(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!_cameraQuery.TryComp(args.OtherEntity, out var cameraCollider)) + return; + + cameraCollider.Enabled = true; + Dirty(args.OtherEntity, cameraCollider); + UpdateVisuals(args.OtherEntity); + } + + private void OnOverrideState(Entity ent, ref SurveillanceCameraGetIsViewedExternallyEvent args) + { + if (ent.Comp.RequiresPower && !_power.IsPowered(ent.Owner)) + return; + + if (!ent.Comp.Enabled) + return; + + args.Viewed = true; + } +} + diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs index cdd032c79c..09c2b39447 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs @@ -13,15 +13,15 @@ using Content.Shared.DeviceNetwork.Components; namespace Content.Server.SurveillanceCamera; -public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem +public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly SurveillanceCameraMapSystem _cameraMapSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; // Pings a surveillance camera subnet. All cameras will always respond // with a data message if they are on the same subnet. @@ -61,6 +61,8 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem SubscribeLocalEvent(OnPacketReceived); SubscribeLocalEvent(OnSetName); SubscribeLocalEvent(OnSetNetwork); + + InitializeCollide(); } private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args) @@ -232,7 +234,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem var ev = new SurveillanceCameraDeactivateEvent(camera); - RemoveActiveViewers(camera, new(component.ActiveViewers), null, component); + RemoveActiveViewers(camera, new(component.ActivePvsViewers), null, component); component.Active = false; // Send a targetted event to all monitors. @@ -249,6 +251,25 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem UpdateVisuals(camera, component); } + /// + /// Checks whether the camera is being viewed through by anyone at all. + /// + /// The camera to check + /// True if the camera is looked through, otherwise False. + public bool IsGettingViewed(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + if (ent.Comp.ActivePvsViewers.Count > 0 || ent.Comp.ActiveMonitors.Count > 0) + return true; + + var ev = new SurveillanceCameraGetIsViewedExternallyEvent(); + RaiseLocalEvent(ent, ref ev); + + return ev.Viewed; + } + public override void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) { if (!Resolve(camera, ref component)) @@ -284,7 +305,8 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem } _viewSubscriberSystem.AddViewSubscriber(camera, actor.PlayerSession); - component.ActiveViewers.Add(player); + + component.ActivePvsViewers.Add(player); if (monitor != null) { @@ -346,7 +368,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem if (Resolve(player, ref actor)) _viewSubscriberSystem.RemoveViewSubscriber(camera, actor.PlayerSession); - component.ActiveViewers.Remove(player); + component.ActivePvsViewers.Remove(player); if (monitor != null) { @@ -391,7 +413,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem key = SurveillanceCameraVisuals.Active; } - if (component.ActiveViewers.Count > 0 || component.ActiveMonitors.Count > 0) + if (IsGettingViewed((uid, component))) { key = SurveillanceCameraVisuals.InUse; } diff --git a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs index 8350c01f7a..bf00f5ebd8 100644 --- a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs +++ b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs @@ -73,7 +73,13 @@ public sealed class AddUplinkCommand : LocalizedEntityCommands } // Finally add uplink - if (!_uplinkSystem.AddUplink(user, 20, uplinkEntity: uplinkEntity, giveDiscounts: isDiscounted)) + var result = _uplinkSystem.AddUplink(user, 20, out var code, uplinkEntity: uplinkEntity, giveDiscounts: isDiscounted); + + if (code != null && result == AddUplinkResult.Pda) + shell.WriteLine(Loc.GetString("add-uplink-command-success-pda", ("code", string.Join("-", code).Replace("sharp", "#")))); + else if (result == AddUplinkResult.Implant) + shell.WriteLine(Loc.GetString("add-uplink-command-success-implant")); + else if (result == AddUplinkResult.Failure) shell.WriteLine(Loc.GetString("add-uplink-command-error-2")); } diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index e8ed868dfb..1daee849dd 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.PDA.Ringer; using Content.Server.Store.Systems; using Content.Server.StoreDiscount.Systems; using Content.Shared.FixedPoint; @@ -9,6 +10,7 @@ using Content.Shared.Mind; using Content.Shared.PDA; using Content.Shared.Store; using Content.Shared.Store.Components; +using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Uplink; @@ -21,38 +23,110 @@ public sealed class UplinkSystem : EntitySystem [Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!; [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly RingerSystem _ringer = default!; + public static readonly EntProtoId TraitorUplinkStore = "StorePresetRemoteUplink"; public static readonly ProtoId TelecrystalCurrencyPrototype = "Telecrystal"; private static readonly EntProtoId FallbackUplinkImplant = "UplinkImplant"; private static readonly ProtoId FallbackUplinkCatalog = "UplinkUplinkImplanter"; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRemoteStoreImplanted); + } + + private void OnRemoteStoreImplanted(Entity entity, ref ImplantImplantedEvent args) + { + if (_mind.GetMind(args.Implanted) is not { } mind ) + return; + + var storeEnumerator = EntityQueryEnumerator(); + while (storeEnumerator.MoveNext(out var uid, out _, out var store)) + { + if (store.AccountOwner != mind) + continue; + + entity.Comp.Store = uid; + return; + } + + // If we didn't have an uplink, make an empty one. + entity.Comp.Store = Spawn(TraitorUplinkStore, MapCoordinates.Nullspace); + SetUplink(args.Implanted, entity.Comp.Store.Value, 0, false); + Log.Error($"{ToPrettyString(args.Implanted)} did not have an uplink when they were implanted."); + } + /// /// Adds an uplink to the target /// /// The person who is getting the uplink /// The amount of currency on the uplink. If null, will just use the amount specified in the preset. + /// The code which was generated, if any. /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. /// Marker that enables discounts for uplink items. - /// Whether or not the uplink was added successfully - public bool AddUplink( + /// Binds the uplink to the specific uplink entity. + /// Whether the uplink was added successfully to a PDA, implant or not at all. + public AddUplinkResult AddUplink( EntityUid user, FixedPoint2 balance, + out Note[]? code, EntityUid? uplinkEntity = null, - bool giveDiscounts = false) + bool giveDiscounts = false, + bool bindToPda = false) { - // Try to find target item if none passed + code = null; + var storeEntity = Spawn(TraitorUplinkStore, MapCoordinates.Nullspace); + if (TryAddEntityUplink(user, balance, out var generatedCode, uplinkEntity, storeEntity, giveDiscounts, bindToPda)) + { + code = generatedCode; + return AddUplinkResult.Pda; + } + + if (TryImplantUplink(user, storeEntity, balance, giveDiscounts)) + { + return AddUplinkResult.Implant; + } + + Del(storeEntity); + return AddUplinkResult.Failure; + } + + public bool TryAddEntityUplink( + EntityUid user, + FixedPoint2 balance, + out Note[]? code, + EntityUid? uplinkEntity, + EntityUid storeEntity, + bool giveDiscounts = false, + bool bindToPda = false) + { + code = null; uplinkEntity ??= FindUplinkTarget(user); if (uplinkEntity == null) - return ImplantUplink(user, balance, giveDiscounts); + return false; - EnsureComp(uplinkEntity.Value); + var ev = new GenerateUplinkCodeEvent(); + RaiseLocalEvent(storeEntity, ref ev); - SetUplink(user, uplinkEntity.Value, balance, giveDiscounts); + if (ev.Code == null) + { + QueueDel(storeEntity); + return false; + } - // TODO add BUI. Currently can't be done outside of yaml -_- - // ^ What does this even mean? + code = ev.Code; + + if (bindToPda) + { + var accessComp = EnsureComp(storeEntity); + _ringer.SetBoundUplinkEntity((storeEntity, accessComp), uplinkEntity.Value); + } + + SetUplink(user, storeEntity, balance, giveDiscounts); return true; } @@ -60,25 +134,25 @@ public sealed class UplinkSystem : EntitySystem /// /// Configure TC for the uplink /// - private void SetUplink(EntityUid user, EntityUid uplink, FixedPoint2 balance, bool giveDiscounts) + private void SetUplink(EntityUid user, EntityUid store, FixedPoint2 balance, bool giveDiscounts) { if (!_mind.TryGetMind(user, out var mind, out _)) return; - var store = EnsureComp(uplink); + var storeComp = EnsureComp(store); - store.AccountOwner = mind; + storeComp.AccountOwner = mind; - store.Balance.Clear(); + storeComp.Balance.Clear(); _store.TryAddCurrency(new Dictionary { { TelecrystalCurrencyPrototype, balance } }, - uplink, - store); + store, + storeComp); var uplinkInitializedEvent = new StoreInitializedEvent( TargetUser: mind, - Store: uplink, + Store: store, UseDiscounts: giveDiscounts, - Listings: _store.GetAvailableListings(mind, uplink, store) + Listings: _store.GetAvailableListings(mind, store, storeComp) .ToArray()); RaiseLocalEvent(ref uplinkInitializedEvent); } @@ -86,9 +160,9 @@ public sealed class UplinkSystem : EntitySystem /// /// Implant an uplink as a fallback measure if the traitor had no PDA /// - private bool ImplantUplink(EntityUid user, FixedPoint2 balance, bool giveDiscounts) + public bool TryImplantUplink(EntityUid user, EntityUid storeEntity, FixedPoint2 balance, bool giveDiscounts) { - if (!_proto.Resolve(FallbackUplinkCatalog, out var catalog)) + if (!_proto.Resolve(FallbackUplinkCatalog, out var catalog)) return false; if (!catalog.Cost.TryGetValue(TelecrystalCurrencyPrototype, out var cost)) @@ -99,15 +173,15 @@ public sealed class UplinkSystem : EntitySystem else balance = balance - cost; + SetUplink(user, storeEntity, balance, giveDiscounts); var implant = _subdermalImplant.AddImplant(user, FallbackUplinkImplant); - if (!HasComp(implant)) + if (!HasComp(implant)) { Log.Error($"Implant does not have the store component {implant}"); return false; } - SetUplink(user, implant.Value, balance, giveDiscounts); return true; } @@ -124,18 +198,25 @@ public sealed class UplinkSystem : EntitySystem { var pdaUid = containerSlot.ContainedEntity; - if (HasComp(pdaUid) && HasComp(pdaUid)) - return pdaUid; + if (HasComp(pdaUid) && HasComp(pdaUid)) + return pdaUid.Value; } } // Also check hands foreach (var item in _handsSystem.EnumerateHeld(user)) { - if (HasComp(item) && HasComp(item)) + if (HasComp(item) && HasComp(item)) return item; } return null; } } + +public enum AddUplinkResult +{ + Pda, + Implant, + Failure, +} diff --git a/Content.Server/Wagging/WaggingSystem.cs b/Content.Server/Wagging/WaggingSystem.cs index 6ece6f0d95..d369ea8689 100644 --- a/Content.Server/Wagging/WaggingSystem.cs +++ b/Content.Server/Wagging/WaggingSystem.cs @@ -35,7 +35,13 @@ public sealed class WaggingSystem : EntitySystem if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) return; - EnsureComp(args.CloneUid); + // Make sure to set the datafields before adding the component so that the correct action gets spawned on map init. + var cloneComp = Factory.GetComponent(); + cloneComp.Action = ent.Comp.Action; + cloneComp.Layer = ent.Comp.Layer; + cloneComp.Organ = ent.Comp.Organ; + cloneComp.Suffix = ent.Comp.Suffix; + AddComp(args.CloneUid, cloneComp, true); } private void OnWaggingMapInit(Entity ent, ref MapInitEvent args) diff --git a/Content.Server/Worldgen/Components/BiomeSelectionComponent.cs b/Content.Server/Worldgen/Components/BiomeSelectionComponent.cs deleted file mode 100644 index 08571cd588..0000000000 --- a/Content.Server/Worldgen/Components/BiomeSelectionComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.Worldgen.Prototypes; -using Content.Server.Worldgen.Systems.Biomes; -using Robust.Shared.Prototypes; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for selecting the biome(s) to be used during world generation. -/// -[RegisterComponent] -[Access(typeof(BiomeSelectionSystem))] -public sealed partial class BiomeSelectionComponent : Component -{ - /// - /// The list of biomes available to this selector. - /// - /// This is always sorted by priority after ComponentStartup. - [DataField(required: true)] - public List> Biomes = new(); -} - diff --git a/Content.Server/Worldgen/Components/Carvers/NoiseRangeCarverComponent.cs b/Content.Server/Worldgen/Components/Carvers/NoiseRangeCarverComponent.cs deleted file mode 100644 index 28724d20a4..0000000000 --- a/Content.Server/Worldgen/Components/Carvers/NoiseRangeCarverComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Numerics; -using Content.Server.Worldgen.Prototypes; -using Content.Server.Worldgen.Systems.Carvers; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Worldgen.Components.Carvers; - -/// -/// This is used for carving out empty space in the game world, providing byways through the debris field. -/// -[RegisterComponent] -[Access(typeof(NoiseRangeCarverSystem))] -public sealed partial class NoiseRangeCarverComponent : Component -{ - /// - /// The noise channel to use as a density controller. - /// - /// This noise channel should be mapped to exactly the range [0, 1] unless you want a lot of warnings in the log. - [DataField("noiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string NoiseChannel { get; private set; } = default!; - - /// - /// The index of ranges in which to cut debris generation. - /// - [DataField("ranges", required: true)] - public List Ranges { get; private set; } = default!; -} - diff --git a/Content.Server/Worldgen/Components/Debris/BlobFloorPlanBuilderComponent.cs b/Content.Server/Worldgen/Components/Debris/BlobFloorPlanBuilderComponent.cs deleted file mode 100644 index a1317ae2ed..0000000000 --- a/Content.Server/Worldgen/Components/Debris/BlobFloorPlanBuilderComponent.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Server.Worldgen.Systems.Debris; -using Content.Shared.Maps; -using Robust.Shared.Prototypes; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for constructing asteroid debris. -/// -[RegisterComponent] -[Access(typeof(BlobFloorPlanBuilderSystem))] -public sealed partial class BlobFloorPlanBuilderComponent : Component -{ - /// - /// The probability that placing a floor tile will add up to three-four neighboring tiles as well. - /// - [DataField("blobDrawProb")] public float BlobDrawProb; - - /// - /// The maximum radius for the structure. - /// - [DataField("radius", required: true)] public float Radius; - - /// - /// The tiles to be used for the floor plan. - /// - [DataField(required: true)] - public List> FloorTileset { get; private set; } = default!; - - /// - /// The number of floor tiles to place when drawing the asteroid layout. - /// - [DataField("floorPlacements", required: true)] - public int FloorPlacements { get; private set; } -} - diff --git a/Content.Server/Worldgen/Components/Debris/DebrisFeaturePlacerControllerComponent.cs b/Content.Server/Worldgen/Components/Debris/DebrisFeaturePlacerControllerComponent.cs deleted file mode 100644 index ae61f0581e..0000000000 --- a/Content.Server/Worldgen/Components/Debris/DebrisFeaturePlacerControllerComponent.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Numerics; -using Content.Server.Worldgen.Prototypes; -using Content.Server.Worldgen.Systems.Debris; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for controlling the debris feature placer. -/// -[RegisterComponent] -[Access(typeof(DebrisFeaturePlacerSystem))] -public sealed partial class DebrisFeaturePlacerControllerComponent : Component -{ - /// - /// Whether or not to clip debris that would spawn at a location that has a density of zero. - /// - [DataField("densityClip")] public bool DensityClip = true; - - /// - /// Whether or not entities are already spawned. - /// - public bool DoSpawns = true; - - [DataField("ownedDebris")] public Dictionary OwnedDebris = new(); - - /// - /// The chance spawning a piece of debris will just be cancelled randomly. - /// - [DataField("randomCancelChance")] public float RandomCancellationChance = 0.1f; - - /// - /// Radius in which there should be no objects for debris to spawn. - /// - [DataField("safetyZoneRadius")] public float SafetyZoneRadius = 16.0f; - - /// - /// The noise channel to use as a density controller. - /// - [DataField("densityNoiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DensityNoiseChannel { get; private set; } = default!; -} - diff --git a/Content.Server/Worldgen/Components/Debris/NoiseDrivenDebrisSelectorComponent.cs b/Content.Server/Worldgen/Components/Debris/NoiseDrivenDebrisSelectorComponent.cs deleted file mode 100644 index af4ef7f1cf..0000000000 --- a/Content.Server/Worldgen/Components/Debris/NoiseDrivenDebrisSelectorComponent.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.Worldgen.Prototypes; -using Content.Server.Worldgen.Systems.Debris; -using Content.Server.Worldgen.Tools; -using Content.Shared.Storage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for selecting debris with a probability determined by a noise channel. -/// Takes priority over SimpleDebrisSelectorComponent and should likely be used in combination. -/// -[RegisterComponent] -[Access(typeof(NoiseDrivenDebrisSelectorSystem))] -public sealed partial class NoiseDrivenDebrisSelectorComponent : Component -{ - private EntitySpawnCollectionCache? _cache; - - /// - /// The prototype-facing debris table entries. - /// - [DataField("debrisTable", required: true)] - private List _entries = default!; - - /// - /// The debris entity spawn collection. - /// - public EntitySpawnCollectionCache CachedDebrisTable - { - get - { - _cache ??= new EntitySpawnCollectionCache(_entries); - return _cache; - } - } - - /// - /// The noise channel to use as a density controller. - /// - /// This noise channel should be mapped to exactly the range [0, 1] unless you want a lot of warnings in the log. - [DataField("noiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string NoiseChannel { get; private set; } = default!; -} - diff --git a/Content.Server/Worldgen/Components/Debris/OwnedDebrisComponent.cs b/Content.Server/Worldgen/Components/Debris/OwnedDebrisComponent.cs deleted file mode 100644 index b211277997..0000000000 --- a/Content.Server/Worldgen/Components/Debris/OwnedDebrisComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Numerics; -using Content.Server.Worldgen.Systems.Debris; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for attaching a piece of debris to it's owning controller. -/// Mostly just syncs deletion. -/// -[RegisterComponent] -[Access(typeof(DebrisFeaturePlacerSystem))] -public sealed partial class OwnedDebrisComponent : Component -{ - /// - /// The last location in the controller's internal structure for this debris. - /// - [DataField("lastKey")] public Vector2 LastKey; - - /// - /// The DebrisFeaturePlacerController-having entity that owns this. - /// - [DataField("owningController")] public EntityUid OwningController; -} - diff --git a/Content.Server/Worldgen/Components/Debris/SimpleDebrisSelectorComponent.cs b/Content.Server/Worldgen/Components/Debris/SimpleDebrisSelectorComponent.cs deleted file mode 100644 index 5db9bad925..0000000000 --- a/Content.Server/Worldgen/Components/Debris/SimpleDebrisSelectorComponent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Server.Worldgen.Systems.Debris; -using Content.Server.Worldgen.Tools; -using Content.Shared.Storage; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for a very simple debris selection for simple biomes. Just uses a spawn table. -/// -[RegisterComponent] -[Access(typeof(DebrisFeaturePlacerSystem))] -public sealed partial class SimpleDebrisSelectorComponent : Component -{ - private EntitySpawnCollectionCache? _cache; - - /// - /// The prototype-facing debris table entries. - /// - [DataField("debrisTable", required: true)] - private List _entries = default!; - - /// - /// The debris entity spawn collection. - /// - public EntitySpawnCollectionCache CachedDebrisTable - { - get - { - _cache ??= new EntitySpawnCollectionCache(_entries); - return _cache; - } - } -} - diff --git a/Content.Server/Worldgen/Components/Debris/SimpleFloorPlanPopulatorComponent.cs b/Content.Server/Worldgen/Components/Debris/SimpleFloorPlanPopulatorComponent.cs deleted file mode 100644 index 4865773bf3..0000000000 --- a/Content.Server/Worldgen/Components/Debris/SimpleFloorPlanPopulatorComponent.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Linq; -using Content.Server.Worldgen.Systems.Debris; -using Content.Server.Worldgen.Tools; -using Content.Shared.Maps; -using Content.Shared.Storage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; - -namespace Content.Server.Worldgen.Components.Debris; - -/// -/// This is used for populating a grid with random entities automatically. -/// -[RegisterComponent] -[Access(typeof(SimpleFloorPlanPopulatorSystem))] -public sealed partial class SimpleFloorPlanPopulatorComponent : Component -{ - private Dictionary? _caches; - - /// - /// The prototype facing floor plan populator entries. - /// - [DataField("entries", required: true, - customTypeSerializer: typeof(PrototypeIdDictionarySerializer, ContentTileDefinition>))] - private Dictionary> _entries = default!; - - /// - /// The spawn collections used to place entities on different tile types. - /// - [ViewVariables] - public Dictionary Caches - { - get - { - if (_caches is null) - { - _caches = _entries - .Select(x => - new KeyValuePair(x.Key, - new EntitySpawnCollectionCache(x.Value))) - .ToDictionary(x => x.Key, x => x.Value); - } - - return _caches; - } - } -} - diff --git a/Content.Server/Worldgen/Components/LoadedChunkComponent.cs b/Content.Server/Worldgen/Components/LoadedChunkComponent.cs deleted file mode 100644 index d2743ad4ab..0000000000 --- a/Content.Server/Worldgen/Components/LoadedChunkComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Server.Worldgen.Systems; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for marking a chunk as loaded. -/// -[RegisterComponent] -[Access(typeof(WorldControllerSystem))] -public sealed partial class LoadedChunkComponent : Component -{ - /// - /// The current list of entities loading this chunk. - /// - [ViewVariables] public List? Loaders = null; -} - diff --git a/Content.Server/Worldgen/Components/LocalityLoaderComponent.cs b/Content.Server/Worldgen/Components/LocalityLoaderComponent.cs deleted file mode 100644 index 1d37ab34c9..0000000000 --- a/Content.Server/Worldgen/Components/LocalityLoaderComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Server.Worldgen.Systems; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for sending a signal to the entity it's on to load contents whenever a loader gets close enough. -/// Does not support unloading. -/// -[RegisterComponent] -[Access(typeof(LocalityLoaderSystem))] -public sealed partial class LocalityLoaderComponent : Component -{ - /// - /// The maximum distance an entity can be from the loader for it to not load. - /// Once a loader is closer than this, the event is fired and this component removed. - /// - [DataField("loadingDistance")] public int LoadingDistance = 32; -} - diff --git a/Content.Server/Worldgen/Components/NoiseIndexComponent.cs b/Content.Server/Worldgen/Components/NoiseIndexComponent.cs deleted file mode 100644 index 06d84d2f85..0000000000 --- a/Content.Server/Worldgen/Components/NoiseIndexComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Server.Worldgen.Prototypes; -using Content.Server.Worldgen.Systems; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for containing configured noise generators. -/// -[RegisterComponent] -[Access(typeof(NoiseIndexSystem))] -public sealed partial class NoiseIndexComponent : Component -{ - /// - /// An index of generators, to avoid having to recreate them every time a noise channel is used. - /// Keyed by noise generator prototype ID. - /// - [Access(typeof(NoiseIndexSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.None)] - public Dictionary Generators { get; } = new(); -} - diff --git a/Content.Server/Worldgen/Components/WorldChunkComponent.cs b/Content.Server/Worldgen/Components/WorldChunkComponent.cs deleted file mode 100644 index 3a91c00756..0000000000 --- a/Content.Server/Worldgen/Components/WorldChunkComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.Worldgen.Systems; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for marking an entity as being a world chunk. -/// -[RegisterComponent] -[Access(typeof(WorldControllerSystem))] -public sealed partial class WorldChunkComponent : Component -{ - /// - /// The coordinates of the chunk, in chunk space. - /// - [DataField("coordinates")] public Vector2i Coordinates; - - /// - /// The map this chunk belongs to. - /// - [DataField("map")] public EntityUid Map; -} - diff --git a/Content.Server/Worldgen/Components/WorldControllerComponent.cs b/Content.Server/Worldgen/Components/WorldControllerComponent.cs deleted file mode 100644 index 63580e7541..0000000000 --- a/Content.Server/Worldgen/Components/WorldControllerComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Server.Worldgen.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for controlling overall world loading, containing an index of all chunks in the map. -/// -[RegisterComponent] -[Access(typeof(WorldControllerSystem))] -public sealed partial class WorldControllerComponent : Component -{ - /// - /// The prototype to use for chunks on this world map. - /// - [DataField("chunkProto", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ChunkProto = "WorldChunk"; - - /// - /// An index of chunks owned by the controller. - /// - [DataField("chunks")] public Dictionary Chunks = new(); -} - diff --git a/Content.Server/Worldgen/Components/WorldLoaderComponent.cs b/Content.Server/Worldgen/Components/WorldLoaderComponent.cs deleted file mode 100644 index e6bb7781e9..0000000000 --- a/Content.Server/Worldgen/Components/WorldLoaderComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.Worldgen.Systems; - -namespace Content.Server.Worldgen.Components; - -/// -/// This is used for allowing some objects to load the game world. -/// -[RegisterComponent] -[Access(typeof(WorldControllerSystem))] -public sealed partial class WorldLoaderComponent : Component -{ - /// - /// The radius in which the loader loads the world. - /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("radius")] - public int Radius = 128; -} - diff --git a/Content.Server/Worldgen/GridPointsNearEnumerator.cs b/Content.Server/Worldgen/GridPointsNearEnumerator.cs deleted file mode 100644 index 24b710626b..0000000000 --- a/Content.Server/Worldgen/GridPointsNearEnumerator.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; - -namespace Content.Server.Worldgen; - -/// -/// A struct enumerator of points on a grid within the given radius. -/// -public struct GridPointsNearEnumerator -{ - private readonly int _radius; - private readonly Vector2i _center; - private int _x; - private int _y; - - /// - /// Initializes a new enumerator with the given center and radius. - /// - public GridPointsNearEnumerator(Vector2i center, int radius) - { - _radius = radius; - _center = center; - _x = -_radius; - _y = -_radius; - } - - /// - /// Gets the next point in the enumeration. - /// - /// The computed point, if any - /// Success - [Pure] - public bool MoveNext([NotNullWhen(true)] out Vector2i? chunk) - { - while (!(_x * _x + _y * _y <= _radius * _radius)) - { - if (_y > _radius) - { - chunk = null; - return false; - } - - if (_x > _radius) - { - _x = -_radius; - _y++; - } - else - { - _x++; - } - } - - chunk = _center + new Vector2i(_x, _y); - _x++; - return true; - } -} - diff --git a/Content.Server/Worldgen/Prototypes/BiomePrototype.cs b/Content.Server/Worldgen/Prototypes/BiomePrototype.cs deleted file mode 100644 index 75e4670e88..0000000000 --- a/Content.Server/Worldgen/Prototypes/BiomePrototype.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Numerics; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; - -namespace Content.Server.Worldgen.Prototypes; - -/// -/// This is a prototype for biome selection, allowing the component list of a chunk to be amended based on the output -/// of noise channels at that location. -/// -[Prototype("spaceBiome")] -public sealed partial class BiomePrototype : IPrototype, IInheritingPrototype -{ - /// - [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] - public string[]? Parents { get; private set; } - - /// - [NeverPushInheritance] - [AbstractDataField] - public bool Abstract { get; private set; } - - /// - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// The valid ranges of noise values under which this biome can be picked. - /// - [DataField("noiseRanges", required: true)] - public Dictionary> NoiseRanges = default!; - - /// - /// Higher priority biomes get picked before lower priority ones. - /// - [DataField("priority", required: true)] - public int Priority { get; private set; } - - /// - /// The components that get added to the target map. - /// - [DataField("chunkComponents")] - [AlwaysPushInheritance] - public ComponentRegistry ChunkComponents = new(); - - //TODO: Get someone to make this a method on componentregistry that does it Correctly. - /// - /// Applies the worldgen config to the given target (presumably a map.) - /// - public void Apply(EntityUid target, ISerializationManager serialization, IEntityManager entityManager) - { - // Add all components required by the prototype. Engine update for this whenst. - foreach (var data in ChunkComponents.Values) - { - var comp = (Component) serialization.CreateCopy(data.Component, notNullableOverride: true); - entityManager.AddComponent(target, comp); - } - } -} - diff --git a/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs b/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs deleted file mode 100644 index 41d89cbaa7..0000000000 --- a/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Numerics; -using Robust.Shared.Noise; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; - -namespace Content.Server.Worldgen.Prototypes; - -/// -/// This is a config for noise channels, used by worldgen. -/// -[Virtual] -public class NoiseChannelConfig -{ - /// - /// The noise type used by the noise generator. - /// - [DataField("noiseType")] - public FastNoiseLite.NoiseType NoiseType { get; private set; } = FastNoiseLite.NoiseType.Cellular; - - /// - /// The fractal type used by the noise generator. - /// - [DataField("fractalType")] - public FastNoiseLite.FractalType FractalType { get; private set; } = FastNoiseLite.FractalType.FBm; - - /// - /// Multiplied by pi in code when used. - /// - [DataField("fractalLacunarityByPi")] - public float FractalLacunarityByPi { get; private set; } = 2.0f / 3.0f; - - /// - /// Ranges of values that get clamped down to the "clipped" value. - /// - [DataField("clippingRanges")] - public List ClippingRanges { get; private set; } = new(); - - /// - /// The value clipped chunks are set to. - /// - [DataField("clippedValue")] - public float ClippedValue { get; private set; } - - /// - /// A value the output is multiplied by. - /// - [DataField("outputMultiplier")] - public float OutputMultiplier { get; private set; } = 1.0f; - - /// - /// A value the input is multiplied by. - /// - [DataField("inputMultiplier")] - public float InputMultiplier { get; private set; } = 1.0f; - - /// - /// Remaps the output of the noise function from the range (-1, 1) to (0, 1). This is done before all other output - /// transformations. - /// - [DataField("remapTo0Through1")] - public bool RemapTo0Through1 { get; private set; } - - /// - /// For when the transformation you need is too complex to describe in YAML. - /// - [DataField("noisePostProcess")] - public NoisePostProcess? NoisePostProcess { get; private set; } - - /// - /// For when you need a complex transformation of the input coordinates. - /// - [DataField("noiseCoordinateProcess")] - public NoiseCoordinateProcess? NoiseCoordinateProcess { get; private set; } - - /// - /// The "center" of the range of values. Or the minimum if mapped 0 through 1. - /// - [DataField("minimum")] - public float Minimum { get; private set; } -} - -[Prototype] -public sealed partial class NoiseChannelPrototype : NoiseChannelConfig, IPrototype, IInheritingPrototype -{ - /// - [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] - public string[]? Parents { get; private set; } - - /// - [NeverPushInheritance] - [AbstractDataField] - public bool Abstract { get; private set; } - - /// - [IdDataField] - public string ID { get; private set; } = default!; -} - -/// -/// A wrapper around FastNoise's noise generation, using noise channel configs. -/// -public struct NoiseGenerator -{ - private readonly NoiseChannelConfig _config; - private readonly FastNoiseLite _noise; - - /// - /// Produces a new noise generator from the given channel config and rng seed. - /// - public NoiseGenerator(NoiseChannelConfig config, int seed) - { - _config = config; - _noise = new FastNoiseLite(); - _noise.SetSeed(seed); - _noise.SetNoiseType(_config.NoiseType); - _noise.SetFractalType(_config.FractalType); - _noise.SetFractalLacunarity(_config.FractalLacunarityByPi * MathF.PI); - } - - /// - /// Evaluates the noise generator at the provided coordinates. - /// - /// Coordinates to use as input - /// Computed noise value - public float Evaluate(Vector2 coords) - { - var finCoords = coords * _config.InputMultiplier; - - if (_config.NoiseCoordinateProcess is not null) - finCoords = _config.NoiseCoordinateProcess.Process(finCoords); - - var value = _noise.GetNoise(finCoords.X, finCoords.Y); - - if (_config.RemapTo0Through1) - value = (value + 1.0f) / 2.0f; - - foreach (var range in _config.ClippingRanges) - { - if (range.X < value && value < range.Y) - { - value = _config.ClippedValue; - break; - } - } - - if (_config.NoisePostProcess is not null) - value = _config.NoisePostProcess.Process(value); - value *= _config.OutputMultiplier; - return value + _config.Minimum; - } -} - -/// -/// A processing class that adjusts the input coordinate space to a noise channel. -/// -[ImplicitDataDefinitionForInheritors] -public abstract partial class NoiseCoordinateProcess -{ - public abstract Vector2 Process(Vector2 inp); -} - -/// -/// A processing class that adjusts the final result of the noise channel. -/// -[ImplicitDataDefinitionForInheritors] -public abstract partial class NoisePostProcess -{ - public abstract float Process(float inp); -} - diff --git a/Content.Server/Worldgen/Prototypes/WorldgenConfigPrototype.cs b/Content.Server/Worldgen/Prototypes/WorldgenConfigPrototype.cs deleted file mode 100644 index c9107fa2bd..0000000000 --- a/Content.Server/Worldgen/Prototypes/WorldgenConfigPrototype.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; - -namespace Content.Server.Worldgen.Prototypes; - -/// -/// This is a prototype for controlling overall world generation. -/// The components included are applied to the map that world generation is configured on. -/// -[Prototype] -public sealed partial class WorldgenConfigPrototype : IPrototype -{ - /// - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// The components that get added to the target map. - /// - [DataField("components", required: true)] - public ComponentRegistry Components { get; private set; } = default!; - - //TODO: Get someone to make this a method on componentregistry that does it Correctly. - /// - /// Applies the worldgen config to the given target (presumably a map.) - /// - public void Apply(EntityUid target, ISerializationManager serialization, IEntityManager entityManager) - { - // Add all components required by the prototype. Engine update for this whenst. - foreach (var data in Components.Values) - { - var comp = (Component) serialization.CreateCopy(data.Component, notNullableOverride: true); - entityManager.AddComponent(target, comp); - } - } -} - diff --git a/Content.Server/Worldgen/Systems/BaseWorldSystem.cs b/Content.Server/Worldgen/Systems/BaseWorldSystem.cs deleted file mode 100644 index 7a9e74375c..0000000000 --- a/Content.Server/Worldgen/Systems/BaseWorldSystem.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Numerics; -using Content.Server.Worldgen.Components; -using JetBrains.Annotations; - -namespace Content.Server.Worldgen.Systems; - -/// -/// This provides some additional functions for world generation systems. -/// Exists primarily for convenience and to avoid code duplication. -/// -[PublicAPI] -public abstract class BaseWorldSystem : EntitySystem -{ - [Dependency] private readonly WorldControllerSystem _worldController = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - - /// - /// Gets a chunk's coordinates in chunk space as an integer value. - /// - /// - /// - /// Chunk space coordinates - [Pure] - public Vector2i GetChunkCoords(EntityUid ent, TransformComponent? xform = null) - { - if (!Resolve(ent, ref xform)) - throw new Exception("Failed to resolve transform, somehow."); - - return WorldGen.WorldToChunkCoords(_transformSystem.GetWorldPosition(xform)).Floored(); - } - - /// - /// Gets a chunk's coordinates in chunk space as a floating point value. - /// - /// - /// - /// Chunk space coordinates - [Pure] - public Vector2 GetFloatingChunkCoords(EntityUid ent, TransformComponent? xform = null) - { - if (!Resolve(ent, ref xform)) - throw new Exception("Failed to resolve transform, somehow."); - - return WorldGen.WorldToChunkCoords(_transformSystem.GetWorldPosition(xform)); - } - - /// - /// Attempts to get a chunk, creating it if it doesn't exist. - /// - /// Chunk coordinates to get the chunk entity for. - /// Map the chunk is in. - /// The controller this chunk belongs to. - /// A chunk, if available. - [Pure] - public EntityUid? GetOrCreateChunk(Vector2i chunk, EntityUid map, WorldControllerComponent? controller = null) - { - return _worldController.GetOrCreateChunk(chunk, map, controller); - } -} - diff --git a/Content.Server/Worldgen/Systems/Biomes/BiomeSelectionSystem.cs b/Content.Server/Worldgen/Systems/Biomes/BiomeSelectionSystem.cs deleted file mode 100644 index 1827f6deed..0000000000 --- a/Content.Server/Worldgen/Systems/Biomes/BiomeSelectionSystem.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Linq; -using Content.Server.Worldgen.Components; -using Content.Server.Worldgen.Prototypes; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; - -namespace Content.Server.Worldgen.Systems.Biomes; - -/// -/// This handles biome selection, evaluating which biome to apply to a chunk based on noise channels. -/// -public sealed class BiomeSelectionSystem : BaseWorldSystem -{ - [Dependency] private readonly NoiseIndexSystem _noiseIdx = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ISerializationManager _ser = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnBiomeSelectionStartup); - SubscribeLocalEvent(OnWorldChunkAdded); - } - - private void OnWorldChunkAdded(EntityUid uid, BiomeSelectionComponent component, ref WorldChunkAddedEvent args) - { - var coords = args.Coords; - foreach (var biomeId in component.Biomes) - { - var biome = _proto.Index(biomeId); - if (!CheckBiomeValidity(args.Chunk, biome, coords)) - continue; - - biome.Apply(args.Chunk, _ser, EntityManager); - return; - } - - Log.Error($"Biome selection ran out of biomes to select? See biomes list: {component.Biomes}"); - } - - private void OnBiomeSelectionStartup(EntityUid uid, BiomeSelectionComponent component, ComponentStartup args) - { - // surely this can't be THAAAAAAAAAAAAAAAT bad right???? - var sorted = component.Biomes - .Select(x => (Id: x, _proto.Index(x).Priority)) - .OrderByDescending(x => x.Priority) - .Select(x => x.Id) - .ToList(); - - component.Biomes = sorted; // my hopes and dreams rely on this being pre-sorted by priority. - } - - private bool CheckBiomeValidity(EntityUid chunk, BiomePrototype biome, Vector2i coords) - { - foreach (var (noise, ranges) in biome.NoiseRanges) - { - var value = _noiseIdx.Evaluate(chunk, noise, coords); - var anyValid = false; - foreach (var range in ranges) - { - if (range.X < value && value < range.Y) - { - anyValid = true; - break; - } - } - - if (!anyValid) - return false; - } - - return true; - } -} - diff --git a/Content.Server/Worldgen/Systems/Carvers/NoiseRangeCarverSystem.cs b/Content.Server/Worldgen/Systems/Carvers/NoiseRangeCarverSystem.cs deleted file mode 100644 index 1207d6f157..0000000000 --- a/Content.Server/Worldgen/Systems/Carvers/NoiseRangeCarverSystem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Server.Worldgen.Components.Carvers; -using Content.Server.Worldgen.Systems.Debris; - -namespace Content.Server.Worldgen.Systems.Carvers; - -/// -/// This handles carving out holes in world generation according to a noise channel. -/// -public sealed class NoiseRangeCarverSystem : EntitySystem -{ - [Dependency] private readonly NoiseIndexSystem _index = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnPrePlaceDebris); - } - - private void OnPrePlaceDebris(EntityUid uid, NoiseRangeCarverComponent component, - ref PrePlaceDebrisFeatureEvent args) - { - var coords = WorldGen.WorldToChunkCoords(_transform.ToMapCoordinates(args.Coords).Position); - var val = _index.Evaluate(uid, component.NoiseChannel, coords); - - foreach (var (low, high) in component.Ranges) - { - if (low > val || high < val) - continue; - - args.Handled = true; - return; - } - } -} - diff --git a/Content.Server/Worldgen/Systems/Debris/BlobFloorPlanBuilderSystem.cs b/Content.Server/Worldgen/Systems/Debris/BlobFloorPlanBuilderSystem.cs deleted file mode 100644 index ba0a3a7132..0000000000 --- a/Content.Server/Worldgen/Systems/Debris/BlobFloorPlanBuilderSystem.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Linq; -using Content.Server.Worldgen.Components.Debris; -using Content.Shared.Maps; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Random; - -namespace Content.Server.Worldgen.Systems.Debris; - -/// -/// This handles building the floor plans for "blobby" debris. -/// -public sealed class BlobFloorPlanBuilderSystem : BaseWorldSystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly ITileDefinitionManager _tileDefinition = default!; - [Dependency] private readonly TileSystem _tiles = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnBlobFloorPlanBuilderStartup); - } - - private void OnBlobFloorPlanBuilderStartup(EntityUid uid, BlobFloorPlanBuilderComponent component, - ComponentStartup args) - { - PlaceFloorplanTiles(uid, component, Comp(uid)); - } - - private void PlaceFloorplanTiles(EntityUid gridUid, BlobFloorPlanBuilderComponent comp, MapGridComponent grid) - { - // NO MORE THAN TWO ALLOCATIONS THANK YOU VERY MUCH. - // TODO: Just put these on a field instead then? - // Also the end of the method has a big LINQ which is gonna blow this out the water. - var spawnPoints = new HashSet(comp.FloorPlacements * 6); - var taken = new Dictionary(comp.FloorPlacements * 5); - - void PlaceTile(Vector2i point) - { - // Assume we already know that the spawn point is safe. - spawnPoints.Remove(point); - var north = point.Offset(Direction.North); - var south = point.Offset(Direction.South); - var east = point.Offset(Direction.East); - var west = point.Offset(Direction.West); - var radsq = Math.Pow(comp.Radius, - 2); // I'd put this outside but i'm not 100% certain caching it between calls is a gain. - - // The math done is essentially a fancy way of comparing the distance from 0,0 to the radius, - // and skipping the sqrt normally needed for dist. - if (!taken.ContainsKey(north) && Math.Pow(north.X, 2) + Math.Pow(north.Y, 2) <= radsq) - spawnPoints.Add(north); - if (!taken.ContainsKey(south) && Math.Pow(south.X, 2) + Math.Pow(south.Y, 2) <= radsq) - spawnPoints.Add(south); - if (!taken.ContainsKey(east) && Math.Pow(east.X, 2) + Math.Pow(east.Y, 2) <= radsq) - spawnPoints.Add(east); - if (!taken.ContainsKey(west) && Math.Pow(west.X, 2) + Math.Pow(west.Y, 2) <= radsq) - spawnPoints.Add(west); - - var tileDef = _tileDefinition[_random.Pick(comp.FloorTileset)]; - taken.Add(point, new Tile(tileDef.TileId, 0, _tiles.PickVariant((ContentTileDefinition) tileDef))); - } - - PlaceTile(Vector2i.Zero); - - for (var i = 0; i < comp.FloorPlacements; i++) - { - var point = _random.Pick(spawnPoints); - PlaceTile(point); - - if (comp.BlobDrawProb > 0.0f) - { - if (!taken.ContainsKey(point.Offset(Direction.North)) && _random.Prob(comp.BlobDrawProb)) - PlaceTile(point.Offset(Direction.North)); - if (!taken.ContainsKey(point.Offset(Direction.South)) && _random.Prob(comp.BlobDrawProb)) - PlaceTile(point.Offset(Direction.South)); - if (!taken.ContainsKey(point.Offset(Direction.East)) && _random.Prob(comp.BlobDrawProb)) - PlaceTile(point.Offset(Direction.East)); - if (!taken.ContainsKey(point.Offset(Direction.West)) && _random.Prob(comp.BlobDrawProb)) - PlaceTile(point.Offset(Direction.West)); - } - } - - _map.SetTiles(gridUid, grid, taken.Select(x => (x.Key, x.Value)).ToList()); - } -} - diff --git a/Content.Server/Worldgen/Systems/Debris/DebrisFeaturePlacerSystem.cs b/Content.Server/Worldgen/Systems/Debris/DebrisFeaturePlacerSystem.cs deleted file mode 100644 index b609f7e1f7..0000000000 --- a/Content.Server/Worldgen/Systems/Debris/DebrisFeaturePlacerSystem.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System.Linq; -using System.Numerics; -using Content.Server.Worldgen.Components; -using Content.Server.Worldgen.Components.Debris; -using Content.Server.Worldgen.Tools; -using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Worldgen.Systems.Debris; - -/// -/// This handles placing debris within the world evenly with rng, primarily for structures like asteroid fields. -/// -public sealed class DebrisFeaturePlacerSystem : BaseWorldSystem -{ - [Dependency] private readonly NoiseIndexSystem _noiseIndex = default!; - [Dependency] private readonly PoissonDiskSampler _sampler = default!; - [Dependency] private readonly TransformSystem _xformSys = default!; - [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - private ISawmill _sawmill = default!; - - private List> _mapGrids = new(); - - /// - public override void Initialize() - { - _sawmill = _logManager.GetSawmill("world.debris.feature_placer"); - SubscribeLocalEvent(OnChunkLoaded); - SubscribeLocalEvent(OnChunkUnloaded); - SubscribeLocalEvent(OnDebrisShutdown); - SubscribeLocalEvent(OnDebrisMove); - SubscribeLocalEvent( - OnTryGetPlacableDebrisEvent); - } - - /// - /// Handles debris moving, and making sure it stays parented to a chunk for loading purposes. - /// - private void OnDebrisMove(EntityUid uid, OwnedDebrisComponent component, ref MoveEvent args) - { - if (!HasComp(component.OwningController)) - return; // Redundant logic, prolly needs it's own handler for your custom system. - - var placer = Comp(component.OwningController); - var xform = args.Component; - var ownerXform = Transform(component.OwningController); - if (xform.MapUid is null || ownerXform.MapUid is null) - return; // not our problem - - if (xform.MapUid != ownerXform.MapUid) - { - _sawmill.Error($"Somehow debris {uid} left it's expected map! Unparenting it to avoid issues."); - RemCompDeferred(uid); - placer.OwnedDebris.Remove(component.LastKey); - return; - } - - placer.OwnedDebris.Remove(component.LastKey); - var newChunk = GetOrCreateChunk(GetChunkCoords(uid), xform.MapUid!.Value); - if (newChunk is null || !TryComp(newChunk, out var newPlacer)) - { - // Whelp. - RemCompDeferred(uid); - return; - } - - newPlacer.OwnedDebris[_xformSys.GetWorldPosition(xform)] = uid; // Change our owner. - component.OwningController = newChunk.Value; - } - - /// - /// Handles debris shutdown/detach. - /// - private void OnDebrisShutdown(EntityUid uid, OwnedDebrisComponent component, ComponentShutdown args) - { - if (!TryComp(component.OwningController, out var placer)) - return; - - placer.OwnedDebris[component.LastKey] = null; - if (Terminating(uid)) - placer.OwnedDebris.Remove(component.LastKey); - } - - /// - /// Queues all debris owned by the placer for garbage collection. - /// - private void OnChunkUnloaded(EntityUid uid, DebrisFeaturePlacerControllerComponent component, - ref WorldChunkUnloadedEvent args) - { - component.DoSpawns = true; - } - - /// - /// Handles providing a debris type to place for SimpleDebrisSelectorComponent. - /// This randomly picks a debris type from the EntitySpawnCollectionCache. - /// - private void OnTryGetPlacableDebrisEvent(EntityUid uid, SimpleDebrisSelectorComponent component, - ref TryGetPlaceableDebrisFeatureEvent args) - { - if (args.DebrisProto is not null) - return; - - var l = new List(1); - component.CachedDebrisTable.GetSpawns(_random, ref l); - - switch (l.Count) - { - case 0: - return; - case > 1: - _sawmill.Warning($"Got more than one possible debris type from {uid}. List: {string.Join(", ", l)}"); - break; - } - - args.DebrisProto = l[0]; - } - - /// - /// Handles loading in debris. This does the following: - /// - Checks if the debris is currently supposed to do spawns, if it isn't, aborts immediately. - /// - Evaluates the density value to be used for placement, if it's zero, aborts. - /// - Generates the points to generate debris at, if and only if they've not been selected already by a prior load. - /// - Does the following in a loop over all generated points: - /// - Raises an event to check if something else wants to intercept debris placement, if the event is handled, - /// continues to the next point without generating anything. - /// - Raises an event to get the debris type that should be used for generation. - /// - Spawns the given debris at the point, adding it to the placer's index. - /// - private void OnChunkLoaded(EntityUid uid, DebrisFeaturePlacerControllerComponent component, - ref WorldChunkLoadedEvent args) - { - if (component.DoSpawns == false) - return; - - component.DoSpawns = false; // Don't repeat yourself if this crashes. - - if (!TryComp(args.Chunk, out var chunk)) - return; - - var chunkMap = chunk.Map; - - if (!TryComp(chunkMap, out var map)) - return; - - var densityChannel = component.DensityNoiseChannel; - var density = _noiseIndex.Evaluate(uid, densityChannel, chunk.Coordinates + new Vector2(0.5f, 0.5f)); - if (density == 0) - return; - - List? points = null; - - // If we've been loaded before, reuse the same coordinates. - if (component.OwnedDebris.Count != 0) - { - //TODO: Remove LINQ. - points = component.OwnedDebris - .Where(x => !Deleted(x.Value)) - .Select(static x => x.Key) - .ToList(); - } - - points ??= GeneratePointsInChunk(args.Chunk, density, chunk.Coordinates, chunkMap); - - var mapId = map.MapId; - - var safetyBounds = Box2.UnitCentered.Enlarged(component.SafetyZoneRadius); - var failures = 0; // Avoid severe log spam. - foreach (var point in points) - { - if (component.OwnedDebris.TryGetValue(point, out var existing)) - { - DebugTools.Assert(Exists(existing)); - continue; - } - - var pointDensity = _noiseIndex.Evaluate(uid, densityChannel, WorldGen.WorldToChunkCoords(point)); - if (pointDensity == 0 && component.DensityClip || _random.Prob(component.RandomCancellationChance)) - continue; - - if (HasCollisions(mapId, safetyBounds.Translated(point))) - continue; - - var coords = new EntityCoordinates(chunkMap, point); - - var preEv = new PrePlaceDebrisFeatureEvent(coords, args.Chunk); - RaiseLocalEvent(uid, ref preEv); - if (uid != args.Chunk) - RaiseLocalEvent(args.Chunk, ref preEv); - - if (preEv.Handled) - continue; - - var debrisFeatureEv = new TryGetPlaceableDebrisFeatureEvent(coords, args.Chunk); - RaiseLocalEvent(uid, ref debrisFeatureEv); - - if (debrisFeatureEv.DebrisProto == null) - { - // Try on the chunk...? - if (uid != args.Chunk) - RaiseLocalEvent(args.Chunk, ref debrisFeatureEv); - - if (debrisFeatureEv.DebrisProto == null) - { - // Nope. - failures++; - continue; - } - } - - var ent = Spawn(debrisFeatureEv.DebrisProto, coords); - component.OwnedDebris.Add(point, ent); - - var owned = EnsureComp(ent); - owned.OwningController = uid; - owned.LastKey = point; - } - - if (failures > 0) - _sawmill.Error($"Failed to place {failures} debris at chunk {args.Chunk}"); - } - - /// - /// Checks to see if the potential spawn point is clear - /// - /// - /// - /// - private bool HasCollisions(MapId mapId, Box2 point) - { - _mapGrids.Clear(); - _mapManager.FindGridsIntersecting(mapId, point, ref _mapGrids); - return _mapGrids.Count > 0; - } - - /// - /// Generates the points to put into a chunk using a poisson disk sampler. - /// - private List GeneratePointsInChunk(EntityUid chunk, float density, Vector2 coords, EntityUid map) - { - var offs = (int) ((WorldGen.ChunkSize - WorldGen.ChunkSize / 8.0f) / 2.0f); - var topLeft = new Vector2(-offs, -offs); - var lowerRight = new Vector2(offs, offs); - var enumerator = _sampler.SampleRectangle(topLeft, lowerRight, density); - var debrisPoints = new List(); - - var realCenter = WorldGen.ChunkToWorldCoordsCentered(coords.Floored()); - - while (enumerator.MoveNext(out var debrisPoint)) - { - debrisPoints.Add(realCenter + debrisPoint.Value); - } - - return debrisPoints; - } -} - -/// -/// Fired directed on the debris feature placer controller and the chunk, ahead of placing a debris piece. -/// -[ByRefEvent] -[PublicAPI] -public record struct PrePlaceDebrisFeatureEvent(EntityCoordinates Coords, EntityUid Chunk, bool Handled = false); - -/// -/// Fired directed on the debris feature placer controller and the chunk, to select which debris piece to place. -/// -[ByRefEvent] -[PublicAPI] -public record struct TryGetPlaceableDebrisFeatureEvent(EntityCoordinates Coords, EntityUid Chunk, - string? DebrisProto = null); - diff --git a/Content.Server/Worldgen/Systems/Debris/NoiseDrivenDebrisSelectorSystem.cs b/Content.Server/Worldgen/Systems/Debris/NoiseDrivenDebrisSelectorSystem.cs deleted file mode 100644 index a02ff81362..0000000000 --- a/Content.Server/Worldgen/Systems/Debris/NoiseDrivenDebrisSelectorSystem.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Content.Server.Worldgen.Components.Debris; -using Robust.Server.GameObjects; -using Robust.Shared.Physics; -using Robust.Shared.Random; - -namespace Content.Server.Worldgen.Systems.Debris; - -/// -/// This handles selecting debris with probability decided by a noise channel. -/// -public sealed class NoiseDrivenDebrisSelectorSystem : BaseWorldSystem -{ - [Dependency] private readonly NoiseIndexSystem _index = default!; - [Dependency] private readonly TransformSystem _xformSys = default!; - [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - private ISawmill _sawmill = default!; - - /// - public override void Initialize() - { - _sawmill = _logManager.GetSawmill("world.debris.noise_debris_selector"); - // Event is forcibly ordered to always be handled after the simple selector. - SubscribeLocalEvent(OnSelectDebrisKind, - after: new[] {typeof(DebrisFeaturePlacerSystem)}); - } - - private void OnSelectDebrisKind(EntityUid uid, NoiseDrivenDebrisSelectorComponent component, - ref TryGetPlaceableDebrisFeatureEvent args) - { - var coords = WorldGen.WorldToChunkCoords(_xformSys.ToMapCoordinates(args.Coords).Position); - var prob = _index.Evaluate(uid, component.NoiseChannel, coords); - - if (prob is < 0 or > 1) - { - _sawmill.Error( - $"Sampled a probability of {prob}, which is outside the [0, 1] range, at {coords} aka {args.Coords}."); - return; - } - - if (!_random.Prob(prob)) - return; - - var l = new List(1); - component.CachedDebrisTable.GetSpawns(_random, ref l); - - switch (l.Count) - { - case 0: - return; - case > 1: - _sawmill.Warning($"Got more than one possible debris type from {uid}. List: {string.Join(", ", l)}"); - break; - } - - args.DebrisProto = l[0]; - } -} - diff --git a/Content.Server/Worldgen/Systems/Debris/SimpleFloorPlanPopulatorSystem.cs b/Content.Server/Worldgen/Systems/Debris/SimpleFloorPlanPopulatorSystem.cs deleted file mode 100644 index dcb7b7fc8f..0000000000 --- a/Content.Server/Worldgen/Systems/Debris/SimpleFloorPlanPopulatorSystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Content.Server.Worldgen.Components.Debris; -using Content.Shared.Maps; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Random; - -namespace Content.Server.Worldgen.Systems.Debris; - -/// -/// This handles populating simple structures, simply using a loot table for each tile. -/// -public sealed class SimpleFloorPlanPopulatorSystem : BaseWorldSystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - [Dependency] private readonly TurfSystem _turf = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnFloorPlanBuilt); - } - - private void OnFloorPlanBuilt(EntityUid uid, SimpleFloorPlanPopulatorComponent component, - LocalStructureLoadedEvent args) - { - var placeables = new List(4); - var grid = Comp(uid); - var enumerator = _map.GetAllTilesEnumerator(uid, grid); - while (enumerator.MoveNext(out var tile)) - { - var coords = _map.GridTileToLocal(uid, grid, tile.Value.GridIndices); - var selector = _turf.GetContentTileDefinition(tile.Value).ID; - if (!component.Caches.TryGetValue(selector, out var cache)) - continue; - - placeables.Clear(); - cache.GetSpawns(_random, ref placeables); - - foreach (var proto in placeables) - { - if (proto is null) - continue; - - Spawn(proto, coords); - } - } - } -} - diff --git a/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs b/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs deleted file mode 100644 index fb02e8aa0b..0000000000 --- a/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Content.Server.Worldgen.Components; -using Robust.Server.GameObjects; - -namespace Content.Server.Worldgen.Systems; - -/// -/// This handles loading in objects based on distance from player, using some metadata on chunks. -/// -public sealed class LocalityLoaderSystem : BaseWorldSystem -{ - [Dependency] private readonly TransformSystem _xformSys = default!; - - /// - public override void Update(float frameTime) - { - var e = EntityQueryEnumerator(); - var loadedQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var controllerQuery = GetEntityQuery(); - - while (e.MoveNext(out var uid, out var loadable, out var xform)) - { - if (!controllerQuery.TryGetComponent(xform.MapUid, out var controller)) - { - RaiseLocalEvent(uid, new LocalStructureLoadedEvent()); - RemCompDeferred(uid); - continue; - } - - var coords = GetChunkCoords(uid, xform); - var done = false; - for (var i = -1; i < 2 && !done; i++) - { - for (var j = -1; j < 2 && !done; j++) - { - var chunk = GetOrCreateChunk(coords + (i, j), xform.MapUid!.Value, controller); - if (!loadedQuery.TryGetComponent(chunk, out var loaded) || loaded.Loaders is null) - continue; - - foreach (var loader in loaded.Loaders) - { - if (!xformQuery.TryGetComponent(loader, out var loaderXform)) - continue; - - if ((_xformSys.GetWorldPosition(loaderXform) - _xformSys.GetWorldPosition(xform)).Length() > loadable.LoadingDistance) - continue; - - RaiseLocalEvent(uid, new LocalStructureLoadedEvent()); - RemCompDeferred(uid); - done = true; - break; - } - } - } - } - } -} - -/// -/// A directed fired on a loadable entity when a local loader enters it's vicinity. -/// -public record struct LocalStructureLoadedEvent; - diff --git a/Content.Server/Worldgen/Systems/NoiseIndexSystem.cs b/Content.Server/Worldgen/Systems/NoiseIndexSystem.cs deleted file mode 100644 index 5a7e02c803..0000000000 --- a/Content.Server/Worldgen/Systems/NoiseIndexSystem.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Numerics; -using Content.Server.Worldgen.Components; -using Content.Server.Worldgen.Prototypes; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server.Worldgen.Systems; - -/// -/// This handles the noise index. -/// -public sealed class NoiseIndexSystem : EntitySystem -{ - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - /// - /// Gets a particular noise channel from the index on the given entity. - /// - /// The holder of the index - /// The channel prototype ID - /// An initialized noise generator - public NoiseGenerator Get(EntityUid holder, string protoId) - { - var idx = EnsureComp(holder); - if (idx.Generators.TryGetValue(protoId, out var generator)) - return generator; - var proto = _prototype.Index(protoId); - var gen = new NoiseGenerator(proto, _random.Next()); - idx.Generators[protoId] = gen; - return gen; - } - - /// - /// Attempts to evaluate the given noise channel using the generator on the given entity. - /// - /// The holder of the index - /// The channel prototype ID - /// The coordinates to evaluate at - /// The result of evaluation - public float Evaluate(EntityUid holder, string protoId, Vector2 coords) - { - var gen = Get(holder, protoId); - return gen.Evaluate(coords); - } -} - diff --git a/Content.Server/Worldgen/Systems/WorldControllerSystem.cs b/Content.Server/Worldgen/Systems/WorldControllerSystem.cs deleted file mode 100644 index 19c777e1ad..0000000000 --- a/Content.Server/Worldgen/Systems/WorldControllerSystem.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System.Linq; -using Content.Server.Worldgen.Components; -using Content.Shared.Ghost; -using Content.Shared.Mind.Components; -using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Timing; - -namespace Content.Server.Worldgen.Systems; - -/// -/// This handles putting together chunk entities and notifying them about important changes. -/// -public sealed class WorldControllerSystem : EntitySystem -{ - [Dependency] private readonly TransformSystem _xformSys = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - - private const int PlayerLoadRadius = 2; - - private ISawmill _sawmill = default!; - - /// - public override void Initialize() - { - _sawmill = _logManager.GetSawmill("world"); - SubscribeLocalEvent(OnChunkLoadedCore); - SubscribeLocalEvent(OnChunkUnloadedCore); - SubscribeLocalEvent(OnChunkShutdown); - } - - /// - /// Handles deleting chunks properly. - /// - private void OnChunkShutdown(EntityUid uid, WorldChunkComponent component, ComponentShutdown args) - { - if (!TryComp(component.Map, out var controller)) - return; - - if (HasComp(uid)) - { - var ev = new WorldChunkUnloadedEvent(uid, component.Coordinates); - RaiseLocalEvent(component.Map, ref ev); - RaiseLocalEvent(uid, ref ev, broadcast: true); - } - - controller.Chunks.Remove(component.Coordinates); - } - - /// - /// Handles the inner logic of loading a chunk, i.e. events. - /// - private void OnChunkLoadedCore(EntityUid uid, LoadedChunkComponent component, ComponentStartup args) - { - if (!TryComp(uid, out var chunk)) - return; - - var ev = new WorldChunkLoadedEvent(uid, chunk.Coordinates); - RaiseLocalEvent(chunk.Map, ref ev); - RaiseLocalEvent(uid, ref ev, broadcast: true); - //_sawmill.Debug($"Loaded chunk {ToPrettyString(uid)} at {chunk.Coordinates}"); - } - - /// - /// Handles the inner logic of unloading a chunk, i.e. events. - /// - private void OnChunkUnloadedCore(EntityUid uid, LoadedChunkComponent component, ComponentShutdown args) - { - if (!TryComp(uid, out var chunk)) - return; - - if (Terminating(uid)) - return; // SAFETY: This is in case a loaded chunk gets deleted, to avoid double unload. - - var ev = new WorldChunkUnloadedEvent(uid, chunk.Coordinates); - RaiseLocalEvent(chunk.Map, ref ev); - RaiseLocalEvent(uid, ref ev); - //_sawmill.Debug($"Unloaded chunk {ToPrettyString(uid)} at {coords}"); - } - - /// - public override void Update(float frameTime) - { - //there was a to-do here about every frame alloc but it turns out it's a nothing burger here. - var chunksToLoad = new Dictionary>>(); - - var controllerEnum = EntityQueryEnumerator(); - while (controllerEnum.MoveNext(out var uid, out _)) - { - chunksToLoad[uid] = new Dictionary>(); - } - - if (chunksToLoad.Count == 0) - return; // Just bail early. - - var loaderEnum = EntityQueryEnumerator(); - - while (loaderEnum.MoveNext(out var uid, out var worldLoader, out var xform)) - { - var mapOrNull = xform.MapUid; - if (mapOrNull is null) - continue; - var map = mapOrNull.Value; - if (!chunksToLoad.ContainsKey(map)) - continue; - - var wc = _xformSys.GetWorldPosition(xform); - var coords = WorldGen.WorldToChunkCoords(wc); - var chunks = new GridPointsNearEnumerator(coords.Floored(), - (int) Math.Ceiling(worldLoader.Radius / (float) WorldGen.ChunkSize) + 1); - - var set = chunksToLoad[map]; - - while (chunks.MoveNext(out var chunk)) - { - if (!set.TryGetValue(chunk.Value, out _)) - set[chunk.Value] = new List(4); - set[chunk.Value].Add(uid); - } - } - - var mindEnum = EntityQueryEnumerator(); - var ghostQuery = GetEntityQuery(); - - // Mindful entities get special privilege as they're always a player and we don't want the illusion being broken around them. - while (mindEnum.MoveNext(out var uid, out var mind, out var xform)) - { - if (!mind.HasMind) - continue; - if (ghostQuery.HasComponent(uid)) - continue; - var mapOrNull = xform.MapUid; - if (mapOrNull is null) - continue; - var map = mapOrNull.Value; - if (!chunksToLoad.ContainsKey(map)) - continue; - - var wc = _xformSys.GetWorldPosition(xform); - var coords = WorldGen.WorldToChunkCoords(wc); - var chunks = new GridPointsNearEnumerator(coords.Floored(), PlayerLoadRadius); - - var set = chunksToLoad[map]; - - while (chunks.MoveNext(out var chunk)) - { - if (!set.TryGetValue(chunk.Value, out _)) - set[chunk.Value] = new List(4); - set[chunk.Value].Add(uid); - } - } - - var loadedEnum = EntityQueryEnumerator(); - var chunksUnloaded = 0; - - // Make sure these chunks get unloaded at the end of the tick. - while (loadedEnum.MoveNext(out var uid, out var _, out var chunk)) - { - var coords = chunk.Coordinates; - - if (!chunksToLoad[chunk.Map].ContainsKey(coords)) - { - RemCompDeferred(uid); - chunksUnloaded++; - } - } - - if (chunksUnloaded > 0) - _sawmill.Debug($"Queued {chunksUnloaded} chunks for unload."); - - if (chunksToLoad.All(x => x.Value.Count == 0)) - return; - - var startTime = _gameTiming.RealTime; - var count = 0; - var loadedQuery = GetEntityQuery(); - var controllerQuery = GetEntityQuery(); - foreach (var (map, chunks) in chunksToLoad) - { - var controller = controllerQuery.GetComponent(map); - foreach (var (chunk, loaders) in chunks) - { - var ent = GetOrCreateChunk(chunk, map, controller); // Ensure everything loads. - LoadedChunkComponent? c = null; - if (ent is not null && !loadedQuery.TryGetComponent(ent.Value, out c)) - { - c = AddComp(ent.Value); - count += 1; - } - - if (c is not null) - c.Loaders = loaders; - } - } - - if (count > 0) - { - var timeSpan = _gameTiming.RealTime - startTime; - _sawmill.Debug($"Loaded {count} chunks in {timeSpan.TotalMilliseconds:N2}ms."); - } - } - - /// - /// Attempts to get a chunk, creating it if it doesn't exist. - /// - /// Chunk coordinates to get the chunk entity for. - /// Map the chunk is in. - /// The controller this chunk belongs to. - /// A chunk, if available. - [Pure] - public EntityUid? GetOrCreateChunk(Vector2i chunk, EntityUid map, WorldControllerComponent? controller = null) - { - if (!Resolve(map, ref controller)) - throw new Exception($"Tried to use {ToPrettyString(map)} as a world map, without actually being one."); - - if (controller.Chunks.TryGetValue(chunk, out var ent)) - return ent; - return CreateChunkEntity(chunk, map, controller); - } - - /// - /// Constructs a new chunk entity, attaching it to the map. - /// - /// The coordinates the new chunk should be initialized for. - /// - /// - /// - private EntityUid CreateChunkEntity(Vector2i chunkCoords, EntityUid map, WorldControllerComponent controller) - { - var chunk = Spawn(controller.ChunkProto, MapCoordinates.Nullspace); - StartupChunkEntity(chunk, chunkCoords, map, controller); - _metaData.SetEntityName(chunk, $"Chunk {chunkCoords.X}/{chunkCoords.Y}"); - return chunk; - } - - private void StartupChunkEntity(EntityUid chunk, Vector2i coords, EntityUid map, - WorldControllerComponent controller) - { - if (!TryComp(chunk, out var chunkComponent)) - { - _sawmill.Error($"Chunk {ToPrettyString(chunk)} is missing WorldChunkComponent."); - return; - } - - ref var chunks = ref controller.Chunks; - - chunks[coords] = chunk; // Add this entity to chunk index. - chunkComponent.Coordinates = coords; - chunkComponent.Map = map; - var ev = new WorldChunkAddedEvent(chunk, coords); - RaiseLocalEvent(map, ref ev, broadcast: true); - } -} - -/// -/// A directed event fired when a chunk is initially set up in the world. The chunk is not loaded at this point. -/// -[ByRefEvent] -[PublicAPI] -public readonly record struct WorldChunkAddedEvent(EntityUid Chunk, Vector2i Coords); - -/// -/// A directed event fired when a chunk is loaded into the world, i.e. a player or other world loader has entered vicinity. -/// -[ByRefEvent] -[PublicAPI] -public readonly record struct WorldChunkLoadedEvent(EntityUid Chunk, Vector2i Coords); - -/// -/// A directed event fired when a chunk is unloaded from the world, i.e. no world loaders remain nearby. -/// -[ByRefEvent] -[PublicAPI] -public readonly record struct WorldChunkUnloadedEvent(EntityUid Chunk, Vector2i Coords); diff --git a/Content.Server/Worldgen/Systems/WorldgenConfigSystem.cs b/Content.Server/Worldgen/Systems/WorldgenConfigSystem.cs deleted file mode 100644 index cc0ec62733..0000000000 --- a/Content.Server/Worldgen/Systems/WorldgenConfigSystem.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Content.Server.Administration; -using Content.Server.GameTicking; -using Content.Server.GameTicking.Events; -using Content.Server.Worldgen.Components; -using Content.Server.Worldgen.Prototypes; -using Content.Shared.Administration; -using Content.Shared.CCVar; -using Robust.Shared.Configuration; -using Robust.Shared.Console; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; -using Robust.Shared.Utility; - -namespace Content.Server.Worldgen.Systems; - -/// -/// This handles configuring world generation during round start. -/// -public sealed class WorldgenConfigSystem : EntitySystem -{ - [Dependency] private readonly GameTicker _gameTicker = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IConsoleHost _conHost = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ISerializationManager _ser = default!; - - private bool _enabled; - private string _worldgenConfig = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnLoadingMaps); - _conHost.RegisterCommand("applyworldgenconfig", Loc.GetString("cmd-applyworldgenconfig-description"), Loc.GetString("cmd-applyworldgenconfig-help"), ApplyWorldgenConfigCommand); - Subs.CVar(_cfg, CCVars.WorldgenEnabled, b => _enabled = b, true); - Subs.CVar(_cfg, CCVars.WorldgenConfig, s => _worldgenConfig = s, true); - } - - [AdminCommand(AdminFlags.Mapping)] - private void ApplyWorldgenConfigCommand(IConsoleShell shell, string argstr, string[] args) - { - if (args.Length != 2) - { - shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", ("properAmount", 2), ("currentAmount", args.Length))); - return; - } - - if (!int.TryParse(args[0], out var mapInt) || !_map.MapExists(new MapId(mapInt))) - { - shell.WriteError(Loc.GetString("shell-invalid-map-id")); - return; - } - - var map = _map.GetMapOrInvalid(new MapId(mapInt)); - - if (!_proto.TryIndex(args[1], out var proto)) - { - shell.WriteError(Loc.GetString("shell-argument-must-be-prototype", ("index", 2), ("prototypeName", "cmd-applyworldgenconfig-prototype"))); - return; - } - - proto.Apply(map, _ser, EntityManager); - shell.WriteLine(Loc.GetString("cmd-applyworldgenconfig-success")); - } - - /// - /// Applies the world config to the default map if enabled. - /// - private void OnLoadingMaps(RoundStartingEvent ev) - { - if (_enabled == false) - return; - - var target = _map.GetMapOrInvalid(_gameTicker.DefaultMap); - Log.Debug($"Trying to configure {_gameTicker.DefaultMap}, aka {ToPrettyString(target)} aka {target}"); - var cfg = _proto.Index(_worldgenConfig); - - cfg.Apply(target, _ser, EntityManager); // Apply the config to the map. - - DebugTools.Assert(HasComp(target)); - } -} - diff --git a/Content.Server/Worldgen/Tools/EntitySpawnCollectionCache.cs b/Content.Server/Worldgen/Tools/EntitySpawnCollectionCache.cs deleted file mode 100644 index 5480575427..0000000000 --- a/Content.Server/Worldgen/Tools/EntitySpawnCollectionCache.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Linq; -using Content.Shared.Storage; -using Robust.Shared.Random; - -namespace Content.Server.Worldgen.Tools; - -/// -/// A faster version of EntitySpawnCollection that requires caching to work. -/// -public sealed class EntitySpawnCollectionCache -{ - [ViewVariables] private readonly Dictionary _orGroups = new(); - - public EntitySpawnCollectionCache(IEnumerable entries) - { - // collect groups together, create singular items that pass probability - foreach (var entry in entries) - { - if (!_orGroups.TryGetValue(entry.GroupId ?? string.Empty, out var orGroup)) - { - orGroup = new OrGroup(); - _orGroups.Add(entry.GroupId ?? string.Empty, orGroup); - } - - orGroup.Entries.Add(entry); - orGroup.CumulativeProbability += entry.SpawnProbability; - } - } - - /// - /// Using a collection of entity spawn entries, picks a random list of entity prototypes to spawn from that collection. - /// - /// - /// This does not spawn the entities. The caller is responsible for doing so, since it may want to do something - /// special to those entities (offset them, insert them into storage, etc) - /// - /// Resolve param. - /// List that spawned entities are inserted into. - /// A list of entity prototypes that should be spawned. - /// This is primarily useful if you're calling it many times over, as it lets you reuse the list repeatedly. - public void GetSpawns(IRobustRandom random, ref List spawned) - { - // handle orgroup spawns - foreach (var spawnValue in _orGroups.Values) - { - //HACK: This doesn't seem to work without this if there's only a single orgroup entry. Not sure how to fix the original math properly, but it works in every other case. - if (spawnValue.Entries.Count == 1) - { - var entry = spawnValue.Entries.First(); - var amount = entry.Amount; - - if (entry.MaxAmount > amount) - amount = random.Next(amount, entry.MaxAmount); - - for (var index = 0; index < amount; index++) - { - spawned.Add(entry.PrototypeId); - } - - continue; - } - - // For each group use the added cumulative probability to roll a double in that range - var diceRoll = random.NextDouble() * spawnValue.CumulativeProbability; - // Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item. - var cumulative = 0.0; - foreach (var entry in spawnValue.Entries) - { - cumulative += entry.SpawnProbability; - if (diceRoll > cumulative) - continue; - // Dice roll succeeded, add item and break loop - - var amount = entry.Amount; - - if (entry.MaxAmount > amount) - amount = random.Next(amount, entry.MaxAmount); - - for (var index = 0; index < amount; index++) - { - spawned.Add(entry.PrototypeId); - } - - break; - } - } - } - - private sealed class OrGroup - { - [ViewVariables] public List Entries { get; } = new(); - - [ViewVariables] public float CumulativeProbability { get; set; } - } -} - diff --git a/Content.Server/Worldgen/Tools/PoissonDiskSampler.cs b/Content.Server/Worldgen/Tools/PoissonDiskSampler.cs deleted file mode 100644 index cb7c5a1411..0000000000 --- a/Content.Server/Worldgen/Tools/PoissonDiskSampler.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Worldgen.Tools; - -/// -/// An implementation of Poisson Disk Sampling, for evenly spreading points across a given area. -/// -public sealed class PoissonDiskSampler -{ - public const int DefaultPointsPerIteration = 30; - [Dependency] private readonly IRobustRandom _random = default!; - - /// - /// Samples for points within the given circle. - /// - /// Center of the sample - /// Radius of the sample - /// Minimum distance between points. Must be above 0! - /// The number of points placed per iteration of the algorithm - /// An enumerator of points - public SampleEnumerator SampleCircle(Vector2 center, float radius, float minimumDistance, - int pointsPerIteration = DefaultPointsPerIteration) - { - return Sample(center - new Vector2(radius, radius), center + new Vector2(radius, radius), radius, - minimumDistance, pointsPerIteration); - } - - /// - /// Samples for points within the given rectangle. - /// - /// The top left of the rectangle - /// The bottom right of the rectangle - /// Minimum distance between points. Must be above 0! - /// The number of points placed per iteration of the algorithm - /// An enumerator of points - public SampleEnumerator SampleRectangle(Vector2 topLeft, Vector2 lowerRight, float minimumDistance, - int pointsPerIteration = DefaultPointsPerIteration) - { - return Sample(topLeft, lowerRight, null, minimumDistance, pointsPerIteration); - } - - /// - /// Samples for points within the given rectangle, with an optional rejection distance. - /// - /// The top left of the rectangle - /// The bottom right of the rectangle - /// The distance at which points will be discarded, if any - /// Minimum distance between points. Must be above 0! - /// The number of points placed per iteration of the algorithm - /// An enumerator of points - public SampleEnumerator Sample(Vector2 topLeft, Vector2 lowerRight, float? rejectionDistance, - float minimumDistance, int pointsPerIteration) - { - // This still doesn't guard against dangerously low but non-zero distances, but this will do for now. - DebugTools.Assert(minimumDistance > 0, "Minimum distance must be above 0, or else an infinite number of points would be generated."); - - var settings = new SampleSettings - { - TopLeft = topLeft, LowerRight = lowerRight, - Dimensions = lowerRight - topLeft, - Center = (topLeft + lowerRight) / 2, - CellSize = minimumDistance / (float) Math.Sqrt(2), - MinimumDistance = minimumDistance, - RejectionSqDistance = rejectionDistance * rejectionDistance - }; - - settings.GridWidth = (int) (settings.Dimensions.X / settings.CellSize) + 1; - settings.GridHeight = (int) (settings.Dimensions.Y / settings.CellSize) + 1; - - var state = new State - { - Grid = new Vector2?[settings.GridWidth, settings.GridHeight], - ActivePoints = new List() - }; - - return new SampleEnumerator(this, state, settings, pointsPerIteration); - } - - private Vector2 AddFirstPoint(ref SampleSettings settings, ref State state) - { - while (true) - { - var d = _random.NextDouble(); - var xr = settings.TopLeft.X + settings.Dimensions.X * d; - - d = _random.NextDouble(); - var yr = settings.TopLeft.Y + settings.Dimensions.Y * d; - - var p = new Vector2((float) xr, (float) yr); - if (settings.RejectionSqDistance != null && - (settings.Center - p).LengthSquared() > settings.RejectionSqDistance) - continue; - - var index = Denormalize(p, settings.TopLeft, settings.CellSize); - - state.Grid[(int) index.X, (int) index.Y] = p; - - state.ActivePoints.Add(p); - return p; - } - } - - private Vector2? AddNextPoint(Vector2 point, ref SampleSettings settings, ref State state) - { - var q = GenerateRandomAround(point, settings.MinimumDistance); - - if (q.X >= settings.TopLeft.X && q.X < settings.LowerRight.X && - q.Y > settings.TopLeft.Y && q.Y < settings.LowerRight.Y && - (settings.RejectionSqDistance == null || - (settings.Center - q).LengthSquared() <= settings.RejectionSqDistance)) - { - var qIndex = Denormalize(q, settings.TopLeft, settings.CellSize); - var tooClose = false; - - for (var i = (int) Math.Max(0, qIndex.X - 2); - i < Math.Min(settings.GridWidth, qIndex.X + 3) && !tooClose; - i++) - for (var j = (int) Math.Max(0, qIndex.Y - 2); - j < Math.Min(settings.GridHeight, qIndex.Y + 3) && !tooClose; - j++) - { - if (state.Grid[i, j].HasValue && (state.Grid[i, j]!.Value - q).Length() < settings.MinimumDistance) - tooClose = true; - } - - if (!tooClose) - { - state.ActivePoints.Add(q); - state.Grid[(int) qIndex.X, (int) qIndex.Y] = q; - return q; - } - } - - return null; - } - - private Vector2 GenerateRandomAround(Vector2 center, float minimumDistance) - { - var d = _random.NextDouble(); - var radius = minimumDistance + minimumDistance * d; - - d = _random.NextDouble(); - var angle = Math.PI * 2 * d; - - var newX = radius * Math.Sin(angle); - var newY = radius * Math.Cos(angle); - - return new Vector2((float) (center.X + newX), (float) (center.Y + newY)); - } - - private static Vector2 Denormalize(Vector2 point, Vector2 origin, double cellSize) - { - return new Vector2((int) ((point.X - origin.X) / cellSize), (int) ((point.Y - origin.Y) / cellSize)); - } - - public struct SampleEnumerator - { - private PoissonDiskSampler _pds; - private State _state; - private SampleSettings _settings; - // These variables make up the state machine. - private bool _returnedFirstPoint; - private int _pointsPerIteration; - private int _iterationListIndex; - private bool _iterationFound; - private int _iterationPosition; - - // This has internal access because C# nested type access is being weird. - internal SampleEnumerator(PoissonDiskSampler pds, State state, SampleSettings settings, int ppi) - { - _pds = pds; - _state = state; - _settings = settings; - _pointsPerIteration = ppi; - } - - public bool MoveNext([NotNullWhen(true)] out Vector2? point) - { - // First point is chosen via a very particular method. - if (!_returnedFirstPoint) - { - _returnedFirstPoint = true; - point = _pds.AddFirstPoint(ref _settings, ref _state); - return true; - } - - // Remaining points have to be fed out carefully. - // We can be interrupted (by a successful point) mid-stream. - while (_state.ActivePoints.Count != 0) - { - if (_iterationPosition == 0) - { - // First point of iteration. - _iterationListIndex = _pds._random.Next(_state.ActivePoints.Count); - _iterationFound = false; - } - - var basePoint = _state.ActivePoints[_iterationListIndex]; - - point = _pds.AddNextPoint(basePoint, ref _settings, ref _state); - - // Set this now, return later after processing is complete. - _iterationFound |= point != null; - - // Iteration loop advance. - _iterationPosition++; - if (_iterationPosition == _pointsPerIteration) - { - // Reached end of this iteration. - _iterationPosition = 0; - if (!_iterationFound) - _state.ActivePoints.RemoveAt(_iterationListIndex); - } - - if (point != null) - return true; - } - point = null; - return false; - } - } - - internal struct State - { - public Vector2?[,] Grid; - public List ActivePoints; - } - - internal struct SampleSettings - { - public Vector2 TopLeft, LowerRight, Center; - public Vector2 Dimensions; - public float? RejectionSqDistance; - public float MinimumDistance; - public float CellSize; - public int GridWidth, GridHeight; - } -} - - - diff --git a/Content.Server/Worldgen/WorldGen.cs b/Content.Server/Worldgen/WorldGen.cs deleted file mode 100644 index 1ed20b9f1f..0000000000 --- a/Content.Server/Worldgen/WorldGen.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Diagnostics.Contracts; -using System.Numerics; - -namespace Content.Server.Worldgen; - -/// -/// Contains a few world-generation related constants and static functions. -/// -public static class WorldGen -{ - /// - /// The size of each chunk (isn't that self-explanatory.) - /// Be careful about how small you make this. - /// - public const int ChunkSize = 128; - - /// - /// Converts world coordinates to chunk coordinates. - /// - /// World coordinates - /// Chunk coordinates - [Pure] - public static Vector2i WorldToChunkCoords(Vector2i inp) - { - return (inp * new Vector2(1.0f / ChunkSize, 1.0f / ChunkSize)).Floored(); - } - - /// - /// Converts world coordinates to chunk coordinates. - /// - /// World coordinates - /// Chunk coordinates - [Pure] - public static Vector2 WorldToChunkCoords(Vector2 inp) - { - return inp * new Vector2(1.0f / ChunkSize, 1.0f / ChunkSize); - } - - /// - /// Converts chunk coordinates to world coordinates. - /// - /// Chunk coordinates - /// World coordinates - [Pure] - public static Vector2 ChunkToWorldCoords(Vector2i inp) - { - return inp * ChunkSize; - } - - /// - /// Converts chunk coordinates to world coordinates. - /// - /// Chunk coordinates - /// World coordinates - [Pure] - public static Vector2 ChunkToWorldCoords(Vector2 inp) - { - return inp * ChunkSize; - } - - /// - /// Converts chunk coordinates to world coordinates, getting the center of the chunk. - /// - /// Chunk coordinates - /// World coordinates - [Pure] - public static Vector2 ChunkToWorldCoordsCentered(Vector2i inp) - { - return inp * ChunkSize + Vector2i.One * (ChunkSize / 2); - } -} - diff --git a/Content.Shared/Access/Components/IdCardConsoleComponent.cs b/Content.Shared/Access/Components/IdCardConsoleComponent.cs index 35af5972c3..89c24940ba 100644 --- a/Content.Shared/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Shared/Access/Components/IdCardConsoleComponent.cs @@ -26,9 +26,9 @@ public sealed partial class IdCardConsoleComponent : Component public readonly string FullName; public readonly string JobTitle; public readonly List> AccessList; - public readonly ProtoId JobPrototype; + public readonly ProtoId? JobPrototype; - public WriteToTargetIdMessage(string fullName, string jobTitle, List> accessList, ProtoId jobPrototype) + public WriteToTargetIdMessage(string fullName, string jobTitle, List> accessList, ProtoId? jobPrototype) { FullName = fullName; JobTitle = jobTitle; diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 10d303cd1d..12af967526 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -138,8 +138,19 @@ namespace Content.Shared.Atmos /// public const float MinimumAirToSuspend = (MolesCellStandard * MinimumAirRatioToSuspend); - public const float MinimumTemperatureToMove = (T20C + 100f); + /// + /// The minimum difference in temperature between s + /// (s) required + /// for LINDA to report a pressure difference between them for space wind. + /// In Kelvin. + /// + public const float MinimumTemperatureToMove = 5f; + /// + /// The minimum difference in moles between s + /// (s) required for LINDA to + /// report a pressure difference between them for space wind. + /// public const float MinimumMolesDeltaToMove = (MolesCellStandard * MinimumAirRatioToMove); /// @@ -170,22 +181,6 @@ namespace Content.Shared.Atmos /// public const float SpaceHeatCapacity = 7000f; - /// - /// Dictionary of chemical abbreviations for - /// - public static Dictionary GasAbbreviations = new Dictionary() - { - [Gas.Ammonia] = Loc.GetString("gas-ammonia-abbreviation"), - [Gas.CarbonDioxide] = Loc.GetString("gas-carbon-dioxide-abbreviation"), - [Gas.Frezon] = Loc.GetString("gas-frezon-abbreviation"), - [Gas.Nitrogen] = Loc.GetString("gas-nitrogen-abbreviation"), - [Gas.NitrousOxide] = Loc.GetString("gas-nitrous-oxide-abbreviation"), - [Gas.Oxygen] = Loc.GetString("gas-oxygen-abbreviation"), - [Gas.Plasma] = Loc.GetString("gas-plasma-abbreviation"), - [Gas.Tritium] = Loc.GetString("gas-tritium-abbreviation"), - [Gas.WaterVapor] = Loc.GetString("gas-water-vapor-abbreviation"), - }; - #region Excited Groups /// @@ -235,8 +230,8 @@ namespace Content.Shared.Atmos public const float SuperSaturationEnds = SuperSaturationThreshold / 3; public const float OxygenBurnRateBase = 1.4f; - public const float PlasmaMinimumBurnTemperature = (100f+T0C); - public const float PlasmaUpperTemperature = (1370f+T0C); + public const float PlasmaMinimumBurnTemperature = 100f + T0C; + public const float PlasmaUpperTemperature = 1370f + T0C; public const float PlasmaOxygenFullburn = 10f; public const float PlasmaBurnRateDelta = 9f; diff --git a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs index 115cb54892..cc3914fe67 100644 --- a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs +++ b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs @@ -1,101 +1,103 @@ using Robust.Shared.GameStates; -using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.Atmos.Components; +/// +/// Used for gas analyzers, an item that shows players the gas contents of an atmos +/// device they use it on or of the tile they are standing on. +/// [RegisterComponent, NetworkedComponent] public sealed partial class GasAnalyzerComponent : Component { - [ViewVariables] + /// + /// The target entity currently being analyzed. + /// + [DataField] public EntityUid? Target; - [ViewVariables] - public EntityUid User; + /// + /// The current user of the gas analyzer. + /// + [DataField] + public EntityUid? User; - [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + /// + /// Is the analyzer currently active? + /// + [DataField] public bool Enabled; - - [Serializable, NetSerializable] - public enum GasAnalyzerUiKey - { - Key, - } - - /// - /// Atmospheric data is gathered in the system and sent to the user - /// - [Serializable, NetSerializable] - public sealed class GasAnalyzerUserMessage : BoundUserInterfaceMessage - { - public string DeviceName; - public NetEntity DeviceUid; - public bool DeviceFlipped; - public string? Error; - public GasMixEntry[] NodeGasMixes; - public GasAnalyzerUserMessage(GasMixEntry[] nodeGasMixes, string deviceName, NetEntity deviceUid, bool deviceFlipped, string? error = null) - { - NodeGasMixes = nodeGasMixes; - DeviceName = deviceName; - DeviceUid = deviceUid; - DeviceFlipped = deviceFlipped; - Error = error; - } - } - - /// - /// Contains information on a gas mix entry, turns into a tab in the UI - /// - [Serializable, NetSerializable] - public struct GasMixEntry - { - /// - /// Name of the tab in the UI - /// - public readonly string Name; - public readonly float Volume; - public readonly float Pressure; - public readonly float Temperature; - public readonly GasEntry[]? Gases; - - public GasMixEntry(string name, float volume, float pressure, float temperature, GasEntry[]? gases = null) - { - Name = name; - Volume = volume; - Pressure = pressure; - Temperature = temperature; - Gases = gases; - } - } - - /// - /// Individual gas entry data for populating the UI - /// - [Serializable, NetSerializable] - public struct GasEntry - { - public readonly string Name; - public readonly float Amount; - public readonly string Color; - - public GasEntry(string name, float amount, string color) - { - Name = name; - Amount = amount; - Color = color; - } - - public override string ToString() - { - // e.g. "Plasma: 2000 mol" - return Loc.GetString( - "gas-entry-info", - ("gasName", Name), - ("gasAmount", Amount)); - } - } } +/// +/// Atmospheric data is gathered in the system and sent to the user. +/// +[Serializable, NetSerializable] +public sealed class GasAnalyzerUserMessage(GasMixEntry[] nodeGasMixes, string deviceName, NetEntity deviceUid, bool deviceFlipped) : BoundUserInterfaceMessage +{ + public string DeviceName = deviceName; + public NetEntity DeviceUid = deviceUid; + public bool DeviceFlipped = deviceFlipped; + public GasMixEntry[] NodeGasMixes = nodeGasMixes; +} + +/// +/// Contains information on a gas mix entry, turns into a tab in the UI. +/// +[Serializable, NetSerializable] +public readonly record struct GasMixEntry(string Name, float Volume, float Pressure, float Temperature, GasEntry[]? Gases = null) +{ + /// + /// Name of the tab in the UI. + /// + public readonly string Name = Name; + /// + /// Volume of this gas mixture. + /// + public readonly float Volume = Volume; + /// + /// Pressure of this gas mixture. + /// + public readonly float Pressure = Pressure; + /// + /// Temperature of this gas mixture. + /// + public readonly float Temperature = Temperature; + /// + /// The gases contained in this gas mixture. + /// The gases below a certain mol threshold are not included. + /// + public readonly GasEntry[]? Gases = Gases; +} + +/// +/// Individual gas entry data for populating the UI. +/// +[Serializable, NetSerializable] +public readonly record struct GasEntry(Gas Gas, float Amount) +{ + /// + /// The gas this entry represents. + /// + public readonly Gas Gas = Gas; + /// + /// The gas amount in mol. + /// + public readonly float Amount = Amount; +} + +/// +/// Key for the GasAnalyzerBoundUserInterface. +/// +[Serializable, NetSerializable] +public enum GasAnalyzerUiKey +{ + Key, +} + +/// +/// Individual gas entry data for populating the UI +/// [Serializable, NetSerializable] public enum GasAnalyzerVisuals : byte { diff --git a/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs index c7267393dd..99b2942f13 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs @@ -16,10 +16,10 @@ public abstract partial class SharedAtmosphereSystem */ /// - /// Cached array of gas specific heats. + /// Cached array of molar heat capacities of the gases. /// - public float[] GasSpecificHeats => _gasSpecificHeats; - private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases]; + public float[] GasMolarHeatCapacities => _gasMolarHeatCapacities; + private float[] _gasMolarHeatCapacities = new float[Atmospherics.TotalNumberOfGases]; /// /// Mask used to determine if a gas is flammable or not. @@ -69,7 +69,7 @@ public abstract partial class SharedAtmosphereSystem GasReagents[idx] = gasPrototype.Reagent; } - Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4)); + Array.Resize(ref _gasMolarHeatCapacities, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4)); for (var i = 0; i < GasPrototypes.Length; i++) { @@ -81,7 +81,7 @@ public abstract partial class SharedAtmosphereSystem If you would like the unscaled specific heat, you'd need to multiply by HeatScale again. TODO ATMOS: please just make this 2 separate arrays instead of invoking multiplication every time. */ - _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale; + _gasMolarHeatCapacities[i] = GasPrototypes[i].MolarHeatCapacity / HeatScale; // """Mask""" built here. Used to determine if a gas is fuel/oxidizer or not decently quickly and clearly. GasFuelMask[i] = GasPrototypes[i].IsFuel ? 1 : 0; diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index 23ea8fa8fa..e9c697c53c 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -31,8 +31,7 @@ public abstract class SharedGasTileOverlaySystem : EntitySystem for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var gasPrototype = _atmosphere.GetGas(i); - if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture) || - (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))) + if (gasPrototype.GasOverlaySprite != null) visibleGases.Add(i); } VisibleGasId = visibleGases.ToArray(); diff --git a/Content.Shared/Atmos/Prototypes/GasPrototype.cs b/Content.Shared/Atmos/Prototypes/GasPrototype.cs index 86304dfdda..bb92d0307d 100644 --- a/Content.Shared/Atmos/Prototypes/GasPrototype.cs +++ b/Content.Shared/Atmos/Prototypes/GasPrototype.cs @@ -1,103 +1,122 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Reagent; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Utility; -namespace Content.Shared.Atmos.Prototypes +namespace Content.Shared.Atmos.Prototypes; + +/// +/// Prototype defining a gas for atmospherics. +/// +/// +/// The total number of gases is hardcoded in a bunch of places. +/// If you add any new ones, make sure to also adjust the constants in accordingly. +/// +[Prototype] +public sealed partial class GasPrototype : IPrototype { - [Prototype] - public sealed partial class GasPrototype : IPrototype - { - [DataField("name")] public string Name { get; set; } = ""; + // TODO: Add interfaces for gas behaviours e.g. breathing, burning - // TODO: Control gas amount necessary for overlay to appear - // TODO: Add interfaces for gas behaviours e.g. breathing, burning + /// + [IdDataField] + public string ID { get; private set; } = default!; - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; + /// + /// The name of the gas as shown to the player. + /// + [DataField(required: true)] + public LocId Name; - /// - /// Specific heat for gas. - /// - [DataField("specificHeat")] - public float SpecificHeat { get; private set; } + /// + /// The abbreviation of the name. For example O₂ for Oxygen. + /// Used for UI purposes. + /// + [DataField(required: true)] + public LocId Abbreviation; - /// - /// Heat capacity ratio for gas - /// - [DataField("heatCapacityRatio")] - public float HeatCapacityRatio { get; private set; } = 1.4f; + /// + /// The molar heat capacity of this gas, in J/(K * mol). + /// Describes how much heat energy is needed to heat up this gas by one Kelvin. + /// Or in other words, the higher this number is the more energy this gas can store. + /// + /// + /// This will be divided by the cvar. + /// + [DataField] + public float MolarHeatCapacity; - /// - /// Molar mass of gas - /// - [DataField("molarMass")] - public float MolarMass { get; set; } = 1f; + /// + /// Heat capacity ratio for gas. + /// TODO: Make gas pumps do proper adiabatic compression so that this is actually used. + /// + [DataField] + public float HeatCapacityRatio = 1.4f; + + /// + /// Molar mass of the gas. + /// TODO: This is not used anywhere, do we even need this? + /// + [DataField] + public float MolarMass = 1f; - /// - /// Minimum amount of moles for this gas to be visible. - /// - [DataField("gasMolesVisible")] - public float GasMolesVisible { get; private set; } = 0.25f; + /// + /// Minimum amount of moles for this gas to be visible. + /// + [DataField] + public float GasMolesVisible = 0.25f; - /// - /// Visibility for this gas will be max after this value. - /// - public float GasMolesVisibleMax => GasMolesVisible * GasVisibilityFactor; + /// + /// Visibility for this gas will be max after this value. + /// + [ViewVariables] + public float GasMolesVisibleMax => GasMolesVisible * GasVisibilityFactor; - [DataField("gasVisbilityFactor")] - public float GasVisibilityFactor = Atmospherics.FactorGasVisibleMax; + /// + /// Multiplier that decides when a gas will be at maximum visibility. + /// + [DataField] + public float GasVisibilityFactor = Atmospherics.FactorGasVisibleMax; - /// - /// If this reagent is in gas form, this is the path to the overlay that will be used to make the gas visible. - /// - [DataField("gasOverlayTexture")] - public string GasOverlayTexture { get; private set; } = string.Empty; + /// + /// Sprite to show in the gas overlay if this gas is present on a tile. + /// If null the gas will be invisible. + /// + [DataField] + public SpriteSpecifier? GasOverlaySprite; - /// - /// If this reagent is in gas form, this will be the path to the RSI sprite that will be used to make the gas visible. - /// - [DataField("gasOverlayState")] - public string GasOverlayState { get; set; } = string.Empty; + /// + /// The reagent that this gas will turn into when inhaled or condensed. + /// + [DataField] + public ProtoId? Reagent; - /// - /// State for the gas RSI overlay. - /// - [DataField("gasOverlaySprite")] - public string GasOverlaySprite { get; set; } = string.Empty; + /// + /// The color of the gas used for UI purposes. + /// + [DataField] + public Color Color = Color.White; - /// - /// Path to the tile overlay used when this gas appears visible. - /// - [DataField("overlayPath")] - public string OverlayPath { get; private set; } = string.Empty; + /// + /// The price per mole when this gas is sold at cargo. + /// The final price will also depend on the purity of the gas mixture. + /// + [DataField] + public float PricePerMole = 0; - /// - /// The reagent that this gas will turn into when inhaled. - /// - [DataField("reagent", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? Reagent { get; private set; } = default!; + /// + /// Whether the gas is considered to be flammable. + /// This is used generically across Atmospherics to determine + /// if things like hotspots are allowed to ignite if an + /// oxidizer is present. + /// + [DataField] + public bool IsFuel; - [DataField("color")] public string Color { get; private set; } = string.Empty; - - [DataField("pricePerMole")] - public float PricePerMole { get; set; } = 0; - - /// - /// Whether the gas is considered to be flammable. - /// This is used generically across Atmospherics to determine - /// if things like hotspots are allowed to ignite if an - /// oxidizer is present. - /// - [DataField] - public bool IsFuel; - - /// - /// Whether the gas is considered to be an oxidizer. - /// Same reasoning as but for oxidizers. - /// - [DataField] - public bool IsOxidizer; - } + /// + /// Whether the gas is considered to be an oxidizer. + /// Same reasoning as but for oxidizers. + /// + [DataField] + public bool IsOxidizer; } diff --git a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs index ec0521b7b6..bde65b99fe 100644 --- a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs +++ b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs @@ -278,7 +278,8 @@ public abstract class SharedBloodstreamSystem : EntitySystem if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution)) { SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value); - TryModifyBloodLevel(ent.AsNullable(), ent.Comp.BloodReferenceSolution.Volume); + // TODO: Use Solutions API for this when it exists + TryRegulateBloodLevel(ent.AsNullable(), ent.Comp.BloodReferenceSolution.Volume); } } @@ -403,12 +404,15 @@ public abstract class SharedBloodstreamSystem : EntitySystem || amount == 0) return false; + // TODO: Either make this percentage based regeneration and pre-pass the percentage. + // TODO: Solution regulation API that doesn't result in very minor FixedPoint2 errors (Currently gingerbreadman only regenerates 0.99u instead of 1.00u) referenceFactor = Math.Clamp(referenceFactor, 0f, ent.Comp.MaxVolumeModifier); + var ratio = (float)amount / (float)ent.Comp.BloodReferenceSolution.Volume; foreach (var (referenceReagent, referenceQuantity) in ent.Comp.BloodReferenceSolution) { var error = referenceQuantity * referenceFactor - bloodSolution.GetTotalPrototypeQuantity(referenceReagent.Prototype); - var adjustedAmount = amount * referenceQuantity / ent.Comp.BloodReferenceSolution.Volume; + var adjustedAmount = referenceQuantity * ratio; if (error > 0) { diff --git a/Content.Shared/CCVar/CCVars.Worldgen.cs b/Content.Shared/CCVar/CCVars.Worldgen.cs deleted file mode 100644 index da165ce74a..0000000000 --- a/Content.Shared/CCVar/CCVars.Worldgen.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Configuration; - -namespace Content.Shared.CCVar; - -public sealed partial class CCVars -{ - /// - /// Whether or not world generation is enabled. - /// - public static readonly CVarDef WorldgenEnabled = - CVarDef.Create("worldgen.enabled", false, CVar.SERVERONLY); - - /// - /// The worldgen config to use. - /// - public static readonly CVarDef WorldgenConfig = - CVarDef.Create("worldgen.worldgen_config", "Default", CVar.SERVERONLY); -} diff --git a/Content.Shared/Cargo/Components/TradeStationComponent.cs b/Content.Shared/Cargo/Components/TradeStationComponent.cs new file mode 100644 index 0000000000..ec0cba5386 --- /dev/null +++ b/Content.Shared/Cargo/Components/TradeStationComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.HijackBeacon; +using Robust.Shared.GameStates; + +namespace Content.Shared.Cargo.Components; + +/// +/// Target for approved orders to spawn at. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TradeStationComponent : Component +{ + /// + /// The Trade Station's current hijack state. Modified by HijackBeaconSystem. + /// + [DataField, AutoNetworkedField] + public bool Hacked = false; +} diff --git a/Content.Shared/Cargo/SharedCargoSystem.cs b/Content.Shared/Cargo/SharedCargoSystem.cs index 9d044d1850..291d3abbc3 100644 --- a/Content.Shared/Cargo/SharedCargoSystem.cs +++ b/Content.Shared/Cargo/SharedCargoSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Prototypes; +using Content.Shared.HijackBeacon; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -17,6 +18,7 @@ public abstract class SharedCargoSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnHijackSuccess); } private void OnMapInit(Entity ent, ref MapInitEvent args) @@ -25,6 +27,23 @@ public abstract class SharedCargoSystem : EntitySystem Dirty(ent); } + private void OnHijackSuccess(ref HijackBeaconSuccessEvent args) + { + var stationQuery = EntityQueryEnumerator(); + while (stationQuery.MoveNext(out var uid, out var comp)) + { + foreach (var (account, cash) in comp.Accounts) + { + comp.Accounts[account] = cash - args.Fine; + args.Total += args.Fine; + } + + var ev = new BankBalanceUpdatedEvent(uid, comp.Accounts); + RaiseLocalEvent(uid, ref ev, true); + Dirty(uid, comp); + } + } + /// /// For a given station, retrieves the balance in a specific account. /// diff --git a/Content.Shared/Changeling/ChangelingTransformEvents.cs b/Content.Shared/Changeling/ChangelingTransformEvents.cs index 9940a60705..9a4ff4454e 100644 --- a/Content.Shared/Changeling/ChangelingTransformEvents.cs +++ b/Content.Shared/Changeling/ChangelingTransformEvents.cs @@ -14,3 +14,26 @@ public sealed partial class ChangelingTransformActionEvent : InstantActionEvent; /// [Serializable, NetSerializable] public sealed partial class ChangelingTransformDoAfterEvent : SimpleDoAfterEvent; + +/// +/// Raised on a changeling before they transform into a stored identity. +/// This is raised after the DoAfter finished. +/// +public readonly record struct BeforeChangelingTransformEvent(EntityUid StoredIdentity) +{ + /// + /// The stored identity the changeling will transform into. + /// + public readonly EntityUid StoredIdentity = StoredIdentity; +}; + +/// +/// Raised on a changeling after they successfully transformed into a stored identity. +/// +public readonly record struct AfterChangelingTransformEvent(EntityUid StoredIdentity) +{ + /// + /// The stored identity the changeling transformed into. + /// + public readonly EntityUid StoredIdentity = StoredIdentity; +}; diff --git a/Content.Shared/Changeling/Components/ChangelingDevourComponent.cs b/Content.Shared/Changeling/Components/ChangelingDevourComponent.cs index f2c5c82ca9..f0a7bb44ba 100644 --- a/Content.Shared/Changeling/Components/ChangelingDevourComponent.cs +++ b/Content.Shared/Changeling/Components/ChangelingDevourComponent.cs @@ -1,12 +1,10 @@ using Content.Shared.Changeling.Systems; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; -using Content.Shared.FixedPoint; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Changeling.Components; @@ -14,24 +12,24 @@ namespace Content.Shared.Changeling.Components; /// Component responsible for Changelings Devour attack. Including the amount of damage /// and how long it takes to devour someone /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [Access(typeof(ChangelingDevourSystem))] public sealed partial class ChangelingDevourComponent : Component { /// - /// The Action for devouring + /// The action for devouring. /// [DataField] public EntProtoId? ChangelingDevourAction = "ActionChangelingDevour"; /// - /// The action entity associated with devouring + /// The action entity associated with devouring. /// [DataField, AutoNetworkedField] public EntityUid? ChangelingDevourActionEntity; /// - /// The whitelist of targets for devouring + /// The whitelist of targets for devouring. /// [DataField, AutoNetworkedField] public EntityWhitelist? Whitelist = new() @@ -44,7 +42,7 @@ public sealed partial class ChangelingDevourComponent : Component }; /// - /// The Sound to use during consumption of a victim + /// The sound to use during consumption of a victim. /// /// /// 6 distance due to the default 15 being hearable all the way across PVS. Changeling is meant to be stealthy. @@ -54,7 +52,7 @@ public sealed partial class ChangelingDevourComponent : Component public SoundSpecifier? ConsumeNoise = new SoundCollectionSpecifier("ChangelingDevourConsume", AudioParams.Default.WithMaxDistance(6)); /// - /// The Sound to use during the windup before consuming a victim + /// The sound to use during the windup before consuming a victim. /// /// /// 6 distance due to the default 15 being hearable all the way across PVS. Changeling is meant to be stealthy. @@ -64,36 +62,31 @@ public sealed partial class ChangelingDevourComponent : Component public SoundSpecifier? DevourWindupNoise = new SoundCollectionSpecifier("ChangelingDevourWindup", AudioParams.Default.WithMaxDistance(6)); /// - /// The time between damage ticks - /// - [DataField, AutoNetworkedField] - public TimeSpan DamageTimeBetweenTicks = TimeSpan.FromSeconds(1); - - /// - /// The windup time before the changeling begins to engage in devouring the identity of a target + /// The windup time before the changeling begins to engage in devouring the identity of a target. /// [DataField, AutoNetworkedField] public TimeSpan DevourWindupTime = TimeSpan.FromSeconds(2); /// - /// The time it takes to FULLY consume someones identity. + /// The time it takes to consume someones identity. + /// Starts after the windup. /// [DataField, AutoNetworkedField] public TimeSpan DevourConsumeTime = TimeSpan.FromSeconds(10); /// - /// The Currently active devour sound in the world + /// The currently active devour sound in the world. /// [DataField] public EntityUid? CurrentDevourSound; /// - /// The damage profile for a single tick of devour damage + /// The damage dealt after the windup finished and devouring started. /// [DataField, AutoNetworkedField] - public DamageSpecifier DamagePerTick = new() + public DamageSpecifier WindupDamage = new() { - DamageDict = new () + DamageDict = new() { { "Slash", 10}, { "Piercing", 10 }, @@ -102,7 +95,21 @@ public sealed partial class ChangelingDevourComponent : Component }; /// - /// The list of protective damage types capable of preventing a devour if over the threshold + /// The damage dealt after the devouring is fully finished. + /// + [DataField, AutoNetworkedField] + public DamageSpecifier DevourDamage = new() + { + DamageDict = new() + { + { "Slash", 20}, + { "Piercing", 20 }, + { "Blunt", 10 }, + }, + }; + + /// + /// The list of protective damage types capable of preventing a devour if over the threshold. /// [DataField, AutoNetworkedField] public List> ProtectiveDamageTypes = new() @@ -113,13 +120,7 @@ public sealed partial class ChangelingDevourComponent : Component }; /// - /// The next Tick to deal damage on (utilized during the consumption "do-during" (a do after with an attempt event)) - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] - public TimeSpan NextTick = TimeSpan.Zero; - - /// - /// The percentage of ANY brute damage resistance that will prevent devouring + /// The percentage of ANY brute damage resistance that will prevent devouring. /// [DataField, AutoNetworkedField] public float DevourPreventionPercentageThreshold = 0.1f; diff --git a/Content.Shared/Changeling/Components/ChangelingDevouredComponent.cs b/Content.Shared/Changeling/Components/ChangelingDevouredComponent.cs new file mode 100644 index 0000000000..c235ea071a --- /dev/null +++ b/Content.Shared/Changeling/Components/ChangelingDevouredComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling.Components; + +/// +/// Component used for marking entities devoured by a changeling. +/// Used to prevent granting the identity several times. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ChangelingDevouredComponent : Component +{ + /// + /// HashSet of all changelings that have devoured this entity. + /// + // TODO: This should be using some sort of relation system in the future. + [DataField, AutoNetworkedField] + public HashSet DevouredBy = new(); +} diff --git a/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs b/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs index 8e74f83537..fa667f4bf2 100644 --- a/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs +++ b/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs @@ -13,11 +13,12 @@ public sealed partial class ChangelingIdentityComponent : Component { /// /// The list of entities that exist on a paused map. They are paused clones of the victims that the ling has consumed, with all relevant components copied from the original. + /// The key is the EntityUid of the stored identity, the value is the original entity the identity came from. + /// The value will be set to null if that entity is deleted. /// - // TODO: Store a reference to the original entity as well so you cannot infinitely devour somebody. Currently very tricky due the inability to send over EntityUid if the original is ever deleted. Can be fixed by something like WeakEntityReference. + // TODO: This should be handled via a relation system in the future. [DataField, AutoNetworkedField] - public List ConsumedIdentities = new(); - + public Dictionary ConsumedIdentities = new(); /// /// The currently assumed identity. diff --git a/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs b/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs index 3406038e9c..b9bdfa29c7 100644 --- a/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs +++ b/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs @@ -2,9 +2,7 @@ using Content.Shared.Actions; using Content.Shared.Administration.Logs; using Content.Shared.Armor; using Content.Shared.Atmos.Rotting; -using Content.Shared.Body; using Content.Shared.Changeling.Components; -using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.DoAfter; @@ -19,13 +17,11 @@ using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; using Robust.Shared.Random; -using Robust.Shared.Timing; namespace Content.Shared.Changeling.Systems; public sealed class ChangelingDevourSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; @@ -47,7 +43,6 @@ public sealed class ChangelingDevourSystem : EntitySystem SubscribeLocalEvent(OnDevourAction); SubscribeLocalEvent(OnDevourWindup); SubscribeLocalEvent(OnDevourConsume); - SubscribeLocalEvent>(OnConsumeAttemptTick); SubscribeLocalEvent(OnShutdown); } @@ -64,31 +59,197 @@ public sealed class ChangelingDevourSystem : EntitySystem } } - //TODO: Allow doafters to have proper update loop support. Attempt events should not be doing state changes. - private void OnConsumeAttemptTick(Entity ent, - ref DoAfterAttemptEvent eventData) + // The action was used. + // Start the first doafter for the windup. + private void OnDevourAction(Entity ent, ref ChangelingDevourActionEvent args) { - - var curTime = _timing.CurTime; - - if (curTime < ent.Comp.NextTick) + if (args.Handled + || _whitelistSystem.IsWhitelistFailOrNull(ent.Comp.Whitelist, args.Target) + || !HasComp(ent)) return; - ConsumeDamageTick(eventData.Event.Target, ent.Comp, eventData.Event.User); - ent.Comp.NextTick += ent.Comp.DamageTimeBetweenTicks; - Dirty(ent, ent.Comp); + args.Handled = true; + var target = args.Target; + + if (!CanDevour(ent.AsNullable(), target)) + return; + + if (_net.IsServer) + { + ent.Comp.CurrentDevourSound = _audio.Stop(ent.Comp.CurrentDevourSound); + ent.Comp.CurrentDevourSound = _audio.PlayPvs(ent.Comp.DevourWindupNoise, ent)?.Entity; + } + + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ent:player} started changeling devour windup against {target:player}"); + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, ent, ent.Comp.DevourWindupTime, new ChangelingDevourWindupDoAfterEvent(), ent, target: target, used: ent) + { + BreakOnMove = true, + CancelDuplicate = true, + DuplicateCondition = DuplicateConditions.None, + }); + + var selfMessage = Loc.GetString("changeling-devour-begin-windup-self", ("user", Identity.Entity(ent.Owner, EntityManager))); + var othersMessage = Loc.GetString("changeling-devour-begin-windup-others", ("user", Identity.Entity(ent.Owner, EntityManager))); + _popupSystem.PopupPredicted( + selfMessage, + othersMessage, + args.Performer, + args.Performer, + PopupType.MediumCaution); } - private void ConsumeDamageTick(EntityUid? target, ChangelingDevourComponent comp, EntityUid? user) + // First doafter finished. + // Start the second doafter for the actual consumption and deal a small amount of damage. + private void OnDevourWindup(Entity ent, ref ChangelingDevourWindupDoAfterEvent args) { - if (target == null) + args.Handled = true; + ent.Comp.CurrentDevourSound = _audio.Stop(ent.Comp.CurrentDevourSound); + + if (args.Cancelled) return; - _damageable.ChangeDamage(target.Value, comp.DamagePerTick, true, true, user); + if (args.Target is not { } target) + return; + + _damageable.ChangeDamage(target, ent.Comp.WindupDamage, true, true, ent.Owner); + + var selfMessage = Loc.GetString("changeling-devour-begin-consume-self", ("user", Identity.Entity(ent.Owner, EntityManager))); + var othersMessage = Loc.GetString("changeling-devour-begin-consume-others", ("user", Identity.Entity(ent.Owner, EntityManager))); + _popupSystem.PopupPredicted( + selfMessage, + othersMessage, + ent.Owner, + ent.Owner, + PopupType.LargeCaution); + + if (_net.IsServer) + ent.Comp.CurrentDevourSound = _audio.PlayPvs(ent.Comp.ConsumeNoise, ent)?.Entity; + + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} began to devour {ToPrettyString(target):player}'s identity"); + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, + ent, + ent.Comp.DevourConsumeTime, + new ChangelingDevourConsumeDoAfterEvent(), + ent, + target: target, + used: ent) + { + BreakOnMove = true, + CancelDuplicate = true, + DuplicateCondition = DuplicateConditions.None, + }); + } + + // Second doafter finished. + // Save the identity and deal more damage. + private void OnDevourConsume(Entity ent, ref ChangelingDevourConsumeDoAfterEvent args) + { + args.Handled = true; + ent.Comp.CurrentDevourSound = _audio.Stop(ent.Comp.CurrentDevourSound); + + if (args.Cancelled) + return; + + if (args.Target is not { } target) + return; + + // Damage first before the CanDevour check to make sure they don't gib in-between and to kill them again in case they somehow revived. + _damageable.ChangeDamage(target, ent.Comp.DevourDamage, true, true, ent.Owner); + + if (!CanDevour(ent.AsNullable(), target)) // Check again if the conditions are still met. + return; + + var selfMessage = Loc.GetString("changeling-devour-consume-complete-self", ("user", Identity.Entity(ent.Owner, EntityManager))); + var othersMessage = Loc.GetString("changeling-devour-consume-complete-others", ("user", Identity.Entity(ent.Owner, EntityManager))); + _popupSystem.PopupPredicted( + selfMessage, + othersMessage, + ent.Owner, + ent.Owner, + PopupType.LargeCaution); + + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} successfully devoured {ToPrettyString(target):player}'s identity"); + + if (_inventorySystem.TryGetSlotEntity(target, "jumpsuit", out var item) + && TryComp(item, out var butcherable)) + RipClothing(target, (item.Value, butcherable)); + + if (!TryComp(ent.Owner, out var identityStorage)) + return; + + _changelingIdentitySystem.CloneToPausedMap((ent, identityStorage), target); + + // We add a reference to ourselves to prevent repeated identity gain. + var targetDevoured = EnsureComp(target); + targetDevoured.DevouredBy.Add(ent.Owner); + Dirty(target, targetDevoured); + Dirty(ent); } /// - /// Checkes if the targets outerclothing is beyond a DamageCoefficientThreshold to protect them from being devoured. + /// Has the given victim been devoured by the given changeling before? + /// + public bool HasDevoured(Entity changeling, EntityUid devoured) + { + if (!Resolve(changeling, ref changeling.Comp, false)) + return false; + + return changeling.Comp.ConsumedIdentities.ContainsValue(devoured); + } + + /// + /// Can the given changeling devour the given victim? + /// + public bool CanDevour(Entity changeling, EntityUid victim, bool showPopup = true) + { + if (!Resolve(changeling, ref changeling.Comp)) + return false; + + if (changeling.Owner == victim) + return false; // Can't devour yourself. + + if (!HasComp(victim)) + { + if (showPopup) + _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-cannot-devour"), changeling.Owner, changeling.Owner, PopupType.Medium); + return false; + } + + if (HasDevoured(changeling.Owner, victim)) + { + if (showPopup) + _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-already-devoured"), changeling.Owner, changeling.Owner, PopupType.Medium); + return false; + } + + if (!_mobState.IsDead(victim)) + { + if (showPopup) + _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-not-dead"), changeling.Owner, changeling.Owner, PopupType.Medium); + return false; + } + + if (HasComp(victim)) + { + if (showPopup) + _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-rotting"), changeling.Owner, changeling.Owner, PopupType.Medium); + return false; + } + + if (IsTargetProtected(victim, changeling!)) + { + if (showPopup) + _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-protected"), changeling.Owner, changeling.Owner, PopupType.Medium); + return false; + } + + return true; + } + + /// + /// Checks if the target's outerclothing is beyond a DamageCoefficientThreshold to protect them from being devoured. /// /// The Targeted entity /// Changelings Devour Component @@ -110,150 +271,7 @@ public sealed class ChangelingDevourSystem : EntitySystem return false; } - private void OnDevourAction(Entity ent, ref ChangelingDevourActionEvent args) - { - if (args.Handled || _whitelistSystem.IsWhitelistFailOrNull(ent.Comp.Whitelist, args.Target) - || !HasComp(ent)) - return; - - args.Handled = true; - var target = args.Target; - - if (target == ent.Owner) - return; // don't eat yourself - - if (HasComp(target)) - { - _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-rotting"), args.Performer, args.Performer, PopupType.Medium); - return; - } - - if (IsTargetProtected(target, ent)) - { - _popupSystem.PopupClient(Loc.GetString("changeling-devour-attempt-failed-protected"), ent, ent, PopupType.Medium); - return; - } - - if (_net.IsServer) - { - var pvsSound = _audio.PlayPvs(ent.Comp.DevourWindupNoise, ent); - if (pvsSound != null) - ent.Comp.CurrentDevourSound = pvsSound.Value.Entity; - } - - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ent:player} started changeling devour windup against {target:player}"); - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, ent, ent.Comp.DevourWindupTime, new ChangelingDevourWindupDoAfterEvent(), ent, target: target, used: ent) - { - BreakOnMove = true, - BlockDuplicate = true, - DuplicateCondition = DuplicateConditions.None, - }); - - var selfMessage = Loc.GetString("changeling-devour-begin-windup-self", ("user", Identity.Entity(ent.Owner, EntityManager))); - var othersMessage = Loc.GetString("changeling-devour-begin-windup-others", ("user", Identity.Entity(ent.Owner, EntityManager))); - _popupSystem.PopupPredicted( - selfMessage, - othersMessage, - args.Performer, - args.Performer, - PopupType.MediumCaution); - } - - private void OnDevourWindup(Entity ent, ref ChangelingDevourWindupDoAfterEvent args) - { - var curTime = _timing.CurTime; - args.Handled = true; - - if (!Exists(ent.Comp.CurrentDevourSound)) - _audio.Stop(ent.Comp.CurrentDevourSound!); - - if (args.Cancelled) - return; - - var selfMessage = Loc.GetString("changeling-devour-begin-consume-self", ("user", Identity.Entity(ent.Owner, EntityManager))); - var othersMessage = Loc.GetString("changeling-devour-begin-consume-others", ("user", Identity.Entity(ent.Owner, EntityManager))); - _popupSystem.PopupPredicted( - selfMessage, - othersMessage, - args.User, - args.User, - PopupType.LargeCaution); - - if (_net.IsServer) - { - var pvsSound = _audio.PlayPvs(ent.Comp.ConsumeNoise, ent); - - if (pvsSound != null) - ent.Comp.CurrentDevourSound = pvsSound.Value.Entity; - } - - - ent.Comp.NextTick = curTime + ent.Comp.DamageTimeBetweenTicks; - - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} began to devour {ToPrettyString(args.Target):player} identity"); - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, - ent, - ent.Comp.DevourConsumeTime, - new ChangelingDevourConsumeDoAfterEvent(), - ent, - target: args.Target, - used: ent) - { - AttemptFrequency = AttemptFrequency.EveryTick, - BreakOnMove = true, - BlockDuplicate = true, - DuplicateCondition = DuplicateConditions.None, - }); - } - - private void OnDevourConsume(Entity ent, ref ChangelingDevourConsumeDoAfterEvent args) - { - args.Handled = true; - var target = args.Target; - - if (target == null) - return; - - if (Exists(ent.Comp.CurrentDevourSound)) - _audio.Stop(ent.Comp.CurrentDevourSound!); - - if (args.Cancelled) - return; - - if (!_mobState.IsDead((EntityUid)target)) - { - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} unsuccessfully devoured {ToPrettyString(args.Target):player}'s identity"); - _popupSystem.PopupClient(Loc.GetString("changeling-devour-consume-failed-not-dead"), args.User, args.User, PopupType.Medium); - return; - } - - var selfMessage = Loc.GetString("changeling-devour-consume-complete-self", ("user", Identity.Entity(args.User, EntityManager))); - var othersMessage = Loc.GetString("changeling-devour-consume-complete-others", ("user", Identity.Entity(args.User, EntityManager))); - _popupSystem.PopupPredicted( - selfMessage, - othersMessage, - args.User, - args.User, - PopupType.LargeCaution); - - if (_mobState.IsDead(target.Value) - && TryComp(target, out var body) - && HasComp(target) - && TryComp(args.User, out var identityStorage)) - { - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} successfully devoured {ToPrettyString(args.Target):player}'s identity"); - _changelingIdentitySystem.CloneToPausedMap((ent, identityStorage), target.Value); - - if (_inventorySystem.TryGetSlotEntity(target.Value, "jumpsuit", out var item) - && TryComp(item, out var butcherable)) - RipClothing(target.Value, (item.Value, butcherable)); - } - - Dirty(ent); - } - + // TODO: This should just be an API method in the butcher system private void RipClothing(EntityUid victim, Entity item) { var spawnEntities = EntitySpawnCollection.GetSpawns(item.Comp.SpawnedEntities, _robustRandom); diff --git a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs index e555147352..50f5ceb547 100644 --- a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs +++ b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs @@ -3,13 +3,25 @@ namespace Content.Shared.Changeling.Systems; /// -/// Send when a player selects an intentity to transform into in the radial menu. +/// Send when a player selects an identity to transform into in the radial menu. /// [Serializable, NetSerializable] public sealed class ChangelingTransformIdentitySelectMessage(NetEntity targetIdentity) : BoundUserInterfaceMessage { /// - /// The uid of the cloned identity. + /// The uid of the stored identity. + /// + public readonly NetEntity TargetIdentity = targetIdentity; +} + +/// +/// Send when a player selects an identity to drop from their storage. +/// +[Serializable, NetSerializable] +public sealed class ChangelingTransformIdentityDropMessage(NetEntity targetIdentity) : BoundUserInterfaceMessage +{ + /// + /// The uid of the stored identity. /// public readonly NetEntity TargetIdentity = targetIdentity; } diff --git a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs index 9e4b1d4d03..8ec2d36cd8 100644 --- a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs +++ b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs @@ -5,10 +5,11 @@ using Content.Shared.Changeling.Components; using Content.Shared.Cloning; using Content.Shared.Database; using Content.Shared.DoAfter; -using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Popups; +using Content.Shared.Storage; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -17,16 +18,19 @@ namespace Content.Shared.Changeling.Systems; public sealed partial class ChangelingTransformSystem : EntitySystem { [Dependency] private readonly INetManager _net = default!; - [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly MetaDataSystem _metaSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedCloningSystem _cloningSystem = default!; + [Dependency] private readonly SharedCloningSystem _cloning = default!; [Dependency] private readonly SharedVisualBodySystem _visualBody = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly IdentitySystem _identity = default!; + [Dependency] private readonly SharedChangelingIdentitySystem _changelingIdentity = default!; private const string ChangelingBuiXmlGeneratedName = "ChangelingTransformBoundUserInterface"; public override void Initialize() @@ -35,24 +39,28 @@ public sealed partial class ChangelingTransformSystem : EntitySystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnTransformAction); - SubscribeLocalEvent(OnSuccessfulTransform); SubscribeLocalEvent(OnTransformSelected); + SubscribeLocalEvent(OnTransformDrop); + SubscribeLocalEvent(OnSuccessfulTransform); SubscribeLocalEvent(OnShutdown); + + // Components that need special handling outside of cloning. + SubscribeLocalEvent(StorageBeforeTransform); } private void OnMapInit(Entity ent, ref MapInitEvent init) { - _actionsSystem.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction); + _actions.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction); var userInterfaceComp = EnsureComp(ent); - _uiSystem.SetUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, new InterfaceData(ChangelingBuiXmlGeneratedName)); + _ui.SetUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, new InterfaceData(ChangelingBuiXmlGeneratedName)); } private void OnShutdown(Entity ent, ref ComponentShutdown args) { if (ent.Comp.ChangelingTransformActionEntity != null) { - _actionsSystem.RemoveAction(ent.Owner, ent.Comp.ChangelingTransformActionEntity); + _actions.RemoveAction(ent.Owner, ent.Comp.ChangelingTransformActionEntity); } } @@ -65,17 +73,54 @@ public sealed partial class ChangelingTransformSystem : EntitySystem if (!TryComp(ent, out var userIdentity)) return; - if (!_uiSystem.IsUiOpen((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer)) + if (!_ui.IsUiOpen((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer)) { - _uiSystem.OpenUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer); + _ui.OpenUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer); } //TODO: Can add a Else here with TransformInto and CloseUI to make a quick switch, // issue right now is that Radials cover the Action buttons so clicking the action closes the UI (due to clicking off a radial causing it to close, even with UI) // but pressing the number does. } + private void OnTransformSelected(Entity ent, + ref ChangelingTransformIdentitySelectMessage args) + { + if (!TryGetEntity(args.TargetIdentity, out var targetIdentity)) + return; + + if (!TryComp(ent, out var identity)) + return; + + if (identity.CurrentIdentity == targetIdentity) + return; // don't transform into ourselves + + if (!identity.ConsumedIdentities.ContainsKey(targetIdentity.Value)) + return; // this identity does not belong to this player + + TransformInto(ent.AsNullable(), targetIdentity.Value); + } + + private void OnTransformDrop(Entity ent, + ref ChangelingTransformIdentityDropMessage args) + { + if (!TryGetEntity(args.TargetIdentity, out var targetIdentity)) + return; + + if (!TryComp(ent, out var identity)) + return; + + if (identity.CurrentIdentity == targetIdentity) + return; // don't drop our current identity + + if (!identity.ConsumedIdentities.ContainsKey(targetIdentity.Value)) + return; // this identity does not belong to this player + + _popup.PopupClient(Loc.GetString("changeling-transform-bui-drop-identity-entity-popup", ("entity", targetIdentity.Value)), ent.Owner, PopupType.Large); + _changelingIdentity.DropStoredIdentity(ent.Owner, targetIdentity.Value); + } + /// /// Transform the changeling into another identity. - /// This can be any cloneable humanoid and doesn't have to be stored in the ChangelingIdentiyComponent, + /// This can be any cloneable humanoid and doesn't have to be stored in the ChangelingIdentityComponent, /// so make sure to validate the target before. /// public void TransformInto(Entity ent, EntityUid targetIdentity) @@ -85,7 +130,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem var selfMessage = Loc.GetString("changeling-transform-attempt-self", ("user", Identity.Entity(ent.Owner, EntityManager))); var othersMessage = Loc.GetString("changeling-transform-attempt-others", ("user", Identity.Entity(ent.Owner, EntityManager))); - _popupSystem.PopupPredicted( + _popup.PopupPredicted( selfMessage, othersMessage, ent, @@ -93,14 +138,17 @@ public sealed partial class ChangelingTransformSystem : EntitySystem PopupType.MediumCaution); if (_net.IsServer) + { + ent.Comp.CurrentTransformSound = _audio.Stop(ent.Comp.CurrentTransformSound); // cancel any previous sounds first ent.Comp.CurrentTransformSound = _audio.PlayPvs(ent.Comp.TransformAttemptNoise, ent)?.Entity; + } if (TryComp(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null) _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\" ({storedIdentity.OriginalSession:player}) "); else _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\""); - _doAfterSystem.TryStartDoAfter(new DoAfterArgs( + _doAfter.TryStartDoAfter(new DoAfterArgs( EntityManager, ent, ent.Comp.TransformWindup, @@ -116,33 +164,11 @@ public sealed partial class ChangelingTransformSystem : EntitySystem }); } - private void OnTransformSelected(Entity ent, - ref ChangelingTransformIdentitySelectMessage args) - { - _uiSystem.CloseUi(ent.Owner, ChangelingTransformUiKey.Key, ent); - - if (!TryGetEntity(args.TargetIdentity, out var targetIdentity)) - return; - - if (!TryComp(ent, out var identity)) - return; - - if (identity.CurrentIdentity == targetIdentity) - return; // don't transform into ourselves - - if (!identity.ConsumedIdentities.Contains(targetIdentity.Value)) - return; // this identity does not belong to this player - - TransformInto(ent.AsNullable(), targetIdentity.Value); - } - private void OnSuccessfulTransform(Entity ent, ref ChangelingTransformDoAfterEvent args) { args.Handled = true; - - if (Exists(ent.Comp.CurrentTransformSound)) - _audio.Stop(ent.Comp.CurrentTransformSound); + ent.Comp.CurrentTransformSound = _audio.Stop(ent.Comp.CurrentTransformSound); if (args.Cancelled) return; @@ -153,14 +179,19 @@ public sealed partial class ChangelingTransformSystem : EntitySystem if (args.Target is not { } targetIdentity) return; + var beforeTransformEvent = new BeforeChangelingTransformEvent(targetIdentity); + RaiseLocalEvent(args.User, beforeTransformEvent); + _visualBody.CopyAppearanceFrom(targetIdentity, args.User); - _cloningSystem.CloneComponents(targetIdentity, args.User, settings); + _cloning.CloneComponents(targetIdentity, args.User, settings); if (TryComp(targetIdentity, out var storedIdentity) && storedIdentity.OriginalSession != null) _adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent.Owner):player} successfully transformed into \"{Name(targetIdentity)}\" ({storedIdentity.OriginalSession:player})"); else _adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent.Owner):player} successfully transformed into \"{Name(targetIdentity)}\""); - _metaSystem.SetEntityName(ent, Name(targetIdentity), raiseEvents: false); + + _metaData.SetEntityName(ent, Name(targetIdentity), raiseEvents: false); // Don't raise events because we don't want to rename the ID card. + _identity.QueueIdentityUpdate(ent); // We have to manually refresh the identity because we did not raise events. Dirty(ent); @@ -169,5 +200,17 @@ public sealed partial class ChangelingTransformSystem : EntitySystem identity.CurrentIdentity = targetIdentity; Dirty(ent.Owner, identity); } + + var afterTransformEvent = new AfterChangelingTransformEvent(targetIdentity); + RaiseLocalEvent(args.User, afterTransformEvent); + } + + private void StorageBeforeTransform(Entity ent, ref BeforeChangelingTransformEvent args) + { + if (HasComp(args.StoredIdentity)) + return; // If we have a storage component and the target has one as well, then do nothing. + + // If the target identity does not have a storage anymore, drop all items inside our storage so that they don't become unreachable. + _container.EmptyContainer(ent.Comp.Container); } } diff --git a/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs b/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs index a3b620d68a..b9e2fbef3e 100644 --- a/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs +++ b/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Linq; +using System.Numerics; using Content.Shared.Body; using Content.Shared.Changeling.Components; using Content.Shared.Cloning; @@ -34,6 +35,8 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnStoredRemove); + + SubscribeLocalEvent(OnDevouredShutdown); } private void OnPlayerAttached(Entity ent, ref PlayerAttachedEvent args) @@ -57,7 +60,32 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem { if (TryComp(ent, out var actor)) CleanupPvsOverride(ent, actor.PlayerSession); + CleanupChangelingNullspaceIdentities(ent); + CleanupDevouredReferences(ent); + } + + // Set all references to this entity to null to prevent PVS errors when networking. + private void OnDevouredShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var ling in ent.Comp.DevouredBy) + { + if (!TryComp(ling, out var identityComp)) + continue; + + var keysToUpdate = identityComp.ConsumedIdentities + .Where(kvp => kvp.Value == ent.Owner) + .Select(kvp => kvp.Key) + .ToList(); + + if (keysToUpdate.Count == 0) + continue; // No need to dirty. + + foreach (var key in keysToUpdate) + identityComp.ConsumedIdentities[key] = null; + + Dirty(ling, identityComp); + } } private void OnStoredRemove(Entity ent, ref ComponentRemove args) @@ -78,7 +106,23 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem foreach (var consumedIdentity in ent.Comp.ConsumedIdentities) { - QueueDel(consumedIdentity); + QueueDel(consumedIdentity.Key); + } + } + + /// + /// Removes all references to the owning changeling from ChangelingDevouredComponents. + /// + /// The changeling entity + private void CleanupDevouredReferences(Entity ent) + { + foreach (var devouredUid in ent.Comp.ConsumedIdentities.Values) + { + if (!TryComp(devouredUid, out var devouredComp)) + continue; + + if (devouredComp.DevouredBy.Remove(ent.Owner)) + Dirty(devouredUid.Value, devouredComp); } } @@ -132,7 +176,7 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem if (clone == null) return null; - ent.Comp.ConsumedIdentities.Add(clone.Value); + ent.Comp.ConsumedIdentities.Add(clone.Value, target); Dirty(ent); HandlePvsOverride(ent, clone.Value); @@ -140,6 +184,22 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem return clone; } + /// + /// Drop a stored identity from the changeling's storage. + /// + public void DropStoredIdentity(Entity ent, EntityUid identity) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + if (!HasComp(identity)) + return; // Not a stored identity. + + PredictedQueueDel(identity); + if (ent.Comp.ConsumedIdentities.Remove(identity)) + Dirty(ent); + } + /// /// Simple helper to add a PVS override to a nullspace identity. /// @@ -157,12 +217,12 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem /// Cleanup all PVS overrides for the owner of the ChangelingIdentity /// /// The changeling storing the identities. - /// + /// The session you wish to remove the overrides from. private void CleanupPvsOverride(Entity ent, ICommonSession session) { foreach (var identity in ent.Comp.ConsumedIdentities) { - _pvsOverrideSystem.RemoveSessionOverride(identity, session); + _pvsOverrideSystem.RemoveSessionOverride(identity.Key, session); } } @@ -175,7 +235,7 @@ public abstract class SharedChangelingIdentitySystem : EntitySystem { foreach (var identity in ent.Comp.ConsumedIdentities) { - _pvsOverrideSystem.AddSessionOverride(identity, session); + _pvsOverrideSystem.AddSessionOverride(identity.Key, session); } } diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index 3985bd3051..b290aae404 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -235,12 +235,8 @@ public sealed partial class AnchorableSystem : EntitySystem // Log anchor attempt (server only) _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}"); - if (TryComp(uid, out var anchorBody) && - !TileFree(transform.Coordinates, anchorBody)) - { - _popup.PopupClient(Loc.GetString("anchorable-occupied"), uid, userUid); + if (!CanAnchorAt(uid, transform.Coordinates, userUid)) return; - } if (AnyUnstackable(uid, transform.Coordinates)) { @@ -285,6 +281,23 @@ public sealed partial class AnchorableSystem : EntitySystem return !attempt.Cancelled; } + public bool CanAnchorAt(Entity entity, EntityUid? user = null) + { + return CanAnchorAt(entity, Transform(entity).Coordinates, user); + } + + public bool CanAnchorAt(Entity entity, EntityCoordinates coordinates, EntityUid? user = null) + { + if (!Resolve(entity, ref entity.Comp)) + return true; + + if (TileFree(coordinates, entity.Comp)) + return true; + + _popup.PopupClient(Loc.GetString("anchorable-occupied"), entity, user); + return false; + } + /// /// Returns true if no hard anchored entities exist on the coordinate tile that would collide with the provided physics body. /// diff --git a/Content.Shared/DisplacementMap/DisplacementData.cs b/Content.Shared/DisplacementMap/DisplacementData.cs index 79f89a1d25..8bc922708f 100644 --- a/Content.Shared/DisplacementMap/DisplacementData.cs +++ b/Content.Shared/DisplacementMap/DisplacementData.cs @@ -13,4 +13,7 @@ public sealed partial class DisplacementData [DataField] public string? ShaderOverride = "DisplacedDraw"; + + [DataField] + public string ShaderOverrideUnshaded = "DisplacedDrawUnshaded"; } diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index 14c2ae67d3..0b5902b5e3 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -17,6 +17,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem [Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] EntityQuery _handsQuery = default!; private DoAfter[] _doAfters = Array.Empty(); @@ -34,7 +35,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem try { - Update(uid, active, comp, time, xformQuery, handsQuery); + Update(uid, active, comp, time); } // ReSharper disable once RedundantCatchClause #if EXCEPTION_TOLERANCE @@ -87,9 +88,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem EntityUid uid, ActiveDoAfterComponent active, DoAfterComponent comp, - TimeSpan time, - EntityQuery xformQuery, - EntityQuery handsQuery) + TimeSpan time) { var dirty = false; @@ -122,7 +121,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem continue; } - if (ShouldCancel(doAfter, xformQuery, handsQuery)) + if (ShouldCancel(doAfter)) { InternalCancel(doAfter, comp); dirty = true; @@ -196,27 +195,24 @@ public abstract partial class SharedDoAfterSystem : EntitySystem } } - private bool ShouldCancel(DoAfter doAfter, - EntityQuery xformQuery, - EntityQuery handsQuery) + private bool ShouldCancel(DoAfter doAfter) { var args = doAfter.Args; - //re-using xformQuery for Exists() checks. - if (args.Used is { } used && !xformQuery.HasComponent(used)) + if (args.Used is { } used && !Exists(used)) return true; - if (args.EventTarget is { Valid: true } eventTarget && !xformQuery.HasComponent(eventTarget)) + if (args.EventTarget is { Valid: true } eventTarget && !Exists(eventTarget)) return true; - if (!xformQuery.TryGetComponent(args.User, out var userXform)) + if (!TryComp(args.User, out TransformComponent? userXform)) return true; TransformComponent? targetXform = null; - if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform)) + if (args.Target is { } target && !TryComp(target, out targetXform)) return true; - if (args.Used is { } @using && !xformQuery.HasComp(@using)) + if (args.Used is { } @using && !Exists(@using)) return true; // TODO: Re-use existing xform query for these calculations. @@ -265,7 +261,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem // This does not mean their hand needs to be empty. if (args.NeedHand) { - if (!handsQuery.TryGetComponent(args.User, out var hands) || hands.Count == 0) + if (!_handsQuery.TryGetComponent(args.User, out var hands) || hands.Count == 0) return true; // If an item was in the user's hand to begin with, diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index 0b72692ea0..8841d8dd26 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -251,7 +251,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem doAfter.NetInitialItem = GetNetEntity(doAfter.InitialItem); // Initial checks - if (ShouldCancel(doAfter, GetEntityQuery(), GetEntityQuery())) + if (ShouldCancel(doAfter)) return false; if (args.AttemptFrequency == AttemptFrequency.StartAndEnd && !TryAttemptEvent(doAfter)) diff --git a/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs index aa5132e596..9614933247 100644 --- a/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs +++ b/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs @@ -30,6 +30,6 @@ public sealed partial class CreateGas : EntityEffectBase return Loc.GetString("entity-effect-guidebook-create-gas", ("chance", Probability), ("moles", Moles), - ("gas", gasProto.Name)); + ("gas", Loc.GetString(gasProto.Name))); } } diff --git a/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs index c54912191e..9b3d731fcc 100644 --- a/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs +++ b/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs @@ -15,7 +15,7 @@ public sealed partial class WashCreamPieEntityEffectSystem : EntityEffectSystem< protected override void Effect(Entity entity, ref EntityEffectEvent args) { - _creamPie.SetCreamPied(entity, entity.Comp, false); + _creamPie.SetCreamPied((entity, entity.Comp), false); } } diff --git a/Content.Shared/Flash/Components/FlashComponent.cs b/Content.Shared/Flash/Components/FlashComponent.cs index d1a8b882d9..9484add5ef 100644 --- a/Content.Shared/Flash/Components/FlashComponent.cs +++ b/Content.Shared/Flash/Components/FlashComponent.cs @@ -16,6 +16,12 @@ public sealed partial class FlashComponent : Component [DataField, AutoNetworkedField] public bool FlashOnUse = true; + /// + /// Flash the area around the entity when the flash is used with ranged interaction? + /// + [DataField, AutoNetworkedField] + public bool FlashOnRangedInteract = false; + /// /// Flash the target when melee attacking them? /// diff --git a/Content.Shared/Flash/FlashEvents.cs b/Content.Shared/Flash/FlashEvents.cs index 1c18ca1676..70a61765ff 100644 --- a/Content.Shared/Flash/FlashEvents.cs +++ b/Content.Shared/Flash/FlashEvents.cs @@ -13,9 +13,15 @@ public record struct FlashAttemptEvent(EntityUid Target, EntityUid? User, Entity } /// -/// Called when a player is successfully flashed. +/// Called when a player is successfully flashed, once for each flashed player. /// Raised on the target hit by the flash, the user of the flash and the flash used. /// The Melee parameter is used to check for rev conversion. /// [ByRefEvent] public record struct AfterFlashedEvent(EntityUid Target, EntityUid? User, EntityUid? Used, bool Melee); + +/// +/// Raised once on the flash entity when it was used, regardless of the flashed status being applied or not. +/// +[ByRefEvent] +public record struct AfterFlashActivatedEvent(EntityUid? Target, EntityUid? User); diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index 4b8ff3ab7b..da43284bee 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -1,13 +1,18 @@ +using System.Linq; using Content.Shared.Charges.Components; using Content.Shared.Charges.Systems; +using Content.Shared.Clothing.Components; using Content.Shared.Examine; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Flash.Components; using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Light; +using Content.Shared.Movement.Systems; using Content.Shared.Popups; +using Content.Shared.Random.Helpers; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; using Content.Shared.Tag; @@ -17,12 +22,7 @@ using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Timing; -using System.Linq; -using Content.Shared.Movement.Systems; -using Content.Shared.Random.Helpers; -using Content.Shared.Clothing.Components; namespace Content.Shared.Flash; @@ -57,6 +57,7 @@ public abstract class SharedFlashSystem : EntitySystem SubscribeLocalEvent(OnFlashMeleeHit); SubscribeLocalEvent(OnFlashUseInHand); + SubscribeLocalEvent(OnRangedInteract); SubscribeLocalEvent(OnLightToggle); SubscribeLocalEvent(OnPermanentBlindnessFlashAttempt); SubscribeLocalEvent(OnTemporaryBlindnessFlashAttempt); @@ -72,7 +73,7 @@ public abstract class SharedFlashSystem : EntitySystem if (!ent.Comp.FlashOnMelee || !args.IsHit || !args.HitEntities.Any() || - !UseFlash(ent, args.User)) + !TryUseFlashItem(ent.AsNullable(), args.User)) { return; } @@ -82,41 +83,66 @@ public abstract class SharedFlashSystem : EntitySystem { Flash(target, args.User, ent.Owner, ent.Comp.MeleeDuration, ent.Comp.SlowTo, melee: true, stunDuration: ent.Comp.MeleeStunDuration); } + + EntityUid? firstTarget = args.HitEntities.Count > 0 ? args.HitEntities[0] : null; // Just pick the first hit entity. + var ev = new AfterFlashActivatedEvent(firstTarget, args.User); + RaiseLocalEvent(ent, ref ev); } private void OnFlashUseInHand(Entity ent, ref UseInHandEvent args) { - if (!ent.Comp.FlashOnUse || args.Handled || !UseFlash(ent, args.User)) + if (!ent.Comp.FlashOnUse || args.Handled || !TryUseFlashItem(ent.AsNullable(), args.User)) return; args.Handled = true; FlashArea(ent.Owner, args.User, ent.Comp.Range, ent.Comp.AoeFlashDuration, ent.Comp.SlowTo, true, ent.Comp.Probability); + var ev = new AfterFlashActivatedEvent(null, args.User); // No direct target. + RaiseLocalEvent(ent, ref ev); + } + + // TODO: This or most of the other systems subscribing to BeforeRangedInteractEvent shouldn't be using this event as handling it stops contact interaction with the used tool, + // but this will need some cleanup of how SharedInteractionSystem handles the code flow. Also the event is raised for both in-range and out of range interactions, which + // is what the subscribers are using it for, but does not seem originally intended from the naming convention. + private void OnRangedInteract(Entity ent, ref BeforeRangedInteractEvent args) + { + if (!ent.Comp.FlashOnRangedInteract || args.Handled || !TryUseFlashItem(ent.AsNullable(), args.User)) + return; + + args.Handled = true; + FlashArea(ent.Owner, args.User, ent.Comp.Range, ent.Comp.AoeFlashDuration, ent.Comp.SlowTo, true, ent.Comp.Probability); + var ev = new AfterFlashActivatedEvent(args.Target, args.User); + RaiseLocalEvent(ent, ref ev); } // needed for the flash lantern and interrogator lamp // TODO: This is awful and all the different components for toggleable lights need to be unified and changed to use Itemtoggle private void OnLightToggle(Entity ent, ref LightToggleEvent args) { - if (!args.IsOn || !UseFlash(ent, null)) + if (!args.IsOn || !TryUseFlashItem(ent.AsNullable(), null)) return; FlashArea(ent.Owner, null, ent.Comp.Range, ent.Comp.AoeFlashDuration, ent.Comp.SlowTo, true, ent.Comp.Probability); + var ev = new AfterFlashActivatedEvent(null, null); // TODO: Add user once someone made toggleable lights not a total mess. + RaiseLocalEvent(ent, ref ev); } /// - /// Use charges and set the visuals. + /// Try to use charges, play the sound and set the visuals of a flash item. + /// This does not actually cause the flash status effect by itself, you will need to either call or as well. /// /// False if no charges are left or the flash is currently in use. - private bool UseFlash(Entity ent, EntityUid? user) + public bool TryUseFlashItem(Entity ent, EntityUid? user) { + if (!Resolve(ent, ref ent.Comp)) + return false; + if (_useDelay.IsDelayed(ent.Owner)) return false; if (TryComp(ent.Owner, out var charges) - && _sharedCharges.IsEmpty((ent.Owner, charges))) + && !_sharedCharges.TryUseCharge((ent.Owner, charges))) return false; - _sharedCharges.TryUseCharge((ent.Owner, charges)); _audio.PlayPredicted(ent.Comp.Sound, ent.Owner, user); var active = EnsureComp(ent.Owner); @@ -126,7 +152,7 @@ public abstract class SharedFlashSystem : EntitySystem if (_sharedCharges.IsEmpty((ent.Owner, charges))) { - _appearance.SetData(ent.Owner, FlashVisuals.Burnt, true); + _appearance.SetData(ent.Owner, FlashVisuals.Burnt, true); // TODO: Reset if charges are refilled. _tag.AddTag(ent.Owner, TrashTag); _popup.PopupClient(Loc.GetString("flash-component-becomes-empty"), user); } diff --git a/Content.Shared/Ghost/GhostComponent.cs b/Content.Shared/Ghost/GhostComponent.cs index 3fc9c081cb..37f5c9e693 100644 --- a/Content.Shared/Ghost/GhostComponent.cs +++ b/Content.Shared/Ghost/GhostComponent.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Ghost; /// Handles limiting interactions, using ghost abilities, ghost visibility, and ghost warping. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedGhostSystem))] -[AutoGenerateComponentState(true), AutoGenerateComponentPause] +[AutoGenerateComponentState(true)] public sealed partial class GhostComponent : Component { // Actions @@ -54,7 +54,7 @@ public sealed partial class GhostComponent : Component /// May not reflect actual time of death if this entity has been paused, /// but will give an accurate length of time since death. /// - [DataField, AutoPausedField] + [DataField, AutoNetworkedField] public TimeSpan TimeOfDeath = TimeSpan.Zero; /// diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs index 7d3561a79f..726c20d31a 100644 --- a/Content.Shared/Ghost/SharedGhostSystem.cs +++ b/Content.Shared/Ghost/SharedGhostSystem.cs @@ -1,9 +1,11 @@ using Content.Shared.Emoting; +using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Serialization; +using Robust.Shared.Timing; namespace Content.Shared.Ghost { @@ -14,6 +16,7 @@ namespace Content.Shared.Ghost public abstract class SharedGhostSystem : EntitySystem { [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] protected readonly IGameTiming _gameTiming = default!; public override void Initialize() { @@ -23,6 +26,17 @@ namespace Content.Shared.Ghost SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnGhostExamine); + } + + private void OnGhostExamine(EntityUid uid, GhostComponent component, ExaminedEvent args) + { + var timeSinceDeath = _gameTiming.RealTime.Subtract(component.TimeOfDeath); + var deathTimeInfo = timeSinceDeath.Minutes > 0 + ? Loc.GetString("comp-ghost-examine-time-minutes", ("minutes", timeSinceDeath.Minutes)) + : Loc.GetString("comp-ghost-examine-time-seconds", ("seconds", timeSinceDeath.Seconds)); + + args.PushMarkup(deathTimeInfo); } private void OnAttemptInteract(Entity ent, ref InteractionAttemptEvent args) diff --git a/Content.Shared/HijackBeacon/ActiveHijackBeaconComponent.cs b/Content.Shared/HijackBeacon/ActiveHijackBeaconComponent.cs new file mode 100644 index 0000000000..b6f1d60c79 --- /dev/null +++ b/Content.Shared/HijackBeacon/ActiveHijackBeaconComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.HijackBeacon; + +/// +/// This is used for tracking a that is currently activated or on cooldown. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ActiveHijackBeaconComponent : Component +{ + /// + /// Remaining time until the hijack is completed. + /// + [DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))] + public TimeSpan CompletionTime = TimeSpan.Zero; +} diff --git a/Content.Shared/HijackBeacon/HijackBeaconComponent.cs b/Content.Shared/HijackBeacon/HijackBeaconComponent.cs new file mode 100644 index 0000000000..ed1d3ca74b --- /dev/null +++ b/Content.Shared/HijackBeacon/HijackBeaconComponent.cs @@ -0,0 +1,53 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; + +namespace Content.Shared.HijackBeacon; + +/// +/// Component for hijack beacons, meant to be planted on the ATS to drain station funds. +/// +/// +/// Status and timer fields are private so the state machine is preserved. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class HijackBeaconComponent : Component +{ + /// + /// Current state of the beacon. + /// + [DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))] + public HijackBeaconStatus Status = HijackBeaconStatus.AwaitActivate; + + /// + /// How long it takes to deactivate the beacon. + /// + [DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))] + public TimeSpan DeactivationLength = TimeSpan.FromSeconds(5); + + /// + /// Remaining time until the hijack is completed. + /// + [DataField, Access(typeof(HijackBeaconSystem))] + public TimeSpan RemainingTime = TimeSpan.FromSeconds(200); + + /// + /// Default amount of time before the beacon can be re-activated, if it is disarmed. + /// + [DataField, Access(typeof(HijackBeaconSystem))] + public TimeSpan Cooldown = TimeSpan.FromSeconds(20); + + /// + /// Remaining cooldown time before the beacon can be reactivated. + /// + [DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))] + public TimeSpan CooldownTime = TimeSpan.Zero; + + /// + /// How much cash should be withdrawn from each department account? + /// + [DataField] + public int Fine = 5000; +} diff --git a/Content.Shared/HijackBeacon/HijackBeaconSystem.cs b/Content.Shared/HijackBeacon/HijackBeaconSystem.cs new file mode 100644 index 0000000000..d3e5810fba --- /dev/null +++ b/Content.Shared/HijackBeacon/HijackBeaconSystem.cs @@ -0,0 +1,353 @@ +using Content.Shared.Cargo.Components; +using Content.Shared.Chat; +using Content.Shared.Construction.Components; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Audio; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; + +namespace Content.Shared.HijackBeacon; + +public sealed class HijackBeaconSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AnchorableSystem _anchor = default!; + [Dependency] private readonly SharedChatSystem _chat = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public readonly SoundSpecifier AnnounceSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg"); + public readonly SoundSpecifier DeactivateSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg"); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetAltVerbs); + SubscribeLocalEvent(OnUnanchorAttempt); + SubscribeLocalEvent(OnAnchorChanged); + SubscribeLocalEvent(OnDeactivateDoAfter); + SubscribeLocalEvent(OnExaminedEvent); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var active, out var comp)) + { + switch (comp.Status) + { + case HijackBeaconStatus.Armed: + if (_gameTiming.CurTime < active.CompletionTime) + return; + + HijackFinish((uid, comp)); + Dirty(uid, comp); + break; + case HijackBeaconStatus.Cooldown: + if (comp.CooldownTime < _gameTiming.CurTime) + { + comp.Status = HijackBeaconStatus.AwaitActivate; + RemCompDeferred(uid); + Dirty(uid, comp); + } + break; + } + } + } + + #region Event Subs + + /// + /// Deactivate beacon if it gets unanchored(via a bomb or something) + /// + private void OnAnchorChanged(Entity ent, ref AnchorStateChangedEvent args) + { + // Unanchoring the beacon deactivates it. This is to prevent people from bombing the tile the beacon is on and running away with it for a free activation. + if (!args.Anchored && ent.Comp.Status == HijackBeaconStatus.Armed) + DeactivateBeacon(ent); + } + + private void OnUnanchorAttempt(Entity entity, ref UnanchorAttemptEvent args) + { + if (entity.Comp.Status == HijackBeaconStatus.Armed) + args.Cancel(); + } + + /// + /// Get the activation and deactivation verbs. + /// + private void OnGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || args.Hands is null) + return; + + if (ent.Comp.Status == HijackBeaconStatus.AwaitActivate) + { + args.Verbs.Add(new() + { + Act = () => + { + ActivateBeacon(ent); + }, + Text = Loc.GetString("hijack-beacon-verb-activate-text"), + Message = Loc.GetString("hijack-beacon-verb-activate-message"), + Disabled = ent.Comp.Status != HijackBeaconStatus.AwaitActivate || !CanActivate(ent), + TextStyleClass = "InteractionVerb", + Impact = LogImpact.High, + }); + } + + if (ent.Comp.Status == HijackBeaconStatus.Armed) + { + var user = args.User; + + args.Verbs.Add(new() + { + Act = () => + { + DeactivateBeaconDoAfter(ent, user); + }, + Text = Loc.GetString("hijack-beacon-verb-deactivate-text"), + Message = Loc.GetString("hijack-beacon-verb-deactivate-message"), + TextStyleClass = "InteractionVerb", + Impact = LogImpact.High, + }); + } + } + + /// + /// When it's examined. + /// + private void OnExaminedEvent(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + switch (ent.Comp.Status) + { + case HijackBeaconStatus.AwaitActivate: + args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-activate")); + break; + case HijackBeaconStatus.Armed: + args.PushMarkup(Loc.GetString("defusable-examine-live", + ("name", ent), + ("time", GetRemainingTime(ent.Owner)))); + break; + case HijackBeaconStatus.Cooldown: + args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-cooldown")); + break; + case HijackBeaconStatus.HijackComplete: + args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-hijack-complete")); + break; + } + } + + /// + /// What happens when you deactivate the beacon. + /// + private void OnDeactivateDoAfter(Entity ent, ref HijackBeaconDeactivateDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + DeactivateBeacon(ent); + + args.Handled = true; + } + + #endregion + + /// + /// Arming the beacon. Should only occur if on the ATS. + /// + private void ActivateBeacon(Entity ent) + { + if (ent.Comp.Status != HijackBeaconStatus.AwaitActivate || !CanActivate(ent)) + return; + + // Activate and start countdown. + // Remaining time is adjusted by current time to simplify the update loop. + EnsureComp(ent, out var activeComp); + activeComp.CompletionTime = _gameTiming.CurTime + ent.Comp.RemainingTime; + ent.Comp.Status = HijackBeaconStatus.Armed; + + //global announcement + var sender = Loc.GetString("hijack-beacon-announcement-sender"); + var message = Loc.GetString("hijack-beacon-announcement-activated", ("time", GetRemainingTime((ent.Owner, activeComp)))); + _chat.DispatchGlobalAnnouncement(message, sender, true, AnnounceSound, Color.Yellow); + + //Anchor. Anchoring is tied to activation. + Anchor(ent); + + Dirty(ent); + } + + /// + /// Deactivates the beacon. + /// + private void DeactivateBeacon(Entity ent) + { + if (ent.Comp.Status != HijackBeaconStatus.Armed) + return; + + // Put beacon on cooldown + ent.Comp.CooldownTime = ent.Comp.Cooldown + _gameTiming.CurTime; + ent.Comp.Status = HijackBeaconStatus.Cooldown; + + //global announcement + var sender = Loc.GetString("hijack-beacon-announcement-sender"); + var message = Loc.GetString("hijack-beacon-announcement-deactivated"); + _chat.DispatchGlobalAnnouncement(message, sender, true, DeactivateSound, Color.Green); + + // Unanchor. we want anchoring to be tied to activation here so we just call this. + Unanchor(ent); + + Dirty(ent); + } + + /// + /// Complete the hijack. The beacon equivalent to a nuke detonation + /// + private void HijackFinish(Entity ent) + { + if (ent.Comp.Status != HijackBeaconStatus.Armed) + return; + + // Hijack is completed and can't be reattempted + ent.Comp.Status = HijackBeaconStatus.HijackComplete; + RemCompDeferred(ent); + + var beaconXForm = Transform(ent); + + // Ensure we are on the trade station still + if (!TryComp(beaconXForm.GridUid, out var station)) + { + Log.Warning($"Trade station hijack tried to succeed on non-trade station grid {beaconXForm.GridUid}!"); + DeactivateBeacon(ent); + return; + } + + if (station.Hacked) + { + Log.Warning("Hack succeeded on an already hacked trade station!"); + return; + } + + // Mark the station as hacked. + station.Hacked = true; + Dirty(beaconXForm.GridUid.Value, station); + + // Broadcast that the ATS has been hacked. + var ev = new HijackBeaconSuccessEvent(ent.Comp.Fine); + RaiseLocalEvent(ref ev); + + //global announcement + var sender = Loc.GetString("hijack-beacon-announcement-sender"); + var message = Loc.GetString("hijack-beacon-announcement-success", ("fine", ev.Total)); + _chat.DispatchGlobalAnnouncement(message, sender, true, AnnounceSound, Color.Red); + + // Unanchoring must occur after updating the status, or it will disarm the beacon + Unanchor(ent, beaconXForm); + + Dirty(ent); + } + + /// + /// Starts the deactivation doafter. + /// + private void DeactivateBeaconDoAfter(Entity beacon, EntityUid user) + { + var doAfter = new DoAfterArgs(EntityManager, user, beacon.Comp.DeactivationLength, new HijackBeaconDeactivateDoAfterEvent(), beacon) + { + BreakOnDamage = true, + BreakOnMove = true, + NeedHand = true, + }; + + // (try to) start doafter + _doAfter.TryStartDoAfter(doAfter); + } + + #region Helpers + + /// + /// Check if the beacon is on the Trade Station and if the Trade Station has not been hijacked already. + /// + private bool CanActivate(Entity ent) + { + return TryComp(Transform(ent).GridUid, out TradeStationComponent? tradeStation) && !tradeStation.Hacked && _anchor.CanAnchorAt(ent.Owner); + } + + /// + /// Anchoring helper + /// + private void Anchor(Entity ent, TransformComponent? beaconXForm = null) + { + beaconXForm ??= Transform(ent); + + if (beaconXForm.Anchored || beaconXForm.GridUid == null) + return; + + _transform.AnchorEntity(ent, beaconXForm); + _popup.PopupPredicted(Loc.GetString("hijack-beacon-popup-anchor"), ent, null); + } + + /// + /// Unanchoring helper + /// + private void Unanchor(Entity ent, TransformComponent? beaconXForm = null) + { + beaconXForm ??= Transform(ent); + + if (!beaconXForm.Anchored) + return; + + _transform.Unanchor(ent, beaconXForm); + _popup.PopupPredicted(Loc.GetString("hijack-beacon-popup-unanchor"), ent, null); + } + + /// + /// Turns time values into usable values for announcements/examine messages + /// + private int GetRemainingTime(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return 69420; // Mature error code + + return (int) (ent.Comp.CompletionTime - _gameTiming.CurTime).TotalSeconds; + } + + #endregion +} + +public enum HijackBeaconStatus : byte +{ + AwaitActivate, + Armed, + Cooldown, + HijackComplete +} + +/// +/// DoAfter event raised when the hijack beacon is deactivated. +/// +[Serializable, NetSerializable] +public sealed partial class HijackBeaconDeactivateDoAfterEvent : SimpleDoAfterEvent; + +/// +/// Event raised when the hijack beacon succeeds in hijacking the ATS. +/// +[ByRefEvent] +public record struct HijackBeaconSuccessEvent(int Fine) +{ + public int Total = 0; +}; diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs index 6595789977..52e25a0b9a 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs @@ -1,9 +1,9 @@ using Content.Shared.Chat; using Content.Shared.IdentityManagement.Components; using Content.Shared.Implants.Components; -using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; +using Content.Shared.Store; using Content.Shared.Corvax.TTS; namespace Content.Shared.Implants; @@ -13,12 +13,15 @@ public abstract partial class SharedSubdermalImplantSystem public void InitializeRelay() { SubscribeLocalEvent(RelayToImplantEvent); - SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); + + // Ref relays, for when you need to write to the event! + SubscribeLocalEvent(RefRelayToImplantEvent); + SubscribeLocalEvent(RefRelayToImplantEvent); } /// @@ -38,6 +41,26 @@ public abstract partial class SharedSubdermalImplantSystem RaiseLocalEvent(implant, relayEv); } } + + /// + /// Relays events from the implanted to the implant. + /// + private void RefRelayToImplantEvent(Entity entity, ref T args) where T : notnull + { + if (!_container.TryGetContainer(entity, ImplanterComponent.ImplantSlotId, out var implantContainer)) + return; + + var relayEv = new ImplantRelayEvent(args, entity); + foreach (var implant in implantContainer.ContainedEntities) + { + if (args is HandledEntityEventArgs { Handled: true }) + return; + + RaiseLocalEvent(implant, relayEv); + } + + args = relayEv.Event; + } } /// @@ -45,7 +68,7 @@ public abstract partial class SharedSubdermalImplantSystem /// public sealed class ImplantRelayEvent where T : notnull { - public readonly T Event; + public T Event; public readonly EntityUid ImplantedEntity; diff --git a/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs b/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs deleted file mode 100644 index 39be05a148..0000000000 --- a/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Light.Components; - -/// -/// Can activate when collided with. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class LightOnCollideColliderComponent : Component -{ - [DataField] - public string FixtureId = "lightTrigger"; -} diff --git a/Content.Shared/Light/Components/LightOnCollideComponent.cs b/Content.Shared/Light/Components/LightOnCollideComponent.cs deleted file mode 100644 index c3b4bd7396..0000000000 --- a/Content.Shared/Light/Components/LightOnCollideComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Light.Components; - -/// -/// Enables / disables pointlight whenever entities are contacting with it -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class LightOnCollideComponent : Component -{ -} diff --git a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs deleted file mode 100644 index 2de7c5591f..0000000000 --- a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Content.Shared.Light.Components; -using Robust.Shared.Physics.Events; -using Robust.Shared.Physics.Systems; - -namespace Content.Shared.Light.EntitySystems; - -public sealed class LightCollideSystem : EntitySystem -{ - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly SlimPoweredLightSystem _lights = default!; - - private EntityQuery _lightQuery; - - public override void Initialize() - { - base.Initialize(); - - _lightQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnPreventCollide); - SubscribeLocalEvent(OnStart); - SubscribeLocalEvent(OnEnd); - - SubscribeLocalEvent(OnCollideShutdown); - } - - private void OnCollideShutdown(Entity ent, ref ComponentShutdown args) - { - // TODO: Check this on the event. - if (TerminatingOrDeleted(ent.Owner)) - return; - - // Regenerate contacts for everything we were colliding with. - var contacts = _physics.GetContacts(ent.Owner); - - while (contacts.MoveNext(out var contact)) - { - if (!contact.IsTouching) - continue; - - var other = contact.OtherEnt(ent.Owner); - - if (_lightQuery.HasComp(other)) - { - _physics.RegenerateContacts(other); - } - } - } - - // You may be wondering what de fok this is doing here. - // At the moment there's no easy way to do collision whitelists based on components. - private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) - { - if (!_lightQuery.HasComp(args.OtherEntity)) - { - args.Cancelled = true; - } - } - - private void OnEnd(Entity ent, ref EndCollideEvent args) - { - if (args.OurFixtureId != ent.Comp.FixtureId) - return; - - if (!_lightQuery.HasComp(args.OtherEntity)) - return; - - // TODO: Engine bug IsTouching box2d yay. - var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1; - - if (contacts > 0) - return; - - _lights.SetEnabled(args.OtherEntity, false); - } - - private void OnStart(Entity ent, ref StartCollideEvent args) - { - if (args.OurFixtureId != ent.Comp.FixtureId) - return; - - if (!_lightQuery.HasComp(args.OtherEntity)) - return; - - _lights.SetEnabled(args.OtherEntity, true); - } -} diff --git a/Content.Shared/Magic/SharedMagicSystem.cs b/Content.Shared/Magic/SharedMagicSystem.cs index 82cae19ec1..62d3dbdbd4 100644 --- a/Content.Shared/Magic/SharedMagicSystem.cs +++ b/Content.Shared/Magic/SharedMagicSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Magic.Components; using Content.Shared.Magic.Events; using Content.Shared.Maps; using Content.Shared.Mind; +using Content.Shared.Objectives.Systems; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Speech.Muting; @@ -67,6 +68,7 @@ public abstract class SharedMagicSystem : EntitySystem [Dependency] private readonly TurfSystem _turf = default!; [Dependency] private readonly SharedChargesSystem _charges = default!; [Dependency] private readonly ExamineSystemShared _examine= default!; + [Dependency] private readonly TargetSystem _target = default!; private static readonly ProtoId InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell"; @@ -472,7 +474,7 @@ public abstract class SharedMagicSystem : EntitySystem ev.Handled = true; - var allHumans = _mind.GetAliveHumans(); + var allHumans = _target.GetAliveHumans(); foreach (var human in allHumans) { diff --git a/Content.Shared/Medical/Cryogenics/CryoPodComponent.cs b/Content.Shared/Medical/Cryogenics/CryoPodComponent.cs index 386d0989d9..6dbe11776d 100644 --- a/Content.Shared/Medical/Cryogenics/CryoPodComponent.cs +++ b/Content.Shared/Medical/Cryogenics/CryoPodComponent.cs @@ -127,7 +127,7 @@ public enum CryoPodUiKey : byte [Serializable, NetSerializable] public sealed class CryoPodUserMessage : BoundUserInterfaceMessage { - public GasAnalyzerComponent.GasMixEntry GasMix; + public GasMixEntry GasMix; public HealthAnalyzerUiState Health; public FixedPoint2? BeakerCapacity; public List? Beaker; @@ -135,7 +135,7 @@ public sealed class CryoPodUserMessage : BoundUserInterfaceMessage public bool HasDamage; public CryoPodUserMessage( - GasAnalyzerComponent.GasMixEntry gasMix, + GasMixEntry gasMix, HealthAnalyzerUiState health, FixedPoint2? beakerCapacity, List? beaker, diff --git a/Content.Shared/Mind/Filters/AliveAiPool.cs b/Content.Shared/Mind/Filters/AliveAiPool.cs new file mode 100644 index 0000000000..88fd557bbf --- /dev/null +++ b/Content.Shared/Mind/Filters/AliveAiPool.cs @@ -0,0 +1,16 @@ +using Content.Shared.Objectives.Systems; +using Content.Shared.Silicons.StationAi; + +namespace Content.Shared.Mind.Filters; + +/// +/// A mind pool that uses . +/// +public sealed partial class AliveAiPool : MindPool +{ + public override void FindMinds(HashSet> minds, EntityUid? exclude, IEntityManager entMan, TargetSystem targetSys) + { + var aiSys = entMan.System(); + aiSys.AddAliveAis(minds, exclude); + } +} diff --git a/Content.Shared/Mind/Filters/AliveHumansPool.cs b/Content.Shared/Mind/Filters/AliveHumansPool.cs index c8e5c55bae..1dfa2d2379 100644 --- a/Content.Shared/Mind/Filters/AliveHumansPool.cs +++ b/Content.Shared/Mind/Filters/AliveHumansPool.cs @@ -1,12 +1,14 @@ +using Content.Shared.Objectives.Systems; + namespace Content.Shared.Mind.Filters; /// -/// A mind pool that uses . +/// A mind pool that uses . /// -public sealed partial class AliveHumansPool : IMindPool +public sealed partial class AliveHumansPool : MindPool { - void IMindPool.FindMinds(HashSet> minds, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + public override void FindMinds(HashSet> minds, EntityUid? exclude, IEntityManager entMan, TargetSystem targetSystem) { - mindSys.AddAliveHumans(minds, exclude); + targetSystem.AddAliveHumans(minds, exclude); } } diff --git a/Content.Shared/Mind/Filters/AntagonistMindFilter.cs b/Content.Shared/Mind/Filters/AntagonistMindFilter.cs index d805138ac3..ff192698f5 100644 --- a/Content.Shared/Mind/Filters/AntagonistMindFilter.cs +++ b/Content.Shared/Mind/Filters/AntagonistMindFilter.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Mind.Filters; /// public sealed partial class AntagonistMindFilter : MindFilter { - protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan) { var roleSys = entMan.System(); return !roleSys.MindIsAntagonist(mind); diff --git a/Content.Shared/Mind/Filters/BodyMindFilter.cs b/Content.Shared/Mind/Filters/BodyMindFilter.cs index 334539634f..0e471b0187 100644 --- a/Content.Shared/Mind/Filters/BodyMindFilter.cs +++ b/Content.Shared/Mind/Filters/BodyMindFilter.cs @@ -10,7 +10,7 @@ public sealed partial class BodyMindFilter : MindFilter [DataField(required: true)] public EntityWhitelist Whitelist = new(); - protected override bool ShouldRemove(Entity ent, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity ent, EntityUid? exclude, IEntityManager entMan) { if (ent.Comp.OwnedEntity is not {} mob) return true; diff --git a/Content.Shared/Mind/Filters/HasRoleMindFilter.cs b/Content.Shared/Mind/Filters/HasRoleMindFilter.cs index e8098d72a2..8e76e5a47e 100644 --- a/Content.Shared/Mind/Filters/HasRoleMindFilter.cs +++ b/Content.Shared/Mind/Filters/HasRoleMindFilter.cs @@ -14,7 +14,7 @@ public sealed partial class HasRoleMindFilter : MindFilter [DataField(required: true)] public EntityWhitelist Whitelist; - protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan) { var roleSys = entMan.System(); return !roleSys.MindHasRole(mind, Whitelist); diff --git a/Content.Shared/Mind/Filters/JobMindFilter.cs b/Content.Shared/Mind/Filters/JobMindFilter.cs index a6565e4d33..d3bec4d8d6 100644 --- a/Content.Shared/Mind/Filters/JobMindFilter.cs +++ b/Content.Shared/Mind/Filters/JobMindFilter.cs @@ -13,7 +13,7 @@ public sealed partial class JobMindFilter : MindFilter [DataField(required: true)] public ProtoId Job; - protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan) { var jobSys = entMan.System(); return jobSys.MindHasJobWithId(mind, Job); diff --git a/Content.Shared/Mind/Filters/MindFilter.cs b/Content.Shared/Mind/Filters/MindFilter.cs index c2daf3e361..47c8c4178b 100644 --- a/Content.Shared/Mind/Filters/MindFilter.cs +++ b/Content.Shared/Mind/Filters/MindFilter.cs @@ -3,25 +3,25 @@ using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Mind.Filters; /// -/// A mind filter that can be used to filter out minds from a . +/// A mind filter that can be used to filter out minds from a . /// [ImplicitDataDefinitionForInheritors] public abstract partial class MindFilter { /// /// The actual filter function, this has to return false for minds that get removed from the pool. - /// An excluded mind will be the same one passed to . + /// An excluded mind will be the same one passed to . /// /// The mind to check /// The same mind passed to FindMinds - protected abstract bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys); + protected abstract bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan); /// /// The high-level filter function to be used by the mind system. /// - public bool Filter(Entity mind, EntityUid? exclude, EntityManager entMan, SharedMindSystem mindSys) + public bool Filter(Entity mind, EntityUid? exclude, EntityManager entMan) { - return ShouldRemove(mind, exclude, entMan, mindSys) ^ Inverted; + return ShouldRemove(mind, exclude, entMan) ^ Inverted; } /// diff --git a/Content.Shared/Mind/Filters/IMindPool.cs b/Content.Shared/Mind/Filters/MindPool.cs similarity index 51% rename from Content.Shared/Mind/Filters/IMindPool.cs rename to Content.Shared/Mind/Filters/MindPool.cs index 263d15d812..0886e0716d 100644 --- a/Content.Shared/Mind/Filters/IMindPool.cs +++ b/Content.Shared/Mind/Filters/MindPool.cs @@ -1,13 +1,13 @@ -using Robust.Shared.Serialization.Manager.Attributes; +using Content.Shared.Objectives.Systems; namespace Content.Shared.Mind.Filters; /// /// A mind pool that can find minds to use for objectives etc. -/// Further filtered by . +/// Further filtered by . /// [ImplicitDataDefinitionForInheritors] -public partial interface IMindPool +public abstract partial class MindPool { /// /// Add minds for this pool to a hashset. @@ -15,5 +15,7 @@ public partial interface IMindPool /// /// The hashset to add to /// A mind entity that must not be returned - void FindMinds(HashSet> minds, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys); + /// entity Manager for further control + /// targeting system which explicitly searches for targets. + public abstract void FindMinds(HashSet> minds, EntityUid? exclude, IEntityManager entMan, TargetSystem targetSys); } diff --git a/Content.Shared/Mind/Filters/ObjectiveMindFilter.cs b/Content.Shared/Mind/Filters/ObjectiveMindFilter.cs index 5b64fc36c6..275f49872d 100644 --- a/Content.Shared/Mind/Filters/ObjectiveMindFilter.cs +++ b/Content.Shared/Mind/Filters/ObjectiveMindFilter.cs @@ -10,7 +10,7 @@ public sealed partial class ObjectiveMindFilter : MindFilter [DataField(required: true)] public EntityWhitelist Blacklist = new(); - protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan, SharedMindSystem mindSys) + protected override bool ShouldRemove(Entity mind, EntityUid? exclude, IEntityManager entMan) { var whitelistSys = entMan.System(); foreach (var obj in mind.Comp.Objectives) diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index a21642c4ec..8571c35aa2 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Systems; using Content.Shared.Players; +using Content.Shared.Silicons.StationAi; using Content.Shared.Speech; using Content.Shared.Whitelist; using Robust.Shared.Containers; @@ -32,20 +33,17 @@ namespace Content.Shared.Mind; public abstract partial class SharedMindSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedObjectivesSystem _objectives = default!; - [Dependency] private readonly SharedPlayerSystem _player = default!; - [Dependency] private readonly MetaDataSystem _metadata = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedPlayerSystem _player = default!; [ViewVariables] protected readonly Dictionary UserMinds = new(); - private HashSet> _pickingMinds = new(); - private readonly EntProtoId _mindProto = "MindBase"; public override void Initialize() @@ -595,57 +593,12 @@ public abstract partial class SharedMindSystem : EntitySystem return TryGetMind(userId, out _, out var mind) ? mind.CharacterName : null; } - /// - /// Returns a list of every living humanoid player's minds, except for a single one which is exluded. - /// A new hashset is allocated for every call, consider using instead. - /// - public HashSet> GetAliveHumans(EntityUid? exclude = null) - { - var allHumans = new HashSet>(); - AddAliveHumans(allHumans, exclude); - return allHumans; - } - - /// - /// Adds to a hashset every living humanoid player's minds, except for a single one which is exluded. - /// - public void AddAliveHumans(HashSet> allHumans, EntityUid? exclude = null) - { - // HumanoidProfileComponent is used to prevent mice, pAIs, etc from being chosen - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var mobState)) - { - // the player needs to have a mind and not be the excluded one + - // the player has to be alive - if (!TryGetMind(uid, out var mind, out var mindComp) || mind == exclude || !_mobState.IsAlive(uid, mobState)) - continue; - - allHumans.Add((mind, mindComp)); - } - } - - /// - /// Picks a random mind from a pool after applying a list of filters. - /// Returns null if no valid mind could be found. - /// - public Entity? PickFromPool(IMindPool pool, List filters, EntityUid? exclude = null) - { - _pickingMinds.Clear(); - pool.FindMinds(_pickingMinds, exclude, EntityManager, this); - FilterMinds(_pickingMinds, filters, exclude); - - if (_pickingMinds.Count == 0) - return null; - - return _random.Pick(_pickingMinds); - } - /// /// Filters minds from a hashset using a single . /// public void FilterMinds(HashSet> minds, MindFilter filter, EntityUid? exclude = null) { - minds.RemoveWhere(mind => filter.Filter(mind, exclude, EntityManager, this)); + minds.RemoveWhere(mind => filter.Filter(mind, exclude, EntityManager)); } /// diff --git a/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs b/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs index 598e4b564a..d55596ad7d 100644 --- a/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs +++ b/Content.Shared/Movement/Systems/SharedJumpAbilitySystem.cs @@ -99,13 +99,15 @@ public sealed partial class SharedJumpAbilitySystem : EntitySystem if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) return; + // Make sure to set the datafields before adding the component so that the correct action gets spawned on map init. var targetComp = Factory.GetComponent(); targetComp.Action = ent.Comp.Action; - targetComp.CanCollide = ent.Comp.CanCollide; - targetComp.JumpSound = ent.Comp.JumpSound; - targetComp.CollideKnockdown = ent.Comp.CollideKnockdown; targetComp.JumpDistance = ent.Comp.JumpDistance; targetComp.JumpThrowSpeed = ent.Comp.JumpThrowSpeed; + targetComp.CanCollide = ent.Comp.CanCollide; + targetComp.CollideKnockdown = ent.Comp.CollideKnockdown; + targetComp.JumpSound = ent.Comp.JumpSound; + targetComp.JumpFailedPopup = ent.Comp.JumpFailedPopup; AddComp(args.CloneUid, targetComp, true); } } diff --git a/Content.Shared/Nutrition/Components/CreamPieComponent.cs b/Content.Shared/Nutrition/Components/CreamPieComponent.cs index b6bd124084..91ba3b3d6e 100644 --- a/Content.Shared/Nutrition/Components/CreamPieComponent.cs +++ b/Content.Shared/Nutrition/Components/CreamPieComponent.cs @@ -1,21 +1,39 @@ using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Shared.Nutrition.Components +namespace Content.Shared.Nutrition.Components; + +/// +/// Component used for banana cream pies. +/// These can be thrown at someone to stun them and cream their face. +/// +[Access(typeof(SharedCreamPieSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class CreamPieComponent : Component { - [Access(typeof(SharedCreamPieSystem))] - [RegisterComponent] - public sealed partial class CreamPieComponent : Component - { - [DataField("paralyzeTime")] - public float ParalyzeTime { get; private set; } = 1f; + /// + /// The time being hit by this entity will stun you. + /// + [DataField, AutoNetworkedField] + public TimeSpan ParalyzeTime = TimeSpan.FromSeconds(1); - [DataField("sound")] - public SoundSpecifier Sound { get; private set; } = new SoundCollectionSpecifier("desecration"); + /// + /// The sound to play when hitting something. + /// + [DataField] + public SoundSpecifier Sound = new SoundCollectionSpecifier("desecration", AudioParams.Default.WithVariation(0.125f)); - [ViewVariables] - public bool Splatted { get; set; } = false; + /// + /// Has this pie been splatted by hitting something? + /// + [DataField, AutoNetworkedField] + public bool Splatted = false; - public const string PayloadSlotName = "payloadSlot"; - } + /// + /// Items in this container will be triggered when the pie hits something. + /// This allows throwable C4 pies or similar. + /// + [ViewVariables] + public const string PayloadSlotName = "payloadSlot"; } diff --git a/Content.Shared/Nutrition/Components/CreamPiedComponent.cs b/Content.Shared/Nutrition/Components/CreamPiedComponent.cs index 6779fe3666..08c583ff7b 100644 --- a/Content.Shared/Nutrition/Components/CreamPiedComponent.cs +++ b/Content.Shared/Nutrition/Components/CreamPiedComponent.cs @@ -1,19 +1,48 @@ using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; +using Robust.Shared.Utility; -namespace Content.Shared.Nutrition.Components +namespace Content.Shared.Nutrition.Components; + +/// +/// Allows this entity to be hit by banana cream pies. +/// See . +/// +[Access(typeof(SharedCreamPieSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)] +public sealed partial class CreamPiedComponent : Component { - [Access(typeof(SharedCreamPieSystem))] - [RegisterComponent] - public sealed partial class CreamPiedComponent : Component - { - [ViewVariables] - public bool CreamPied { get; set; } = false; - } + /// + /// Was this entity hit by a banana cream pie? + /// This is reset if they get splashed with water. + /// + [DataField, AutoNetworkedField] + public bool CreamPied; - [Serializable, NetSerializable] - public enum CreamPiedVisuals - { - Creamed, - } + /// + /// The sprite to draw on someone's face if they were hit by a pie. + /// The layer will be dynamically added with the component. + /// + [DataField, AutoNetworkedField] + public SpriteSpecifier? Sprite; +} + +/// +/// Key to be used with appearance data, indicating if the entity has a banana cream pie in their face. +/// +[Serializable, NetSerializable] +public enum CreamPiedVisuals +{ + Creamed, +} + +/// +/// The visual layer for the creampied face. +/// Will be dynamically added and removed with the component. +/// +[Serializable, NetSerializable] +public enum CreamPiedVisualLayer +{ + Key, } diff --git a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs index a0a82d63ef..84827ed0e3 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -1,72 +1,164 @@ +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Fluids; +using Content.Shared.IdentityManagement; using Content.Shared.Nutrition.Components; +using Content.Shared.Popups; +using Content.Shared.Rejuvenate; using Content.Shared.Stunnable; using Content.Shared.Throwing; -using JetBrains.Annotations; +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Systems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Player; -namespace Content.Shared.Nutrition.EntitySystems +namespace Content.Shared.Nutrition.EntitySystems; + +public abstract class SharedCreamPieSystem : EntitySystem { - [UsedImplicitly] - public abstract class SharedCreamPieSystem : EntitySystem + [Dependency] private readonly SharedStunSystem _stunSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IngestionSystem _ingestion = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPuddleSystem _puddle = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutions = default!; + [Dependency] private readonly TriggerSystem _trigger = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() { - [Dependency] private SharedStunSystem _stunSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnCreamPieHit); + SubscribeLocalEvent(OnCreamPieLand); + SubscribeLocalEvent(OnCreamPiedHitBy); + SubscribeLocalEvent(OnSlice); + SubscribeLocalEvent(OnRejuvenate); + } + + /// + /// SPLAT! + /// + public void SplatCreamPie(Entity creamPie) + { + // Already splatted! Do nothing. + if (creamPie.Comp.Splatted) + return; + + // The pie will be queued for deletion but there may be multiple collisions in the same tick, so we prevent it from splatting more than once. + creamPie.Comp.Splatted = true; + Dirty(creamPie); + + // The entity is being deleted, so play the sound at its position rather than parenting. + if (_net.IsServer) // we don't have a user to pass in TODO: make the popup API sane and remove this guard { - base.Initialize(); - - SubscribeLocalEvent(OnCreamPieHit); - SubscribeLocalEvent(OnCreamPieLand); - SubscribeLocalEvent(OnCreamPiedHitBy); + var coordinates = Transform(creamPie).Coordinates; + _audio.PlayPvs(creamPie.Comp.Sound, coordinates); } - public void SplatCreamPie(Entity creamPie) + if (TryComp(creamPie, out var edibleComp)) { - // Already splatted! Do nothing. - if (creamPie.Comp.Splatted) - return; + if (_solutions.TryGetSolution(creamPie.Owner, edibleComp.Solution, out _, out var solution)) + _puddle.TrySpillAt(creamPie.Owner, solution, out _, false); - creamPie.Comp.Splatted = true; - - SplattedCreamPie(creamPie); + _ingestion.SpawnTrash((creamPie.Owner, edibleComp)); } - protected virtual void SplattedCreamPie(Entity entity) { } + ActivatePayload(creamPie); + PredictedQueueDel(creamPie); + } - public void SetCreamPied(EntityUid uid, CreamPiedComponent creamPied, bool value) - { - if (value == creamPied.CreamPied) - return; + /// + /// Drop any item hidden in the cream pie and trigger it. + /// + public void ActivatePayload(EntityUid uid) + { + // Keep this server side for now since we don't have a user we can pass in for prediction purposes. + // Ideally the popup and audio API will be reworked so that is not needed anymore. + if (_net.IsClient) + return; - creamPied.CreamPied = value; + if (_itemSlots.TryGetSlot(uid, CreamPieComponent.PayloadSlotName, out var itemSlot) + && _itemSlots.TryEject(uid, itemSlot, user: null, out var item) + && TryComp(item.Value, out var timerTrigger)) + _trigger.ActivateTimerTrigger((item.Value, timerTrigger)); + } - if (TryComp(uid, out AppearanceComponent? appearance)) - { - _appearance.SetData(uid, CreamPiedVisuals.Creamed, value, appearance); - } - } + /// + /// Sets the creampied status of an entity. + /// This toggles the visuals for the pie in their face. + /// + public void SetCreamPied(Entity ent, bool value) + { + if (!Resolve(ent, ref ent.Comp)) + return; - private void OnCreamPieLand(Entity entity, ref LandEvent args) - { - SplatCreamPie(entity); - } + if (value == ent.Comp.CreamPied) + return; - private void OnCreamPieHit(Entity entity, ref ThrowDoHitEvent args) - { - SplatCreamPie(entity); - } + ent.Comp.CreamPied = value; + Dirty(ent); - private void OnCreamPiedHitBy(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) - { - if (!Exists(args.Thrown) || !TryComp(args.Thrown, out CreamPieComponent? creamPie)) return; + _appearance.SetData(ent.Owner, CreamPiedVisuals.Creamed, value); + } - SetCreamPied(uid, creamPied, true); + private void OnCreamPieLand(Entity ent, ref LandEvent args) + { + SplatCreamPie(ent); + } - CreamedEntity(uid, creamPied, args); + private void OnCreamPieHit(Entity ent, ref ThrowDoHitEvent args) + { + SplatCreamPie(ent); + } - _stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(creamPie.ParalyzeTime)); - } + private void OnCreamPiedHitBy(Entity creamPied, ref ThrowHitByEvent args) + { + if (creamPied.Comp.CreamPied || !Exists(args.Thrown) || !TryComp(args.Thrown, out var creamPie)) + return; - protected virtual void CreamedEntity(EntityUid uid, CreamPiedComponent creamPied, ThrowHitByEvent args) {} + // TODO: Check if they even have a head that can be hit. + SetCreamPied(creamPied.AsNullable(), true); + _stunSystem.TryUpdateParalyzeDuration(creamPied.Owner, creamPie.ParalyzeTime); + + // Throwing is not predicted, so the thrower is not equal to the client predicting the collision, so we cannot pass in a user. + // TODO: Make the popup API sane. + if (_net.IsClient) + return; + + // Shown only to the player that was hit. + _popup.PopupEntity( + Loc.GetString( + "cream-pied-component-on-hit-by-message", + ("thrown", args.Thrown)), + creamPied.Owner, creamPied.Owner); + + var otherPlayers = Filter.PvsExcept(creamPied.Owner); + + // Show to everyone else. + _popup.PopupEntity( + Loc.GetString( + "cream-pied-component-on-hit-by-message-others", + ("owner", Identity.Entity(creamPied.Owner, EntityManager)), + ("thrown", args.Thrown)), + creamPied.Owner, otherPlayers, false); + } + + private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) + { + SetCreamPied(ent.AsNullable(), false); + } + + // TODO + // A regression occured here. Previously creampies would activate their hidden payload if you tried to eat them. + // However, the refactor to IngestionSystem caused the event to not be reached, + // because eating is blocked if an item is inside the food. + + private void OnSlice(Entity ent, ref SliceFoodEvent args) + { + ActivatePayload(ent); } } diff --git a/Content.Shared/Objectives/Systems/TargetSystem.cs b/Content.Shared/Objectives/Systems/TargetSystem.cs new file mode 100644 index 0000000000..215d87d62f --- /dev/null +++ b/Content.Shared/Objectives/Systems/TargetSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Mind.Filters; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Random; + +namespace Content.Shared.Objectives.Systems; + +/// +/// This system stores enumerators to find valid Targets, typically searching for minds. +/// Typically used in conjunction with a or an Objective. +/// +public sealed class TargetSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + + private HashSet> _pickingMinds = new(); + + /// + /// Returns a list of every living humanoid player's minds, except for a single one which is exluded. + /// A new hashset is allocated for every call, consider using instead. + /// + public HashSet> GetAliveHumans(EntityUid? exclude = null) + { + var allHumans = new HashSet>(); + AddAliveHumans(allHumans, exclude); + return allHumans; + } + + /// + /// Adds to a hashset every living humanoid player's minds, except for a single one which is exluded. + /// + public void AddAliveHumans(HashSet> allHumans, EntityUid? exclude = null) + { + // HumanoidProfileComponent is used to prevent mice, pAIs, etc from being chosen + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var mobState)) + { + // the player needs to have a mind and not be the excluded one + + // the player has to be alive + if (!_mind.TryGetMind(uid, out var mind, out var mindComp) || mind == exclude || !_mobState.IsAlive(uid, mobState)) + continue; + + allHumans.Add((mind, mindComp)); + } + } + + /// + /// Picks a random mind from a pool after applying a list of filters. + /// Returns null if no valid mind could be found. + /// + public Entity? PickFromPool(MindPool pool, List filters, EntityUid? exclude = null) + { + _pickingMinds.Clear(); + pool.FindMinds(_pickingMinds, exclude, EntityManager, this); + _mind.FilterMinds(_pickingMinds, filters, exclude); + + if (_pickingMinds.Count == 0) + return null; + + return _random.Pick(_pickingMinds); + } +} diff --git a/Content.Shared/PDA/Ringer/RingerUplinkComponent.cs b/Content.Shared/PDA/Ringer/RingerUplinkComponent.cs index 2dbbfb5efc..327cd608b5 100644 --- a/Content.Shared/PDA/Ringer/RingerUplinkComponent.cs +++ b/Content.Shared/PDA/Ringer/RingerUplinkComponent.cs @@ -3,19 +3,12 @@ using Robust.Shared.GameStates; namespace Content.Shared.PDA.Ringer; /// -/// Opens the store UI when the ringstone is set to the secret code. +/// Makes a PDA able to open store UIs when the ringtone is set to a secret code. /// Traitors are told the code when greeted. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedRingerSystem))] public sealed partial class RingerUplinkComponent : Component { - /// - /// Notes to set ringtone to in order to lock or unlock the uplink. - /// Set via GenerateUplinkCodeEvent. - /// - [DataField] - public Note[]? Code; - /// /// Whether to show the toggle uplink button in PDA settings. /// diff --git a/Content.Shared/PDA/SharedRingerSystem.cs b/Content.Shared/PDA/SharedRingerSystem.cs index 392cd46e72..ee0a7fb0bc 100644 --- a/Content.Shared/PDA/SharedRingerSystem.cs +++ b/Content.Shared/PDA/SharedRingerSystem.cs @@ -27,6 +27,7 @@ public abstract class SharedRingerSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPdaSystem _pda = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] protected readonly SharedStoreSystem Store = default!; [Dependency] private readonly SharedTransformSystem _xform = default!; [Dependency] protected readonly SharedUserInterfaceSystem UI = default!; @@ -145,12 +146,12 @@ public abstract class SharedRingerSystem : EntitySystem /// On the client side, it does nothing since the client cannot know the code in advance. /// On the server side, the code is verified. /// - /// The entity with the RingerUplinkComponent. + /// The entity with the RingerUplinkComponent. /// The ringtone to check against the uplink code. /// The entity attempting to toggle the uplink. /// True if the uplink state was toggled, false otherwise. [PublicAPI] - public virtual bool TryToggleUplink(EntityUid uid, Note[] ringtone, EntityUid? user = null) + public virtual bool TryToggleUplink(Entity entity, Note[] ringtone, EntityUid? user = null) { return false; } @@ -177,7 +178,7 @@ public abstract class SharedRingerSystem : EntitySystem return; // Try to toggle the uplink first - if (TryToggleUplink(ent, args.Ringtone)) + if (TryToggleUplink(ent.Owner, args.Ringtone)) return; // Don't save the uplink code as the ringtone UpdateRingerRingtone(ent, args.Ringtone); @@ -244,12 +245,13 @@ public abstract class SharedRingerSystem : EntitySystem ent.Comp.Unlocked = !ent.Comp.Unlocked; // Update PDA UI if needed - if (TryComp(ent, out var pda)) - _pda.UpdatePdaUi(ent, pda); + _pda.UpdatePdaUi(ent.Owner); // Close store UI if we're locking if (!ent.Comp.Unlocked) + { UI.CloseUi(ent.Owner, StoreUiKey.Key); + } return true; } diff --git a/Content.Shared/Photography/PhotographComponent.cs b/Content.Shared/Photography/PhotographComponent.cs new file mode 100644 index 0000000000..3fb065a6e7 --- /dev/null +++ b/Content.Shared/Photography/PhotographComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.Photography; + +/// +/// Represents the photograph data on a picture. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PhotographComponent : Component +{ + /// + /// The description of the photographed object. + /// + [DataField, AutoNetworkedField] + public FormattedMessage? Description; + + /// + /// The full text mentioning the name of the photographed object. + /// For example "This is a picture of Urist McHands" + /// + [DataField, AutoNetworkedField] + public string? NameText; +} diff --git a/Content.Shared/Photography/PhotographySystem.cs b/Content.Shared/Photography/PhotographySystem.cs new file mode 100644 index 0000000000..e8b2334b25 --- /dev/null +++ b/Content.Shared/Photography/PhotographySystem.cs @@ -0,0 +1,96 @@ +using Content.Shared.EntityTable; +using Content.Shared.Examine; +using Content.Shared.Flash; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; +using Robust.Shared.Network; +using Robust.Shared.Utility; + +namespace Content.Shared.Photography; + +/// +/// Handles everything related to photography. +/// +public sealed class PhotographySystem : EntitySystem +{ + [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly EntityTableSystem _tables = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnFlashActivated); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + using (args.PushGroup(nameof(PhotographComponent))) + { + if (string.IsNullOrEmpty(ent.Comp.NameText)) + args.PushText(Loc.GetString("photograph-name-text-empty")); + else + args.PushText(ent.Comp.NameText); + if (ent.Comp.Description != null) + // TODO: For some weird reason ExamineSystem is adding a new line at the end of message we are pushing with each examine. + // I'm not soaping this PR even more, so for now I'll just bandaid that by sending a clone to prevent it from getting modified. + args.PushMessage(new FormattedMessage(ent.Comp.Description)); + } + } + + // The flash system is handling charges and all interactions, we just print the picture afterwards. + private void OnFlashActivated(Entity ent, ref AfterFlashActivatedEvent args) + { + TakePicture(ent, args.Target, args.User); + } + + /// + /// Processes entity aimed at with a camera and prints a picture of it. + /// TODO: This is basically a placeholder mechanic for a more elaborate photography system. + /// However, this will need major refactoring to be possible. See + /// https://github.com/space-wizards/docs/pull/307 and + /// https://github.com/space-wizards/space-station-14/pull/43327 + /// for details. + /// + public void TakePicture(Entity camera, EntityUid? target, EntityUid? user) + { + if (_net.IsClient) + return; // Can't interact with predictively spawned entities yet. + + var tableResult = _tables.GetSpawns(camera.Comp.Photographs); + var coords = Transform(camera).Coordinates; + + FormattedMessage? description = null; + string? nameText = null; + if (target != null) + { + description = _examine.GetExamineText(target.Value, user); + // Get the full string now instead of indexing it later because we need the entity to know if it uses a proper noun or not. + nameText = Loc.GetString("photograph-name-text", ("entity", Identity.Entity(target.Value, EntityManager))); + // We don't want photographs to contain the descriptions of other photographs, because that makes entities with, in theory, infinite descriptions. + if (HasComp(target.Value)) + { + description = null; + nameText = Loc.GetString("photograph-name-text-photograph"); + } + } + + foreach (var prototype in tableResult) + { + // we generate an individual photograph (there should be only one tough) + var spawned = Spawn(prototype, coords); + var photoComp = EnsureComp(spawned); + photoComp.NameText = nameText; + photoComp.Description = description; + Dirty(spawned, photoComp); + + _hands.PickupOrDrop(user, spawned, dropNear: true); + } + } +} diff --git a/Content.Shared/Photography/PictureTakerComponent.cs b/Content.Shared/Photography/PictureTakerComponent.cs new file mode 100644 index 0000000000..a636b1ede2 --- /dev/null +++ b/Content.Shared/Photography/PictureTakerComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.EntityTable.EntitySelectors; +using Robust.Shared.GameStates; + +namespace Content.Shared.Photography; + +// since camera is taken... +/// +/// Marks an entity as able to take pictures (when you smash other entities with it). +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PictureTakerComponent : Component +{ + /// + /// The entities that will be spawned & given a PhotographComponent when the owning entity is used. + /// The table should only select one item at a time. + /// + [DataField] + public EntityTableSelector? Photographs; +} diff --git a/Content.Shared/Radiation/Components/RadiationSourceComponent.cs b/Content.Shared/Radiation/Components/RadiationSourceComponent.cs index 874024a905..367d38309f 100644 --- a/Content.Shared/Radiation/Components/RadiationSourceComponent.cs +++ b/Content.Shared/Radiation/Components/RadiationSourceComponent.cs @@ -1,9 +1,13 @@ +using Content.Shared.Radiation.Systems; +using Robust.Shared.Physics; + namespace Content.Shared.Radiation.Components; /// /// Irradiate all objects in range. /// [RegisterComponent] +[Access(typeof(SharedRadiationSystem))] public sealed partial class RadiationSourceComponent : Component { /// @@ -26,4 +30,7 @@ public sealed partial class RadiationSourceComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true; + + [ViewVariables] + public DynamicTree.Proxy Proxy = DynamicTree.Proxy.Free; } diff --git a/Content.Shared/Radiation/Systems/SharedRadiationSystem.cs b/Content.Shared/Radiation/Systems/SharedRadiationSystem.cs new file mode 100644 index 0000000000..78e061b9c3 --- /dev/null +++ b/Content.Shared/Radiation/Systems/SharedRadiationSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared.Radiation.Components; + +namespace Content.Shared.Radiation.Systems; + +public abstract partial class SharedRadiationSystem : EntitySystem +{ + [Dependency] protected readonly EntityQuery SourceQuery = default!; + + /// + /// Sets the intensity of a to the passed intensity. + /// + /// Radiation source we're attempting to update + /// Intensity we're setting the source to. + public void SetIntensity(Entity entity, float intensity) + { + if (!SourceQuery.Resolve(entity, ref entity.Comp, false)) + return; + + entity.Comp.Intensity = intensity; + UpdateSource((entity, entity.Comp)); + } + + /// + /// Updates the radiation source cache. Does nothing on client, see server! + /// + protected virtual void UpdateSource(Entity entity) { } +} diff --git a/Content.Shared/Research/TechnologyDisk/Components/TechnologyDiskComponent.cs b/Content.Shared/Research/TechnologyDisk/Components/TechnologyDiskComponent.cs index 02ffc77616..0afb845a9f 100644 --- a/Content.Shared/Research/TechnologyDisk/Components/TechnologyDiskComponent.cs +++ b/Content.Shared/Research/TechnologyDisk/Components/TechnologyDiskComponent.cs @@ -39,8 +39,8 @@ public sealed partial class TechnologyDiskComponent : Component [DataField] public Dictionary DiskPricePerTier = new() { - [1] = 100, - [2] = 500, - [3] = 1500 + [1] = 50, + [2] = 135, + [3] = 1000, }; } diff --git a/Content.Shared/Rootable/RootableSystem.cs b/Content.Shared/Rootable/RootableSystem.cs index 1d718dbdf3..1b9be3537b 100644 --- a/Content.Shared/Rootable/RootableSystem.cs +++ b/Content.Shared/Rootable/RootableSystem.cs @@ -121,12 +121,15 @@ public sealed class RootableSystem : EntitySystem if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) return; - var cloneComp = EnsureComp(args.CloneUid); + // Make sure to set the datafields before adding the component so that the correct action gets spawned on map init. + var cloneComp = Factory.GetComponent(); + cloneComp.Action = ent.Comp.Action; + cloneComp.RootedAlert = ent.Comp.RootedAlert; cloneComp.TransferRate = ent.Comp.TransferRate; cloneComp.TransferFrequency = ent.Comp.TransferFrequency; cloneComp.SpeedModifier = ent.Comp.SpeedModifier; cloneComp.RootSound = ent.Comp.RootSound; - Dirty(args.CloneUid, cloneComp); + AddComp(args.CloneUid, cloneComp, true); } private void OnRootableMapInit(Entity ent, ref MapInitEvent args) diff --git a/Content.Shared/Sericulture/SericultureSystem.cs b/Content.Shared/Sericulture/SericultureSystem.cs index fb87c907f4..e733b93483 100644 --- a/Content.Shared/Sericulture/SericultureSystem.cs +++ b/Content.Shared/Sericulture/SericultureSystem.cs @@ -41,13 +41,15 @@ public abstract partial class SharedSericultureSystem : EntitySystem if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name)) return; - var comp = EnsureComp(args.CloneUid); - comp.PopupText = ent.Comp.PopupText; - comp.ProductionLength = ent.Comp.ProductionLength; - comp.HungerCost = ent.Comp.HungerCost; - comp.EntityProduced = ent.Comp.EntityProduced; - comp.MinHungerThreshold = ent.Comp.MinHungerThreshold; - Dirty(args.CloneUid, comp); + // Make sure to set the datafields before adding the component so that the correct action gets spawned on map init. + var cloneComp = Factory.GetComponent(); + cloneComp.PopupText = ent.Comp.PopupText; + cloneComp.EntityProduced = ent.Comp.EntityProduced; + cloneComp.Action = ent.Comp.Action; + cloneComp.ProductionLength = ent.Comp.ProductionLength; + cloneComp.HungerCost = ent.Comp.HungerCost; + cloneComp.MinHungerThreshold = ent.Comp.MinHungerThreshold; + AddComp(args.CloneUid, cloneComp, true); } /// diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index fde4952f86..cd801511ca 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -623,6 +623,29 @@ public abstract partial class SharedStationAiSystem : EntitySystem return _blocker.CanComplexInteract(entity.Owner); } + + /// + /// Gets all alive AI minds and adds them to the inputted hashset, excluding one optional mind + /// + /// Hashset of alive AI minds + /// Optional mind to exclude + public void AddAliveAis(HashSet> aliveAis, EntityUid? exclude = null) + { + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _, out var aiHolder)) + { + // the player needs to have a mind and not be the excluded one + + // the player has to be alive + if (!TryGetHeld((uid, aiHolder), out var held) || _mobState.IsDead(held.Value)) + continue; + + if (!_mind.TryGetMind(held.Value, out var mind, out var mindComp) || mind == exclude) + continue; + + aliveAis.Add((mind, mindComp)); + } + } } public sealed partial class JumpToCoreEvent : InstantActionEvent diff --git a/Content.Shared/Singularity/EntitySystems/SharedSingularitySystem.cs b/Content.Shared/Singularity/EntitySystems/SharedSingularitySystem.cs index b7bf071457..eccfbe58d8 100644 --- a/Content.Shared/Singularity/EntitySystems/SharedSingularitySystem.cs +++ b/Content.Shared/Singularity/EntitySystems/SharedSingularitySystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Shared.Radiation.Components; +using Content.Shared.Radiation.Systems; using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Events; using Robust.Shared.Containers; @@ -19,6 +20,7 @@ public abstract class SharedSingularitySystem : EntitySystem [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedEventHorizonSystem _horizons = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedRadiationSystem _radiation = default!; [Dependency] protected readonly IViewVariablesManager Vvm = default!; #endregion Dependencies @@ -138,10 +140,7 @@ public abstract class SharedSingularitySystem : EntitySystem _visualizer.SetData(uid, SingularityAppearanceKeys.Singularity, singularity.Level, appearance); } - if (TryComp(uid, out var radiationSource)) - { - UpdateRadiation(uid, singularity, radiationSource); - } + UpdateRadiation(uid, singularity); RaiseLocalEvent(uid, new SingularityLevelChangedEvent(singularity.Level, oldValue, singularity)); if (singularity.Level <= 0) @@ -165,12 +164,12 @@ public abstract class SharedSingularitySystem : EntitySystem /// /// The uid of the singularity to update the radiation of. /// The state of the singularity to update the radiation of. - /// The state of the radioactivity of the singularity to update. - private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null, RadiationSourceComponent? rads = null) + private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null) { - if(!Resolve(uid, ref singularity, ref rads, logMissing: false)) + if(!Resolve(uid, ref singularity, logMissing: false)) return; - rads.Intensity = singularity.Level * singularity.RadsPerLevel; + + _radiation.SetIntensity(uid, singularity.Level * singularity.RadsPerLevel); } #endregion Getters/Setters diff --git a/Content.Shared/Stacks/SharedStackSystem.API.cs b/Content.Shared/Stacks/SharedStackSystem.API.cs index 1356c8ecda..372255701c 100644 --- a/Content.Shared/Stacks/SharedStackSystem.API.cs +++ b/Content.Shared/Stacks/SharedStackSystem.API.cs @@ -7,6 +7,29 @@ namespace Content.Shared.Stacks; // Partial for public API functions. public abstract partial class SharedStackSystem { + #region Spawning + // Interactions with spawned entities can not currently be predicted. + // This means that when spawning a stack it should not be given directly to the player, but have some intermediary. + + /// + /// Gets or spawns an entity with a stack count of 1. + /// Useful when you don't know if something is a stack, and want to make sure you just have a single entity. + /// + /// An entity to pop one count off the stack. + /// An entity with a stack count of 1, or a non-stack. + [PublicAPI] + public EntityUid GetOne(Entity stackEnt) + { + if (!Resolve(stackEnt.Owner, ref stackEnt.Comp, logMissing: false) // If it's not a stack, you already have the one + || stackEnt.Comp.Count == 1) // If it's at one, just use this + return stackEnt.Owner; + + ReduceCount(stackEnt, 1); + var stackId = _prototype.Index(stackEnt.Comp.StackTypeId); + return PredictedSpawnNextToOrDrop(stackId.Spawn, stackEnt.Owner); + } + + #endregion #region Merge Stacks /// diff --git a/Content.Shared/Sticky/Systems/StickySystem.cs b/Content.Shared/Sticky/Systems/StickySystem.cs index bc04c81f55..f060cfe700 100644 --- a/Content.Shared/Sticky/Systems/StickySystem.cs +++ b/Content.Shared/Sticky/Systems/StickySystem.cs @@ -102,7 +102,7 @@ public sealed class StickySystem : EntitySystem private void OnStickyDoAfter(Entity ent, ref StickyDoAfterEvent args) { - // target is the sticky item when unsticking and the surface when sticking, it will never be null + // target is the surface when sticking/unsticking, it will never be null if (args.Handled || args.Cancelled || args.Args.Target is not {} target) return; @@ -141,7 +141,7 @@ public sealed class StickySystem : EntitySystem } // start unsticking object - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.UnstickDelay, new StickyDoAfterEvent(), uid, target: uid) + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.UnstickDelay, new StickyDoAfterEvent(), uid, target: stuckTo) { BreakOnMove = true, NeedHand = true, diff --git a/Content.Shared/Storage/Components/EntityStorageComponent.cs b/Content.Shared/Storage/Components/EntityStorageComponent.cs index ecfcccc45b..ba267aa5bf 100644 --- a/Content.Shared/Storage/Components/EntityStorageComponent.cs +++ b/Content.Shared/Storage/Components/EntityStorageComponent.cs @@ -131,7 +131,21 @@ public sealed partial class EntityStorageComponent : Component, IGasMixtureHolde /// standard requirement that the entity must be an item or mob is waived. /// [DataField] - public EntityWhitelist? Whitelist; + public EntityWhitelist? Whitelist = new() + { + Components = + [ + "MobState", + "Item", + ], + }; + + /// + /// Blacklist for what entities are not allowed to be inserted into this container. + /// Blacklist takes priority over whitelist. + /// + [DataField] + public EntityWhitelist? Blacklist; /// /// The contents of the storage. diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 93a534843a..9618ee900b 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -377,12 +377,8 @@ public abstract class SharedEntityStorageSystem : EntitySystem if (containerAttemptEvent.Cancelled) return false; - // Consult the whitelist. The whitelist ignores the default assumption about how entity storage works. - if (component.Whitelist != null) - return _whitelistSystem.IsValid(component.Whitelist, toInsert); - - // The inserted entity must be a mob or an item. - return HasComp(toInsert) || HasComp(toInsert); + // Check the whitelist/blacklist. + return _whitelistSystem.CheckBoth(toInsert, component.Blacklist, component.Whitelist); } public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false) diff --git a/Content.Server/Store/Components/CurrencyComponent.cs b/Content.Shared/Store/Components/CurrencyComponent.cs similarity index 95% rename from Content.Server/Store/Components/CurrencyComponent.cs rename to Content.Shared/Store/Components/CurrencyComponent.cs index eb4ca1c0e3..a4c5b3c027 100644 --- a/Content.Server/Store/Components/CurrencyComponent.cs +++ b/Content.Shared/Store/Components/CurrencyComponent.cs @@ -1,8 +1,7 @@ using Content.Shared.FixedPoint; -using Content.Shared.Store; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -namespace Content.Server.Store.Components; +namespace Content.Shared.Store.Components; /// /// Identifies a component that can be inserted into a store diff --git a/Content.Shared/Store/Components/RemoteStoreComponent.cs b/Content.Shared/Store/Components/RemoteStoreComponent.cs new file mode 100644 index 0000000000..3b07e03e09 --- /dev/null +++ b/Content.Shared/Store/Components/RemoteStoreComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Store.Components; + +/// +/// This component manages a store which players can use to purchase different listings +/// through the ui. The currency, listings, and categories are defined in yaml. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class RemoteStoreComponent : Component +{ + /// + /// The store which is currently being targetted for remote opening + /// + [DataField] + public EntityUid? Store; +} diff --git a/Content.Server/Store/Systems/StoreSystem.Listings.cs b/Content.Shared/Store/SharedStoreSystem.Listings.cs similarity index 93% rename from Content.Server/Store/Systems/StoreSystem.Listings.cs rename to Content.Shared/Store/SharedStoreSystem.Listings.cs index 412114ce03..fa0f29bb4c 100644 --- a/Content.Server/Store/Systems/StoreSystem.Listings.cs +++ b/Content.Shared/Store/SharedStoreSystem.Listings.cs @@ -1,12 +1,12 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Content.Shared.Mind; -using Content.Shared.Store; using Content.Shared.Store.Components; using Robust.Shared.Prototypes; -namespace Content.Server.Store.Systems; +namespace Content.Shared.Store; -public sealed partial class StoreSystem + +public abstract partial class SharedStoreSystem { /// /// Refreshes all listings on a store. @@ -46,7 +46,7 @@ public sealed partial class StoreSystem public HashSet GetAllListings() { var clones = new HashSet(); - foreach (var prototype in _proto.EnumeratePrototypes()) + foreach (var prototype in Proto.EnumeratePrototypes()) { clones.Add(new ListingDataWithCostModifiers(prototype)); } @@ -62,7 +62,7 @@ public sealed partial class StoreSystem /// Whether or not the listing was added successfully public bool TryAddListing(StoreComponent component, string listingId) { - if (!_proto.TryIndex(listingId, out var proto)) + if (!Proto.TryIndex(listingId, out var proto)) { Log.Error("Attempted to add invalid listing."); return false; @@ -145,7 +145,7 @@ public sealed partial class StoreSystem /// The buying entity. public EntityUid GetBuyerMind(EntityUid buyer) { - if (!HasComp(buyer) && _mind.TryGetMind(buyer, out var buyerMind, out var _)) + if (!HasComp(buyer) && Mind.TryGetMind(buyer, out var buyerMind, out _)) return buyerMind; return buyer; diff --git a/Content.Shared/Store/SharedStoreSystem.UI.cs b/Content.Shared/Store/SharedStoreSystem.UI.cs new file mode 100644 index 0000000000..2d4b166c27 --- /dev/null +++ b/Content.Shared/Store/SharedStoreSystem.UI.cs @@ -0,0 +1,105 @@ +using System.Linq; +using Content.Shared.FixedPoint; +using Content.Shared.PDA.Ringer; +using Content.Shared.Store.Components; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Store; + +/// +/// This handles... +/// +public abstract partial class SharedStoreSystem +{ + /// + /// Toggles the store Ui open and closed + /// + /// the person doing the toggling + /// the store being toggled + /// + /// The entity remotely accessing the store, if any. + /// The remote access component, if any. + public void ToggleUi(EntityUid user, EntityUid storeEnt, StoreComponent? component = null, EntityUid? remoteAccess = null, RemoteStoreComponent? remoteComponent = null) + { + if (!Resolve(storeEnt, ref component)) + return; + + if (remoteAccess != null && !Resolve(remoteAccess.Value, ref remoteComponent) && remoteComponent!.Store != storeEnt) + return; + + if (!TryComp(user, out var actor)) + return; + + if (!UI.TryToggleUi(remoteAccess != null ? remoteAccess.Value : storeEnt, StoreUiKey.Key, actor.PlayerSession)) + return; + + UpdateUserInterface(user, storeEnt, component); + } + + /// + /// Closes the store UI for everyone, if it's open + /// + public void CloseUi(EntityUid uid, StoreComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + UI.CloseUi(uid, StoreUiKey.Key); + } + + /// + /// Updates the user interface for a store and refreshes the listings + /// + /// The person who if opening the store ui. Listings are filtered based on this. + /// The store entity itself + /// The store component being refreshed. + public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) + { + if (!Resolve(store, ref component)) + return; + + //this is the person who will be passed into logic for all listing filtering. + if (user != null) //if we have no "buyer" for this update, then don't update the listings + { + component.LastAvailableListings = GetAvailableListings(component.AccountOwner ?? user.Value, store, component).ToHashSet(); + } + + //dictionary for all currencies, including 0 values for currencies on the whitelist + Dictionary, FixedPoint2> allCurrency = new(); + foreach (var supported in component.CurrencyWhitelist) + { + allCurrency.Add(supported, FixedPoint2.Zero); + + if (component.Balance.TryGetValue(supported, out var value)) + allCurrency[supported] = value; + } + + // TODO: if multiple users are supposed to be able to interact with a single BUI & see different + // stores/listings, this needs to use session specific BUI states. + + // only tell operatives to lock their uplink if it can be locked + var showFooter = HasComp(store); + + var state = new StoreUpdateState(component.LastAvailableListings, allCurrency, showFooter, component.RefundAllowed); + UpdateRemoteStores(store, state); + UI.SetUiState(store, StoreUiKey.Key, state); + } + + /// + /// Updates any remote store connections to a specific store. + /// + /// The store being updated. + /// The state being applied. + public void UpdateRemoteStores(EntityUid store, StoreUpdateState state) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var remote, out var ui)) + { + if (remote.Store != store) + continue; + + UI.SetUiState((uid, ui), StoreUiKey.Key, state); + } + } +} diff --git a/Content.Shared/Store/SharedStoreSystem.cs b/Content.Shared/Store/SharedStoreSystem.cs new file mode 100644 index 0000000000..315d70d52a --- /dev/null +++ b/Content.Shared/Store/SharedStoreSystem.cs @@ -0,0 +1,254 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.FixedPoint; +using Content.Shared.Implants; +using Content.Shared.Interaction; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Stacks; +using Content.Shared.Store.Components; +using Content.Shared.Store.Events; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Store; + +/// +/// Manages general interactions with a store and different entities, +/// getting listings for stores, and interfacing with the store UI. +/// +public abstract partial class SharedStoreSystem : EntitySystem +{ + [Dependency] protected readonly IPrototypeManager Proto = default!; + [Dependency] protected readonly SharedMindSystem Mind = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] protected readonly SharedStackSystem Stack = default!; + [Dependency] protected readonly SharedUserInterfaceSystem UI = default!; + + [Dependency] protected readonly EntityQuery StoreQuery = default!; + [Dependency] protected readonly EntityQuery RemoteStoreQuery = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnGetStore); + SubscribeLocalEvent>((x, ref y) => + { + var ev = y.Event; + OnGetStore(x, ref ev); + y.Event = ev; + }); + SubscribeLocalEvent>(OnImplantInsertAttempt); + SubscribeLocalEvent(OnIntrinsicStoreAction); + } + + private void OnGetStore(Entity entity, ref GetStoreEvent args) + { + if (args.Handled) + return; + + if (!StoreQuery.TryComp(entity.Comp.Store, out var store)) + return; + + args.Store = (entity.Comp.Store.Value, store); + } + + private void OnImplantInsertAttempt(Entity implant, ref ImplantRelayEvent args) + { + var ev = args.Event; + + // Only allow insertion if the person implanted is doing the action. + if (ev.User == ev.Target) + ev.TargetOverride = implant; + else + ev.Cancel(); + + args.Event = ev; + } + + private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args) + { + if (args.Handled || !args.CanReach || args.Target is not { } target) + return; + + if (!TryGetStore(target, out var store)) + return; + + var ev = new CurrencyInsertAttemptEvent(args.User, target, args.Used, store.Value.Comp); + RaiseLocalEvent(target, ev); + if (ev.Cancelled) + return; + + if (!TryAddCurrency((uid, component), (store.Value, store.Value.Comp))) + return; + + args.Handled = true; + var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", ev.TargetOverride ?? target)); + Popup.PopupEntity(msg, target, args.User); + } + + /// + /// Attempts to find a store connected to this entity. + /// First checking for a on this entity, + /// then checking for a to find a remotely connected store. + /// + /// Entity we're checking for an attached store on + /// Store entity we're returning. + /// True if a store was found. + public bool TryGetStore(EntityUid entity, [NotNullWhen(true)] out Entity? store) + { + store = GetStore(entity); + return store != null; + } + + /// + /// Attempts to find a store connected to this entity. + /// First checking for a on this entity, + /// then checking for a to find a remotely connected store. + /// + /// Entity we're checking for an attached store on + /// The store entity and component if found. + public Entity? GetStore(EntityUid entity) + { + if (StoreQuery.TryComp(entity, out var storeComp)) + return (entity, storeComp); + + var ev = new GetStoreEvent(); + RaiseLocalEvent(entity, ref ev); + return ev.Store; + } + + /// + /// Attempts to find a remote store connected to this entity. + /// Checking for a with an attached store entity. + /// + /// Entity we're checking for an attached store on + /// The store entity and component if found. + public Entity? GetRemoteStore(Entity entity) + { + if (RemoteStoreQuery.Resolve(entity, ref entity.Comp) + && entity.Comp.Store != null + && StoreQuery.TryComp(entity.Comp.Store, out var storeComp)) + return (entity.Comp.Store.Value, storeComp); + + return null; + } + + public void SetRemoteStore(Entity entity, EntityUid? store) + { + if (!RemoteStoreQuery.Resolve(entity, ref entity.Comp)) + return; + + entity.Comp.Store = store; + } + + /// + /// Gets the value from an entity's currency component. + /// Scales with stacks. + /// + /// + /// If this result is intended to be used with , + /// consider using instead to ensure that the currency is consumed in the process. + /// + /// + /// + /// The value of the currency + public Dictionary GetCurrencyValue(EntityUid uid, CurrencyComponent component) + { + var amount = EntityManager.GetComponentOrNull(uid)?.Count ?? 1; + return component.Price.ToDictionary(v => v.Key, p => p.Value * amount); + } + + /// + /// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process. + /// + public bool TryAddCurrency(Entity currency, Entity store) + { + if (!Resolve(currency.Owner, ref currency.Comp)) + return false; + + if (!Resolve(store.Owner, ref store.Comp)) + return false; + + var value = currency.Comp.Price; + if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1) + { + value = currency.Comp.Price + .ToDictionary(v => v.Key, p => p.Value * stack.Count); + } + + if (!TryAddCurrency(value, store, store.Comp)) + return false; + + // Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the + // same tick + currency.Comp.Price.Clear(); + if (stack != null) + Stack.SetCount((currency.Owner, stack), 0); + + QueueDel(currency); + return true; + } + + /// + /// Tries to add a currency to a store's balance + /// + /// The value to add to the store + /// + /// The store to add it to + /// Whether or not the currency was succesfully added + public bool TryAddCurrency(Dictionary currency, EntityUid uid, StoreComponent? store = null) + { + if (!Resolve(uid, ref store)) + return false; + + //verify these before values are modified + foreach (var type in currency) + { + if (!store.CurrencyWhitelist.Contains(type.Key)) + return false; + } + + foreach (var type in currency) + { + if (!store.Balance.TryAdd(type.Key, type.Value)) + store.Balance[type.Key] += type.Value; + } + + UpdateUserInterface(null, uid, store); + return true; + } + + private void OnIntrinsicStoreAction(Entity ent, ref IntrinsicStoreActionEvent args) + { + ToggleUi(args.Performer, ent.Owner, ent.Comp); + } +} + +[ByRefEvent] +public record struct GetStoreEvent +{ + public readonly bool Handled => Store != null; + public Entity? Store; +} + +public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs +{ + public readonly EntityUid User; + public readonly EntityUid Target; + public readonly EntityUid Used; + public readonly StoreComponent Store; + + // An optional override for the "Target" of this interaction, used to change the name that pops up! + public EntityUid? TargetOverride; + + public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid used, StoreComponent store) + { + User = user; + Target = target; + Used = used; + Store = store; + } +} + diff --git a/Content.Shared/Store/StoreUi.cs b/Content.Shared/Store/StoreUi.cs index d8cb9e6ca8..531788df1f 100644 --- a/Content.Shared/Store/StoreUi.cs +++ b/Content.Shared/Store/StoreUi.cs @@ -37,9 +37,10 @@ public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessa } [Serializable, NetSerializable] -public sealed class StoreBuyListingMessage(ProtoId listing) : BoundUserInterfaceMessage +public sealed class StoreBuyListingMessage(ProtoId listing, NetEntity? soundSource) : BoundUserInterfaceMessage { public ProtoId Listing = listing; + public NetEntity? SoundSource = soundSource; } [Serializable, NetSerializable] diff --git a/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideColliderComponent.cs b/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideColliderComponent.cs new file mode 100644 index 0000000000..f92f395eb9 --- /dev/null +++ b/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideColliderComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SurveillanceCamera.Components; + +/// +/// Can activate when collided with. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class CameraActiveOnCollideColliderComponent : Component +{ + /// + /// The fixture id used for detecting the collision. + /// + [DataField] + public string FixtureId = "lightTrigger"; +} diff --git a/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideComponent.cs b/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideComponent.cs new file mode 100644 index 0000000000..27c4bf7c09 --- /dev/null +++ b/Content.Shared/SurveillanceCamera/Components/CameraActiveOnCollideComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SurveillanceCamera.Components; + +/// +/// Marks an entity with whenever entities are contacting with it. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class CameraActiveOnCollideComponent : Component +{ + /// + /// Whether this camera is currently being collided with. + /// + [DataField, AutoNetworkedField] + public bool Enabled; + + /// + /// Whether the entity must be powered for this component to work. + /// + [DataField, AutoNetworkedField] + public bool RequiresPower = true; +} diff --git a/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs b/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs index 21381e753f..914ba4a63f 100644 --- a/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs +++ b/Content.Shared/SurveillanceCamera/Components/SurveillanceCameraComponent.cs @@ -8,18 +8,23 @@ namespace Content.Shared.SurveillanceCamera.Components; [Access(typeof(SharedSurveillanceCameraSystem))] public sealed partial class SurveillanceCameraComponent : Component { - // List of active viewers. This is for bookkeeping purposes, - // so that when a camera shuts down, any entity viewing it - // will immediately have their subscription revoked. + /// + /// List of active viewers who have a PVS view subscription on this camera. + /// This is for bookkeeping purposes, + /// so that when a camera shuts down, any entity viewing it + /// will immediately have their subscription revoked. + /// [ViewVariables] - public HashSet ActiveViewers { get; } = new(); + public HashSet ActivePvsViewers { get; } = new(); - // Monitors != Viewers, as viewers are entities that are tied - // to a player session that's viewing from this camera - // - // Monitors are grouped sets of viewers, and may be - // completely different monitor types (e.g., monitor console, - // AI, etc.) + /// + /// Monitors != Viewers, as viewers are entities that are tied + /// to a player session that's viewing from this camera + /// + /// Monitors are grouped sets of viewers, and may be + /// completely different monitor types (e.g., monitor console, + /// AI, etc.) + /// [ViewVariables] public HashSet ActiveMonitors { get; } = new(); diff --git a/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs b/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs index b6743669e9..1be49f8fc8 100644 --- a/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs +++ b/Content.Shared/SurveillanceCamera/SharedSurveillanceCameraSystem.cs @@ -5,7 +5,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.SurveillanceCamera; -public abstract class SharedSurveillanceCameraSystem : EntitySystem +public abstract partial class SharedSurveillanceCameraSystem : EntitySystem { public override void Initialize() { @@ -68,3 +68,11 @@ public enum SurveillanceCameraVisuals : byte Xray, Emp } + +/// +/// Raised on a camera entity to find whether it is externally viewed by some entity. +/// This does not use the actual viewers or monitors camera has and is simply used to see whether the camera is "technically" +/// being looked through by somebody, such as the Station AI. +/// +[ByRefEvent] +public record struct SurveillanceCameraGetIsViewedExternallyEvent(bool Viewed = false); diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.cs b/Content.Shared/Trigger/Systems/TriggerSystem.cs index 1e7261043f..3ba9e76186 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.cs @@ -90,6 +90,9 @@ public sealed partial class TriggerSystem : EntitySystem if (!Resolve(ent, ref ent.Comp)) return false; + if (Terminating(ent)) + return false; // Stop trying to resurrect a dead horse. + if (HasComp(ent)) return false; // already activated diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 6f4b078fe2..4937952dc4 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -574,7 +574,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem _meleeSound.PlayHitSound(target.Value, user, GetHighestDamageSound(modifiedDamage, _protoManager), hitEvent.HitSoundOverride, component); - if (damageResult.GetTotal() > FixedPoint2.Zero) + if (damageResult.GetTotal() > FixedPoint2.Zero && !TerminatingOrDeleted(target.Value)) { DoDamageEffect(targets, user, targetXform); } @@ -633,7 +633,15 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem // Validate client for (var i = entities.Count - 1; i >= 0; i--) { - if (ArcRaySuccessful(entities[i], + var entity = entities[i]; + + if (TerminatingOrDeleted(entity)) + { + entities.RemoveAt(i); + continue; + } + + if (!ArcRaySuccessful(entity, userPos, direction.ToWorldAngle(), component.Angle, @@ -642,11 +650,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem user, session)) { - continue; + // Bad input + entities.RemoveAt(i); } - - // Bad input - entities.RemoveAt(i); } var targets = new List(); @@ -728,6 +734,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem $"{ToPrettyString(user):actor} melee attacked (heavy) {ToPrettyString(entity):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.GetTotal():damage} damage"); } } + + if (TerminatingOrDeleted(entity)) + targets.RemoveAt(i); } if (entities.Count != 0) @@ -736,7 +745,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem _meleeSound.PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component); } - if (appliedDamage.GetTotal() > FixedPoint2.Zero) + if (appliedDamage.GetTotal() > FixedPoint2.Zero && targets.Count > 0) { DoDamageEffect(targets, user, Transform(targets[0])); } diff --git a/Content.Shared/Weapons/Ranged/Components/BallisticAmmoInteractLoaderComponent.cs b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoInteractLoaderComponent.cs new file mode 100644 index 0000000000..04d4a80774 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoInteractLoaderComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Ranged.Components; + +/// +/// If an entity with has this component, it can be used to interact +/// with the ammo entity to load it into the gun (or magazine). +/// Basically the reverse order (used vs target) to achieve the same result (loading the gun) +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class BallisticAmmoInteractLoaderComponent : Component; diff --git a/Content.Shared/Weapons/Ranged/Events/BeforeAmmoLoadedEvent.cs b/Content.Shared/Weapons/Ranged/Events/BeforeAmmoLoadedEvent.cs new file mode 100644 index 0000000000..896bfbcb6d --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Events/BeforeAmmoLoadedEvent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Weapons.Ranged.Components; + +namespace Content.Shared.Weapons.Ranged.Events; + +/// +/// Raised on the ammo before it is loaded into a gun (or a magazine) +/// +[ByRefEvent] +public struct BeforeAmmoLoadedEvent() +{ + /// + /// if the entity can be used to load the gun or magazine + /// + public bool CanLoad = true; + + /// + /// If null the entity itself is used to load a gun or magazine, + /// if not null, the entity provided is used to load the gun or magazine + /// + public EntityUid? AmmoOverride; +} diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 831db83c3b..ffd1d71cc0 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -3,6 +3,7 @@ using Content.Shared.Emp; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; +using Content.Shared.Stacks; using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -16,6 +17,7 @@ public abstract partial class SharedGunSystem { [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedStackSystem _stack = null!; [MustCallBase] protected virtual void InitializeBallistic() @@ -34,6 +36,8 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent(OnBallisticRefillerMapInit); SubscribeLocalEvent(OnRefillerEmpPulsed); + + SubscribeLocalEvent(OnBallisticAmmoLoad); } private void OnBallisticRefillerMapInit(Entity entity, ref MapInitEvent _) @@ -63,7 +67,6 @@ public abstract partial class SharedGunSystem { if (args.Handled || !component.MayTransfer || - !Timing.IsFirstTimePredicted || args.Target == null || args.Used == args.Target || Deleted(args.Target) || @@ -324,11 +327,20 @@ public abstract partial class SharedGunSystem bool suppressInsertionSound = false ) { - if (!CanInsertBallistic(entity, inserted)) + inserted = _stack.GetOne(inserted); + var ammoEv = new BeforeAmmoLoadedEvent(); + RaiseLocalEvent(inserted, ref ammoEv); + + if (!ammoEv.CanLoad) return false; - entity.Comp.Entities.Add(inserted); - Containers.Insert(inserted, entity.Comp.Container); + var ammo = ammoEv.AmmoOverride ?? inserted; + + if (!CanInsertBallistic(entity, ammo)) + return false; + + entity.Comp.Entities.Add(ammo); + Containers.Insert(ammo, entity.Comp.Container); if (!suppressInsertionSound) { Audio.PlayPredicted(entity.Comp.SoundInsert, entity, user); @@ -343,11 +355,8 @@ public abstract partial class SharedGunSystem public void UpdateBallisticAppearance(Entity ent) { - if (!Timing.IsFirstTimePredicted || !TryComp(ent, out var appearance)) - return; - - Appearance.SetData(ent, AmmoVisuals.AmmoCount, GetBallisticShots(ent.Comp), appearance); - Appearance.SetData(ent, AmmoVisuals.AmmoMax, ent.Comp.Capacity, appearance); + Appearance.SetData(ent, AmmoVisuals.AmmoCount, GetBallisticShots(ent.Comp)); + Appearance.SetData(ent, AmmoVisuals.AmmoMax, ent.Comp.Capacity); } public void SetBallisticUnspawned(Entity entity, int count) @@ -369,6 +378,21 @@ public abstract partial class SharedGunSystem PauseSelfRefill(entity, args.Duration); } + private void OnBallisticAmmoLoad(Entity ent, ref AfterInteractEvent args) + { + if (args.Handled || args.Target == null) + return; + + if (!TryComp(ent, out var ballisticAmmoProviderComp)) + return; + + if (TryBallisticInsert( + (ent, ballisticAmmoProviderComp), + args.Target.Value, + args.User)) + args.Handled = true; + } + private void UpdateBallistic(float frameTime) { var query = EntityQueryEnumerator(); diff --git a/Content.Shared/Wires/SharedWiresComponent.cs b/Content.Shared/Wires/SharedWiresComponent.cs index d691e854ea..8fdb3de0b1 100644 --- a/Content.Shared/Wires/SharedWiresComponent.cs +++ b/Content.Shared/Wires/SharedWiresComponent.cs @@ -242,7 +242,7 @@ namespace Content.Shared.Wires WireLetter.γ => "wire-letter-name-gamma", WireLetter.δ => "wire-letter-name-delta", WireLetter.ε => "wire-letter-name-epsilon", - WireLetter.ζ => "wire-letter-name-zeta ", + WireLetter.ζ => "wire-letter-name-zeta", WireLetter.η => "wire-letter-name-eta", WireLetter.θ => "wire-letter-name-theta", WireLetter.ι => "wire-letter-name-iota", diff --git a/Resources/Audio/Effects/Footsteps/suitstep1.ogg b/Resources/Audio/Effects/Footsteps/suitstep1.ogg deleted file mode 100644 index 8de7412490..0000000000 Binary files a/Resources/Audio/Effects/Footsteps/suitstep1.ogg and /dev/null differ diff --git a/Resources/Audio/Effects/Footsteps/suitstep2.ogg b/Resources/Audio/Effects/Footsteps/suitstep2.ogg deleted file mode 100644 index 56396fb71e..0000000000 Binary files a/Resources/Audio/Effects/Footsteps/suitstep2.ogg and /dev/null differ diff --git a/Resources/Audio/Misc/attributions.yml b/Resources/Audio/Misc/attributions.yml index 8fda833b1d..ada6b70983 100644 --- a/Resources/Audio/Misc/attributions.yml +++ b/Resources/Audio/Misc/attributions.yml @@ -87,3 +87,8 @@ license: "CC-BY-SA-3.0" copyright: "Taken from Citadel station" source: "https://github.com/Citadel-Station-13/Citadel-Station-13/blob/e31455667ebf3331255c1143e1e7215bc737a287/sound/misc/deltakalaxon.ogg" + +- files: ["camera_snap.ogg"] + license: "CC-BY-4.0" + copyright: "Taken from freesound, edited by ketufaispikinut(GitHub)" + source: "https://freesound.org/people/thecheeseman/sounds/51360/" diff --git a/Resources/Audio/Misc/camera_snap.ogg b/Resources/Audio/Misc/camera_snap.ogg new file mode 100644 index 0000000000..5730547ad4 Binary files /dev/null and b/Resources/Audio/Misc/camera_snap.ogg differ diff --git a/Resources/Audio/Misc/delta_alt.ogg b/Resources/Audio/Misc/delta_alt.ogg deleted file mode 100644 index b389910f13..0000000000 Binary files a/Resources/Audio/Misc/delta_alt.ogg and /dev/null differ diff --git a/Resources/Audio/Weapons/Guns/Empty/attributions.yml b/Resources/Audio/Weapons/Guns/Empty/attributions.yml new file mode 100644 index 0000000000..2789237e39 --- /dev/null +++ b/Resources/Audio/Weapons/Guns/Empty/attributions.yml @@ -0,0 +1,4 @@ +- files: ["empty_beep.ogg"] + license: "CC-BY-4.0" + copyright: "Created by altemark" + source: "https://freesound.org/people/altemark/sounds/39747" diff --git a/Resources/Audio/Weapons/Guns/Empty/empty_beep.ogg b/Resources/Audio/Weapons/Guns/Empty/empty_beep.ogg new file mode 100644 index 0000000000..512cd8c12f Binary files /dev/null and b/Resources/Audio/Weapons/Guns/Empty/empty_beep.ogg differ diff --git a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml index e9154f9917..f3067ad07e 100644 --- a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml +++ b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml @@ -47,3 +47,8 @@ license: "CC-BY-4.0" copyright: "Created by UnderlinedDesigns and DrinkingWindGames (https://freesound.org/people/DrinkingWindGames/sounds/789647), modified by Halycon" source: "https://freesound.org/people/UnderlinedDesigns/sounds/188499/" + +- files: ["magnetic_shot.ogg"] + license: "CC-BY-4.0" + copyright: "Created by simeonradivoev" + source: "https://freesound.org/people/simeonradivoev/sounds/740218" diff --git a/Resources/Audio/Weapons/Guns/Gunshots/magnetic_shot.ogg b/Resources/Audio/Weapons/Guns/Gunshots/magnetic_shot.ogg new file mode 100644 index 0000000000..9f77a610bb Binary files /dev/null and b/Resources/Audio/Weapons/Guns/Gunshots/magnetic_shot.ogg differ diff --git a/Resources/Audio/Weapons/Guns/MagIn/attributions.yml b/Resources/Audio/Weapons/Guns/MagIn/attributions.yml new file mode 100644 index 0000000000..cb739cec28 --- /dev/null +++ b/Resources/Audio/Weapons/Guns/MagIn/attributions.yml @@ -0,0 +1,4 @@ +- files: ["tile_load.ogg"] + license: "CC-BY-NC-4.0" + copyright: "Created by LloydEvans09" + source: "https://freesound.org/people/LloydEvans09/sounds/321807" diff --git a/Resources/Audio/Weapons/Guns/MagIn/tile_load.ogg b/Resources/Audio/Weapons/Guns/MagIn/tile_load.ogg new file mode 100644 index 0000000000..c2346ad8c3 Binary files /dev/null and b/Resources/Audio/Weapons/Guns/MagIn/tile_load.ogg differ diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index dcaae63db7..43420c1019 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -1691,5 +1691,21 @@ Entries: id: 206 time: '2026-03-08T23:41:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/41427 +- author: SlamBamActionman + changes: + - message: adduplink command now works properly, gives an uplink implant if there's + no PDA, and returns the uplink code in the command terminal. + type: Fix + id: 207 + time: '2026-04-04T20:14:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38712 +- author: mikeysaurus + changes: + - message: The debug SetOutfit command now also fills target's storage containers + based on the gear preset. + type: Tweak + id: 208 + time: '2026-04-09T05:05:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43346 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6a8d7125ab..6b0659ee76 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,290 +1,4 @@ Entries: -- author: aada - changes: - - message: Cannabis no longer stacks infinitely. Sorry, botanists! - type: Fix - id: 9080 - time: '2025-10-12T01:29:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/38412 -- author: GovnokradZXC - changes: - - message: New hair named Pigtail (Over Eye) - type: Add - id: 9081 - time: '2025-10-12T05:45:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39850 -- author: Princess-Cheeseballs - changes: - - message: Meat Kudzu gasps less often. - type: Tweak - id: 9082 - time: '2025-10-12T10:47:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39304 -- author: PJB3005 - changes: - - message: The patrons list in the in-game credits works again. - type: Fix - id: 9083 - time: '2025-10-12T11:02:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40840 -- author: DrSmugleaf - changes: - - message: Fixed species not being ordered alphabetically in the character customization - UI. - type: Fix - id: 9084 - time: '2025-10-12T11:14:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39359 -- author: Callmore - changes: - - message: Bullet casings can now be picked up again. - type: Fix - id: 9085 - time: '2025-10-12T18:45:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40829 -- author: CoconutThunder - changes: - - message: Fixed lubed items being thrown when looking at the pickup verb. - type: Fix - - message: Fixed multihanded items showing a popup when looking at the pickup verb. - type: Fix - - message: Fixed a bug with lubed handcuffs. - type: Fix - id: 9086 - time: '2025-05-25T05:10:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/38705 -- author: GitHubUser53123 - changes: - - message: Zombies that aren't supposed to spread the zombie virus now don't do - so on mobs in critical state. - type: Fix - id: 9087 - time: '2025-10-13T01:24:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40857 -- author: PotentiallyTom - changes: - - message: Added a page in the guidebook listing common AI and silicon lawsets. - type: Add - id: 9088 - time: '2025-10-13T11:55:26.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/38225 -- author: meganerobot - changes: - - message: SmartFridges are now airtight. - type: Tweak - id: 9089 - time: '2025-10-13T16:19:03.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40196 -- author: TrixxedHeart - changes: - - message: Renamed "trash" reagent to "reprocessed material" - type: Tweak - id: 9090 - time: '2025-10-13T17:18:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39761 -- author: ScarKy0 - changes: - - message: Station AI can no longer control bolts, emergency access and electrify - status on doors it has no access to. - type: Fix - id: 9091 - time: '2025-10-13T20:53:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/38444 -- author: Myra - changes: - - message: The built-in soundfont for MIDI's should sound better, especially for - songs with percussion. - type: Tweak - id: 9092 - time: '2025-10-13T21:22:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40888 -- author: KittyCat432 - changes: - - message: Slime guidebook is more representative of their actual strengths. - type: Tweak - id: 9093 - time: '2025-10-14T04:21:07.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40842 -- author: MilenVolf - changes: - - message: Fixed some physics bugs that could cause the grappling hook to teleport - entities. - type: Fix - id: 9094 - time: '2025-10-14T10:35:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40899 -- author: SirWarock - changes: - - message: Made Bodybags slightly harder to drag, and rollerbeds slightly easier. - They have wheels. - type: Tweak - id: 9095 - time: '2025-10-14T11:41:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40880 -- author: aada - changes: - - message: Geiger counters can now be placed on a utility belt. - type: Fix - id: 9096 - time: '2025-10-14T12:12:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40898 -- author: kontakt - changes: - - message: Singulo now consumes carpet tiles. - type: Fix - id: 9097 - time: '2025-10-14T18:27:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40896 -- author: B_Kirill - changes: - - message: Fixed blocked rotation of zombie NPCs. - type: Fix - id: 9098 - time: '2025-10-14T19:12:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40812 -- author: temm1ie, aksosotl - changes: - - message: Added new botany-themed poster. - type: Add - id: 9099 - time: '2025-10-14T19:42:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40908 -- author: ArtisticRoomba - changes: - - message: Atmospherics Delta-Pressure now properly computes delta pressure for - structures that can hold air in their tile while still being airtight (ex. directional - windows, diagonal windows). - type: Fix - id: 9100 - time: '2025-10-14T22:46:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40435 -- author: SlamBamActionman - changes: - - message: Lying trait now features more grammatically correct lying. - type: Fix - id: 9101 - time: '2025-10-14T23:13:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39370 -- author: Winkarst-cpu - changes: - - message: Now doors play an animation while being emagged. - type: Fix - id: 9102 - time: '2025-10-14T23:31:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40350 -- author: slarticodefast - changes: - - message: Zombified arachnids can no longer spam infinite silk due to having no - hunger. - type: Fix - id: 9103 - time: '2025-10-14T23:31:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40279 -- author: telavivgamers - changes: - - message: You may put ashes and matchsticks into ashtrays. - type: Tweak - id: 9104 - time: '2025-10-15T12:38:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40926 -- author: slarticodefast, Davyei - changes: - - message: You can now use parcelwrap on humanoids. - type: Add - id: 9105 - time: '2025-10-15T20:30:45.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40911 -- author: aada - changes: - - message: Grenade penguins have new AI. They won't bite your hand while holding - them, and hone in on a single target when released. - type: Tweak - id: 9106 - time: '2025-10-15T23:59:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/34935 -- author: DinnerCalzone - changes: - - message: ID card sprites have been tweaked to unsquish their job icons. - type: Tweak - id: 9107 - time: '2025-10-16T03:35:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40414 -- author: redmushie - changes: - - message: Fixed power sensors not respecting their configured network setting - type: Fix - id: 9108 - time: '2025-10-16T12:38:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40934 -- author: Quasr - changes: - - message: Sky blue fancy tables and curtains are now construct-able - type: Add - - message: Sky blue carpet is now printable - type: Fix - id: 9109 - time: '2025-10-16T13:37:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40867 -- author: Worldwaker - changes: - - message: Fixed parcels being indestructible for all damage types except Slash - damage. - type: Fix - id: 9110 - time: '2025-10-16T16:28:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40940 -- author: FairlySadPanda - changes: - - message: Two new instrument options for the microphone, Wa and Wah. - type: Add - - message: Fixed the gilded bike horn's instrument not playing properly, causing - listening to it to be even worse than it is supposed to be. - type: Fix - - message: Fixed the microphone's Kweh instrument not playing properly, causing - listening it to be even worse than it is supposed to be. - type: Fix - id: 9111 - time: '2025-10-16T17:03:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39210 -- author: MilenVolf - changes: - - message: Go go hat's activation phrase can be reset to the default without having - to record it manually to reset the phrase. - type: Tweak - id: 9112 - time: '2025-10-16T18:47:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/35636 -- author: Wolfkey-SomeoneElseTookMyUsername - changes: - - message: The recipes for grilled cheese, cotton buns, cotton cakes, and cotton - grilled cheese are now in the correct category in the guidebook - type: Fix - id: 9113 - time: '2025-10-17T18:56:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40949 -- author: TrixxedHeart - changes: - - message: Added Enter/Leave Genpop access to Security by default, allowing them - to be able to fix Genpop turnstiles with the access configurator if they are - destroyed. - type: Tweak - id: 9114 - time: '2025-10-18T00:28:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39515 -- author: TrixxedHeart - changes: - - message: Added Vox Chitter and Clicking emotes - type: Add - id: 9115 - time: '2025-10-18T00:28:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40878 -- author: Kittygyat - changes: - - message: Added a new generic Artistry borg module! - type: Add - id: 9116 - time: '2025-10-18T07:13:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39679 - author: Hitlinemoss changes: - message: Folders and clipboards now recycle into sensible material components, @@ -4033,3 +3747,286 @@ Entries: id: 9591 time: '2026-03-27T23:00:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/43361 +- author: Gentleman-Bird + changes: + - message: Clockwork Glass is now grindable + type: Fix + id: 9592 + time: '2026-03-28T15:14:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43371 +- author: Minerva + changes: + - message: Singularity distortion effects now respect the reduced motion accessibility + option. + type: Tweak + id: 9593 + time: '2026-03-28T16:01:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43362 +- author: M4rchy-S + changes: + - message: '"Reduce motion of visual effects" now works for drunk and blood loss + status' + type: Add + id: 9594 + time: '2026-03-28T22:19:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/41878 +- author: insoPL, Quantum-cross, ArtisticRoomba + changes: + - message: Hot gasses now have a visible distortion effect. + type: Add + id: 9595 + time: '2026-03-29T02:36:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42973 +- author: HoofedEar + changes: + - message: Lathe machine interfaces now display their proper names + type: Tweak + id: 9596 + time: '2026-03-29T23:26:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43392 +- author: aada + changes: + - message: The value of tier 1 tech disks has been lowered. + type: Tweak + id: 9597 + time: '2026-03-29T23:41:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43395 +- author: MissKay1994 + changes: + - message: Salt is now significantly less common in ore generation + type: Tweak + id: 9598 + time: '2026-03-31T00:31:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43012 +- author: Tayrtahn + changes: + - message: Ghost time of death display is now accurate. + type: Fix + id: 9599 + time: '2026-03-31T03:41:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43411 +- author: ThatGuyUSA + changes: + - message: Wizard robes and hats have been given proper in-hand sprites. + type: Tweak + id: 9600 + time: '2026-04-02T22:11:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43429 +- author: Booblesnoot42 + changes: + - message: 'EXPERIMENTAL: Player-controlled spiders and slimes are now forced to + attack if they are passive near enemies for too long.' + type: Tweak + id: 9601 + time: '2026-04-03T06:18:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42399 +- author: alexalexmax + changes: + - message: Added a new uplink category for objective items. + type: Add + - message: 'Added a new objective for traitors: Hijack the Automated Trade Station. + Your uplink will have a hijack beacon for you to use if you have this objective' + type: Add + id: 9602 + time: '2026-04-03T10:35:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42135 +- author: Minemoder + changes: + - message: Humanoids can now break out of locked genpop lockers + type: Tweak + id: 9603 + time: '2026-04-03T18:01:11.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/41633 +- author: sudobeans + changes: + - message: Crew Manifest PDA program now displays a message when it fails to load. + type: Tweak + id: 9604 + time: '2026-04-03T18:16:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43400 +- author: slarticodefast + changes: + - message: You can now throw banana cream pies at the station AI. + type: Add + id: 9605 + time: '2026-04-03T18:35:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43388 +- author: Samuka + changes: + - message: When reloading guns or magazines with stack items, it only reloads one + at the time, instead of inserting the whole stack. + type: Fix + id: 9606 + time: '2026-04-03T21:44:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/41503 +- author: 0-Anon + changes: + - message: Rebalanced the syndicate reinforcement specializations to be more effective + at their roles. + type: Tweak + id: 9607 + time: '2026-04-04T04:06:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43057 +- author: spanky-spanky + changes: + - message: Cell rechargers, weapon rechargers and turbo rechargers can now be picked + up to transport them when unanchored. + type: Tweak + id: 9608 + time: '2026-04-04T07:15:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/40645 +- author: ThatGuyUSA + changes: + - message: Midround Wizards now show up on the end results screen. + type: Fix + id: 9609 + time: '2026-04-04T15:51:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42766 +- author: Zekins3366 + changes: + - message: Fixed being able to set multiple conflicting head-top visuals on humans + at the same time. + type: Fix + id: 9610 + time: '2026-04-04T16:48:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43465 +- author: SlamBamActionman + changes: + - message: Player sell costs are now randomized when scanned with an appraisal tool. + type: Tweak + id: 9611 + time: '2026-04-04T17:04:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43445 +- author: themias + changes: + - message: Instead of disappearing, space dragons now burst into giblets if they + go too long without summoning a rift. + type: Tweak + id: 9612 + time: '2026-04-04T18:35:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43296 +- author: VanderslootAssgiraffe + changes: + - message: Added new lobby art depicting a mime mocking nukies trapped behind an + invisible wall as the mime defuses the nuke + type: Add + id: 9613 + time: '2026-04-04T19:38:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42947 +- author: 0-Anon + changes: + - message: Rat Kings can now pull objects. + type: Tweak + id: 9614 + time: '2026-04-04T19:58:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42701 +- author: SlamBamActionman + changes: + - message: Uplink PDA codes are now universal. This means any PDA can open an uplink + store, as long as they have the correct code. + type: Tweak + - message: PDA ringtones now feature more notes. + type: Tweak + id: 9615 + time: '2026-04-04T20:14:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38712 +- author: Naxel + changes: + - message: Heavily optimized the radiation system to prevent severe server lag when + a large number of radiation sources are active. + type: Fix + id: 9616 + time: '2026-04-05T04:53:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39173 +- author: Princess-Cheeseballs + changes: + - message: Random pill bottles can no longer spawn with ambuzol or ambuzol+ + type: Remove + id: 9617 + time: '2026-04-05T05:36:30.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43458 +- author: K-Dynamic + changes: + - message: Clowns may change their loadout to start with the honkmother mitre. + type: Add + - message: Renames robes of the honkmother to honkmother coat. + type: Tweak + id: 9618 + time: '2026-04-05T10:17:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35237 +- author: ScarKy0 + changes: + - message: Cameras show as in-use when AI is nearby. + type: Add + - message: AI can no longer enable lights on cameras. + type: Remove + id: 9619 + time: '2026-04-05T15:17:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43466 +- author: Princess-Cheeseballs + changes: + - message: Web vests have new more visible sprites. + type: Add + id: 9620 + time: '2026-04-05T21:58:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43486 +- author: Princess-Cheeseballs + changes: + - message: Uplink implant now links to the same store as your PDA ringtone. + type: Add + id: 9621 + time: '2026-04-05T22:16:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43485 +- author: AndrewFenriz + changes: + - message: Added a wide variety of new tile recipes to the Cutter Machine. + type: Add + id: 9622 + time: '2026-04-05T22:33:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43431 +- author: psykana + changes: + - message: Nuclear operatives round end summary now shows the Disk's location. If + it wasn't atomized, that is. + type: Add + id: 9623 + time: '2026-04-06T21:41:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39767 +- author: Princess-Cheeseballs + changes: + - message: Added a new Traitor objective to kill the Station AI! + type: Add + - message: Traitor kill objectives will now show stage names instead of character + names if applicable + type: Fix + id: 9624 + time: '2026-04-06T22:23:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43462 +- author: SuperGDPWYL + changes: + - message: Traitors can now be tasked with causing anomalies to go supercritical. + type: Add + id: 9625 + time: '2026-04-07T18:59:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43505 +- author: ketufaispikinut + changes: + - message: Added the tourist's travel camera along with very basic textual photographs. + type: Add + id: 9626 + time: '2026-04-07T20:33:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43322 +- author: alexalexmax + changes: + - message: Items that can be stuck to walls are now able to be unstuck properly. + type: Fix + id: 9627 + time: '2026-04-08T04:13:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43514 +- author: TriviaSolari + changes: + - message: The Station AI is now notified when their core is taking damage. + type: Add + id: 9628 + time: '2026-04-08T05:40:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43513 diff --git a/Resources/Changelog/Maps.yml b/Resources/Changelog/Maps.yml index b971abf643..df01e9ce16 100644 --- a/Resources/Changelog/Maps.yml +++ b/Resources/Changelog/Maps.yml @@ -1078,4 +1078,13 @@ id: 132 time: '2026-03-24T21:43:08.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/43046 +- author: ProPeperos + changes: + - message: On Fland Evac shuttle (Delta) Added missing APC in the upper left room + type: Tweak + - message: On Fland Evac shuttle (Delta) Moved air canister connector + type: Tweak + id: 133 + time: '2026-04-06T18:34:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/43481 Order: 2 diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index fd60cbee29..64ab3e20ae 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0-Anon, 0leshe, 0tito, 0x6273, 11BelowStudio, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 27alaing, 2DSiggy, 3nderall, 4310v343k, 4dplanner, 5tickman, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, AffleWaffle, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, ahandleman, Ahion, aiden, Aidenkrz, aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexalexmax, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, AlphaQwerty, Altoids1, amatwiedle, amylizzle, Andre19926, Andrew-Fall, AndrewEyeke, AndrewFenriz, AndreyCamper, anri, Anzarot121, ApolloVector, Appiah, april-gras, ar4ill, Arcane-Waffle, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, arthropodia, ArthurMousatov, ArtisticRoomba, artur, Artxmisery, ArZarLordOfMango, as334, AshBats, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, Atakku, Ataman, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, AwareFoxy, Awlod, Axionyxx, azloserbits, AzzyIsNotHere, azzyisnothere, B-Kirill, B3CKDOOR, baa14453, BackeTako, BadaBoomie, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, bea, bebr3ght, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, Blobadoodle, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, BombasterDS, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, breeplayx3, BriBrooo, BRINGit34, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, buunie099, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, cammusubi, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, CawsForConcern, CDWimmer, Centronias, Chaboricks, chairbender, chaisftw, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, cloudyias, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, CoolioDudio, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, CraftyRenter, crazybrain23, Crazydave91920, CrazyPhantom779, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DaCookieCakes, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkie, DaturoDewitt, david, DawBla, Daxxi3, dch-GH, ddeegan, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, debugok, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, DieselMohawkTheSequel, digitalic, Dimastra, dimmoon1, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, EchoOfNothing, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, emberwinters, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, erorr404v1, Errant-4, ertanic, esguard, estacaoespacialpirata, eternally-confused, eugene, ewokswagger, exincore, exp111, f0x-n3rd, F1restar4, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, fillervk, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Fruitsalad, Funce, FungiFellow, FunkySphere, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, GitHubUser53123, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, GR1231, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, happyrobot33, Hardly3D, harikattar, Hayden, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, Hi-Im-Shot, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, hoshizora-sayo, Hreno, Hrosts, htmlsystem, Huaqas, hubismal, Hugal31, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, JackRyd3r, jacksonzck, JackspajfMain, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap-havok, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, JesterX666, Jewelots, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, johnjjohn, JohnJJohn, johnku1, Jophire, Jopogrechkin, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KeTuFaisPiKiNut, KIBORG04, KieueCaprie, Kimpes, kin98, KingFroozy, kipdotnet, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit, Kit0vras, KittenColony, Kittygyat, klaypexx, kleinerstation13, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, korczoczek, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kryyto, Kupie, kxvvv, Kyoth25f, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, Le-Arctic-Fox, leahcat, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, liem161, LightVillet, lilazero, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, lolman360, Lomcastar, Lordbrandon12, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luegamer, luizwritescode, LukaSlade, luminight, lunarcomets, Lusatia, Luxeator, lvvova1, Lyndomen, lyroth001, lyxcaster, lzimann, lzk228, M1tht1c, M3739, M4rchy-S, M87S, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, magnnusson, magnuscrowe, maland1, malchanceux, MaloTV, manelnavola, ManelNavola, Mangohydra, marboww, Markek1, MarkerWicker, marlyn, mastermiller01, matt, Matz05, max, MaxNox7, maylokana, mdrkrg, MDuch369, meara1179, meganerobot, MehimoNemo, Mehnix, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, mikeysaurus, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mohamedwidar, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mqole, mr-bo-jangles, Mr0maks, MrFippik, MrPersival, mrrobdemo, mtrs163, muburu, MureixloI, murolem, murphyneko, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neborsh, nekokiwa, neomoth, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, NoreUhh, not-gavnaed, notafet, notquitehadouken, notsodana, noudoit, noverd, Nox38, NuclearWinter, Nuggets219, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, Ohelig, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnsenCapy, OnyxTheBrave, opl-, Orange-Winds, OrangeMoronage9622, OrbitSystem07, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, PAFFhassoocks, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, pavlockblaine03, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, pgraycs, PGrayCS, Pgriha, phantom-lily, Pharaz4, pheenty, philingham, Phill101, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, Pixel8-dev, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, Pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, Prole0, ProPandaBear, ProPeperos, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quasr-9, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, Redrover1760, redspyy, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rewafflution, rhailrake, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, robinthegirlthing, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, roryflowers, rosieposieeee, Roudenn, router, ruddygreat, rumaks-xyz, RumiTiger, Ruzihm, rwrv, S1rFl0, S1ss3l, Saakra, SabreML, Sadie-silly, saga3152, saintmuntzer, salarua, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, ScholarNZL, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, ser1-1y, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, Shegare, shepardtothestars, shibechef, Siginanto, signalsender, SignalWalker, siigiil, silicon14wastaken, Silverfur-underscore, Simyon264, sirdragooon, Sirionaut, SirWarock, Sk1tch, SkaldetSkaeg, Skarletto, skeeka-dev, skrybl, Skybailey-dev, skye, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, SnappingOpossum, snebl, snicket, sniperchance, Snowni, snowsignal, SolidSyn, SolidusSnek, solstar2, SomegnihT, SonarZeBat, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, SpaceLizard24, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, steel, Steffo99, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, SuperGDPWYL, superjj18, Supernorn, SurrealShibe, SweetAplle, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Synthestra, Szunti, t, Tainakov, takemysoult, taonewt, tap, TaralGit, Taran, taserthefox, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telavivgamers, telyonok, temm1ie, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, thanosdegraf, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, theexetron, TheFlyingSentry, thefoty, TheGrimbeeper, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheSecondLord, TheShuEd, thetolbean, thevinter, TheWaffleJesus, Thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, travis-g-reid, treytipton, TriviaSolari, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, tuchila-adi-bogdan, Tunguso4ka, TurboTrackerss14, TVK-04, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, Uberration, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, Unkn0wnGh0st333, unusualcrow, UpAndLeaves, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, vgskye, viceemargo, VigersRay, violet754, Visne, vitopigno, vitusveit, vlad, vlados1408, VMSolidus, vmzd, VoidMeticulous, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wachte1, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, Worldwaker, wrexbe, wtcwr68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex +0-Anon, 0leshe, 0tito, 0x6273, 11BelowStudio, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 27alaing, 2DSiggy, 3nderall, 4310v343k, 4dplanner, 5tickman, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, AffleWaffle, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, ahandleman, Ahion, aiden, Aidenkrz, aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexalexmax, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, AlphaQwerty, Altoids1, amatwiedle, amylizzle, Andre19926, Andrew-Fall, AndrewEyeke, AndrewFenriz, AndreyCamper, anri, Anzarot121, ApolloVector, Appiah, april-gras, ar4ill, Arcane-Waffle, arcanevaliance, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, arthropodia, ArthurMousatov, ArtisticRoomba, artur, Artxmisery, ArZarLordOfMango, as334, AshBats, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, Atakku, Ataman, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, AwareFoxy, Awlod, Axionyxx, azloserbits, AzzyIsNotHere, azzyisnothere, B-Kirill, B3CKDOOR, baa14453, BackeTako, BadaBoomie, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, baynarikattu, bea, bebr3ght, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, Blobadoodle, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, BombasterDS, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, breeplayx3, BriBrooo, BRINGit34, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, buunie099, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, cammusubi, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, CawsForConcern, CDWimmer, Centronias, Chaboricks, chairbender, chaisftw, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, cloudyias, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, CoolioDudio, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, CraftyRenter, crazybrain23, Crazydave91920, CrazyPhantom779, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DaCookieCakes, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkie, DaturoDewitt, david, DawBla, Daxxi3, dch-GH, ddeegan, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, debugok, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, DieselMohawkTheSequel, digitalic, Dimastra, dimmoon1, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, EchoOfNothing, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, emberwinters, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, erorr404v1, Errant-4, ertanic, esguard, estacaoespacialpirata, eternally-confused, eugene, eveloop, ewokswagger, exincore, exp111, f0x-n3rd, F1restar4, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, fillervk, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Fruitsalad, Funce, FungiFellow, FunkySphere, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, GitHubUser53123, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, GR1231, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, happyrobot33, Hardly3D, harikattar, Hayden, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, Hi-Im-Shot, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, hoshizora-sayo, Hreno, Hrosts, htmlsystem, Huaqas, hubismal, Hugal31, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, JackRyd3r, jacksonzck, JackspajfMain, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap-havok, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, JesterX666, Jewelots, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, JohnJJohn, johnjjohn, johnku1, Jophire, Jopogrechkin, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KeTuFaisPiKiNut, KIBORG04, KieueCaprie, Kimpes, kin98, KingFroozy, kipdotnet, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit, Kit0vras, KittenColony, Kittygyat, klaypexx, kleinerstation13, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, korczoczek, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kryyto, Kupie, kxvvv, Kyoth25f, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, Le-Arctic-Fox, leahcat, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, liem161, LightVillet, lilazero, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, lolman360, Lomcastar, Lordbrandon12, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luegamer, luizwritescode, LukaSlade, luminight, lunarcomets, Lusatia, Luxeator, lvvova1, Lyndomen, lyroth001, lyxcaster, lzimann, lzk228, M1tht1c, M3739, M4rchy-S, M87S, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, magnnusson, magnuscrowe, maland1, malchanceux, MaloTV, ManelNavola, manelnavola, Mangohydra, marboww, Markek1, MarkerWicker, marlyn, mastermiller01, matt, Matz05, max, MaxNox7, maylokana, mdrkrg, MDuch369, meara1179, meganerobot, MehimoNemo, Mehnix, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, mikeysaurus, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mohamedwidar, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mqole, mr-bo-jangles, Mr0maks, MrFippik, MrPersival, mrrobdemo, mtrs163, muburu, MureixloI, murolem, murphyneko, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neborsh, nekokiwa, neomoth, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, NoreUhh, Not-A-Chair, not-gavnaed, NotActuallyMarty, notafet, notquitehadouken, notsodana, noudoit, noverd, Nox38, NuclearWinter, Nuggets219, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, Ohelig, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnsenCapy, OnyxTheBrave, opl-, Orange-Winds, OrangeMoronage9622, OrbitSystem07, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, PAFFhassoocks, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, pavlockblaine03, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, pgraycs, PGrayCS, Pgriha, phantom-lily, Pharaz4, philingham, Phill101, Phonix, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, Pixel8-dev, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, Pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, Prole0, ProPandaBear, ProPeperos, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quasr-9, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, RainyGale, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, Redrover1760, redspyy, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rewafflution, rhailrake, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, robinthegirlthing, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, roryflowers, rosieposieeee, Roudenn, router, ruddygreat, rumaks-xyz, RumiTiger, Ruzihm, rwrv, S1rFl0, S1ss3l, Saakra, SabreML, Sadie-silly, saga3152, saintmuntzer, salarua, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, ScholarNZL, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, ser1-1y, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, Shegare, shepardtothestars, shibechef, Siginanto, signalsender, SignalWalker, siigiil, silicon14wastaken, Silverfur-underscore, Simyon264, sirdragooon, Sirionaut, SirWarock, Sk1tch, SkaldetSkaeg, Skarletto, skeeka-dev, skrybl, Skybailey-dev, skye, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, SnappingOpossum, snebl, snicket, sniperchance, Snowni, snowsignal, SolidSyn, SolidusSnek, solstar2, SomegnihT, SonarZeBat, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, SpaceLizard24, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, steel, Steffo99, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, SuperGDPWYL, superjj18, Supernorn, SurrealShibe, SweetAplle, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Synthestra, Szunti, t, Tainakov, takemysoult, taonewt, tap, TaralGit, Taran, taserthefox, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telavivgamers, telyonok, temm1ie, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, thanosdegraf, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, theexetron, TheFlyingSentry, thefoty, TheGrimbeeper, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheSecondLord, TheShuEd, thetolbean, thevinter, TheWaffleJesus, Thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, travis-g-reid, treytipton, TriviaSolari, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, tuchila-adi-bogdan, Tunguso4ka, TurboTrackerss14, TVK-04, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, Uberration, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, Unkn0wnGh0st333, unusualcrow, UpAndLeaves, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, VanderslootAssgiraffe, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, vgskye, viceemargo, VigersRay, violet754, Visne, vitopigno, vitusveit, vlad, vlados1408, VMSolidus, vmzd, VoidMeticulous, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wachte1, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, Worldwaker, wrexbe, wtcwr68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, zekins3366, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex diff --git a/Resources/Locale/en-US/administration/commands/add-uplink-command.ftl b/Resources/Locale/en-US/administration/commands/add-uplink-command.ftl index 40d0c5fa1b..a0cee58050 100644 --- a/Resources/Locale/en-US/administration/commands/add-uplink-command.ftl +++ b/Resources/Locale/en-US/administration/commands/add-uplink-command.ftl @@ -5,4 +5,6 @@ add-uplink-command-completion-1 = Username (defaults to self) add-uplink-command-completion-2 = Uplink uid (default to PDA) add-uplink-command-completion-3 = Is uplink discount enabled add-uplink-command-error-1 = Selected player doesn't control any entity -add-uplink-command-error-2 = Failed to add uplink to the player \ No newline at end of file +add-uplink-command-error-2 = Failed to add uplink to the player +add-uplink-command-success-pda = Uplink added to player PDA with code {$code} +add-uplink-command-success-implant = Uplink added to player as an implant diff --git a/Resources/Locale/en-US/administration/smites.ftl b/Resources/Locale/en-US/administration/smites.ftl index 0702afb33c..2de4e79081 100644 --- a/Resources/Locale/en-US/administration/smites.ftl +++ b/Resources/Locale/en-US/administration/smites.ftl @@ -107,7 +107,6 @@ admin-smite-disarm-prone-description = Makes them get disarmed 100% of the time admin-smite-garbage-can-description = Turn them into a garbage bin to emphasize what they remind you of. admin-smite-super-bonk-description = Slams them on every single table on the Station and beyond. admin-smite-super-bonk-lite-description= Slams them on every single table on the Station and beyond. Stops when the target is dead. -admin-smite-terminate-description = Creates a Terminator ghost role with the sole objective of killing them. admin-smite-super-slip-description = Slips them really, really hard. admin-smite-omni-accent-description = Makes the target speak with almost every accent available. admin-smite-crawler-description = Makes the target fall down and be unable to stand up. Remove their hands too for added effect! diff --git a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl index a2cb5301b2..57ed361248 100644 --- a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl +++ b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl @@ -11,7 +11,6 @@ gas-analyzer-window-tab-title-capitalized = {CAPITALIZE($title)} gas-analyzer-window-refresh-button = Refresh gas-analyzer-window-no-data = No Data gas-analyzer-window-no-gas-text = No Gases -gas-analyzer-window-error-text = Error: {$errorText} gas-analyzer-window-volume-text = Volume: gas-analyzer-window-volume-val-text = {$volume} L gas-analyzer-window-pressure-text = Pressure: diff --git a/Resources/Locale/en-US/atmos/gases.ftl b/Resources/Locale/en-US/atmos/gases.ftl index 5c540c46df..b5d9ed6aae 100644 --- a/Resources/Locale/en-US/atmos/gases.ftl +++ b/Resources/Locale/en-US/atmos/gases.ftl @@ -1,10 +1,18 @@ -gas-ammonia-abbreviation = NH₃ -gas-carbon-dioxide-abbreviation = CO₂ -gas-frezon-abbreviation = F -gas-nitrogen-abbreviation = N₂ -gas-nitrous-oxide-abbreviation = N₂O +gas-oxygen = Oxygen gas-oxygen-abbreviation = O₂ +gas-nitrogen = Nitrogen +gas-nitrogen-abbreviation = N₂ +gas-carbon-dioxide = Carbon Dioxide +gas-carbon-dioxide-abbreviation = CO₂ +gas-plasma = Plasma gas-plasma-abbreviation = P +gas-tritium = Tritium gas-tritium-abbreviation = T +gas-water-vapor = Water Vapor gas-water-vapor-abbreviation = H₂O -gas-unknown-abbreviation = X +gas-ammonia = Ammonia +gas-ammonia-abbreviation = NH₃ +gas-nitrous-oxide = Nitrous Oxide +gas-nitrous-oxide-abbreviation = N₂O +gas-frezon = Frezon +gas-frezon-abbreviation = F diff --git a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl index 11b2fb9402..7dd7c779ac 100644 --- a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl @@ -7,6 +7,7 @@ news-read-program-name = Station news crew-manifest-program-name = Crew manifest crew-manifest-cartridge-loading = Loading ... +crew-manifest-cartridge-loading-failed = Failed to load crew manifest! net-probe-program-name = NetProbe net-probe-scan = Scanned {$device}! diff --git a/Resources/Locale/en-US/changeling/changeling.ftl b/Resources/Locale/en-US/changeling/changeling.ftl index 57ad3550bf..d5c88d06f2 100644 --- a/Resources/Locale/en-US/changeling/changeling.ftl +++ b/Resources/Locale/en-US/changeling/changeling.ftl @@ -1,6 +1,11 @@ -roles-antag-changeling-name = Changeling +# antag selection +roles-antag-changeling-name = Changeling roles-antag-changeling-objective = A intelligent predator that assumes the identities of its victims. +# devour +changeling-devour-attempt-failed-cannot-devour = We cannot devour this! +changeling-devour-attempt-failed-already-devoured = We already consumed this body! +changeling-devour-attempt-failed-not-dead = This body yet lives! We cannot consume it alive! changeling-devour-attempt-failed-rotting = This corpse has only rotted biomass. changeling-devour-attempt-failed-protected = This victim's biomass is protected by armor! @@ -8,12 +13,19 @@ changeling-devour-begin-windup-self = Our uncanny mouth reveals itself with othe changeling-devour-begin-windup-others = { CAPITALIZE(POSS-ADJ($user)) } uncanny mouth reveals itself with otherworldly hunger. changeling-devour-begin-consume-self = The uncanny mouth digs deep into its victim. changeling-devour-begin-consume-others = { CAPITALIZE(POSS-ADJ($user)) } uncanny mouth digs deep into { POSS-ADJ($user) } victim. - -changeling-devour-consume-failed-not-dead = This body yet lives! We cannot consume it alive! changeling-devour-consume-complete-self = Our uncanny mouth retreats, biomass consumed. changeling-devour-consume-complete-others = { CAPITALIZE(POSS-ADJ($user)) } uncanny mouth retreats. +# transformation changeling-transform-attempt-self = Our bones snap, muscles tear, one flesh becomes another. changeling-transform-attempt-others = { CAPITALIZE(POSS-ADJ($user)) } bones snap, muscles tear, body shifts into another. +# transformation BUI +changeling-transform-bui-select-entity = {$entity} +changeling-transform-bui-drop-identity-menu = Drop a devoured identity from your memory. +changeling-transform-bui-drop-identity-entity = Drop {$entity} +changeling-transform-bui-drop-identity-entity-popup = You dropped {$entity} from your memory. +changeling-transform-bui-drop-identity-cannot-drop = You cannot drop your current identity. + +# other changeling-paused-map-name = Changeling identity storage map diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl index 1343aaec7e..da1794badb 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl @@ -1,4 +1,4 @@ -nukeops-title = Nuclear Operatives +nukeops-title = Nuclear Operatives nukeops-description = Nuclear operatives have targeted the station. Try to keep them from arming and detonating the nuke by protecting the nuke disk! nukeops-welcome = @@ -24,6 +24,17 @@ nukeops-cond-allnukiesdead = All nuclear operatives have died. nukeops-cond-somenukiesalive = Some nuclear operatives died. nukeops-cond-allnukiesalive = No nuclear operatives died. +nukeops-disk-location-title = Final location of Disk: +nukeops-disk-carried-by = {" "}carried by [color=White]{$name}[/color], [color=orange]{$job}[/color], {$location} { $user -> + [unknown] { "" } + *[other] ([color=gray]{$user}[/color]) +} + +storage-hierarchy-list = { $items-left -> + [0] { $existing-text } { $item }, + *[other] { $existing-text } { $item }, in +} + nukeops-list-start = The nuclear operatives were: nukeops-list-name = - [color=White]{$name}[/color] nukeops-list-name-user = - [color=White]{$name}[/color] ([color=gray]{$user}[/color]) diff --git a/Resources/Locale/en-US/gases/gases.ftl b/Resources/Locale/en-US/gases/gases.ftl deleted file mode 100644 index e41aa4fc99..0000000000 --- a/Resources/Locale/en-US/gases/gases.ftl +++ /dev/null @@ -1,9 +0,0 @@ -gases-oxygen = Oxygen -gases-nitrogen = Nitrogen -gases-co2 = Carbon Dioxide -gases-plasma = Plasma -gases-tritium = Tritium -gases-water-vapor = Water Vapor -gases-ammonia = Ammonia -gases-n2o = Nitrous Oxide -gases-frezon = Frezon diff --git a/Resources/Locale/en-US/headset/headset-component.ftl b/Resources/Locale/en-US/headset/headset-component.ftl index d61fb8edb2..f9f9c17411 100644 --- a/Resources/Locale/en-US/headset/headset-component.ftl +++ b/Resources/Locale/en-US/headset/headset-component.ftl @@ -1,6 +1,6 @@ # Chat window radio wrap (prefix and postfix) -chat-radio-message-wrap = [color={$color}]{$channel} [bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}]"{$message}"[/font][/color] -chat-radio-message-wrap-bold = [color={$color}]{$channel} [bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}][bold]"{$message}"[/bold][/font][/color] +chat-radio-message-wrap = [color={$color}]{$channel} [bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}]“{$message}”[/font][/color] +chat-radio-message-wrap-bold = [color={$color}]{$channel} [bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}][bold]“{$message}”[/bold][/font][/color] examine-headset-default-channel = Use {$prefix} for the default channel ([color={$color}]{$channel}[/color]). diff --git a/Resources/Locale/en-US/hijack-beacon/hijack-beacon.ftl b/Resources/Locale/en-US/hijack-beacon/hijack-beacon.ftl new file mode 100644 index 0000000000..a98370e891 --- /dev/null +++ b/Resources/Locale/en-US/hijack-beacon/hijack-beacon.ftl @@ -0,0 +1,16 @@ +hijack-beacon-announcement-sender = Automated Trade Station +hijack-beacon-announcement-activated = Attention! An Attempted Breach of the Automated Trade Station's firewall has been detected! Estimated {$time} seconds until firewall breach! +hijack-beacon-announcement-deactivated = Firewall breach failed. Firewall integrity partially restored. Have a nice day! +hijack-beacon-announcement-success = Successfully disengaged Automated Trade Station firewall. {$fine} spessos has been transferred from station funds to [%ERROR%]. Your trade station warranty is now void. This incident has been reported. + +hijack-beacon-examine-await-activate = The beacon is [color=green]ready to activate[/color]. +hijack-beacon-examine-await-cooldown = The beacon is [color=red]on cooldown[/color]. +hijack-beacon-examine-await-hijack-complete = The beacon is [color=red]spent[/color]. + +hijack-beacon-popup-anchor = The beacon anchors itself into the ground! +hijack-beacon-popup-unanchor = The beacon unanchors itself from the ground. + +hijack-beacon-verb-activate-text = Activate +hijack-beacon-verb-activate-message = The beacon can only be armed on the Automated Trade Station, on an unoccupied tile. +hijack-beacon-verb-deactivate-text = Deactivate +hijack-beacon-verb-deactivate-message = The beacon isn't going to deactivate itself, you know. diff --git a/Resources/Locale/en-US/lathe/lathe-categories.ftl b/Resources/Locale/en-US/lathe/lathe-categories.ftl index 209daf1ad3..54841b2101 100644 --- a/Resources/Locale/en-US/lathe/lathe-categories.ftl +++ b/Resources/Locale/en-US/lathe/lathe-categories.ftl @@ -31,8 +31,12 @@ lathe-category-faux-tile = Faux lathe-category-maints-tile = Maints lathe-category-marble = Marble lathe-category-steel-tile = Steel +lathe-category-shuttle-tile = Shuttle lathe-category-white-tile = White lathe-category-wood-tile = Wood +lathe-category-plastic-tile = Plastic +lathe-category-precious-tile = Precious +lathe-category-industrial-tile = Industrial # Science lathe-category-mechs = Mechs diff --git a/Resources/Locale/en-US/lobby/lobby-state-background.ftl b/Resources/Locale/en-US/lobby/lobby-state-background.ftl index c0046ca693..7191ebbe12 100644 --- a/Resources/Locale/en-US/lobby/lobby-state-background.ftl +++ b/Resources/Locale/en-US/lobby/lobby-state-background.ftl @@ -1,6 +1,9 @@ lobby-state-background-warden-title = Warden lobby-state-background-warden-artist = Solbusaur +lobby-state-background-invisiblewall-title = Invisible Wall +lobby-state-background-invisiblewall-artist = Vandersloot + lobby-state-background-pharmacy-title = Pharmacy lobby-state-background-pharmacy-artist = Solbusaur diff --git a/Resources/Locale/en-US/objectives/conditions/anomaly-supercrit.ftl b/Resources/Locale/en-US/objectives/conditions/anomaly-supercrit.ftl new file mode 100644 index 0000000000..358b9e8673 --- /dev/null +++ b/Resources/Locale/en-US/objectives/conditions/anomaly-supercrit.ftl @@ -0,0 +1 @@ +objective-condition-supercrit-anomalies-title = Cause {$count} anomalies to go supercritical diff --git a/Resources/Locale/en-US/objectives/conditions/kill-person.ftl b/Resources/Locale/en-US/objectives/conditions/kill-person.ftl index aad31d26f9..c5ea6f47fc 100644 --- a/Resources/Locale/en-US/objectives/conditions/kill-person.ftl +++ b/Resources/Locale/en-US/objectives/conditions/kill-person.ftl @@ -1,3 +1,4 @@ objective-condition-kill-person-title = Kill or maroon {$targetName}, {CAPITALIZE($job)} objective-condition-kill-maroon-title = Kill and maroon {$targetName}, {CAPITALIZE($job)} +objective-condition-kill-station-ai = Destroy {$targetName}, {CAPITALIZE($job)} and ensure they remain out of commission. objective-condition-maroon-person-title = Prevent {$targetName}, {CAPITALIZE($job)} from reaching CentComm. diff --git a/Resources/Locale/en-US/photography/photography.ftl b/Resources/Locale/en-US/photography/photography.ftl new file mode 100644 index 0000000000..1876fb194d --- /dev/null +++ b/Resources/Locale/en-US/photography/photography.ftl @@ -0,0 +1,7 @@ +# TODO: Make this a fluent function in RT +photograph-name-text = This is a photograph of { PROPER($entity) -> + *[false] { INDEFINITE($entity) } { $entity } + [true] { $entity } + }. +photograph-name-text-empty = This is a photograph. +photograph-name-text-photograph = This is a photograph of another photograph. diff --git a/Resources/Locale/en-US/round-end/round-end-system.ftl b/Resources/Locale/en-US/round-end/round-end-system.ftl index 30069f7171..6068e4385e 100644 --- a/Resources/Locale/en-US/round-end/round-end-system.ftl +++ b/Resources/Locale/en-US/round-end/round-end-system.ftl @@ -7,5 +7,11 @@ round-end-system-shuttle-recalled-announcement = The emergency shuttle has been round-end-system-shuttle-sender-announcement = Station round-end-system-round-restart-eta-announcement = Restarting the round in {$time} {$units}... -eta-units-minutes = minutes -eta-units-seconds = seconds +eta-units-minutes = {$amount -> + [one] minute + *[other] minutes +} +eta-units-seconds = {$amount -> + [one] second + *[other] seconds +} diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl index cbe3ef6ec0..1f35af41a8 100644 --- a/Resources/Locale/en-US/silicons/station-ai.ftl +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -8,6 +8,7 @@ station-ai-has-no-power-for-upload = Upload failed - the AI core is unpowered. station-ai-is-too-damaged-for-upload = Upload failed - the AI core must be repaired. station-ai-core-losing-power = Your AI core is now running on reserve battery power. station-ai-core-critical-power = Your AI core is critically low on power. External power must be re-established or severe data corruption may occur! +station-ai-core-taking-damage = Your AI core is sustaining physical damage. # Ghost role station-ai-ghost-role-name = Station AI diff --git a/Resources/Locale/en-US/store/categories.ftl b/Resources/Locale/en-US/store/categories.ftl index 4469a576cd..9ecf4f91d2 100644 --- a/Resources/Locale/en-US/store/categories.ftl +++ b/Resources/Locale/en-US/store/categories.ftl @@ -12,6 +12,7 @@ store-category-allies = Allies store-category-job = Job store-category-wearables = Wearables store-category-pointless = Pointless +store-category-objective = Objective store-discounted-items = Discounts # Revenant diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 4f67fae1cd..60f13dbc42 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -409,6 +409,9 @@ uplink-soap-desc = An untrustworthy bar of soap. Smells of fear. uplink-ultrabright-lantern-name = Extra-Bright Lantern uplink-ultrabright-lantern-desc = This ultra-bright lantern can be used to blind people, similar to a flash. +uplink-travel-camera-name = Travel Camera +uplink-travel-camera-desc = Stun people with your photography skills and the conveniently legal camera flash. Makes you look like a tourist. + uplink-combat-medkit-name = Combat Medical Kit uplink-combat-medkit-desc = A medkit made for fixing combat injuries. @@ -520,3 +523,7 @@ uplink-briefcase-gun-desc = An indistinct briefcase with a highly compact C-20K uplink-energycrossbow-name = Mini Energy Crossbow uplink-energycrossbow-desc = The go-to sidearm of any operative who prefers their victims not to be moving. Fires regenerating toxic arrows that floors victims in an instant. + +#Objective items +uplink-hijack-beacon-name = Hijack Beacon +uplink-hijack-beacon-desc = A syndicate-brand hijack beacon designed to get around the firewalls of Nanotrasen-brand Automated Trade Stations. They take 200 seconds to work and Trade Stations will announce they are being hacked, so prepare accordingly. diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl index c02e72da19..37a52a8d4d 100644 --- a/Resources/Locale/en-US/tips.ftl +++ b/Resources/Locale/en-US/tips.ftl @@ -6,9 +6,9 @@ tips-dataset-5 = Artifacts have the ability to gain permanent effects for some t tips-dataset-6 = You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway. tips-dataset-7 = Some plants, such as galaxy thistle, can be ground up into extremely useful and potent medicines. tips-dataset-8 = Mopping up puddles and draining them into other containers conserves the reagents found in the puddle. -tips-dataset-9 = Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood. +tips-dataset-9 = Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them — including blood. tips-dataset-10 = Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it. -tips-dataset-11 = Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants. +tips-dataset-11 = Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs — including Rat Servants. tips-dataset-12 = Fire extinguishers can be loaded with any reagent in the game. tips-dataset-13 = Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam. tips-dataset-14 = Remember to touch grass in between playing Space Station 14 every once in a while. @@ -19,7 +19,7 @@ tips-dataset-18 = When running the Singularity, make sure to check on it periodi tips-dataset-19 = If the Singularity is up, make sure to refuel the radiation collectors once in a while. tips-dataset-20 = Chemicals don't react while inside the ChemMaster's buffer. tips-dataset-21 = Don't anger the bartender by throwing their glasses! Politely place them on the table by tapping Q. -tips-dataset-22 = You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements--or even coming to a complete stop. +tips-dataset-22 = You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements — or even coming to a complete stop. tips-dataset-23 = Dexalin, Dexalin Plus, and Epinephrine will all purge heartbreaker toxin from your bloodstream while metabolizing. tips-dataset-24 = Every crewmember comes with an emergency medipen in their survival box containing epinephrine and tranexamic acid. tips-dataset-25 = The AME is a high-priority target and is easily sabotaged. Make sure to set up the Singularity or Solars so that you don't run out of power if it blows. @@ -29,15 +29,15 @@ tips-dataset-28 = Riot armor is significantly more powerful against opponents th tips-dataset-29 = As a ghost, you can use the Verb Menu to orbit around and follow any entity in game automatically. tips-dataset-30 = As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others. tips-dataset-31 = As a Traitor, the syndicate encryption key can be used to communicate through a secure channel with any other traitors who have purchased it. -tips-dataset-32 = As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways - security can compromise syndicate communications as well! +tips-dataset-32 = As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways — security can compromise syndicate communications as well! tips-dataset-33 = As a Traitor, the syndicate toolbox is extremely versatile. For only 2 telecrystals, you can get a full set of tools to help you in an emergency, insulated combat gloves and a syndicate gas mask. tips-dataset-34 = As a Traitor, never underestimate the web vest. It may not provide space protection, but its cheap cost and robust protection makes it handy for protecting against trigger-happy foes. tips-dataset-35 = As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed. tips-dataset-36 = As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you. tips-dataset-37 = As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach. -tips-dataset-38 = As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it. +tips-dataset-38 = As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra uninhabitable, and can cause toxin damage to those that inhale it. tips-dataset-39 = As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction. -tips-dataset-40 = As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient! +tips-dataset-40 = As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use, hurting the user and the patient! tips-dataset-41 = As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire. tips-dataset-42 = As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan. tips-dataset-43 = As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger! @@ -49,7 +49,7 @@ tips-dataset-48 = As a Salvage Specialist, never forget to mine ore! Ore can be tips-dataset-49 = As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently! tips-dataset-50 = As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator. tips-dataset-51 = Tip #51 does not exist and has never existed. Ignore any rumors to the contrary. -tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials. +tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such as laser guns and shuttle building materials. tips-dataset-53 = As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner. tips-dataset-54 = As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to. tips-dataset-55 = As a Cargo Technician, if you have a surplus of cash try gambling! Sometimes you gain more money than you begin with. @@ -70,28 +70,28 @@ tips-dataset-69 = As an Atmospheric Technician, your ATMOS holofan projector blo tips-dataset-70 = As an Atmospheric Technician, try to resist the temptation of making canister bombs for Nuclear Operatives, unless you're in a last-ditch scenario. They often lead to large amounts of unnecessary friendly fire! tips-dataset-71 = As an Engineer, you can repair cracked windows by using a lit welding tool on them while not in combat mode. tips-dataset-72 = As an Engineer, you can electrify grilles by placing powered cables beneath them. -tips-dataset-73 = As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it! +tips-dataset-73 = As an Engineer, always double-check when you're setting up the singularity. It is easier than you think to loose it! tips-dataset-74 = As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation. -tips-dataset-75 = As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about. +tips-dataset-75 = As the Captain, you are one of the highest-priority targets on the station. Everything from revolutions, to Nuclear Operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about. tips-dataset-76 = As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe. tips-dataset-77 = As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny. tips-dataset-78 = As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back! tips-dataset-79 = As a Scientist, you can use the node scanner to see when an artifact is able to react to triggers, and which triggers it is currently reacting to; each number it shows corresponds to a different trigger! tips-dataset-80 = As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor! -tips-dataset-81 = As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member. +tips-dataset-81 = As a Scientist, you can build cyborgs using positronic brains and a chassis; they are just as useful as a new crew member. tips-dataset-82 = As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit! tips-dataset-83 = As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch. tips-dataset-84 = As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine. tips-dataset-85 = As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. tips-dataset-86 = As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide? -tips-dataset-87 = As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients. +tips-dataset-87 = As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to nonlethally sedate unruly patients. tips-dataset-88 = Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins! -tips-dataset-89 = You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people--it's a much better experience! +tips-dataset-89 = You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people — it's a much better experience! tips-dataset-90 = If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together! tips-dataset-91 = The station's nuke is invincible. Go find the disk instead of trying to destroy it. tips-dataset-92 = Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. -tips-dataset-93 = We were all new once, be patient and guide new players, especially those playing intern roles, in the right direction. -tips-dataset-94 = Firesuits, winter coats and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. +tips-dataset-93 = We were all new once. Be patient and guide new players, especially those playing intern roles, in the right direction. +tips-dataset-94 = Firesuits, winter coats, and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. tips-dataset-95 = In an emergency, you can always rely on firesuits and emergency EVA suits; they will always spawn in their respective lockers. They might be awkward to move around in, but can easily save your life in a dangerous situation. tips-dataset-96 = In an emergency, remember that you can craft improvised weapons! A baseball bat or spear could easily mean the difference between deterring an attacker or perishing from the hands of one. tips-dataset-97 = Spears can be tipped with chemicals, and will inject a few units every time you hit someone with them directly. @@ -112,7 +112,7 @@ tips-dataset-111 = You can move an item out of the way by dragging it, and then tips-dataset-112 = When dealing with security, you can often get your sentence negated entirely through cooperation and deception. tips-dataset-113 = Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them. tips-dataset-114 = Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away. -tips-dataset-115 = Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding. +tips-dataset-115 = Burn damage, such as from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding. tips-dataset-116 = Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly. tips-dataset-117 = In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze. tips-dataset-118 = You can use sharp objects to butcher clothes or animals in the right click context menu. This includes glass shards. @@ -121,7 +121,7 @@ tips-dataset-120 = You can stun grenade penguins, which can bide valuable time f tips-dataset-121 = You can click on the names of items to pick them up in the right click menu, instead of hovering over the item and then selecting pick up. tips-dataset-122 = Space Station 14 is open source! If there's a change you want to make, or a simple item you want to add, then try contributing to the game. It's not as hard as you'd think it is. tips-dataset-123 = In a pinch, you can throw drinks or other reagent containers behind you to create a spill that can slip people chasing you. -tips-dataset-124 = Some weapons, such as knives & shivs, have a fast attack speed. +tips-dataset-124 = Some weapons, such as knives and shivs, have a fast attack speed. tips-dataset-125 = The jaws of life can be used to open powered doors. tips-dataset-126 = If you're not a human, you can drink blood to heal back some of your blood volume, albeit very inefficiently. tips-dataset-127 = If you're a human, don't drink blood! It makes you sick and you'll begin to take damage. @@ -135,5 +135,5 @@ tips-dataset-134 = You can tell if an area with firelocks up is spaced by lookin tips-dataset-135 = Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands. tips-dataset-136 = If you're trapped behind an electrified door, disable the APC or throw your ID at the door to avoid getting shocked! tips-dataset-137 = If the AI electrifies a door and you have insulated gloves, snip and mend the power wire to reset their electrification! -tips-dataset-138 = If you want to stop your prisoner from escaping from the cell right after being uncuffed, turn on combat mode while uncuffing - this will shove the prisoner down. +tips-dataset-138 = If you want to stop your prisoner from escaping from the cell right after being uncuffed, turn on combat mode while uncuffing — this will shove the prisoner down. tips-dataset-139 = Make sure to clean your illegal implanters with a soap after you use them! Detectives can scan used implanters for incriminating DNA evidence, but not if they've been wiped clean. diff --git a/Resources/Locale/en-US/worldgen/applyworldgenconfig.ftl b/Resources/Locale/en-US/worldgen/applyworldgenconfig.ftl deleted file mode 100644 index a2144d08b2..0000000000 --- a/Resources/Locale/en-US/worldgen/applyworldgenconfig.ftl +++ /dev/null @@ -1,4 +0,0 @@ -cmd-applyworldgenconfig-description = Applies the given worldgen configuration to a map, setting it up for chunk loading/etc. -cmd-applyworldgenconfig-help = applyworldgenconfig -cmd-applyworldgenconfig-prototype = worldgen config prototype -cmd-applyworldgenconfig-success = Config applied successfully. Do not rerun this command on this map. diff --git a/Resources/Maps/Shuttles/emergency_delta.yml b/Resources/Maps/Shuttles/emergency_delta.yml index c2d2628fb9..1797ce0ecb 100644 --- a/Resources/Maps/Shuttles/emergency_delta.yml +++ b/Resources/Maps/Shuttles/emergency_delta.yml @@ -1,11 +1,11 @@ meta: format: 7 category: Grid - engineVersion: 266.0.0 + engineVersion: 275.2.0 forkId: "" forkVersion: "" - time: 09/17/2025 04:14:52 - entityCount: 957 + time: 04/05/2026 13:24:37 + entityCount: 959 maps: [] grids: - 1 @@ -516,6 +516,9 @@ entities: - type: GasTileOverlay - type: RadiationGridResistance - type: ImplicitRoof + - type: TileHistory + chunkHistory: {} + - type: ExplosionAirtightGrid - proto: AirCanister entities: - uid: 326 @@ -523,10 +526,10 @@ entities: - type: Transform pos: -13.5,-3.5 parent: 1 - - uid: 445 + - uid: 643 components: - type: Transform - pos: -1.5,-18.5 + pos: 0.5,-20.5 parent: 1 - proto: AirlockBrigGlassLocked entities: @@ -735,6 +738,14 @@ entities: parent: 1 - type: Fixtures fixtures: {} + - uid: 795 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -9.5,0.5 + parent: 1 + - type: Fixtures + fixtures: {} - proto: AtmosDeviceFanDirectional entities: - uid: 71 @@ -1714,6 +1725,11 @@ entities: - type: Transform pos: -2.5,-22.5 parent: 1 + - uid: 802 + components: + - type: Transform + pos: -9.5,0.5 + parent: 1 - proto: CableHV entities: - uid: 33 @@ -2223,6 +2239,16 @@ entities: - type: Transform pos: -4.5,-17.5 parent: 1 + - uid: 958 + components: + - type: Transform + pos: -9.5,0.5 + parent: 1 + - uid: 959 + components: + - type: Transform + pos: -8.5,0.5 + parent: 1 - proto: CableTerminal entities: - uid: 277 @@ -3134,11 +3160,11 @@ entities: parent: 1 - proto: GasOutletInjector entities: - - uid: 801 + - uid: 687 components: - type: Transform rot: -1.5707963267948966 rad - pos: 1.5,-18.5 + pos: 1.5,-20.5 parent: 1 - proto: GasPassiveVent entities: @@ -3146,10 +3172,16 @@ entities: components: - type: Transform rot: -1.5707963267948966 rad - pos: 1.5,-20.5 + pos: 1.5,-19.5 parent: 1 - proto: GasPipeBend entities: + - uid: 680 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-20.5 + parent: 1 - uid: 856 components: - type: Transform @@ -3181,7 +3213,7 @@ entities: components: - type: Transform rot: -1.5707963267948966 rad - pos: 0.5,-20.5 + pos: -0.5,-19.5 parent: 1 - uid: 514 components: @@ -3189,45 +3221,28 @@ entities: rot: 3.141592653589793 rad pos: -2.5,-16.5 parent: 1 - - uid: 643 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -1.5,-20.5 - parent: 1 - uid: 644 components: - type: Transform rot: -1.5707963267948966 rad - pos: -0.5,-20.5 + pos: -1.5,-19.5 parent: 1 - uid: 675 components: - type: Transform pos: -2.5,-17.5 parent: 1 - - uid: 680 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,-18.5 - parent: 1 - uid: 699 components: - type: Transform - pos: -2.5,-19.5 + rot: -1.5707963267948966 rad + pos: 0.5,-19.5 parent: 1 - uid: 707 components: - type: Transform pos: -2.5,-18.5 parent: 1 - - uid: 802 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,-18.5 - parent: 1 - uid: 840 components: - type: Transform @@ -3627,11 +3642,11 @@ entities: parent: 1 - proto: GasPipeTJunction entities: - - uid: 795 + - uid: 445 components: - type: Transform - rot: 3.141592653589793 rad - pos: -2.5,-20.5 + rot: 1.5707963267948966 rad + pos: -2.5,-19.5 parent: 1 - uid: 841 components: @@ -3741,11 +3756,11 @@ entities: parent: 1 - proto: GasPort entities: - - uid: 687 + - uid: 460 components: - type: Transform rot: 1.5707963267948966 rad - pos: -1.5,-18.5 + pos: 0.5,-20.5 parent: 1 - proto: GasVentPump entities: @@ -4270,32 +4285,24 @@ entities: rot: -1.5707963267948966 rad pos: 1.5,-18.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 119 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-20.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 130 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-19.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 293 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-21.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - proto: PosterLegitEnlist entities: - uid: 788 @@ -4657,393 +4664,281 @@ entities: - type: Transform pos: 2.5,1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 7 components: - type: Transform pos: 2.5,2.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 15 components: - type: Transform pos: -3.5,1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 18 components: - type: Transform pos: 0.5,3.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 19 components: - type: Transform pos: -0.5,3.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 81 components: - type: Transform pos: -0.5,-1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 82 components: - type: Transform pos: -1.5,-2.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 101 components: - type: Transform pos: -12.5,3.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 142 components: - type: Transform pos: -4.5,-1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 170 components: - type: Transform pos: -1.5,6.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 171 components: - type: Transform pos: -10.5,6.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 173 components: - type: Transform pos: -10.5,7.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 174 components: - type: Transform pos: -1.5,7.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 182 components: - type: Transform pos: -8.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 183 components: - type: Transform pos: -3.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 184 components: - type: Transform pos: -4.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 185 components: - type: Transform pos: -5.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 186 components: - type: Transform pos: -6.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 187 components: - type: Transform pos: -7.5,9.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 261 components: - type: Transform pos: -14.5,-3.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 262 components: - type: Transform pos: -14.5,-4.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 263 components: - type: Transform pos: -14.5,-5.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 264 components: - type: Transform pos: -14.5,-6.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 265 components: - type: Transform pos: -14.5,-7.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 267 components: - type: Transform pos: -1.5,-22.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 274 components: - type: Transform pos: -6.5,-1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 327 components: - type: Transform pos: -2.5,-22.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 330 components: - type: Transform pos: -9.5,-11.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 331 components: - type: Transform pos: -9.5,-14.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 338 components: - type: Transform pos: -11.5,-17.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 339 components: - type: Transform pos: -12.5,-17.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 342 components: - type: Transform pos: -14.5,-12.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 343 components: - type: Transform pos: -14.5,-13.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 344 components: - type: Transform pos: -14.5,-14.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 345 components: - type: Transform pos: -14.5,-15.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 430 components: - type: Transform pos: -8.5,-17.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 432 components: - type: Transform pos: -6.5,-17.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 469 components: - type: Transform pos: 2.5,-4.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 470 components: - type: Transform pos: 2.5,-5.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 471 components: - type: Transform pos: 2.5,-6.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 478 components: - type: Transform pos: 2.5,-13.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 479 components: - type: Transform pos: 2.5,-14.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 513 components: - type: Transform pos: -1.5,-8.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 515 components: - type: Transform pos: -1.5,-4.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 530 components: - type: Transform pos: -5.5,-10.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 533 components: - type: Transform pos: -1.5,-10.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 535 components: - type: Transform pos: -1.5,-14.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 557 components: - type: Transform pos: -5.5,-4.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 712 components: - type: Transform pos: -8.5,-1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 760 components: - type: Transform pos: -9.5,-7.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 764 components: - type: Transform pos: -9.5,-6.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 817 components: - type: Transform pos: -5.5,-8.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 819 components: - type: Transform pos: -5.5,-14.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 820 components: - type: Transform pos: -5.5,-16.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 821 components: - type: Transform pos: -1.5,-16.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 837 components: - type: Transform pos: -5.5,-2.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - proto: SignBridge entities: - uid: 11 @@ -5064,10 +4959,10 @@ entities: fixtures: {} - proto: SignEVA entities: - - uid: 460 + - uid: 801 components: - type: Transform - pos: -9.5,0.5 + pos: -9.5,2.5 parent: 1 - type: Fixtures fixtures: {} @@ -5952,8 +5847,6 @@ entities: rot: 1.5707963267948966 rad pos: -1.5,-20.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - proto: WindoorSecureSecurityLocked entities: - uid: 37 @@ -5962,8 +5855,6 @@ entities: rot: 3.141592653589793 rad pos: -2.5,1.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - proto: WindowReinforcedDirectional entities: - uid: 660 @@ -5972,30 +5863,22 @@ entities: rot: 1.5707963267948966 rad pos: -1.5,-19.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 682 components: - type: Transform rot: 1.5707963267948966 rad pos: -1.5,-18.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 746 components: - type: Transform rot: 1.5707963267948966 rad pos: -1.5,-21.5 parent: 1 - - type: DeltaPressure - gridUid: 1 - uid: 755 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,2.5 parent: 1 - - type: DeltaPressure - gridUid: 1 ... diff --git a/Resources/Prototypes/Actions/station_ai.yml b/Resources/Prototypes/Actions/station_ai.yml index 4dbaf07aab..9e9bc9f650 100644 --- a/Resources/Prototypes/Actions/station_ai.yml +++ b/Resources/Prototypes/Actions/station_ai.yml @@ -14,34 +14,6 @@ - type: InstantAction event: !type:JumpToCoreEvent -- type: entity - parent: BaseAction - id: ActionSurvCameraLights - name: Toggle camera lights - description: Enable surveillance camera lights near wherever you're viewing. - components: - - type: Action - priority: -5 - itemIconStyle: BigAction - icon: - sprite: Interface/Actions/actions_ai.rsi - state: camera_light - - type: InstantAction - event: !type:RelayedActionComponentChangeEvent - components: - - type: LightOnCollideCollider - - type: FixturesChange - fixtures: - lightTrigger: - shape: - !type:PhysShapeCircle - radius: 0.35 - density: 80 - hard: false - layer: - - GhostImpassable - - - type: entity parent: BaseMentalAction id: ActionAIViewLaws diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index e0956a76f7..c07584e737 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -439,6 +439,17 @@ - type: InstantAction event: !type:GravityJumpEvent {} +- type: entity + parent: ActionGravityJump + id: ActionJumpBoost + name: Jump boost + components: + - type: Action + useDelay: 16 + icon: + sprite: Interface/Actions/actions_borg.rsi + state: xenoborg-jump-module + - type: entity parent: BaseAction id: ActionVulpkaninGravityJump diff --git a/Resources/Prototypes/Atmospherics/gases.yml b/Resources/Prototypes/Atmospherics/gases.yml index f27bab9074..2108b68e53 100644 --- a/Resources/Prototypes/Atmospherics/gases.yml +++ b/Resources/Prototypes/Atmospherics/gases.yml @@ -1,105 +1,119 @@ - type: gas id: Oxygen - name: gases-oxygen - specificHeat: 20 + name: gas-oxygen + abbreviation: gas-oxygen-abbreviation + molarHeatCapacity: 20 heatCapacityRatio: 1.4 molarMass: 32 - color: 2887E8 + color: '#2887E8' reagent: Oxygen pricePerMole: 0 isOxidizer: true - type: gas id: Nitrogen - name: gases-nitrogen - specificHeat: 30 + name: gas-nitrogen + abbreviation: gas-nitrogen-abbreviation + molarHeatCapacity: 30 heatCapacityRatio: 1.4 molarMass: 28 - color: DA1010 + color: '#DA1010' reagent: Nitrogen pricePerMole: 0 - type: gas id: CarbonDioxide - name: gases-co2 - specificHeat: 30 + name: gas-carbon-dioxide + abbreviation: gas-carbon-dioxide-abbreviation + molarHeatCapacity: 30 heatCapacityRatio: 1.3 molarMass: 44 - color: 4e4e4e + color: '#4e4e4e' reagent: CarbonDioxide pricePerMole: 0 - type: gas id: Plasma - name: gases-plasma - specificHeat: 200 + name: gas-plasma + abbreviation: gas-plasma-abbreviation + molarHeatCapacity: 200 heatCapacityRatio: 1.7 molarMass: 120 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: plasma - color: FF3300 + gasOverlaySprite: + sprite: /Textures/Effects/atmospherics.rsi + state: plasma + color: '#FF3300' reagent: Plasma pricePerMole: 0 isFuel: true - type: gas id: Tritium - name: gases-tritium - specificHeat: 10 + name: gas-tritium + abbreviation: gas-tritium-abbreviation + molarHeatCapacity: 10 heatCapacityRatio: 1.3 molarMass: 6 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: tritium - color: 13FF4B + gasOverlaySprite: + sprite: /Textures/Effects/atmospherics.rsi + state: tritium + color: '#13FF4B' reagent: Tritium pricePerMole: 2.5 isFuel: true - type: gas id: WaterVapor - name: gases-water-vapor - specificHeat: 40 + name: gas-water-vapor + abbreviation: gas-water-vapor-abbreviation + molarHeatCapacity: 40 heatCapacityRatio: 1.33 molarMass: 18 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: water_vapor - color: bffffd + gasOverlaySprite: + sprite: /Textures/Effects/atmospherics.rsi + state: water_vapor + color: '#bffffd' reagent: Water pricePerMole: 0 - type: gas id: Ammonia - name: gases-ammonia - specificHeat: 20 + name: gas-ammonia + abbreviation: gas-ammonia-abbreviation + molarHeatCapacity: 20 heatCapacityRatio: 1.4 molarMass: 44 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: miasma + gasOverlaySprite: + sprite: /Textures/Effects/atmospherics.rsi + state: miasma gasMolesVisible: 2 - gasVisbilityFactor: 3.5 - color: 56941E + gasVisibilityFactor: 3.5 + color: '#56941E' reagent: Ammonia pricePerMole: 0.15 - type: gas id: NitrousOxide - name: gases-n2o - specificHeat: 40 + name: gas-nitrous-oxide + abbreviation: gas-nitrous-oxide-abbreviation + molarHeatCapacity: 40 heatCapacityRatio: 1.3 molarMass: 44 - color: 8F00FF + color: '#8F00FF' reagent: NitrousOxide pricePerMole: 0.1 - type: gas id: Frezon - name: gases-frezon - specificHeat: 600 # Strongest by far + name: gas-frezon + abbreviation: gas-frezon-abbreviation + molarHeatCapacity: 600 # Strongest by far heatCapacityRatio: 1.33 molarMass: 50 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: frezon + gasOverlaySprite: + sprite: /Textures/Effects/atmospherics.rsi + state: frezon gasMolesVisible: 0.6 - color: 3a758c + color: '#3a758c' reagent: Frezon pricePerMole: 1 diff --git a/Resources/Prototypes/Body/Species/arachnid.yml b/Resources/Prototypes/Body/Species/arachnid.yml index 8455f33981..e1b8e5dcdd 100644 --- a/Resources/Prototypes/Body/Species/arachnid.yml +++ b/Resources/Prototypes/Body/Species/arachnid.yml @@ -36,6 +36,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceArachnid + categories: [ HideSpawnMenu ] name: arachnid appearance components: - type: Inventory @@ -137,6 +138,10 @@ Unsexed: UnisexArachnid - type: TypingIndicator proto: spider + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_arachnid - type: entity parent: OrganBase diff --git a/Resources/Prototypes/Body/Species/diona.yml b/Resources/Prototypes/Body/Species/diona.yml index 2c0264f85e..2f1e49ddbc 100644 --- a/Resources/Prototypes/Body/Species/diona.yml +++ b/Resources/Prototypes/Body/Species/diona.yml @@ -35,6 +35,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceDiona + categories: [ HideSpawnMenu ] name: diona appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/dwarf.yml b/Resources/Prototypes/Body/Species/dwarf.yml index 1d64142ca9..5b053f0735 100644 --- a/Resources/Prototypes/Body/Species/dwarf.yml +++ b/Resources/Prototypes/Body/Species/dwarf.yml @@ -1,6 +1,7 @@ - type: entity parent: AppearanceHuman id: AppearanceDwarf + categories: [ HideSpawnMenu ] name: dwarf appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/gingerbread.yml b/Resources/Prototypes/Body/Species/gingerbread.yml index b32cbac91d..5c429af01c 100644 --- a/Resources/Prototypes/Body/Species/gingerbread.yml +++ b/Resources/Prototypes/Body/Species/gingerbread.yml @@ -1,6 +1,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceGingerbread + categories: [ HideSpawnMenu ] name: gingerbread appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/human.yml b/Resources/Prototypes/Body/Species/human.yml index 1f59cbefe5..6777d64797 100644 --- a/Resources/Prototypes/Body/Species/human.yml +++ b/Resources/Prototypes/Body/Species/human.yml @@ -2,6 +2,9 @@ parent: Undergarments id: Human limits: + enum.HumanoidVisualLayers.HeadTop: + limit: 1 + required: false enum.HumanoidVisualLayers.Hair: limit: 1 required: false @@ -48,6 +51,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceHuman + categories: [ HideSpawnMenu ] name: human appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/moth.yml b/Resources/Prototypes/Body/Species/moth.yml index af243333a6..4b46381c3c 100644 --- a/Resources/Prototypes/Body/Species/moth.yml +++ b/Resources/Prototypes/Body/Species/moth.yml @@ -51,6 +51,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceMoth + categories: [ HideSpawnMenu ] name: moth appearance components: - type: Inventory @@ -148,6 +149,10 @@ allowedEmotes: ['Chitter', 'Squeak', 'Flap'] - type: TypingIndicator proto: moth + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_moth - type: Bloodstream bloodReferenceSolution: reagents: diff --git a/Resources/Prototypes/Body/Species/reptilian.yml b/Resources/Prototypes/Body/Species/reptilian.yml index 9a0d745027..7891d167c1 100644 --- a/Resources/Prototypes/Body/Species/reptilian.yml +++ b/Resources/Prototypes/Body/Species/reptilian.yml @@ -54,6 +54,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceReptilian + categories: [ HideSpawnMenu ] name: reptilian appearance components: - type: Inventory @@ -150,6 +151,10 @@ allowedEmotes: ['Thump'] - type: TypingIndicator proto: lizard + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_lizard - type: Vocal sounds: Male: MaleReptilian diff --git a/Resources/Prototypes/Body/Species/skeleton.yml b/Resources/Prototypes/Body/Species/skeleton.yml index 8372e4923d..953a8c7bae 100644 --- a/Resources/Prototypes/Body/Species/skeleton.yml +++ b/Resources/Prototypes/Body/Species/skeleton.yml @@ -42,6 +42,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceSkeletonPerson + categories: [ HideSpawnMenu ] name: skeletonperson appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/slime.yml b/Resources/Prototypes/Body/Species/slime.yml index 7b073a9930..a0dc240c44 100644 --- a/Resources/Prototypes/Body/Species/slime.yml +++ b/Resources/Prototypes/Body/Species/slime.yml @@ -54,6 +54,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceSlimePerson + categories: [ HideSpawnMenu ] name: SlimePerson appearance components: - type: Inventory diff --git a/Resources/Prototypes/Body/Species/vox.yml b/Resources/Prototypes/Body/Species/vox.yml index 9fab05ea20..b947b3e15f 100644 --- a/Resources/Prototypes/Body/Species/vox.yml +++ b/Resources/Prototypes/Body/Species/vox.yml @@ -74,6 +74,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceVox + categories: [ HideSpawnMenu ] name: vox appearance components: - type: Inventory @@ -170,6 +171,10 @@ allowedEmotes: ['Click', 'Chitter'] - type: TypingIndicator proto: vox + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_vox - type: Vocal sounds: Male: UnisexVox diff --git a/Resources/Prototypes/Body/Species/vulpkanin.yml b/Resources/Prototypes/Body/Species/vulpkanin.yml index 293ecbc7af..29b4effedf 100644 --- a/Resources/Prototypes/Body/Species/vulpkanin.yml +++ b/Resources/Prototypes/Body/Species/vulpkanin.yml @@ -63,6 +63,7 @@ - type: entity parent: BaseSpeciesAppearance id: AppearanceVulpkanin + categories: [ HideSpawnMenu ] name: vulpkanin appearance components: - type: InitialBody @@ -233,6 +234,10 @@ reagents: - ReagentId: SulfurBlood Quantity: 300 + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_vulpkanin # Corvax-Vulp_Port start - type: GrowlingAccent - type: Respirator diff --git a/Resources/Prototypes/Body/species_appearance.yml b/Resources/Prototypes/Body/species_appearance.yml index 44369b1548..e70b6abb38 100644 --- a/Resources/Prototypes/Body/species_appearance.yml +++ b/Resources/Prototypes/Body/species_appearance.yml @@ -66,11 +66,6 @@ state: body-overlay-2 visible: false - - map: [ "clownedon" ] # Dynamically generated - sprite: "Effects/creampie.rsi" - state: "creampie_human" - visible: false - - type: entity id: BaseSpeciesAppearance parent: diff --git a/Resources/Prototypes/Body/species_base.yml b/Resources/Prototypes/Body/species_base.yml index eadd6747e9..3beff6258c 100644 --- a/Resources/Prototypes/Body/species_base.yml +++ b/Resources/Prototypes/Body/species_base.yml @@ -26,12 +26,6 @@ color: "#FF0000" Burn: sprite: Mobs/Effects/burn_damage.rsi - - type: GenericVisualizer - visuals: - enum.CreamPiedVisuals.Creamed: - clownedon: - True: { visible: true } - False: { visible: false } - type: StatusIcon bounds: -0.5,-0.5,0.5,0.5 - type: JobStatus @@ -127,6 +121,9 @@ factions: - NanoTrasen - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_human - type: ParcelWrapOverride parcelPrototype: WrappedParcelHumanoid wrapDelay: 5 @@ -155,6 +152,8 @@ - type: MobPrice price: 1500 # Kidnapping a living person and selling them for cred is a good move. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. + - type: RandomPrice + maxRandomPrice: 20000 - type: Tag tags: - CanPilot diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml index 7e7bb37162..4ceaeef80c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml @@ -40,6 +40,7 @@ ClothingUniformJumpsuitDameDane: 2 ClothingShoesDameDane: 2 ClothingOuterDameDane: 2 + ClothingHeadHatMitreClown: 1 ClothingOuterClownPriest: 1 ClothingMaskSadMime: 1 ClothingMaskScaredMime: 1 diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 1d304d021e..ac7630132a 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -1092,6 +1092,16 @@ categories: - UplinkDisruption +- type: listing + id: UplinkTravelCamera + name: uplink-travel-camera-name + description: uplink-travel-camera-desc + productEntity: TravelCamera + cost: + Telecrystal: 1 + categories: + - UplinkDeception + # Note: Removed for the time being until surgery/newmed is added. Considered bloat until then. # - type: listing # id: UplinkDuffelSurgery @@ -2269,3 +2279,20 @@ - !type:BuyerJobCondition whitelist: - Lawyer + +#Objective items + +- type: listing + id: uplinkHijackBeacon + name: uplink-hijack-beacon-name + description: uplink-hijack-beacon-desc + productEntity: HijackBeacon + categories: + - UplinkObjective + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - !type:BuyerObjectiveWhitelistCondition + whitelist: + components: + - HijackTradeStationCondition diff --git a/Resources/Prototypes/Chat/notifications.yml b/Resources/Prototypes/Chat/notifications.yml index cea67fa0ee..f155b99433 100644 --- a/Resources/Prototypes/Chat/notifications.yml +++ b/Resources/Prototypes/Chat/notifications.yml @@ -32,4 +32,11 @@ message: station-ai-core-critical-power sound: /Audio/Effects/alert.ogg color: Red - nextDelay: 120 \ No newline at end of file + nextDelay: 120 + +- type: chatNotification + id: AiTakingDamage + message: station-ai-core-taking-damage + sound: /Audio/Misc/notice2.ogg + color: Orange + nextDelay: 30 diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 44e4983fd8..af513936da 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -1535,3 +1535,14 @@ tags: - PetWearable - CorgiWearable + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatMitreClown + name: honkmother mitre + description: It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau. + components: + - type: Sprite + sprite: Clothing/Head/Hats/mitre_clown.rsi + - type: Clothing + sprite: Clothing/Head/Hats/mitre_clown.rsi diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml index 22ad9096f4..a05cf9baa4 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml @@ -348,7 +348,7 @@ - type: entity parent: ClothingOuterStorageBase id: ClothingOuterClownPriest - name: robes of the honkmother + name: honkmother coat description: Meant for a clown of the cloth. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/donkpocketbox.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/donkpocketbox.yml index 05ad0ba725..b921582ff7 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/donkpocketbox.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/donkpocketbox.yml @@ -1,23 +1,24 @@ - type: entity - name: Donkpocket Box Spawner - id: DonkpocketBoxSpawner parent: MarkerBase + id: DonkpocketBoxSpawner + name: Donkpocket Box Spawner components: - - type: Sprite - layers: - - state: red - - sprite: Objects/Consumable/Food/Baked/donkpocket.rsi - state: box - - type: RandomSpawner - prototypes: - - FoodBoxDonkpocket - - FoodBoxDonkpocketSpicy - - FoodBoxDonkpocketTeriyaki - - FoodBoxDonkpocketPizza - - FoodBoxDonkpocketStonk - - FoodBoxDonkpocketBerry - - FoodBoxDonkpocketHonk - - FoodBoxDonkpocketDink - - FoodBoxDonkpocketMoth - chance: 0.5 - offset: 0.0 + - type: Sprite + layers: + - state: red + - sprite: Objects/Consumable/Food/Baked/donkpocket.rsi + state: box + - type: EntityTableSpawner + table: !type:GroupSelector + prob: 0.5 + children: + - id: FoodBoxDonkpocket + - id: FoodBoxDonkpocketSpicy + - id: FoodBoxDonkpocketTeriyaki + - id: FoodBoxDonkpocketPizza + - id: FoodBoxDonkpocketStonk + - id: FoodBoxDonkpocketBerry + - id: FoodBoxDonkpocketHonk + - id: FoodBoxDonkpocketDink + - id: FoodBoxDonkpocketMoth + offset: 0.0 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml index d049250b55..ac845e55d4 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml @@ -1,140 +1,143 @@ - type: entity + parent: MarkerBase id: RandomDrinkGlass name: random drink spawner suffix: Glass - parent: MarkerBase placement: mode: AlignTileAny components: - type: Sprite layers: - - state: red - - sprite: Objects/Consumable/Drinks/beerglass.rsi - state: icon - - type: RandomSpawner - #small item - prototypes: - - DrinkAbsintheGlass - - DrinkAleGlass - - DrinkAlienBrainHemorrhage - - DrinkAloe - - DrinkAndalusia - - DrinkAntifreeze - - DrinkArnoldPalmer - - DrinkB52Glass - - DrinkBahamaMama - - DrinkBananaHonkGlass - - DrinkBarefootGlass - - DrinkBeerglass - - DrinkBerryJuice - - DrinkBlackRussianGlass - - DrinkBlueCuracaoGlass - - DrinkBlueHawaiianGlass - - DrinkBloodyMaryGlass - - DrinkBooger - - DrinkBraveBullGlass - - DrinkBronxGlass - - BudgetInsulsDrinkGlass - - DrinkCarrotJuice - - DrinkCoconutRum - - DrinkChocolateGlass - - DrinkCognacGlass - - DrinkCosmopolitan - - DrinkCrushDepthGlass - - DrinkCubaLibreGlass - - DrinkDarkandStormyGlass - - DrinkDeadRumGlass - - DrinkDevilsKiss - - DrinkDriestMartiniGlass - - DrinkDrGibbGlass - - DrinkElectricSharkGlass - - DrinkErikaSurprise - - DrinkFourteenLokoGlass - - DrinkGargleBlasterGlass - - DrinkGinFizzGlass - - DrinkGinGlass - - DrinkGinTonicglass - - DrinkGildlagerGlass - - DrinkGrapeJuice - - DrinkGreenTeaGlass - - DrinkGrogGlass - - DrinkHippiesDelightGlass - - DrinkIcedCoffeeGlass - - DrinkIcedGreenTeaGlass - - DrinkIcedBeerGlass - - DrinkIceCreamGlass - - IrishBoolGlass - - DrinkIrishSlammer - - DrinkIrishCoffeeGlass - - DrinkLemonadeGlass - - DrinkJackRoseGlass - - DrinkJungleBirdGlass - - DrinkKalimotxoGlass - - DrinkOrangeLimeSodaGlass - - DrinkLongIslandIcedTeaGlass - - DrinkManhattanGlass - - DrinkManlyDorfGlass - - DrinkMargaritaGlass - - DrinkMartiniGlass - - DrinkMeadGlass - - DrinkMilkshake - - DrinkMojito - - DrinkMonkeyBusinessGlass - - DrinkNTCahors - - DrinkPainkillerGlass - - DrinkPatronGlass - - DrinkPinaColadaGlass - - DrinkPoscaGlass - - DrinkRadlerGlass - - DrinkRedMeadGlass - - DrinkRewriter - - DrinkRoyRogersGlass - - DrinkRootBeerFloatGlass - - RubberneckGlass - - DrinkRumGlass - - DrinkSakeGlass - - DrinkSbitenGlass - - DrinkScrewdriverCocktailGlass - - DrinkShirleyTempleGlass - - DrinkSuiDreamGlass - - DrinkSingulo - - DrinkSoyLatte - - DrinkSyndicatebomb - - DrinkTequilaSunriseGlass - - DrinkThreeMileIslandGlass - - DrinkTortugaGlass - - DrinkToxinsSpecialGlass - - DrinkVampiroGlass - - DrinkVodkaMartiniGlass - - DrinkVodkaRedBool - - DrinkVodkaTonicGlass - - DrinkWatermelonJuice - - DrinkWatermelonWakeup - - DrinkWhiskeyColaGlass - - DrinkWhiskeySodaGlass - - DrinkWhiteRussianGlass - - DrinkWineGlass - - XenoBasherGlass - - DrinkShakeBlue - - DrinkShakeWhite - - DrinkTheMartinez - - DrinkMoonshineGlass - chance: 0.8 + - state: red + - sprite: Objects/Consumable/Drinks/beerglass.rsi + state: icon + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - !type:GroupSelector #small item + weight: 0.95 + prob: 0.8 + children: + - id: DrinkAbsintheGlass + - id: DrinkAleGlass + - id: DrinkAlienBrainHemorrhage + - id: DrinkAloe + - id: DrinkAndalusia + - id: DrinkAntifreeze + - id: DrinkArnoldPalmer + - id: DrinkB52Glass + - id: DrinkBahamaMama + - id: DrinkBananaHonkGlass + - id: DrinkBarefootGlass + - id: DrinkBeerglass + - id: DrinkBerryJuice + - id: DrinkBlackRussianGlass + - id: DrinkBlueCuracaoGlass + - id: DrinkBlueHawaiianGlass + - id: DrinkBloodyMaryGlass + - id: DrinkBooger + - id: DrinkBraveBullGlass + - id: DrinkBronxGlass + - id: BudgetInsulsDrinkGlass + - id: DrinkCarrotJuice + - id: DrinkCoconutRum + - id: DrinkChocolateGlass + - id: DrinkCognacGlass + - id: DrinkCosmopolitan + - id: DrinkCrushDepthGlass + - id: DrinkCubaLibreGlass + - id: DrinkDarkandStormyGlass + - id: DrinkDeadRumGlass + - id: DrinkDevilsKiss + - id: DrinkDriestMartiniGlass + - id: DrinkDrGibbGlass + - id: DrinkElectricSharkGlass + - id: DrinkErikaSurprise + - id: DrinkFourteenLokoGlass + - id: DrinkGargleBlasterGlass + - id: DrinkGinFizzGlass + - id: DrinkGinGlass + - id: DrinkGinTonicglass + - id: DrinkGildlagerGlass + - id: DrinkGrapeJuice + - id: DrinkGreenTeaGlass + - id: DrinkGrogGlass + - id: DrinkHippiesDelightGlass + - id: DrinkIcedCoffeeGlass + - id: DrinkIcedGreenTeaGlass + - id: DrinkIcedBeerGlass + - id: DrinkIceCreamGlass + - id: IrishBoolGlass + - id: DrinkIrishSlammer + - id: DrinkIrishCoffeeGlass + - id: DrinkLemonadeGlass + - id: DrinkJackRoseGlass + - id: DrinkJungleBirdGlass + - id: DrinkKalimotxoGlass + - id: DrinkOrangeLimeSodaGlass + - id: DrinkLongIslandIcedTeaGlass + - id: DrinkManhattanGlass + - id: DrinkManlyDorfGlass + - id: DrinkMargaritaGlass + - id: DrinkMartiniGlass + - id: DrinkMeadGlass + - id: DrinkMilkshake + - id: DrinkMojito + - id: DrinkMonkeyBusinessGlass + - id: DrinkNTCahors + - id: DrinkPainkillerGlass + - id: DrinkPatronGlass + - id: DrinkPinaColadaGlass + - id: DrinkPoscaGlass + - id: DrinkRadlerGlass + - id: DrinkRedMeadGlass + - id: DrinkRewriter + - id: DrinkRoyRogersGlass + - id: DrinkRootBeerFloatGlass + - id: RubberneckGlass + - id: DrinkRumGlass + - id: DrinkSakeGlass + - id: DrinkSbitenGlass + - id: DrinkScrewdriverCocktailGlass + - id: DrinkShirleyTempleGlass + - id: DrinkSuiDreamGlass + - id: DrinkSingulo + - id: DrinkSoyLatte + - id: DrinkSyndicatebomb + - id: DrinkTequilaSunriseGlass + - id: DrinkThreeMileIslandGlass + - id: DrinkTortugaGlass + - id: DrinkToxinsSpecialGlass + - id: DrinkVampiroGlass + - id: DrinkVodkaMartiniGlass + - id: DrinkVodkaRedBool + - id: DrinkVodkaTonicGlass + - id: DrinkWatermelonJuice + - id: DrinkWatermelonWakeup + - id: DrinkWhiskeyColaGlass + - id: DrinkWhiskeySodaGlass + - id: DrinkWhiteRussianGlass + - id: DrinkWineGlass + - id: XenoBasherGlass + - id: DrinkShakeBlue + - id: DrinkShakeWhite + - id: DrinkTheMartinez + - id: DrinkMoonshineGlass + - !type:GroupSelector #rare + weight: 0.05 + children: + - id: DrinkAcidSpitGlass + - id: DrinkAlliesCocktail + - id: DrinkAmasecGlass + - id: DrinkAtomicBombGlass + - id: DrinkDemonsBlood + - id: DrinkDoctorsDelightGlass + - id: DrinkNeurotoxinGlass + - id: DrinkNuclearColaGlass + - id: DrinkSilencerGlass + - id: DrinkShakeMeat + - id: DrinkShakeRobo + - id: DrinkHoochGlass + - id: DrinkBeepskySmashGlass + - id: DrinkBacchusBlessing offset: 0.0 - #rare - rarePrototypes: - - DrinkAcidSpitGlass - - DrinkAlliesCocktail - - DrinkAmasecGlass - - DrinkAtomicBombGlass - - DrinkDemonsBlood - - DrinkDoctorsDelightGlass - - DrinkNeurotoxinGlass - - DrinkNuclearColaGlass - - DrinkSilencerGlass - - DrinkShakeMeat - - DrinkShakeRobo - - DrinkHoochGlass - - DrinkBeepskySmashGlass - - DrinkBacchusBlessing - rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_single.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_single.yml index 7d0f734acc..1a4d8defb8 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_single.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_single.yml @@ -1,96 +1,100 @@ - type: entity + parent: MarkerBase id: RandomFoodBakedSingle name: random baked food spawner suffix: Single Serving - parent: MarkerBase placement: mode: AlignTileAny components: - type: Sprite layers: - - state: red - - sprite: Objects/Consumable/Food/Baked/pie.rsi - state: plain-slice - - type: RandomSpawner - prototypes: - - FoodBreadPlainSlice - - FoodBreadMeatSlice - - FoodBreadBananaSlice - - FoodBreadCreamcheeseSlice - - FoodBreadMoldySlice - - FoodBreadGarlicSlice - - FoodBreadCornSlice - - FoodBreadSausageSlice - - FoodBreadButteredToast - - FoodBreadJellySlice - - FoodBreadFrenchToast - - FoodBreadTwoSlice - - FoodBreadVolcanicSlice - - FoodBreadTofuSlice - - FoodBreadBaguetteSlice - - FoodCakeBlueberrySlice - - FoodCakePlainSlice - - FoodCakeCarrotSlice - - FoodCakeCheeseSlice - - FoodCakeOrangeSlice - - FoodCakeLimeSlice - - FoodCakeLemonSlice - - FoodCakeChocolateSlice - - FoodCakeAppleSlice - - FoodCakeSlimeSlice - - FoodCakePumpkinSlice - - FoodCakeChristmasSlice - - FoodCakeVanillaSlice - - FoodCakeBirthdaySlice - - FoodCakeBerryDelightSlice - - FoodCakeCottonSlice - - FoodBakedMuffin - - FoodBakedMuffinBerry - - FoodBakedMuffinCherry - - FoodBakedMuffinBluecherry - - FoodBakedMuffinChocolate - - FoodBakedMuffinBanana - - FoodBakedBunHoney - - FoodBakedBunHotX - - FoodBakedBunMeat - - FoodBakedCookie - - FoodBakedCookieOatmeal - - FoodBakedCookieRaisin - - FoodBakedCookieSugar - - FoodBakedGrilledCheeseSandwich - - FoodBakedGrilledCheeseSandwichCotton - - FoodBakedNugget - - FoodBakedPancake - - FoodBakedPancakeBb - - FoodBakedPancakeCc - - FoodBakedWaffle - - FoodBakedWaffleSoy - - FoodBakedWaffleSoylent - - FoodBakedWaffleRoffle - - FoodBakedPretzel - - FoodBakedCannoli - - FoodPieAppleSlice - - FoodPieBaklavaSlice - - FoodPieClafoutisSlice - - FoodPieMeatSlice - - FoodPieCherrySlice - - FoodPieFrostySlice - - FoodTartGrape - - FoodTartCoco - - FoodBakedBrownie - - FoodPieBananaCreamSlice - - FoodTartMimeSlice - chance: 0.8 + - state: red + - sprite: Objects/Consumable/Food/Baked/pie.rsi + state: plain-slice + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - !type:GroupSelector + weight: 0.95 + prob: 0.8 + children: + - id: FoodBreadPlainSlice + - id: FoodBreadMeatSlice + - id: FoodBreadBananaSlice + - id: FoodBreadCreamcheeseSlice + - id: FoodBreadMoldySlice + - id: FoodBreadGarlicSlice + - id: FoodBreadCornSlice + - id: FoodBreadSausageSlice + - id: FoodBreadButteredToast + - id: FoodBreadJellySlice + - id: FoodBreadFrenchToast + - id: FoodBreadTwoSlice + - id: FoodBreadVolcanicSlice + - id: FoodBreadTofuSlice + - id: FoodBreadBaguetteSlice + - id: FoodCakeBlueberrySlice + - id: FoodCakePlainSlice + - id: FoodCakeCarrotSlice + - id: FoodCakeCheeseSlice + - id: FoodCakeOrangeSlice + - id: FoodCakeLimeSlice + - id: FoodCakeLemonSlice + - id: FoodCakeChocolateSlice + - id: FoodCakeAppleSlice + - id: FoodCakeSlimeSlice + - id: FoodCakePumpkinSlice + - id: FoodCakeChristmasSlice + - id: FoodCakeVanillaSlice + - id: FoodCakeBirthdaySlice + - id: FoodCakeBerryDelightSlice + - id: FoodCakeCottonSlice + - id: FoodBakedMuffin + - id: FoodBakedMuffinBerry + - id: FoodBakedMuffinCherry + - id: FoodBakedMuffinBluecherry + - id: FoodBakedMuffinChocolate + - id: FoodBakedMuffinBanana + - id: FoodBakedBunHoney + - id: FoodBakedBunHotX + - id: FoodBakedBunMeat + - id: FoodBakedCookie + - id: FoodBakedCookieOatmeal + - id: FoodBakedCookieRaisin + - id: FoodBakedCookieSugar + - id: FoodBakedGrilledCheeseSandwich + - id: FoodBakedGrilledCheeseSandwichCotton + - id: FoodBakedNugget + - id: FoodBakedPancake + - id: FoodBakedPancakeBb + - id: FoodBakedPancakeCc + - id: FoodBakedWaffle + - id: FoodBakedWaffleSoy + - id: FoodBakedWaffleSoylent + - id: FoodBakedWaffleRoffle + - id: FoodBakedPretzel + - id: FoodBakedCannoli + - id: FoodPieAppleSlice + - id: FoodPieBaklavaSlice + - id: FoodPieClafoutisSlice + - id: FoodPieMeatSlice + - id: FoodPieCherrySlice + - id: FoodPieFrostySlice + - id: FoodTartGrape + - id: FoodTartCoco + - id: FoodBakedBrownie + - id: FoodPieBananaCreamSlice + - id: FoodTartMimeSlice + - !type:GroupSelector #rare + weight: 0.05 + children: + - id: FoodBreadMeatSpiderSlice + - id: FoodBreadMimanaSlice + - id: FoodCakeBrainSlice + - id: FoodCakeClownSlice + - id: FoodCakeSpacemanSlice + - id: FoodTartGapple + - id: FoodBreadMeatXenoSlice + - id: FoodPieXenoSlice + - id: FoodBakedCannabisBrownie offset: 0.0 - #rare - rarePrototypes: - - FoodBreadMeatSpiderSlice - - FoodBreadMimanaSlice - - FoodCakeBrainSlice - - FoodCakeClownSlice - - FoodCakeSpacemanSlice - - FoodTartGapple - - FoodBreadMeatXenoSlice - - FoodPieXenoSlice - - FoodBakedCannabisBrownie - rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_whole.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_whole.yml index 7683f19884..f7c553b022 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_whole.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_baked_whole.yml @@ -1,65 +1,68 @@ - type: entity + parent: MarkerBase id: RandomFoodBakedWhole name: random baked food spawner suffix: Whole - parent: MarkerBase placement: mode: AlignTileAny components: - type: Sprite layers: - - state: red - - sprite: Objects/Consumable/Food/Baked/pie.rsi - state: plain - - type: RandomSpawner - #small item - prototypes: - - FoodBreadVolcanic - - FoodBreadPlain - - FoodBreadMeat - - FoodBreadCorn - - FoodBreadSausage - - FoodBreadBanana - - FoodBreadTofu - - FoodBreadCreamcheese - - FoodBreadBaguette - - FoodCakeBlueberry - - FoodCakePlain - - FoodCakeCheese - - FoodCakeCarrot - - FoodCakeOrange - - FoodCakeLime - - FoodCakeLemon - - FoodCakeChocolate - - FoodCakeApple - - FoodCakeSlime - - FoodCakePumpkin - - FoodCakeChristmas - - FoodCakeBirthday - - FoodCakeVanilla - - FoodCakeBerryDelight - - FoodCakeCotton - - FoodPieApple - - FoodPieBaklava - - FoodPieBananaCream - - FoodPieClafoutis - - FoodPieCherry - - FoodPieMeat - - FoodPieFrosty - - FoodBakedBrownieBatch - chance: 0.8 + - state: red + - sprite: Objects/Consumable/Food/Baked/pie.rsi + state: plain + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - !type:GroupSelector #small item + weight: 0.95 + prob: 0.8 + children: + - id: FoodBreadVolcanic + - id: FoodBreadPlain + - id: FoodBreadMeat + - id: FoodBreadCorn + - id: FoodBreadSausage + - id: FoodBreadBanana + - id: FoodBreadTofu + - id: FoodBreadCreamcheese + - id: FoodBreadBaguette + - id: FoodCakeBlueberry + - id: FoodCakePlain + - id: FoodCakeCheese + - id: FoodCakeCarrot + - id: FoodCakeOrange + - id: FoodCakeLime + - id: FoodCakeLemon + - id: FoodCakeChocolate + - id: FoodCakeApple + - id: FoodCakeSlime + - id: FoodCakePumpkin + - id: FoodCakeChristmas + - id: FoodCakeBirthday + - id: FoodCakeVanilla + - id: FoodCakeBerryDelight + - id: FoodCakeCotton + - id: FoodPieApple + - id: FoodPieBaklava + - id: FoodPieBananaCream + - id: FoodPieClafoutis + - id: FoodPieCherry + - id: FoodPieMeat + - id: FoodPieFrosty + - id: FoodBakedBrownieBatch + - !type:GroupSelector #rare + weight: 0.05 + children: + - id: FoodBreadMeatSpider + - id: FoodBreadMimana + - id: FoodCakeBrain + - id: FoodCakeClown + - id: FoodCakeSpaceman + - id: FoodPieXeno + - id: FoodPieAmanita + - id: FoodPiePlump + - id: FoodBreadMeatXeno + - id: FoodPieXeno + - id: FoodBakedCannabisBrownieBatch offset: 0.0 - #rare - rarePrototypes: - - FoodBreadMeatSpider - - FoodBreadMimana - - FoodCakeBrain - - FoodCakeClown - - FoodCakeSpaceman - - FoodPieXeno - - FoodPieAmanita - - FoodPiePlump - - FoodBreadMeatXeno - - FoodPieXeno - - FoodBakedCannabisBrownieBatch - rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_breakfast.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_breakfast.yml index e4213ad31f..27256c673e 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_breakfast.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_breakfast.yml @@ -1,18 +1,19 @@ - type: entity + parent: MarkerBase id: RandomFoodBreakfast name: random food spawner - suffix: Meal - parent: MarkerBase + suffix: Breakfast placement: mode: AlignTileAny components: - type: Sprite layers: - - sprite: Objects/Consumable/Food/breakfast.rsi - state: fullamerican - - type: RandomSpawner - prototypes: - - FoodBreakfastAmerican - - FoodBreakfastEnglish - chance: 0.8 + - sprite: Objects/Consumable/Food/breakfast.rsi + state: fullamerican + - type: EntityTableSpawner + table: !type:GroupSelector + prob: 0.8 + children: + - id: FoodBreakfastAmerican + - id: FoodBreakfastEnglish offset: 0.0 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_meal.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_meal.yml index 4c64bdd786..14aea5b79c 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_meal.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_meal.yml @@ -1,97 +1,101 @@ - type: entity + parent: MarkerBase id: RandomFoodMeal name: random food spawner suffix: Meal - parent: MarkerBase placement: mode: AlignTileAny components: - type: Sprite layers: - - state: red - - sprite: Objects/Consumable/Food/meals.rsi - state: cornedbeef - - type: RandomSpawner - prototypes: - - FoodMealPotatoLoaded - - FoodMealFries - - FoodMealFriesCheesy - - FoodMealFriesCarrot - - FoodMealNachos - - FoodMealNachosCheesy - - FoodMealNachosCuban - - FoodMealEggplantParm - - FoodMealPotatoYaki - - FoodMealCubancarp - - FoodMealCornedbeef - - FoodMealPigblanket - - FoodMealRibs - - FoodMealEggsbenedict - - FoodMealOmelette - - FoodMealFriedegg - - FoodMealQueso - - FoodMealSashimi - - FoodMealEnchiladas - - FoodNoodlesChowmein - - FoodNoodlesSpesslaw - - FoodNoodlesMeatball - - FoodNoodlesBoiled - - FoodNoodles - - FoodNoodlesButter - - FoodMealHappyHonkClown - - FoodSoupPea - - FoodSaladHerb - - FoodSaladValid - - FoodSaladFruit - - FoodSaladJungle - - FoodSaladCitrus - - FoodSaladCaesar - - FoodSaladColeslaw - - FoodSaladKimchi - - FoodSaladWatermelonFruitBowl - - FoodRiceBoiled - - FoodRiceEgg - - FoodRicePork - - FoodRicePudding - - FoodRiceGumbo - - FoodOatmeal - - FoodSoupMeatball - - FoodSoupVegetable - - FoodSoupNettle - - FoodSoupChiliHot - - FoodSoupChiliCold - - FoodSoupTomato - - FoodSoupMiso - - FoodSoupMushroom - - FoodSoupBeet - - FoodSoupBeetRed - - FoodSoupPotato - - FoodSoupOnion - - FoodSoupBisque - - FoodSoupBungo - - FoodMealCornInButter - - FoodSoupStew - chance: 0.8 + - state: red + - sprite: Objects/Consumable/Food/meals.rsi + state: cornedbeef + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - !type:GroupSelector + weight: 0.95 + prob: 0.8 + children: + - id: FoodMealPotatoLoaded + - id: FoodMealFries + - id: FoodMealFriesCheesy + - id: FoodMealFriesCarrot + - id: FoodMealNachos + - id: FoodMealNachosCheesy + - id: FoodMealNachosCuban + - id: FoodMealEggplantParm + - id: FoodMealPotatoYaki + - id: FoodMealCubancarp + - id: FoodMealCornedbeef + - id: FoodMealPigblanket + - id: FoodMealRibs + - id: FoodMealEggsbenedict + - id: FoodMealOmelette + - id: FoodMealFriedegg + - id: FoodMealQueso + - id: FoodMealSashimi + - id: FoodMealEnchiladas + - id: FoodNoodlesChowmein + - id: FoodNoodlesSpesslaw + - id: FoodNoodlesMeatball + - id: FoodNoodlesBoiled + - id: FoodNoodles + - id: FoodNoodlesButter + - id: FoodMealHappyHonkClown + - id: FoodSoupPea + - id: FoodSaladHerb + - id: FoodSaladValid + - id: FoodSaladFruit + - id: FoodSaladJungle + - id: FoodSaladCitrus + - id: FoodSaladCaesar + - id: FoodSaladColeslaw + - id: FoodSaladKimchi + - id: FoodSaladWatermelonFruitBowl + - id: FoodRiceBoiled + - id: FoodRiceEgg + - id: FoodRicePork + - id: FoodRicePudding + - id: FoodRiceGumbo + - id: FoodOatmeal + - id: FoodSoupMeatball + - id: FoodSoupVegetable + - id: FoodSoupNettle + - id: FoodSoupChiliHot + - id: FoodSoupChiliCold + - id: FoodSoupTomato + - id: FoodSoupMiso + - id: FoodSoupMushroom + - id: FoodSoupBeet + - id: FoodSoupBeetRed + - id: FoodSoupPotato + - id: FoodSoupOnion + - id: FoodSoupBisque + - id: FoodSoupBungo + - id: FoodMealCornInButter + - id: FoodSoupStew + - !type:GroupSelector #rare + weight: 0.05 + children: + - id: FoodMealMint + - id: FoodMealBearsteak + - id: FoodMealMilkape + - id: DisgustingSweptSoup + - id: FoodMealMemoryleek + - id: FoodSaladAesir + - id: FoodSaladEden + - id: FoodJellyDuff + - id: FoodJellyAmanita + - id: FoodSoupSlime + - id: FoodSoupTomatoBlood + - id: FoodSoupWingFangChu + - id: FoodSoupClown + - id: FoodSoupMystery + - id: FoodSoupChiliClown + - id: FoodSoupMonkey + - id: FoodSoupEyeball + - id: FoodSoupElectron + - id: FoodNoodlesCopy offset: 0.0 - #rare - rarePrototypes: - - FoodMealMint - - FoodMealBearsteak - - FoodMealMilkape - - DisgustingSweptSoup - - FoodMealMemoryleek - - FoodSaladAesir - - FoodSaladEden - - FoodJellyDuff - - FoodJellyAmanita - - FoodSoupSlime - - FoodSoupTomatoBlood - - FoodSoupWingFangChu - - FoodSoupClown - - FoodSoupMystery - - FoodSoupChiliClown - - FoodSoupMonkey - - FoodSoupEyeball - - FoodSoupElectron - - FoodNoodlesCopy - rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml index fda7b85b75..ea44f18599 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml @@ -1,71 +1,75 @@ - type: entity + parent: MarkerBase id: RandomFoodSingle name: random food spawner suffix: Single Serving - parent: MarkerBase placement: mode: AlignTileAny components: - type: Sprite layers: - - state: red - - sprite: Objects/Consumable/Food/burger.rsi - state: plain - - type: RandomSpawner - prototypes: - - FoodBagel - - FoodBagelPoppy - - FoodBurgerJelly - - FoodBurgerCarp - - FoodBurgerTofu - - FoodBurgerXeno - - FoodBurgerBig - - FoodBurgerFive - - FoodBurgerRat - - FoodBurgerBacon - - FoodBurgerEmpowered - - FoodBurgerCrab - - FoodBurgerHuman - - FoodBurgerSoy - - FoodBurgerMcrib - - FoodBurgerMcguffin - - FoodBurgerChicken - - FoodBurgerDuck - - FoodBurgerCheese - - FoodNoodlesBoiled - - FoodNoodles - - FoodNoodlesCopy - - FoodNoodlesButter - - FoodPizzaMargheritaSlice - - FoodPizzaMeatSlice - - FoodPizzaMushroomSlice - - FoodPizzaVegetableSlice - - FoodPizzaDonkpocketSlice - - FoodPizzaDankSlice - - FoodPizzaSassysageSlice - - FoodPizzaPineappleSlice - - FoodPizzaMoldySlice - - FoodBakedDumplings - - FoodBakedChevreChaud - - FoodBakedNugget - - FoodTacoShell - chance: 0.8 + - state: red + - sprite: Objects/Consumable/Food/burger.rsi + state: plain + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - !type:GroupSelector + weight: 0.95 + prob: 0.8 + children: + - id: FoodBagel + - id: FoodBagelPoppy + - id: FoodBurgerJelly + - id: FoodBurgerCarp + - id: FoodBurgerTofu + - id: FoodBurgerXeno + - id: FoodBurgerBig + - id: FoodBurgerFive + - id: FoodBurgerRat + - id: FoodBurgerBacon + - id: FoodBurgerEmpowered + - id: FoodBurgerCrab + - id: FoodBurgerHuman + - id: FoodBurgerSoy + - id: FoodBurgerMcrib + - id: FoodBurgerMcguffin + - id: FoodBurgerChicken + - id: FoodBurgerDuck + - id: FoodBurgerCheese + - id: FoodNoodlesBoiled + - id: FoodNoodles + - id: FoodNoodlesCopy + - id: FoodNoodlesButter + - id: FoodPizzaMargheritaSlice + - id: FoodPizzaMeatSlice + - id: FoodPizzaMushroomSlice + - id: FoodPizzaVegetableSlice + - id: FoodPizzaDonkpocketSlice + - id: FoodPizzaDankSlice + - id: FoodPizzaSassysageSlice + - id: FoodPizzaPineappleSlice + - id: FoodPizzaMoldySlice + - id: FoodBakedDumplings + - id: FoodBakedChevreChaud + - id: FoodBakedNugget + - id: FoodTacoShell + - !type:GroupSelector #rare + weight: 0.05 + children: + - id: FoodBurgerAppendix + - id: FoodBurgerRobot + - id: FoodBurgerBaseball + - id: FoodBurgerBear + - id: FoodBurgerCat + - id: FoodBurgerClown + - id: FoodBurgerMime + - id: FoodBurgerBrain + - id: FoodBurgerGhost + - id: FoodBurgerSpell + - id: FoodBurgerSuper + - id: FoodBurgerCrazy + - id: FoodPizzaArnoldSlice + - id: FoodPizzaUraniumSlice + - id: FoodPizzaWorldpeasSlice offset: 0.0 - #rare - rarePrototypes: - - FoodBurgerAppendix - - FoodBurgerRobot - - FoodBurgerBaseball - - FoodBurgerBear - - FoodBurgerCat - - FoodBurgerClown - - FoodBurgerMime - - FoodBurgerBrain - - FoodBurgerGhost - - FoodBurgerSpell - - FoodBurgerSuper - - FoodBurgerCrazy - - FoodPizzaArnoldSlice - - FoodPizzaUraniumSlice - - FoodPizzaWorldpeasSlice - rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index df730a6567..7cc9527869 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -95,6 +95,7 @@ - !type:GroupSelector weight: 23 children: + - id: TravelCamera - id: ClothingNeckCloakHerald - id: ClothingHeadHelmetTemplar # Cloaks diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/vending.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/vending.yml index 0b530c68b1..98dfc20c13 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/vending.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/vending.yml @@ -1,38 +1,38 @@ - type: entity + parent: MarkerBase id: RandomVending name: random vending machine spawner suffix: Any - parent: MarkerBase components: - type: Sprite layers: - state: red - sprite: Structures/Machines/VendingMachines/random.rsi state: any - - type: RandomSpawner - prototypes: - - VendingMachineChang - - VendingMachineCigs - - VendingMachineCoffee - - VendingMachineCola #Robust Sofdrinks - - VendingMachineColaBlack #Robust Sofdrinks [Black] - - VendingMachineColaRed #Space Cola - - VendingMachineDiscount - - VendingMachineDonut - - VendingMachineDrGibb - - VendingMachinePwrGame - - VendingMachineShamblersJuice - - VendingMachineSmite - - VendingMachineSnack - - VendingMachineSnackBlue - - VendingMachineSnackGreen - - VendingMachineSnackOrange - - VendingMachineSnackTeal - - VendingMachineSoda #Robust Sofdrinks [Soda] - - VendingMachineSovietSoda #Boda - - VendingMachineSpaceUp - - VendingMachineStarkist - chance: 1 + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - id: VendingMachineChang + - id: VendingMachineCigs + - id: VendingMachineCoffee + - id: VendingMachineCola #Robust Sofdrinks + - id: VendingMachineColaBlack #Robust Sofdrinks [Black] + - id: VendingMachineColaRed #Space Cola + - id: VendingMachineDiscount + - id: VendingMachineDonut + - id: VendingMachineDrGibb + - id: VendingMachinePwrGame + - id: VendingMachineShamblersJuice + - id: VendingMachineSmite + - id: VendingMachineSnack + - id: VendingMachineSnackBlue + - id: VendingMachineSnackGreen + - id: VendingMachineSnackOrange + - id: VendingMachineSnackTeal + - id: VendingMachineSoda #Robust Sofdrinks [Soda] + - id: VendingMachineSovietSoda #Boda + - id: VendingMachineSpaceUp + - id: VendingMachineStarkist - type: entityTable @@ -40,19 +40,17 @@ table: !type:GroupSelector children: - id: VendingMachineClothing - weight: 40 + weight: 4 - id: VendingMachineWinter - weight: 40 + weight: 4 - id: VendingMachinePride - weight: 10 - id: VendingMachineTheater - weight: 10 - type: entity + parent: MarkerBase id: RandomVendingClothing name: random vending machine spawner suffix: Clothing - parent: MarkerBase components: - type: Sprite layers: diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingdrinks.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingdrinks.yml index 1c90664b3d..09c4ce8761 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingdrinks.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingdrinks.yml @@ -1,26 +1,26 @@ - type: entity + parent: MarkerBase id: RandomVendingDrinks name: random vending machine spawner suffix: Drinks - parent: MarkerBase components: - type: Sprite layers: - - state: red - - sprite: Structures/Machines/VendingMachines/random.rsi - state: drink - - type: RandomSpawner - prototypes: - - VendingMachineCoffee - - VendingMachineCola #Robust Sofdrinks - - VendingMachineColaBlack #Robust Sofdrinks [Black] - - VendingMachineColaRed #Space Cola - - VendingMachineDrGibb - - VendingMachinePwrGame - - VendingMachineShamblersJuice - - VendingMachineSmite - - VendingMachineSoda #Robust Sofdrinks [Soda] - - VendingMachineSovietSoda #Boda - - VendingMachineSpaceUp - - VendingMachineStarkist - chance: 1 + - state: red + - sprite: Structures/Machines/VendingMachines/random.rsi + state: drink + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - id: VendingMachineCoffee + - id: VendingMachineCola #Robust Sofdrinks + - id: VendingMachineColaBlack #Robust Sofdrinks [Black] + - id: VendingMachineColaRed #Space Cola + - id: VendingMachineDrGibb + - id: VendingMachinePwrGame + - id: VendingMachineShamblersJuice + - id: VendingMachineSmite + - id: VendingMachineSoda #Robust Sofdrinks [Soda] + - id: VendingMachineSovietSoda #Boda + - id: VendingMachineSpaceUp + - id: VendingMachineStarkist diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingsnacks.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingsnacks.yml index b634d50cc6..e919cf192d 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingsnacks.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/vendingsnacks.yml @@ -1,22 +1,22 @@ - type: entity + parent: MarkerBase id: RandomVendingSnacks name: random vending machine spawner suffix: Snacks - parent: MarkerBase components: - type: Sprite layers: - - state: red - - sprite: Structures/Machines/VendingMachines/random.rsi - state: snack - - type: RandomSpawner - prototypes: - - VendingMachineDiscount - - VendingMachineSnack - - VendingMachineSnackBlue - - VendingMachineSnackGreen - - VendingMachineSnackOrange - - VendingMachineSnackTeal - - VendingMachineChang - - VendingMachineDonut - chance: 1 + - state: red + - sprite: Structures/Machines/VendingMachines/random.rsi + state: snack + - type: EntityTableSpawner + table: !type:GroupSelector + children: + - id: VendingMachineDiscount + - id: VendingMachineSnack + - id: VendingMachineSnackBlue + - id: VendingMachineSnackGreen + - id: VendingMachineSnackOrange + - id: VendingMachineSnackTeal + - id: VendingMachineChang + - id: VendingMachineDonut diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index f2e4bb002c..890d21b597 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -349,6 +349,7 @@ - type: GhostRole name: ghost-role-information-wizard-name description: ghost-role-information-wizard-desc + rules: ghost-role-information-antagonist-rules mindRoles: - MindRoleWizard raffle: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 3b99d62124..439f2b41a4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1392,23 +1392,16 @@ - map: [ "outerClothing" ] - map: [ "mask" ] - map: [ "head" ] - - map: [ "clownedon" ] - sprite: "Effects/creampie.rsi" - state: "creampie_human" - visible: false - type: Hands activeHandId: Hand hands: Hand: location: Left - type: ComplexInteraction - - type: GenericVisualizer - visuals: - enum.CreamPiedVisuals.Creamed: - clownedon: - True: {visible: true} - False: {visible: false} - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_monkey - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Monkey_burning @@ -1505,7 +1498,7 @@ - type: entity name: monkey id: MobBaseSyndicateMonkey - parent: MobBaseAncestor + parent: MobBaseAncestor # TODO: fix copy paste from MobMonkey description: New church of neo-darwinists actually believe that EVERY animal evolved from a monkey. Tastes like pork, and killing them is both fun and relaxing. suffix: syndicate base components: @@ -1629,10 +1622,6 @@ - map: [ "id" ] - map: [ "mask" ] - map: [ "head" ] - - map: [ "clownedon" ] - sprite: "Effects/creampie.rsi" - state: "creampie_human" - visible: false - type: RandomSprite getAllGroups: true available: @@ -2975,6 +2964,11 @@ - type: MobPrice price: 200 - type: MessyDrinker + # TODO: Fix the sprite offset + #- type: CreamPied + # sprite: + # sprite: Effects/creampie.rsi + # state: creampie_corgi - type: entity parent: MobCorgiBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 43578f6e1b..973111c787 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -33,6 +33,8 @@ proto: regal - type: Physics bodyType: KinematicController + - type: Puller + needsHands: false - type: Fixtures fixtures: fix1: diff --git a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml index 6ee9de8d01..424c942c66 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/changeling.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/changeling.yml @@ -13,8 +13,3 @@ - type: Store balance: ChangelingDNA: 50 - - type: UserInterface - interfaces: - enum.StoreUiKey.Key: - type: StoreBoundUserInterface - requireInputValidation: false diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index 108329c78c..c8321468d5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml @@ -15,6 +15,8 @@ - Grammar # Pronouns - HumanoidProfile # Age, Sex, Gender - NpcFactionMember + - RandomPrice + - CreamPied # traits - BlackAndWhiteOverlay - Clumsy diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 57b682caea..a1e2e962df 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -285,3 +285,16 @@ range: 0 - type: WorldTargetAction event: !type:ActionGunShootEvent + +- type: entity + parent: Smoke + id: BloodSmoke + name: smoke + categories: [ HideSpawnMenu ] + components: + - type: Smoke + spreadAmount: 3 + duration: 2 + - type: Sprite + sprite: Effects/chemsmoke.rsi + state: chemsmoke_white diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 3c9b85e197..58217db3ef 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -34,7 +34,6 @@ - type: ActionGrant actions: - ActionJumpToCore - - ActionSurvCameraLights - ActionAIViewLaws - type: UserInterface interfaces: @@ -284,6 +283,16 @@ enum.StationAiVisualLayers.Base: False: { state: ai_empty } True: { state: ai_error } + - type: CreamPied + sprite: + sprite: Effects/creampie.rsi + state: creampie_ai + - type: Reactive + reactions: + - reagents: [Water, SpaceCleaner] + methods: [Touch] + effects: + - !type:WashCreamPie - type: InteractionPopup interactSuccessString: petting-success-station-ai interactFailureString: petting-failure-station-ai @@ -480,6 +489,16 @@ description: The AI's viewer. categories: [ HideSpawnMenu, DoNotMap ] components: + - type: Fixtures + fixtures: + lightTrigger: + shape: + !type:PhysShapeCircle + radius: 8 + density: 80 + hard: false + layer: + - GhostImpassable - type: NoFTL - type: Tag tags: @@ -499,6 +518,7 @@ - state: ai_camera shader: unshaded map: ["base"] + - type: CameraActiveOnCollideCollider # The holographic representation of the AI that is projected from a holopad. - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 2e52646dac..118c008291 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -883,11 +883,6 @@ heldPrefix: produce - type: Produce seedId: potato - - type: Extractable - juiceSolution: - reagents: - - ReagentId: JuicePotato - Quantity: 10 - type: Tag tags: - Potato diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 79beb70551..ad3b59b0ea 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: [ BaseItem, StorePresetUplink ] #PDA's have uplinks so they have to inherit the data. + parent: [ BaseItem ] id: BasePDA name: PDA description: Personal Data Assistant. @@ -107,6 +107,7 @@ type: InstrumentBoundUserInterface enum.HealthAnalyzerUiKey.Key: type: HealthAnalyzerBoundUserInterface + - type: RemoteStore # PDAs can access uplinks so they need this component. - type: Tag tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Objects/Devices/travel_camera.yml b/Resources/Prototypes/Entities/Objects/Devices/travel_camera.yml new file mode 100644 index 0000000000..5ec8b87ae0 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/travel_camera.yml @@ -0,0 +1,116 @@ +- type: entity + parent: BaseItem + id: TravelCamera + name: travel camera + description: A picture says more than a thousand words. Comes with an ultrabright flash and internal recharging photo roll. + components: + # aptly named component for picture taking + - type: PictureTaker + photographs: !type:GroupSelector + children: + - !type:NestedSelector + tableId: TravelCameraPhotographs + - type: Sprite + sprite: Objects/Devices/travel_camera.rsi + state: icon + - type: Flash + flashOnRangedInteract: true + sound: !type:SoundPathSpecifier + path: /Audio/Misc/camera_snap.ogg + - type: MeleeWeapon # allows using the camera in melee + damage: + types: + Blunt: 0 + - type: LimitedCharges + maxCharges: 4 + - type: AutoRecharge + rechargeDuration: 60 + - type: UseDelay + - type: StaticPrice + price: 100 + - type: Clothing + slots: + - NECK + sprite: Objects/Devices/travel_camera.rsi + +# we have a base prototype so that if we need to add components later on its easier +- type: entity + abstract: true + parent: BasePaper + id: BasePhotograph + name: photograph + +- type: entity + parent: BasePhotograph + id: PhotographBlack + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: black + +- type: entity + parent: BasePhotograph + id: PhotographRed + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: red + +- type: entity + parent: BasePhotograph + id: PhotographBlue + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: blue + +- type: entity + parent: BasePhotograph + id: PhotographGreen + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: green + +- type: entity + parent: BasePhotograph + id: PhotographYellow + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: yellow + +- type: entity + parent: BasePhotograph + id: PhotographPurple + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: purple + +- type: entity + parent: BasePhotograph + id: PhotographRainbow + components: + - type: Sprite + sprite: Objects/Misc/photograph.rsi + layers: + - state: rainbow + +- type: entityTable + id: TravelCameraPhotographs + table: !type:GroupSelector + children: + - id: PhotographBlack + - id: PhotographRed + - id: PhotographBlue + - id: PhotographGreen + - id: PhotographYellow + - id: PhotographPurple + - id: PhotographRainbow diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 532d0b9cca..70c823b765 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -500,7 +500,7 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: Extractable - grindableSolutionName: brassglass + grindableSolutionName: cglass - type: SolutionContainerManager solutions: cglass: diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 8e29fdd88f..f114ece546 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -158,7 +158,7 @@ - Syndicate - type: entity - parent: [ BaseSubdermalImplant, StorePresetUplink ] + parent: BaseSubdermalImplant id: UplinkImplant name: uplink implant description: This implant lets the user access a hidden Syndicate uplink at will. @@ -169,9 +169,7 @@ whitelist: components: - Hands # prevent mouse buying grenade penguin since its not telepathic - - type: Store - balance: - Telecrystal: 0 + - type: RemoteStore - type: UserInterface interfaces: enum.StoreUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml index c09bef4a3a..7555e88861 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml @@ -834,40 +834,33 @@ storagebase: !type:GroupSelector children: - id: PillDexalin - amount: &Range1to6 !type:RangeNumberSelector - range: 1, 6 + amount: 1, 6 - id: PillDylovene - amount: *Range1to6 + amount: 1, 6 - id: PillHyronalin - amount: *Range1to6 + amount: 1, 6 - id: PillPotassiumIodide - amount: *Range1to6 + amount: 1, 6 - id: PillIron - amount: *Range1to6 + amount: 1, 6 - id: PillCopper - amount: *Range1to6 + amount: 1, 6 - id: PillKelotane - amount: *Range1to6 + amount: 1, 6 - id: PillDermaline - amount: *Range1to6 + amount: 1, 6 - id: PillTricordrazine - amount: *Range1to6 + amount: 1, 6 - id: PillBicaridine - amount: *Range1to6 + amount: 1, 6 - id: PillCharcoal - amount: *Range1to6 - - id: PillAmbuzol - weight: 0.75 - amount: *Range1to6 - - id: PillAmbuzolPlus - weight: 0.75 - amount: *Range1to6 + amount: 1, 6 - id: PillSpaceDrugs weight: 0.75 - amount: *Range1to6 + amount: 1, 6 - id: StrangePill weight: 0.75 - amount: *Range1to6 + amount: 1, 6 # Syringes - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 0618e308d5..a10437d5b7 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -1540,6 +1540,22 @@ - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-door-remote-module } +- type: entity + parent: [ BaseXenoborgModuleEngi, BaseProviderBorgModule, BaseXenoborgContraband ] + id: XenoborgModuleTileGun + name: tile gun xenoborg module + description: Module with a tile gun. wait, a what? + components: + - type: Sprite + layers: + - state: xenoborg_engi + - state: icon-xenoborg-tile + - type: ItemBorgModule + hands: + - item: WeaponTileGunEmpty + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-tile-module } + - type: entity parent: [ BaseXenoborgModuleHeavy, BaseProviderBorgModule, BaseXenoborgContraband ] id: XenoborgModuleJammer @@ -1609,6 +1625,23 @@ - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-space-movement-module } +- type: entity + parent: [ BaseXenoborgModuleScout, BaseXenoborgContraband ] + id: XenoborgModuleJump + name: xenoborg jump module + description: Module that allows a xenoborg to jump forward. + components: + - type: ComponentBorgModule + components: + - type: JumpAbility + action: ActionJumpBoost + jumpDistance: 4 + jumpSound: /Audio/Effects/stealthoff.ogg + - type: Sprite + layers: + - state: xenoborg_scout + - state: icon-xenoborg-jump + - type: entity parent: [ BaseXenoborgModuleScout, BaseProviderBorgModule, BaseXenoborgContraband ] id: XenoborgModuleSword diff --git a/Resources/Prototypes/Entities/Objects/Tools/hijack_beacon.yml b/Resources/Prototypes/Entities/Objects/Tools/hijack_beacon.yml new file mode 100644 index 0000000000..d15b845322 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Tools/hijack_beacon.yml @@ -0,0 +1,34 @@ +- type: entity + parent: [ BaseItem, BaseSyndicateContraband ] + id: HijackBeacon + name: hijack beacon + description: A device that bypasses the firewall on Nanotrasen-brand Automated Trade Stations. + components: + - type: Transform + anchored: false + noRot: true + - type: Physics + bodyType: Dynamic + - type: Anchorable + - type: Item + size: Normal + - type: HijackBeacon + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.3,-0.3,0.3,0.3" + density: 190 + mask: + - MachineMask + layer: + - MachineLayer + - type: Clickable + - type: InteractionOutline + - type: Appearance + - type: Sprite + sprite: Objects/Tools/hijack_beacon.rsi + noRot: true + layers: + - state: extraction_point diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/tile_gun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/tile_gun.yml new file mode 100644 index 0000000000..a3fc4df7f4 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/tile_gun.yml @@ -0,0 +1,58 @@ +- type: entity + parent: [BaseItem, BaseXenoborgContraband] + id: WeaponTileGun + name: tile gun + description: A strange gun that shoots tiles. Shoot them with the floor! + components: + - type: Appearance + - type: Sprite + sprite: Objects/Weapons/Guns/Basic/tilegun.rsi + layers: + - state: icon + - state: mag-1 + map: ["enum.GunVisualLayers.Mag"] + - type: MagazineVisuals + magState: mag + steps: 7 + zeroVisible: false + - type: Item + size: Large + - type: ToolTileCompatible + - type: Tool + qualities: + - Prying + - type: AmmoCounter + - type: Gun + fireRate: 4 + maxAngle: 15 + minAngle: 1 + selectedMode: FullAuto + availableModes: + - FullAuto + soundGunshot: + path: /Audio/Weapons/Guns/Gunshots/magnetic_shot.ogg + soundEmpty: + path: /Audio/Weapons/Guns/Empty/empty_beep.ogg + - type: BallisticAmmoInteractLoader + - type: BallisticAmmoProvider + whitelist: + components: + - FloorTile + capacity: 30 + proto: FloorTileItemSteel + soundInsert: + path: /Audio/Weapons/Guns/MagIn/tile_load.ogg + - type: ContainerContainer + containers: + ballistic-ammo: !type:Container + ents: [] + - type: StaticPrice + price: 5000 + +- type: entity + parent: WeaponTileGun + id: WeaponTileGunEmpty + suffix: empty + components: + - type: BallisticAmmoProvider + proto: null diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 2f6ac834ba..6d5830e161 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -145,6 +145,7 @@ visible: false shader: unshaded - type: Flash + - type: RevolutionaryConverter - type: LimitedCharges maxCharges: 5 - type: MeleeWeapon diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 3a3d6949d7..8b5ba24714 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -130,8 +130,6 @@ enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: RadarConsole - - type: WorldLoader - radius: 256 - type: PointLight radius: 1.5 energy: 4.0 @@ -200,8 +198,6 @@ - Syndicate - type: RadarConsole maxRange: 384 - - type: WorldLoader - radius: 1536 - type: PointLight radius: 1.5 energy: 4.0 diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml index dd58b9709d..5b250969ce 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml @@ -31,6 +31,8 @@ energy: 0.5 color: "#b53ca1" +# The value per-disk equate to an average of 1000 research points -> 100 spesos +# Average is calculated from this table, default speso value in TechnologyDiskComponent, and default point cost in DiskConsoleComponent - type: weightedRandom id: TechDiskTierWeights weights: diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 8c8c3473da..1dcf38e504 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -691,10 +691,15 @@ - FloorSteelTilesStatic - FloorWhiteTilesStatic - FloorMaintsTilesStatic + - FloorIndustrialTilesStatic - FloorWoodTilesStatic - FloorConcreteTilesStatic - CircuitFloorsStatic - FloorMarbleTilesStatic + - FloorShuttleTilesStatic + - PlasticTilesStatic + - CarpetTilesStatic + - PreciousTilesStatic dynamicPacks: - FauxTiles - type: MaterialStorage diff --git a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml index cafdc7cef2..dad4176617 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml @@ -54,11 +54,3 @@ - type: SalvageMagnet - type: ApcPowerReceiver powerLoad: 1000 - -- type: weightedRandomEntity - id: RandomAsteroidPool - weights: - AsteroidSalvageSmall: 3 - AsteroidSalvageMedium: 7 - AsteroidSalvageLarge: 5 - AsteroidSalvageHuge: 3 diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index e80bb90941..469de4bad3 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -111,6 +111,9 @@ blacklist: tags: - PotatoBattery + - type: Item + size: Ginormous + - type: MultiHandedItem - type: entity parent: [ BaseItemRecharger, ConstructibleMachine ] @@ -175,6 +178,9 @@ blacklist: tags: - PotatoBattery + - type: Item + size: Ginormous + - type: MultiHandedItem - type: entity parent: BaseItemRecharger @@ -200,6 +206,9 @@ blacklist: tags: - PotatoBattery + - type: Item + size: Ginormous + - type: MultiHandedItem - type: entity parent: [ BaseItemRecharger, BaseWallmount ] diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index 8f22f8d68c..1410eb4e1c 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -454,6 +454,7 @@ hard: True restitution: 0 friction: 0.4 + - type: EntityStorage - type: entity parent: LockerPrisoner diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/WallmountMachines/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/WallmountMachines/surveillance_camera.yml index 561991cd26..a12bca758d 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/WallmountMachines/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/WallmountMachines/surveillance_camera.yml @@ -11,7 +11,7 @@ light: shape: !type:PhysShapeCircle - radius: 5 + radius: 0.5 hard: false mask: - GhostImpassable @@ -24,7 +24,8 @@ - None layer: - TabletopMachineLayer - - type: LightOnCollide + - type: CameraActiveOnCollide + requiresPower: false # TODO: Change this when AI can no longer see via unpowered cameras - type: PointLight enabled: false radius: 5 diff --git a/Resources/Prototypes/Entities/World/Debris/asteroids.yml b/Resources/Prototypes/Entities/World/Debris/asteroids.yml deleted file mode 100644 index bb33801e2d..0000000000 --- a/Resources/Prototypes/Entities/World/Debris/asteroids.yml +++ /dev/null @@ -1,144 +0,0 @@ -- type: entity - id: BaseAsteroidDebris - parent: BaseDebris - name: asteroid debris - abstract: true - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorTileset: - - FloorAsteroidSand - blobDrawProb: 0.5 - radius: 6 - floorPlacements: 16 - - type: SimpleFloorPlanPopulator - entries: - FloorAsteroidSand: - - id: WallRock - prob: 0.5 - orGroup: rock - - id: WallRockCoal - prob: 0.15 - orGroup: rock - - id: WallRockTin - prob: 0.15 - orGroup: rock - - id: WallRockQuartz - prob: 0.15 - orGroup: rock - - id: WallRockSalt - prob: 0.15 - orGroup: rock - - id: WallRockGold - prob: 0.05 - orGroup: rock - - id: WallRockDiamond - prob: 0.005 - orGroup: rock - - id: WallRockSilver - prob: 0.05 - orGroup: rock - - id: WallRockPlasma - prob: 0.05 - orGroup: rock - - id: WallRockUranium - prob: 0.02 - orGroup: rock - - id: WallRockBananium - prob: 0.02 - orGroup: rock - - id: WallRockArtifactFragment - prob: 0.01 - orGroup: rock - - type: IFF - flags: HideLabel - color: "#d67e27" - -- type: entity - id: AsteroidDebrisSmall - parent: BaseAsteroidDebris - name: asteroid debris small - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 8 - -- type: entity - id: AsteroidDebrisMedium - parent: BaseAsteroidDebris - name: asteroid debris medium - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 16 - -- type: entity - id: AsteroidDebrisLarge - parent: BaseAsteroidDebris - name: asteroid debris large - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 24 - -- type: entity - id: AsteroidDebrisLarger - parent: BaseAsteroidDebris - name: asteroid debris larger - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - radius: 12 - floorPlacements: 36 - -- type: entity - id: AsteroidSalvageSmall - parent: BaseAsteroidDebris - name: salvage asteroid small - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - blobDrawProb: 0.66 - radius: 15 - floorPlacements: 100 - -- type: entity - id: AsteroidSalvageMedium - parent: BaseAsteroidDebris - name: salvage asteroid medium - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - blobDrawProb: 0.66 - radius: 17 - floorPlacements: 150 - -- type: entity - id: AsteroidSalvageLarge - parent: BaseAsteroidDebris - name: salvage asteroid large - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - blobDrawProb: 0.66 - radius: 20 - floorPlacements: 200 - -- type: entity - id: AsteroidSalvageHuge - parent: BaseAsteroidDebris - name: salvage asteroid huge - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - blobDrawProb: 0.66 - radius: 23 - floorPlacements: 250 diff --git a/Resources/Prototypes/Entities/World/Debris/base_debris.yml b/Resources/Prototypes/Entities/World/Debris/base_debris.yml deleted file mode 100644 index c125d991fd..0000000000 --- a/Resources/Prototypes/Entities/World/Debris/base_debris.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: entity - id: BaseDebris - abstract: true - components: - - type: OwnedDebris - - type: LocalityLoader diff --git a/Resources/Prototypes/Entities/World/Debris/wrecks.yml b/Resources/Prototypes/Entities/World/Debris/wrecks.yml deleted file mode 100644 index 7bbeadeb5b..0000000000 --- a/Resources/Prototypes/Entities/World/Debris/wrecks.yml +++ /dev/null @@ -1,80 +0,0 @@ -- type: entity - id: BaseScrapDebris - parent: BaseDebris - name: scrap debris - abstract: true - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorTileset: - - Plating - - Plating - - Plating - - FloorSteel - - Lattice - blobDrawProb: 0.5 - radius: 6 - floorPlacements: 16 - - type: SimpleFloorPlanPopulator - entries: - Plating: - - prob: 3 # Intentional blank. - - id: SalvageMaterialCrateSpawner - prob: 1 - - id: SalvageCanisterSpawner - prob: 0.2 - - id: SalvageMobSpawner - prob: 0.7 - - id: WallSolid - prob: 1 - - id: Grille - prob: 0.5 - Lattice: - - prob: 2 - - id: Grille - prob: 0.2 - - id: SalvageMaterialCrateSpawner - prob: 0.3 - - id: SalvageCanisterSpawner - prob: 0.2 - FloorSteel: - - prob: 3 # Intentional blank. - - id: SalvageMaterialCrateSpawner - prob: 1 - - id: SalvageCanisterSpawner - prob: 0.2 - - id: SalvageMobSpawner - prob: 0.7 - - type: IFF - flags: HideLabel - color: "#88b0d1" - -- type: entity - id: ScrapDebrisSmall - parent: BaseScrapDebris - name: scrap debris small - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 8 - -- type: entity - id: ScrapDebrisMedium - parent: BaseScrapDebris - name: scrap debris medium - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 16 - -- type: entity - id: ScrapDebrisLarge - parent: BaseScrapDebris - name: scrap debris large - categories: [ HideSpawnMenu ] - components: - - type: MapGrid - - type: BlobFloorPlanBuilder - floorPlacements: 24 diff --git a/Resources/Prototypes/Entities/World/chunk.yml b/Resources/Prototypes/Entities/World/chunk.yml deleted file mode 100644 index c7cb09c2a4..0000000000 --- a/Resources/Prototypes/Entities/World/chunk.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: entity - id: WorldChunk - parent: MarkerBase - name: world chunk - description: | - It's rude to stare. - It's also a bit odd you're looking at the abstract representation of the grid of reality. - categories: [ HideSpawnMenu ] - components: - - type: WorldChunk - - type: Sprite - sprite: Markers/cross.rsi - layers: - - state: blue diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 6a7a1f15c2..87f396e841 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -329,8 +329,8 @@ components: - type: StationEvent weight: 1 # rare - duration: 1 - earliestStart: 40 # Corvax-MRP # 30 + duration: null + earliestStart: 30 reoccurrenceDelay: 60 minimumPlayers: 30 - type: AntagSelection diff --git a/Resources/Prototypes/GameRules/meteorswarms.yml b/Resources/Prototypes/GameRules/meteorswarms.yml index 99a803f1d7..9d047a25d5 100644 --- a/Resources/Prototypes/GameRules/meteorswarms.yml +++ b/Resources/Prototypes/GameRules/meteorswarms.yml @@ -4,14 +4,14 @@ id: MeteorSwarmDustEventsTable table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp children: - - id: GameRuleSpaceDustMinor - - id: GameRuleSpaceDustMajor + - id: SpaceDustMinor + - id: SpaceDustMajor - type: entityTable id: MeteorSwarmSmallChanceEventsTable table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp children: - - id: GameRuleMeteorSwarmSmall + - id: MeteorSwarmSmall prob: 0.15 - type: entityTable @@ -29,14 +29,14 @@ children: - !type:NestedSelector tableId: MeteorSwarmDustEventsTable - - id: GameRuleMeteorSwarmSmall - - id: GameRuleMeteorSwarmMedium - - id: GameRuleMeteorSwarmLarge - - id: GameRuleUristSwarm - - id: GameRuleClownSwarm - - id: GameRuleCowSwarm - - id: GameRulePotatoSwarm - - id: GameRuleFunSwarm + - id: MeteorSwarmSmall + - id: MeteorSwarmMedium + - id: MeteorSwarmLarge + - id: UristSwarm + - id: ClownSwarm + - id: CowSwarm + - id: PotatoSwarm + - id: FunSwarm - id: ImmovableRodSpawn - type: weightedRandomEntity @@ -94,7 +94,7 @@ - type: entity parent: BaseGameRule - id: GameRuleMeteorSwarm + id: MeteorSwarm abstract: true components: - type: GameRule @@ -106,8 +106,8 @@ announcementSound: /Audio/Announcements/meteors_start.ogg # Corvax-Announcements - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleSpaceDustMinor + parent: MeteorSwarm + id: SpaceDustMinor components: - type: StationEvent weight: 44 @@ -127,8 +127,8 @@ max: 5 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleSpaceDustMajor + parent: MeteorSwarm + id: SpaceDustMajor components: - type: StationEvent weight: 22 @@ -147,8 +147,8 @@ max: 12 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleMeteorSwarmSmall + parent: MeteorSwarm + id: MeteorSwarmSmall components: - type: StationEvent weight: 18 @@ -159,8 +159,8 @@ MeteorMedium: 3 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleMeteorSwarmMedium + parent: MeteorSwarm + id: MeteorSwarmMedium components: - type: StationEvent weight: 10 @@ -171,8 +171,8 @@ MeteorLarge: 1 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleMeteorSwarmLarge + parent: MeteorSwarm + id: MeteorSwarmLarge components: - type: StationEvent weight: 5 @@ -183,8 +183,8 @@ MeteorLarge: 4 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleUristSwarm + parent: MeteorSwarm + id: UristSwarm components: - type: StationEvent weight: 0.05 @@ -244,8 +244,8 @@ orGroup: rodProto - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleCowSwarm + parent: MeteorSwarm + id: CowSwarm components: - type: StationEvent weight: 0.05 @@ -262,8 +262,8 @@ max: 10 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleClownSwarm + parent: MeteorSwarm + id: ClownSwarm components: - type: StationEvent weight: 0.05 @@ -280,8 +280,8 @@ max: 10 - type: entity - parent: GameRuleMeteorSwarm - id: GameRulePotatoSwarm + parent: MeteorSwarm + id: PotatoSwarm components: - type: StationEvent weight: 0.05 @@ -298,8 +298,8 @@ max: 10 - type: entity - parent: GameRuleMeteorSwarm - id: GameRuleFunSwarm + parent: MeteorSwarm + id: FunSwarm components: - type: StationEvent weight: 0.03 diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index d191ad2860..f80d89a70f 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -286,11 +286,6 @@ - ChangelingDNA balance: ChangelingDNA: 50 - - type: UserInterface - interfaces: - enum.StoreUiKey.Key: - type: StoreBoundUserInterface - requireInputValidation: false mindRoles: - MindRoleChangeling - type: AntagObjectives diff --git a/Resources/Prototypes/Hydroponics/randomChemicals.yml b/Resources/Prototypes/Hydroponics/randomChemicals.yml index 3b8d225c36..b3468c0848 100644 --- a/Resources/Prototypes/Hydroponics/randomChemicals.yml +++ b/Resources/Prototypes/Hydroponics/randomChemicals.yml @@ -114,7 +114,6 @@ - SodaWater - Ice - JuiceCarrot - - JuicePotato - Protein - Flour - Soysauce diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml index 3e4823d449..b9c87343b2 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/clown.yml @@ -9,6 +9,11 @@ equipment: head: ClothingHeadHatJesterAlt +- type: loadout + id: ClownMitre + equipment: + head: ClothingHeadHatMitreClown + # Jumpsuit - type: loadout id: ClownSuit diff --git a/Resources/Prototypes/Loadouts/LoadoutGroups/loadout_groups.yml b/Resources/Prototypes/Loadouts/LoadoutGroups/loadout_groups.yml index 5a84e039c5..60ada10443 100644 --- a/Resources/Prototypes/Loadouts/LoadoutGroups/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/LoadoutGroups/loadout_groups.yml @@ -534,6 +534,7 @@ loadouts: - JesterHat - JesterAltHat + - ClownMitre - type: loadoutGroup id: ClownJumpsuit diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index c77ccfb3ed..9a5365c266 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -6,6 +6,8 @@ TraitorObjectiveGroupKill: 1 TraitorObjectiveGroupState: 1 #As in, something about your character. Alive, dead, arrested, gained an ability... TraitorObjectiveGroupSocial: 1 #Involves helping/harming others without killing them or stealing their stuff + TraitorObjectiveGroupSabotage: 1 + TraitorObjectiveGroupOther: 1 TraitorObjectiveGroupNukieDisk: 0.05 #Corvax - type: weightedRandom @@ -29,6 +31,7 @@ id: TraitorObjectiveGroupKill weights: KillRandomPersonObjective: 1 + KillStationAiObjective: 0.25 KillRandomHeadObjective: 0.25 - type: weightedRandom @@ -44,6 +47,18 @@ # RandomTraitorAliveObjective: 1 - Removed because the objective was boring and didn't progress the round. RandomTraitorProgressObjective: 1 +- type: weightedRandom + id: TraitorObjectiveGroupSabotage + weights: + SupercritAnomaliesObjective: 1 + +#misc objectives that don't fall other another category +- type: weightedRandom + id: TraitorObjectiveGroupOther + weights: + HijackTradeStationObjective: 1 + + #Thief groups - type: weightedRandom id: ThiefObjectiveGroups diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 89692b6225..393203cc28 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -131,6 +131,31 @@ requireDead: true requireMaroon: true +- type: entity + parent: [BaseTraitorObjective, BaseKillObjective] + id: KillStationAiObjective + description: Nanotrasen proudly boasts about their state of the art artificial intelligence. Remind them it's just another toy that can be broken. + components: + - type: Objective + difficulty: 3.0 + # there's probably only one AI + unique: true + icon: + sprite: Mobs/Silicon/station_ai.rsi + state: ai_dead # TODO: Change this to broken when we have a better objectives UI, broken sprite is currently too big! + - type: TargetObjective + title: objective-condition-kill-station-ai + - type: PickRandomPerson + pool: !type:AliveAiPool + filters: + # Can't have multiple objectives to kill the same person. + - !type:TargetObjectiveMindFilter + blacklist: + components: + - KillPersonCondition + - type: KillPersonCondition + requireDead: true + # social - type: entity @@ -354,3 +379,39 @@ # stealGroup: NukeDisk # owner: objective-condition-steal-station # Corvax-MRP-End + +# sabotage +- type: entity + parent: BaseTraitorObjective + id: SupercritAnomaliesObjective + description: Nanotrasen is very interested in anomalies with potentially catastrophic consequences. Introduce them to the fire they're playing with. + components: + - type: Objective + difficulty: 2 + icon: + sprite: Structures/Specific/anomaly.rsi + state: anom1 + - type: SupercriticalAnomaliesCondition + - type: NumberObjective + min: 3 + max: 4 + title: objective-condition-supercrit-anomalies-title + - type: ObjectiveLimit + limit: 1 + +# other +- type: entity + parent: BaseTraitorObjective + id: HijackTradeStationObjective + name: Hijack the Automated Trade Station. + description: Your uplink has been authorized one hijack beacon. Deploy it on the Automated Trade Station and defend it whilst it hijacks the trade station. + components: + - type: Objective + difficulty: 3 + icon: + sprite: Objects/Tools/hijack_beacon.rsi + state: extraction_point + - type: HijackTradeStationCondition + - type: ObjectiveLimit + # There is only one trade station so there should never be more than one of these objectives. + limit: 1 diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml index 661350f6de..8d4632c0f3 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml @@ -4,7 +4,7 @@ OreIron: 1.0 OreQuartz: 1.0 OreCoal: 0.33 - OreSalt: 0.25 + OreSalt: 0.20 OreGold: 0.25 OreSilver: 0.25 OrePlasma: 0.20 diff --git a/Resources/Prototypes/Procedural/vgroid.yml b/Resources/Prototypes/Procedural/vgroid.yml index 0caa9f0e1f..7cc40dbb8f 100644 --- a/Resources/Prototypes/Procedural/vgroid.yml +++ b/Resources/Prototypes/Procedural/vgroid.yml @@ -50,7 +50,7 @@ - !type:OreDunGen replacement: IronRock entity: IronRockSalt - count: 50 + count: 25 minGroupSize: 8 maxGroupSize: 12 - !type:OreDunGen diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml index 43bd9efedc..e22993d0a3 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml @@ -123,15 +123,6 @@ flavor: pineapple color: yellow -- type: reagent - id: JuicePotato - name: reagent-name-juice-potato - parent: BaseJuice - desc: reagent-desc-juice-potato - physicalDesc: reagent-physical-desc-starchy - flavor: potatoes - color: "#302000" - - type: reagent id: JuiceTomato name: reagent-name-juice-tomato diff --git a/Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml b/Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml index 2df2891b7c..f9147efe4f 100644 --- a/Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml +++ b/Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml @@ -15,6 +15,7 @@ - FloorTileItemDarkPavement - FloorTileItemDarkPavementVertical - FloorTileItemDarkOffset + - FloorTileItemDarkSquiggly - type: latheRecipePack id: FloorSteelTilesStatic @@ -58,6 +59,18 @@ - FloorTileItemSteelMaint - FloorTileItemTechmaintDark +- type: latheRecipePack + id: FloorIndustrialTilesStatic + recipes: + - FloorTileItemMining + - FloorTileItemMiningDark + - FloorTileItemMiningLight + - FloorTileItemElevatorShaft + - FloorTileItemRockVault + - FloorTileItemMetalDiamond + - FloorTileItemFreezer + - FloorTileItemShowroom + - type: latheRecipePack id: FloorWoodTilesStatic recipes: @@ -93,6 +106,42 @@ recipes: - FloorTileItemWhiteMarble - FloorTileItemDarkMarble + - FloorTileItemUraniumMarble + - FloorTileItemPlasmaMarble + +- type: latheRecipePack + id: FloorShuttleTilesStatic + recipes: + - FloorTileItemShuttleWhite + - FloorTileItemShuttleBlue + - FloorTileItemShuttleOrange + - FloorTileItemShuttlePurple + - FloorTileItemShuttleRed + - FloorTileItemShuttleGrey + - FloorTileItemShuttleBlack + +- type: latheRecipePack + id: PlasticTilesStatic + recipes: + - FloorTileItemLino + - FloorTileItemBoxing + - FloorTileItemGym + +- type: latheRecipePack + id: CarpetTilesStatic + recipes: + - FloorTileItemArcadeBlue + - FloorTileItemArcadeBlue2 + - FloorTileItemArcadeRed + - FloorTileItemEighties + - FloorTileItemCarpetClown + - FloorTileItemCarpetOffice + +- type: latheRecipePack + id: PreciousTilesStatic + recipes: + - FloorTileItemGold + - FloorTileItemSilver ## Dynamic @@ -107,6 +156,7 @@ - FauxTileAstroIce - FauxTileAstroSnow - FauxTileAstroAsteroidSand + - FauxTileAstroAsteroidSandBorderless - FauxTileDesertAstroSand - FauxTileAstroIronsand - FauxTileAstroIronsandBorderless diff --git a/Resources/Prototypes/Recipes/Lathes/categories.yml b/Resources/Prototypes/Recipes/Lathes/categories.yml index fd25394e67..99c1ac30e5 100644 --- a/Resources/Prototypes/Recipes/Lathes/categories.yml +++ b/Resources/Prototypes/Recipes/Lathes/categories.yml @@ -114,6 +114,22 @@ id: Marble name: lathe-category-marble +- type: latheCategory + id: Shuttle + name: lathe-category-shuttle-tile + +- type: latheCategory + id: Plastic + name: lathe-category-plastic-tile + +- type: latheCategory + id: Precious + name: lathe-category-precious-tile + +- type: latheCategory + id: Industrial + name: lathe-category-industrial-tile + # Science - type: latheCategory id: Mech diff --git a/Resources/Prototypes/Recipes/Lathes/tiles.yml b/Resources/Prototypes/Recipes/Lathes/tiles.yml index e30174a6ae..29aa64cac5 100644 --- a/Resources/Prototypes/Recipes/Lathes/tiles.yml +++ b/Resources/Prototypes/Recipes/Lathes/tiles.yml @@ -42,6 +42,16 @@ - Tiles - Maints +- type: latheRecipe + abstract: true + parent: BaseSteelTileRecipe + id: BaseIndustrialTileRecipe + categories: + - Tiles + - Industrial + materials: + Steel: 50 + - type: latheRecipe abstract: true parent: BaseSteelTileRecipe @@ -92,6 +102,46 @@ Steel: 25 Glass: 25 +- type: latheRecipe + abstract: true + parent: BaseTileRecipe + id: BaseShuttleTileRecipe + categories: + - Tiles + - Shuttle + completetime: 1 + materials: + Plasteel: 25 + +- type: latheRecipe + abstract: true + parent: BaseTileRecipe + id: BasePlasticTileRecipe + categories: + - Tiles + - Plastic + materials: + Plastic: 25 + +- type: latheRecipe + abstract: true + parent: BaseTileRecipe + id: BaseCarpetTileRecipe + categories: + - Tiles + - Carpets + materials: + Cloth: 25 + +- type: latheRecipe + abstract: true + parent: BaseTileRecipe + id: BasePreciousTileRecipe + applyMaterialDiscount: false + categories: + - Tiles + - Precious + ## Recipes # Steel tiles @@ -313,6 +363,47 @@ id: FloorTileItemTechmaintDark result: FloorTileItemTechmaintDark +# Industrial +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemMining + result: FloorTileItemMining + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemMiningDark + result: FloorTileItemMiningDark + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemMiningLight + result: FloorTileItemMiningLight + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemElevatorShaft + result: FloorTileItemElevatorShaft + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemRockVault + result: FloorTileItemRockVault + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemMetalDiamond + result: FloorTileItemMetalDiamond + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemShowroom + result: FloorTileItemShowroom + +- type: latheRecipe + parent: BaseIndustrialTileRecipe + id: FloorTileItemFreezer + result: FloorTileItemFreezer + # Circuit - type: latheRecipe parent: BaseCircuitTileRecipe @@ -477,3 +568,119 @@ parent: BaseMarbleTileRecipe id: FloorTileItemDarkMarble result: FloorTileItemDarkMarble + +- type: latheRecipe + parent: BaseMarbleTileRecipe + id: FloorTileItemUraniumMarble + result: FloorTileItemUraniumMarble + materials: + Steel: 25 + Glass: 25 + Uranium: 25 + +- type: latheRecipe + parent: BaseMarbleTileRecipe + id: FloorTileItemPlasmaMarble + result: FloorTileItemPlasmaMarble + materials: + Steel: 25 + Glass: 25 + Plasma: 25 + +# Shuttle +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleWhite + result: FloorTileItemShuttleWhite + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleBlue + result: FloorTileItemShuttleBlue + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleOrange + result: FloorTileItemShuttleOrange + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttlePurple + result: FloorTileItemShuttlePurple + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleRed + result: FloorTileItemShuttleRed + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleGrey + result: FloorTileItemShuttleGrey + +- type: latheRecipe + parent: BaseShuttleTileRecipe + id: FloorTileItemShuttleBlack + result: FloorTileItemShuttleBlack + +# Precious +- type: latheRecipe + parent: BasePreciousTileRecipe + id: FloorTileItemGold + result: FloorTileItemGold + materials: + Gold: 50 + +- type: latheRecipe + parent: BasePreciousTileRecipe + id: FloorTileItemSilver + result: FloorTileItemSilver + materials: + Silver: 50 + +# Carpets +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemArcadeBlue + result: FloorTileItemArcadeBlue + +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemArcadeBlue2 + result: FloorTileItemArcadeBlue2 + +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemArcadeRed + result: FloorTileItemArcadeRed + +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemEighties + result: FloorTileItemEighties + +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemCarpetClown + result: FloorTileItemCarpetClown + +- type: latheRecipe + parent: BaseCarpetTileRecipe + id: FloorTileItemCarpetOffice + result: FloorTileItemCarpetOffice + +# Plastic +- type: latheRecipe + parent: BasePlasticTileRecipe + id: FloorTileItemLino + result: FloorTileItemLino + +- type: latheRecipe + parent: BasePlasticTileRecipe + id: FloorTileItemBoxing + result: FloorTileItemBoxing + +- type: latheRecipe + parent: BasePlasticTileRecipe + id: FloorTileItemGym + result: FloorTileItemGym diff --git a/Resources/Prototypes/Research/civilianservices.yml b/Resources/Prototypes/Research/civilianservices.yml index 622df1ba5e..b90649b438 100644 --- a/Resources/Prototypes/Research/civilianservices.yml +++ b/Resources/Prototypes/Research/civilianservices.yml @@ -103,6 +103,7 @@ - FauxTileAstroIce - FauxTileAstroSnow - FauxTileAstroAsteroidSand + - FauxTileAstroAsteroidSandBorderless - FauxTileDesertAstroSand - FauxTileAstroIronsand - FauxTileAstroIronsandBorderless diff --git a/Resources/Prototypes/Roles/Antags/traitor.yml b/Resources/Prototypes/Roles/Antags/traitor.yml index 0cd255b7fd..f274d99fb2 100644 --- a/Resources/Prototypes/Roles/Antags/traitor.yml +++ b/Resources/Prototypes/Roles/Antags/traitor.yml @@ -52,30 +52,53 @@ - type: startingGear id: SyndicateReinforcementMedic - parent: SyndicateOperativeClothing equipment: - pocket1: WeaponPistolViper + jumpsuit: ClothingUniformJumpsuitOperative + shoes: ClothingShoesColorRed + gloves: ClothingHandsGlovesLatex + head: ClothingHeadHatSurgcapPurple + id: VisitorPDA + belt: ClothingBeltMedicalFilled + back: ClothingBackpackDuffelMedical + ears: ClothingHeadsetMedical + eyes: ClothingEyesHudMedical + pocket1: HandheldHealthAnalyzer + pocket2: PillCanisterBicaridine inhand: - MedkitCombatFilled - + storage: + back: + - Defibrillator + - CigPackSyndicate + - UniformScrubsColorPurple + - ChemistryBottleToxin + - WeaponPistolViper + - Syringe + - Syringe + - type: startingGear id: SyndicateReinforcementSpy - parent: SyndicateOperativeClothing equipment: - id: AgentIDCard - mask: ClothingMaskGasVoiceChameleon + jumpsuit: ClothingUniformJumpsuitOperative + back: ClothingBackpack + shoes: ClothingShoesColorBlack + gloves: ClothingHandsGlovesColorBlack pocket1: WeaponPistolViper - + pocket2: VoiceMaskImplanter + inhand: + - ClothingBackpackChameleonFillAgent - type: startingGear id: SyndicateReinforcementThief parent: SyndicateOperativeClothing equipment: pocket1: WeaponPistolViper + pocket2: MagazinePistolHighCapacity inhand: - ToolboxSyndicateFilled storage: back: - SyndicateJawsOfLife + - CameraBug #Syndicate Operative Outfit - Basic - type: startingGear diff --git a/Resources/Prototypes/Shaders/displacement.yml b/Resources/Prototypes/Shaders/displacement.yml index 70c9dce6f7..debb68fb1f 100644 --- a/Resources/Prototypes/Shaders/displacement.yml +++ b/Resources/Prototypes/Shaders/displacement.yml @@ -15,3 +15,10 @@ path: "/Textures/Shaders/displacement.swsl" params: displacementSize: 127 + +- type: shader + id: DisplacedDrawUnshaded + kind: source + path: "/Textures/Shaders/displacement_unshaded.swsl" + params: + displacementSize: 127 diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index 057abf0ac2..bf740ba1f6 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -115,3 +115,8 @@ id: Hologram kind: source path: "/Textures/Shaders/hologram.swsl" + +- type: shader + id: HeatBlur + kind: source + path: "/Textures/Shaders/heatBlur.swsl" diff --git a/Resources/Prototypes/SoundCollections/footsteps.yml b/Resources/Prototypes/SoundCollections/footsteps.yml index 26ecee5269..8c619226ff 100644 --- a/Resources/Prototypes/SoundCollections/footsteps.yml +++ b/Resources/Prototypes/SoundCollections/footsteps.yml @@ -94,12 +94,6 @@ files: - /Audio/Items/Toys/weh.ogg -- type: soundCollection - id: FootstepHeavy - files: - - /Audio/Effects/Footsteps/suitstep1.ogg - - /Audio/Effects/Footsteps/suitstep2.ogg - - type: soundCollection id: FootstepAsteroid files: diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index ccdb349d9c..a4c339d0e7 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -89,6 +89,11 @@ name: store-category-pointless priority: 10 +- type: storeCategory + id: UplinkObjective + name: store-category-objective + priority: 11 + #nukie delivery - type: storeCategory diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index 37329d4096..d4c1245c55 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -16,6 +16,7 @@ - UplinkWearables - UplinkJob - UplinkPointless + - UplinkObjective currencyWhitelist: - Telecrystal balance: @@ -50,3 +51,10 @@ - ChangelingAbilities currencyWhitelist: - ChangelingDNA + +- type: entity + parent: StorePresetUplink + id: StorePresetRemoteUplink + categories: [ HideSpawnMenu ] + components: + - type: RingerAccessUplink diff --git a/Resources/Prototypes/World/Biomes/basic.yml b/Resources/Prototypes/World/Biomes/basic.yml deleted file mode 100644 index 5ecd85bbc4..0000000000 --- a/Resources/Prototypes/World/Biomes/basic.yml +++ /dev/null @@ -1,26 +0,0 @@ -- type: spaceBiome - id: AsteroidsStandard - priority: 0 # This probably shouldn't get selected. - noiseRanges: {} - chunkComponents: - - type: DebrisFeaturePlacerController - densityNoiseChannel: Density - - type: SimpleDebrisSelector - debrisTable: - - id: AsteroidDebrisSmall - - id: AsteroidDebrisMedium - - id: AsteroidDebrisLarge - prob: 0.7 - - id: AsteroidDebrisLarger - prob: 0.4 - - type: NoiseDrivenDebrisSelector - noiseChannel: Wreck - debrisTable: - - id: ScrapDebrisSmall - - id: ScrapDebrisMedium - - id: ScrapDebrisLarge - prob: 0.5 - - type: NoiseRangeCarver - ranges: - - 0.4, 0.6 - noiseChannel: Carver diff --git a/Resources/Prototypes/World/Biomes/failsafes.yml b/Resources/Prototypes/World/Biomes/failsafes.yml deleted file mode 100644 index 5e3c50b44c..0000000000 --- a/Resources/Prototypes/World/Biomes/failsafes.yml +++ /dev/null @@ -1,21 +0,0 @@ -- type: spaceBiome - id: Failsafe - priority: -999999 # This DEFINITELY shouldn't get selected! - noiseRanges: {} - -- type: spaceBiome - id: AsteroidsFallback - priority: -999998 # This probably shouldn't get selected. - noiseRanges: {} - chunkComponents: - - type: DebrisFeaturePlacerController - densityNoiseChannel: Density - - type: SimpleDebrisSelector - debrisTable: - - id: AsteroidDebrisSmall - - id: AsteroidDebrisMedium - - id: AsteroidDebrisLarge - prob: 0.7 - - id: AsteroidDebrisLarger - prob: 0.4 - diff --git a/Resources/Prototypes/World/noise_channels.yml b/Resources/Prototypes/World/noise_channels.yml deleted file mode 100644 index 668b338dd3..0000000000 --- a/Resources/Prototypes/World/noise_channels.yml +++ /dev/null @@ -1,44 +0,0 @@ -- type: noiseChannel - id: Density - noiseType: Perlin - fractalLacunarityByPi: 0.666666666 - remapTo0Through1: true - clippingRanges: - - 0.4, 0.6 - clippedValue: 1.658 # magic number for chunk size. - inputMultiplier: 6 # Makes density hopefully low noise in the local area while still being interesting at scale. - outputMultiplier: 50.0 # We scale density up significantly for more human-friendly numbers. - minimum: 45.0 - -- type: noiseChannel - id: DensityUnclipped - noiseType: Perlin - fractalLacunarityByPi: 0.666666666 - remapTo0Through1: true - inputMultiplier: 6 # Makes density hopefully low noise in the local area while still being interesting at scale. - outputMultiplier: 50.0 # We scale density up significantly for more human-friendly numbers. - minimum: 45.0 - -- type: noiseChannel - id: Carver - noiseType: Perlin - fractalLacunarityByPi: 0.666666666 - remapTo0Through1: true - inputMultiplier: 6 - -- type: noiseChannel - id: Wreck - noiseType: Perlin - fractalLacunarityByPi: 0.666666666 - clippingRanges: - - 0.0, 0.4 - clippedValue: 0 - remapTo0Through1: true - inputMultiplier: 16 # Makes wreck concentration very low noise at scale. - -- type: noiseChannel - id: Temperature - noiseType: Perlin - fractalLacunarityByPi: 0.666666666 - remapTo0Through1: true - inputMultiplier: 6 # Makes wreck concentration very low noise at scale. diff --git a/Resources/Prototypes/World/worldgen_default.yml b/Resources/Prototypes/World/worldgen_default.yml deleted file mode 100644 index af52c30cf1..0000000000 --- a/Resources/Prototypes/World/worldgen_default.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: worldgenConfig - id: Default - components: - - type: WorldController - - type: BiomeSelection - biomes: - - AsteroidsFallback - - Failsafe - - AsteroidsStandard diff --git a/Resources/Prototypes/lobbyscreens.yml b/Resources/Prototypes/lobbyscreens.yml index d0f6da99d5..94fc8bd3ba 100644 --- a/Resources/Prototypes/lobbyscreens.yml +++ b/Resources/Prototypes/lobbyscreens.yml @@ -4,6 +4,12 @@ title: lobby-state-background-warden-title artist: lobby-state-background-warden-artist +- type: lobbyBackground + id: InvisibleWall + background: /Textures/LobbyScreens/invisiblewall.webp + title: lobby-state-background-invisiblewall-title + artist: lobby-state-background-invisiblewall-artist + - type: lobbyBackground id: Pharmacy background: /Textures/LobbyScreens/pharmacy.webp diff --git a/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..5647e82437 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/icon.png new file mode 100644 index 0000000000..abf8dbbcdf Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-left.png new file mode 100644 index 0000000000..1329de8c11 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-right.png new file mode 100644 index 0000000000..afb2afbac9 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/meta.json new file mode 100644 index 0000000000..8084ccf467 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/mitre_clown.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station 13 at commit https://github.com/tgstation/tgstation/commit/3dbc8c3c121e3ade9158303c2819b47206b0f93d | inhand-left and inhand-right by K-Dynamic (github) | icon modified by TiniestShark (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-left.png index dabd7cb3d3..bac0bd75d9 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-left.png and b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-right.png index af141d7eec..cd3e95bb9a 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-right.png and b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/meta.json index 4e1c6c10da..1d42deb94c 100644 --- a/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/redwizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e.", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. In-hand by ThatGuyUSA (github).", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/equipped-HELMET.png index f457d22f2f..ca99276fce 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-left.png index 759e6b3ed9..27ff39da48 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-left.png and b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-right.png index dfbf1399ce..5cee1be27c 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-right.png and b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/meta.json index 4e1c6c10da..1d42deb94c 100644 --- a/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/violetwizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e.", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. In-hand by ThatGuyUSA (github).", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/equipped-HELMET.png index 51d09dc18c..3a2095c227 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-left.png index 4a3bb5d4b8..8c45376672 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-left.png and b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-right.png index 4f669b4bb7..d4a9e495ff 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-right.png and b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/meta.json index cb9b30f249..7566e714e2 100644 --- a/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/wizardhat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-dog modified from equipped-HELMET by Sparlight (GitHub).", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-dog modified from equipped-HELMET by Sparlight (GitHub). In-hand by ThatGuyUSA (github).", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/inhand-left.png index d3c0f91adf..d01869b45f 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/meta.json index bacde82361..4e665a18e8 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/clownpriest.rsi/meta.json @@ -1,26 +1,26 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from Yogstation at https://github.com/yogstation13/Yogstation/commit/18ab2203bc47b7590f2c72b5f7969eafa723f033", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station 13 at commit https://github.com/tgstation/tgstation/commit/3dbc8c3c121e3ade9158303c2819b47206b0f93d. inhand-left modified by K-Dynamic (github).", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} \ No newline at end of file + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-left.png index eec3320f0f..991445e582 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-right.png index e3340fa623..5669e56e45 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-left.png index 7b7a9696dc..142d5e7744 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-right.png index e2542d772c..84f70a58a6 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING-vox.png deleted file mode 100644 index c075eb9a7b..0000000000 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING-vox.png and /dev/null differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING.png index 350f6a288b..1ca03449f8 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING.png and b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/icon.png index 65c104d1c7..7fadd0c88b 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/icon.png and b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-left.png index 520d5c6340..0008322748 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-right.png index e2842ac274..8dd5db1669 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/meta.json index 84009efde3..1797234d0d 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/elitevest.rsi/meta.json @@ -14,10 +14,6 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, - { - "name": "equipped-OUTERCLOTHING-vox", - "directions": 4 - }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/equipped-OUTERCLOTHING.png index b9273f213f..4f08ace712 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/equipped-OUTERCLOTHING.png and b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/icon.png index aac777b0f9..61811311f7 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/icon.png and b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-left.png index 8ecff94859..0aca74f6b6 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-right.png index 3abdad0857..0ecd48a486 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/Vests/mercwebvest.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png deleted file mode 100644 index 17d75d29b3..0000000000 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png and /dev/null differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING.png index a6947e2278..4b45304637 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING.png and b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/icon.png index c208aba1f8..8edafea2ee 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/icon.png and b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-left.png index bb043734cd..910c02b87f 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-right.png index 36c7711a7d..7b3ddbcc1f 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json index 0e30ab4d3f..789f9d124f 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json @@ -14,10 +14,6 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, - { - "name": "equipped-OUTERCLOTHING-vox", - "directions": 4 - }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Effects/HeatBlur/perlin_noise.png b/Resources/Textures/Effects/HeatBlur/perlin_noise.png new file mode 100644 index 0000000000..f1422d52a8 Binary files /dev/null and b/Resources/Textures/Effects/HeatBlur/perlin_noise.png differ diff --git a/Resources/Textures/Effects/HeatBlur/soft_circle.png b/Resources/Textures/Effects/HeatBlur/soft_circle.png new file mode 100644 index 0000000000..f3ef97531c Binary files /dev/null and b/Resources/Textures/Effects/HeatBlur/soft_circle.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json index cbfcdf45d0..073e279f0c 100644 --- a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json @@ -134,6 +134,9 @@ { "name":"xenoborg-basic-module" }, + { + "name":"xenoborg-jump-module" + }, { "name":"xenoborg-camera-computer" }, @@ -179,6 +182,9 @@ { "name":"xenoborg-sword2-module" }, + { + "name":"xenoborg-tile-module" + }, { "name":"xenoborg-tool-module" }, diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-jump-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-jump-module.png new file mode 100644 index 0000000000..bb09b413da Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-jump-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-tile-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-tile-module.png new file mode 100644 index 0000000000..75ac1819cc Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-tile-module.png differ diff --git a/Resources/Textures/LobbyScreens/attributions.yml b/Resources/Textures/LobbyScreens/attributions.yml index 1df5da9a2b..dead0a28fe 100644 --- a/Resources/Textures/LobbyScreens/attributions.yml +++ b/Resources/Textures/LobbyScreens/attributions.yml @@ -48,6 +48,11 @@ copyright: "plantyfern on discord" source: "https://github.com/space-wizards/space-station-14" +- files: ["invisiblewall.webp"] + license: "CC-BY-SA-3.0" + copyright: "vanderslootassgiraffe on discord" + source: "https://github.com/space-wizards/space-station-14" + - files: ["janishootout.webp"] license: "CC0-1.0" copyright: "psychpsyo on Github/Twitter" @@ -56,4 +61,4 @@ - files: ["reclaimer-nuke.webp"] license: "CC-BY-NC-SA-3.0" copyright: "GetOutMarutak,aka.Snicket on Discord/github/Cara/Ko-fi" - source: "https://github.com/space-wizards/space-station-14" \ No newline at end of file + source: "https://github.com/space-wizards/space-station-14" diff --git a/Resources/Textures/LobbyScreens/invisiblewall.webp b/Resources/Textures/LobbyScreens/invisiblewall.webp new file mode 100644 index 0000000000..ac38cdbac0 Binary files /dev/null and b/Resources/Textures/LobbyScreens/invisiblewall.webp differ diff --git a/Resources/Textures/LobbyScreens/invisiblewall.webp.yml b/Resources/Textures/LobbyScreens/invisiblewall.webp.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/LobbyScreens/invisiblewall.webp.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/broken.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/broken.png new file mode 100644 index 0000000000..02fc551ed7 Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/station_ai.rsi/broken.png differ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json index 6409354fa8..93fa4f1f55 100644 --- a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -254,6 +254,9 @@ }, { "name": "frame_4" + }, + { + "name": "broken" } ] } diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/full.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/full.png deleted file mode 100644 index 44e3df3e91..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/full.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_f.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_f.png deleted file mode 100644 index dada5727bf..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_f.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_m.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_m.png deleted file mode 100644 index dada5727bf..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/head_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_arm.png deleted file mode 100644 index bb7425405c..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_arm.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_foot.png deleted file mode 100644 index 8e0b3f1507..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_foot.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_hand.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_hand.png deleted file mode 100644 index cf93432a5e..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_hand.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_leg.png deleted file mode 100644 index d693b3696d..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/l_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/meta.json deleted file mode 100644 index 688877a32d..0000000000 --- a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/meta.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Created by ps3moira#9488 (discord) for SS14.", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "full" - }, - { - "name": "head_f", - "directions": 4 - }, - { - "name": "head_m", - "directions": 4 - }, - { - "name": "l_arm", - "directions": 4 - }, - { - "name": "l_foot", - "directions": 4 - }, - { - "name": "l_hand", - "directions": 4 - }, - { - "name": "l_leg", - "directions": 4 - }, - { - "name": "r_arm", - "directions": 4 - }, - { - "name": "r_foot", - "directions": 4 - }, - { - "name": "r_hand", - "directions": 4 - }, - { - "name": "r_leg", - "directions": 4 - }, - { - "name": "skull_icon", - "directions": 1 - }, - { - "name": "torso_f", - "directions": 4 - }, - { - "name": "torso_m", - "directions": 4 - } - ] -} diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_arm.png deleted file mode 100644 index 51f05a4773..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_arm.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_foot.png deleted file mode 100644 index 19ac240da3..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_foot.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_hand.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_hand.png deleted file mode 100644 index 6cd2eb37cc..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_hand.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_leg.png deleted file mode 100644 index e1ea113ca3..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/r_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/skull_icon.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/skull_icon.png deleted file mode 100644 index 6d0ea66780..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/skull_icon.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_f.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_f.png deleted file mode 100644 index 2bcb3cc9eb..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_f.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_m.png b/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_m.png deleted file mode 100644 index 2bcb3cc9eb..0000000000 Binary files a/Resources/Textures/Mobs/Species/Terminator/parts.rsi/torso_m.png and /dev/null differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/equipped-BELT.png b/Resources/Textures/Objects/Devices/pda.rsi/equipped-BELT.png deleted file mode 100644 index 6901e6c33b..0000000000 Binary files a/Resources/Textures/Objects/Devices/pda.rsi/equipped-BELT.png and /dev/null differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/equipped-IDCARD.png b/Resources/Textures/Objects/Devices/pda.rsi/equipped-IDCARD.png deleted file mode 100644 index 6901e6c33b..0000000000 Binary files a/Resources/Textures/Objects/Devices/pda.rsi/equipped-IDCARD.png and /dev/null differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/meta.json b/Resources/Textures/Objects/Devices/pda.rsi/meta.json index e5833360bd..984030280f 100644 --- a/Resources/Textures/Objects/Devices/pda.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/pda.rsi/meta.json @@ -13,10 +13,6 @@ { "name": "id_overlay_wide" }, - { - "name": "equipped-BELT", - "directions": 4 - }, { "name": "inhand-left", "directions": 4 @@ -254,10 +250,6 @@ 0.3 ] ] - }, - { - "name": "equipped-IDCARD", - "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Devices/travel_camera.rsi/equipped-NECK.png b/Resources/Textures/Objects/Devices/travel_camera.rsi/equipped-NECK.png new file mode 100644 index 0000000000..03ae539d6f Binary files /dev/null and b/Resources/Textures/Objects/Devices/travel_camera.rsi/equipped-NECK.png differ diff --git a/Resources/Textures/Objects/Devices/travel_camera.rsi/icon.png b/Resources/Textures/Objects/Devices/travel_camera.rsi/icon.png new file mode 100644 index 0000000000..2e13f71cfa Binary files /dev/null and b/Resources/Textures/Objects/Devices/travel_camera.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-left.png b/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-left.png new file mode 100644 index 0000000000..c475dea254 Binary files /dev/null and b/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-right.png b/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-right.png new file mode 100644 index 0000000000..6df91ff4a2 Binary files /dev/null and b/Resources/Textures/Objects/Devices/travel_camera.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Devices/travel_camera.rsi/meta.json b/Resources/Textures/Objects/Devices/travel_camera.rsi/meta.json new file mode 100644 index 0000000000..acd1d82967 --- /dev/null +++ b/Resources/Textures/Objects/Devices/travel_camera.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation, edited by SlamBamActionman", + "size": { + "x": 32, + "y": 32 + }, + + "states": [ + { + "name": "equipped-NECK", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/black.png b/Resources/Textures/Objects/Misc/photograph.rsi/black.png new file mode 100644 index 0000000000..2e1284ebeb Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/black.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/blue.png b/Resources/Textures/Objects/Misc/photograph.rsi/blue.png new file mode 100644 index 0000000000..d32bb7534c Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/blue.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/green.png b/Resources/Textures/Objects/Misc/photograph.rsi/green.png new file mode 100644 index 0000000000..6635d990ec Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/green.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/meta.json b/Resources/Textures/Objects/Misc/photograph.rsi/meta.json new file mode 100644 index 0000000000..a76a07d169 --- /dev/null +++ b/Resources/Textures/Objects/Misc/photograph.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by ketufaispikinut (Github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "black" + }, + { + "name": "red" + }, + { + "name": "blue" + }, + { + "name": "green" + }, + { + "name": "yellow" + }, + { + "name": "purple" + }, + { + "name": "rainbow" + } + ] +} diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/purple.png b/Resources/Textures/Objects/Misc/photograph.rsi/purple.png new file mode 100644 index 0000000000..6bf8b7b252 Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/purple.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/rainbow.png b/Resources/Textures/Objects/Misc/photograph.rsi/rainbow.png new file mode 100644 index 0000000000..5aaef8a35b Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/rainbow.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/red.png b/Resources/Textures/Objects/Misc/photograph.rsi/red.png new file mode 100644 index 0000000000..9df1683989 Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/red.png differ diff --git a/Resources/Textures/Objects/Misc/photograph.rsi/yellow.png b/Resources/Textures/Objects/Misc/photograph.rsi/yellow.png new file mode 100644 index 0000000000..6cfc8c71ed Binary files /dev/null and b/Resources/Textures/Objects/Misc/photograph.rsi/yellow.png differ diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-jump.png b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-jump.png new file mode 100644 index 0000000000..e667754481 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-jump.png differ diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-tile.png b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-tile.png new file mode 100644 index 0000000000..5fc8309c43 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-tile.png differ diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json index 345a622f3f..59c4c75ec8 100644 --- a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json @@ -124,6 +124,9 @@ { "name": "icon-xenoborg-cloak" }, + { + "name": "icon-xenoborg-jump" + }, { "name": "icon-xenoborg-cloak2" }, @@ -154,6 +157,9 @@ { "name": "icon-xenoborg-sword2" }, + { + "name": "icon-xenoborg-tile" + }, { "name": "icon-xenoborg-tools" }, diff --git a/Resources/Textures/Objects/Tools/hijack_beacon.rsi/extraction_point.png b/Resources/Textures/Objects/Tools/hijack_beacon.rsi/extraction_point.png new file mode 100644 index 0000000000..7b8dd4c3af Binary files /dev/null and b/Resources/Textures/Objects/Tools/hijack_beacon.rsi/extraction_point.png differ diff --git a/Resources/Textures/Objects/Tools/hijack_beacon.rsi/meta.json b/Resources/Textures/Objects/Tools/hijack_beacon.rsi/meta.json new file mode 100644 index 0000000000..5177acf42f --- /dev/null +++ b/Resources/Textures/Objects/Tools/hijack_beacon.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/austation/austation/commit/e2a4fefd01e702f48d3d4cc8d6a2686d54d104fa and edited by TheShuEd. Recolored by alexalexmax", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "extraction_point", + "delays": [ + [ + 0.5, + 0.5 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/icon.png new file mode 100644 index 0000000000..5e345c10e7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-1.png new file mode 100644 index 0000000000..171ad18162 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-2.png new file mode 100644 index 0000000000..c51709c6ad Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-3.png new file mode 100644 index 0000000000..bdf4d8ab35 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-4.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-4.png new file mode 100644 index 0000000000..8c8fdb1e0c Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-4.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-5.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-5.png new file mode 100644 index 0000000000..d3bf22c77d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-5.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-6.png b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-6.png new file mode 100644 index 0000000000..23c81a161a Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/mag-6.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/meta.json new file mode 100644 index 0000000000..a9711a6798 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Basic/tilegun.rsi/meta.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "created by Samuka-C (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "mag-1" + }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, + { + "name": "mag-4" + }, + { + "name": "mag-5" + }, + { + "name": "mag-6" + } + ] +} diff --git a/Resources/Textures/Shaders/displacement_unshaded.swsl b/Resources/Textures/Shaders/displacement_unshaded.swsl new file mode 100644 index 0000000000..cc389b9a9b --- /dev/null +++ b/Resources/Textures/Shaders/displacement_unshaded.swsl @@ -0,0 +1,20 @@ +light_mode unshaded; + +uniform sampler2D displacementMap; +uniform highp float displacementSize; +uniform highp vec4 displacementUV; + +varying highp vec2 displacementUVOut; + +void vertex() { + displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2); +} + +void fragment() { + highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut); + highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0); + COLOR = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0)); + COLOR.a *= displacementSample.a; +} + + diff --git a/Resources/Textures/Shaders/drunk.swsl b/Resources/Textures/Shaders/drunk.swsl index c2657c18a5..1410baa823 100644 --- a/Resources/Textures/Shaders/drunk.swsl +++ b/Resources/Textures/Shaders/drunk.swsl @@ -1,22 +1,29 @@ uniform sampler2D SCREEN_TEXTURE; uniform highp float boozePower; -const highp float TimeScale = 0.5; -const highp float DistortionScale = 0.01; +// How fast to do the rotating motion. +// 1 for normal effect. +// 0 for for no motion. +uniform highp float timeScale; +// Starting phase for the rotation effect. +// Needed so it doesn't always look the same for 0 motion. +uniform highp float phase; +// Multiplier for the amplitude of the offset. 1 for normal strength. +uniform highp float distortionScale; void fragment() { - highp float mod = mix(0.0, DistortionScale, boozePower); + highp float amplitudeMod = mix(0.0, distortionScale * 0.01, boozePower); highp vec2 coord = FRAGCOORD.xy * SCREEN_PIXEL_SIZE.xy; - highp float time = TIME * TimeScale; + highp float time = TIME * timeScale * 0.5 + phase; - highp vec2 offset = vec2((mod * 1.5) * sin(time * 1.5), (mod * 2.0) * cos(time * 1.5 - 0.2)); + highp vec2 offset = vec2((amplitudeMod * 1.5) * sin(time * 1.5), (amplitudeMod * 2.0) * cos(time * 1.5 - 0.2)); highp vec4 tex1 = zTextureSpec(SCREEN_TEXTURE, coord + offset); - - if (boozePower > 0.5) { - offset = vec2((mod * 2.0 - DistortionScale) * sin(time * 0.333 - 0.2), (mod * 2.0 - DistortionScale) * cos(time * 0.333)); - tex1 = mix(tex1, zTextureSpec(SCREEN_TEXTURE, coord + offset), mix(0.0, 0.3, boozePower*2.0-1.0)); + + if (boozePower > 0.25) { + offset = vec2((amplitudeMod * 2.0) * sin(time * 0.333 - 0.2), (amplitudeMod * 2.0) * cos(time * 0.333)); + tex1 = mix(tex1, zTextureSpec(SCREEN_TEXTURE, coord + offset), mix(0.0, 0.3, boozePower * 2.0 - 0.5)); } - - offset = vec2((mod * 1.0) * sin(time * 1.0 + 0.1), (mod * 1.0) * cos(time * 1.0)); + + offset = vec2(amplitudeMod * sin(time * 1.0 + 0.1), amplitudeMod * cos(time * 1.0)); COLOR = mix(tex1, zTextureSpec(SCREEN_TEXTURE, coord + offset), mix(0.0, 0.5, boozePower)); } diff --git a/Resources/Textures/Shaders/heatBlur.swsl b/Resources/Textures/Shaders/heatBlur.swsl new file mode 100644 index 0000000000..269a74b3f9 --- /dev/null +++ b/Resources/Textures/Shaders/heatBlur.swsl @@ -0,0 +1,34 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform sampler2D NOISE_TEXTURE; // The pre-generated noise + +// Those 3 are overwritten at SetReducedMotion +uniform highp float spatial_scale; // Makes more waves +uniform highp float strength_scale; // Makes waves stronger +uniform highp float speed_scale; // Makes waves run faster + +void fragment() +{ + // Calculate scrolling UVs for the noise + highp vec2 flow1 = vec2(0.0, TIME * speed_scale); + highp vec2 flow2 = vec2(TIME * 0.5 * 0.1, TIME * speed_scale * 1.2); + + // Sample the pre-calculated noise + highp float noiseVal = texture2D(NOISE_TEXTURE, fract((UV * spatial_scale) - flow1)).r; + highp float noiseVal2 = texture2D(NOISE_TEXTURE, fract((UV * spatial_scale) - flow2 + vec2(0.43, 0.12))).r; + + // Create distortion vector + highp vec2 distortion = vec2( + noiseVal - 0.5, + noiseVal2 - 0.5 + ); + + highp float heatStrength = texture2D(TEXTURE, UV).r; + + // Non-linear curve to make the heat look more "intense" in the center + heatStrength = clamp(-heatStrength * heatStrength + 2.0 * heatStrength, 0.0, 1.0); + + // Apply Distortion to Screen + highp vec2 distortedUV = UV + (distortion * strength_scale * heatStrength); + + COLOR = texture2D(SCREEN_TEXTURE, distortedUV); +} diff --git a/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-left.png b/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-left.png new file mode 100644 index 0000000000..12d689adf9 Binary files /dev/null and b/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-left.png differ diff --git a/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-right.png b/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-right.png new file mode 100644 index 0000000000..4133de5276 Binary files /dev/null and b/Resources/Textures/Structures/Power/cell_recharger.rsi/inhand-right.png differ diff --git a/Resources/Textures/Structures/Power/cell_recharger.rsi/meta.json b/Resources/Textures/Structures/Power/cell_recharger.rsi/meta.json index 42ad3ab990..4a59465e53 100644 --- a/Resources/Textures/Structures/Power/cell_recharger.rsi/meta.json +++ b/Resources/Textures/Structures/Power/cell_recharger.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "Taken from Goonstation at commit https://github.com/goonstation/goonstation/commit/4f88b9314336631929c9cdddb1567fc08f83bf9e and modified by potato1234x (github), then again by EmoGarbage404 (github) for ss14", + "copyright": "Taken from Goonstation at commit https://github.com/goonstation/goonstation/commit/4f88b9314336631929c9cdddb1567fc08f83bf9e and modified by potato1234x (github), then again by EmoGarbage404 (github) for ss14, in-hands by spanky-spanky (GitHub)", "states": [ { "name": "light-off" @@ -47,6 +47,14 @@ 0.8 ] ] + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Power/recharger.rsi/inhand-left.png b/Resources/Textures/Structures/Power/recharger.rsi/inhand-left.png new file mode 100644 index 0000000000..e9d698667d Binary files /dev/null and b/Resources/Textures/Structures/Power/recharger.rsi/inhand-left.png differ diff --git a/Resources/Textures/Structures/Power/recharger.rsi/inhand-right.png b/Resources/Textures/Structures/Power/recharger.rsi/inhand-right.png new file mode 100644 index 0000000000..e9d698667d Binary files /dev/null and b/Resources/Textures/Structures/Power/recharger.rsi/inhand-right.png differ diff --git a/Resources/Textures/Structures/Power/recharger.rsi/meta.json b/Resources/Textures/Structures/Power/recharger.rsi/meta.json index 1aee0099ef..4ca570e071 100644 --- a/Resources/Textures/Structures/Power/recharger.rsi/meta.json +++ b/Resources/Textures/Structures/Power/recharger.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/discordia-space/CEV-Eris/raw/9ea3eccbe22e18d24653949067f3d7dd12194ea9/icons/obj/stationobjs.dmi", + "copyright": "https://github.com/discordia-space/CEV-Eris/raw/9ea3eccbe22e18d24653949067f3d7dd12194ea9/icons/obj/stationobjs.dmi , in-hands by spanky-spanky (GitHub)", "states": [ { "name": "empty" @@ -55,6 +55,14 @@ 0.1 ] ] + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-left.png b/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-left.png new file mode 100644 index 0000000000..40ab6caf12 Binary files /dev/null and b/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-left.png differ diff --git a/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-right.png b/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-right.png new file mode 100644 index 0000000000..40ab6caf12 Binary files /dev/null and b/Resources/Textures/Structures/Power/turbo_recharger.rsi/inhand-right.png differ diff --git a/Resources/Textures/Structures/Power/turbo_recharger.rsi/meta.json b/Resources/Textures/Structures/Power/turbo_recharger.rsi/meta.json index 50271d1e33..0483760670 100644 --- a/Resources/Textures/Structures/Power/turbo_recharger.rsi/meta.json +++ b/Resources/Textures/Structures/Power/turbo_recharger.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "adapted from https://github.com/discordia-space/CEV-Eris/raw/9ea3eccbe22e18d24653949067f3d7dd12194ea9/icons/obj/stationobjs.dmi by EmoGarbage404 (github)", + "copyright": "adapted from https://github.com/discordia-space/CEV-Eris/raw/9ea3eccbe22e18d24653949067f3d7dd12194ea9/icons/obj/stationobjs.dmi by EmoGarbage404 (github), in-hands by spanky-spanky (GitHub)", "states": [ { "name": "empty" @@ -57,6 +57,14 @@ 0.05 ] ] + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 } ] } diff --git a/RobustToolbox b/RobustToolbox index dad56301e1..3136118b53 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit dad56301e115f79f03852e3a8dfe485f0db667c3 +Subproject commit 3136118b5338ef2d9580178caf5c723e65eb76e7 diff --git a/Tools/generate_heat_distortion_textures.py b/Tools/generate_heat_distortion_textures.py new file mode 100644 index 0000000000..2c8c3dd106 --- /dev/null +++ b/Tools/generate_heat_distortion_textures.py @@ -0,0 +1,54 @@ +#This is script that was used to generate textures for heatdistortion + +from pyfastnoiselite.pyfastnoiselite import FastNoiseLite, NoiseType, FractalType +from PIL import Image +import math + +def generate_noise_image(output_filename="perlin_noise.png"): + width = 512 + height = 512 + + noise = FastNoiseLite() + + noise.noise_type = NoiseType.NoiseType_Perlin + noise.fractal_type = FractalType.FractalType_FBm + noise.fractal_octaves = 4 + noise.frequency = 0.01 + + image = Image.new("RGBA", (width, height)) + pixels = image.load() + + for x in range(width): + for y in range(height): + value = (noise.get_noise(x, y) + 1.0) / 2.0 + color_val = int(value * 255) + color_val = max(0, min(255, color_val)) + + pixels[x, y] = (color_val, color_val, color_val, 255) + image.save(output_filename) + print(f"Success! Image exported to: {output_filename}") + +def generate_soft_circle_texture(output_filename="soft_circle.png"): + width = 64 + height = 64 + + image = Image.new("RGBA", (width, height)) + pixels = image.load() + + center_x = width / 2.0 + center_y = height / 2.0 + max_dist = width / 2.0 + + for x in range(width): + for y in range(height): + dist = math.sqrt((x - center_x)**2 + (y - center_y)**2) + fade = 1.0 - max(0.0, min(1.0, dist / max_dist)) + alpha_val = fade * fade * (3.0 - 2.0 * fade) + alpha_byte = int(alpha_val * 255) + pixels[x, y] = (255, 255, 255, alpha_byte) + image.save(output_filename) + print(f"Success! Image exported to: {output_filename}") + +if __name__ == "__main__": + generate_noise_image() + generate_soft_circle_texture()