diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b2aeb6197a..f8f22fb3f8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,7 +8,7 @@ This isn’t an exhaustive list of things that you can’t do. Rather, take it i This code of conduct applies specifically to the Github repositories and its spaces managed by the Space Station 14 project or Space Wizards Federation. Some spaces, such as the Space Station 14 Discord or the official Wizard's Den game servers, have their own rules but are in spirit equal to what may be found in here. -If you believe someone is violating the code of conduct, we ask that you report it by contacting a Maintainer, Project Manager or Wizard staff member through [Discord](https://discord.ss14.io/), [the forums](https://forum.spacestation14.com/), or emailing [telecommunications@spacestation14.com](mailto:telecommunications@spacestation14.com). +If you believe someone is violating the code of conduct, we ask that you report it by contacting a Maintainer, Project Manager or Wizard staff member through [Discord](https://discord.ss14.io/), [the forums](https://forum.spacestation14.com/), or emailing [support@spacestation14.com](mailto:support@spacestation14.com). - **Be friendly and patient.** - **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. diff --git a/Content.Benchmarks/DestructibleBenchmark.cs b/Content.Benchmarks/DestructibleBenchmark.cs new file mode 100644 index 0000000000..1b54bacca0 --- /dev/null +++ b/Content.Benchmarks/DestructibleBenchmark.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Server.Destructible; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Damage.Systems; +using Content.Shared.FixedPoint; +using Content.Shared.Maps; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Benchmarks; + +[Virtual] +[GcServer(true)] +[MemoryDiagnoser] +public class DestructibleBenchmark +{ + /// + /// Number of destructible entities per prototype to spawn with a . + /// + [Params(1, 10, 100, 1000, 5000)] + public int EntityCount; + + /// + /// Amount of blunt damage we do to each entity. + /// + [Params(10000)] + public FixedPoint2 DamageAmount; + + [Params("Blunt")] + public ProtoId DamageType; + + private static readonly EntProtoId WindowProtoId = "Window"; + private static readonly EntProtoId WallProtoId = "WallReinforced"; + private static readonly EntProtoId HumanProtoId = "MobHuman"; + + private static readonly ProtoId TileRef = "Plating"; + + private readonly EntProtoId[] _prototypes = [WindowProtoId, WallProtoId, HumanProtoId]; + + private readonly List> _damageables = new(); + private readonly List> _destructbiles = new(); + + private DamageSpecifier _damage; + + private TestPair _pair = default!; + private IEntityManager _entMan = default!; + private IPrototypeManager _protoMan = default!; + private IRobustRandom _random = default!; + private ITileDefinitionManager _tileDefMan = default!; + private DamageableSystem _damageable = default!; + private DestructibleSystem _destructible = default!; + private SharedMapSystem _map = default!; + + [GlobalSetup] + public async Task SetupAsync() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(); + _pair = await PoolManager.GetServerClient(); + var server = _pair.Server; + + var mapdata = await _pair.CreateTestMap(); + + _entMan = server.ResolveDependency(); + _protoMan = server.ResolveDependency(); + _random = server.ResolveDependency(); + _tileDefMan = server.ResolveDependency(); + _damageable = _entMan.System(); + _destructible = _entMan.System(); + _map = _entMan.System(); + + if (!_protoMan.Resolve(DamageType, out var type)) + return; + + _damage = new DamageSpecifier(type, DamageAmount); + + _random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking. + + var plating = _tileDefMan[TileRef].TileId; + + // We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system. + // Needed for managing the performance of destructive effects and damage application. + await server.WaitPost(() => + { + // Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario... + for (var x = 0; x < EntityCount; x++) + { + for (var y = 0; y < _prototypes.Length; y++) + { + _map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating)); + } + } + + for (var x = 0; x < EntityCount; x++) + { + var y = 0; + foreach (var protoId in _prototypes) + { + var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f); + _entMan.SpawnEntity(protoId, coords); + y++; + } + } + + var query = _entMan.EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var damageable, out var destructible)) + { + _damageables.Add((uid, damageable)); + _destructbiles.Add((uid, damageable, destructible)); + } + }); + } + + [Benchmark] + public async Task PerformDealDamage() + { + await _pair.Server.WaitPost(() => + { + _damageable.ApplyDamageToAllEntities(_damageables, _damage); + }); + } + + [Benchmark] + public async Task PerformTestTriggers() + { + await _pair.Server.WaitPost(() => + { + _destructible.TestAllTriggers(_destructbiles); + }); + } + + [Benchmark] + public async Task PerformTestBehaviors() + { + await _pair.Server.WaitPost(() => + { + _destructible.TestAllBehaviors(_destructbiles); + }); + } + + + [GlobalCleanup] + public async Task CleanupAsync() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } +} diff --git a/Content.Benchmarks/GasReactionBenchmark.cs b/Content.Benchmarks/GasReactionBenchmark.cs new file mode 100644 index 0000000000..9ed30373d1 --- /dev/null +++ b/Content.Benchmarks/GasReactionBenchmark.cs @@ -0,0 +1,253 @@ +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Server.Atmos; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Reactions; +using Content.Shared.Atmos; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; + +namespace Content.Benchmarks; + +/// +/// Benchmarks the performance of different gas reactions. +/// Tests each reaction type with realistic gas mixtures to measure computational cost. +/// +[Virtual] +[GcServer(true)] +[MemoryDiagnoser] +public class GasReactionBenchmark +{ + private const int Iterations = 1000; + private TestPair _pair = default!; + private AtmosphereSystem _atmosphereSystem = default!; + + // Grid and tile for reactions that need a holder + private EntityUid _testGrid = default!; + private TileAtmosphere _testTile = default!; + // Reaction instances + private PlasmaFireReaction _plasmaFireReaction = default!; + private TritiumFireReaction _tritiumFireReaction = default!; + private FrezonProductionReaction _frezonProductionReaction = default!; + private FrezonCoolantReaction _frezonCoolantReaction = default!; + private AmmoniaOxygenReaction _ammoniaOxygenReaction = default!; + private N2ODecompositionReaction _n2oDecompositionReaction = default!; + private WaterVaporReaction _waterVaporReaction = default!; + // Gas mixtures for each reaction type + private GasMixture _plasmaFireMixture = default!; + private GasMixture _tritiumFireMixture = default!; + private GasMixture _frezonProductionMixture = default!; + private GasMixture _frezonCoolantMixture = default!; + private GasMixture _ammoniaOxygenMixture = default!; + private GasMixture _n2oDecompositionMixture = default!; + private GasMixture _waterVaporMixture = default!; + + [GlobalSetup] + public async Task SetupAsync() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(); + _pair = await PoolManager.GetServerClient(); + var server = _pair.Server; + + // Create test map and grid + var mapData = await _pair.CreateTestMap(); + _testGrid = mapData.Grid; + + await server.WaitPost(() => + { + var entMan = server.ResolveDependency(); + _atmosphereSystem = entMan.System(); + + _plasmaFireReaction = new PlasmaFireReaction(); + _tritiumFireReaction = new TritiumFireReaction(); + _frezonProductionReaction = new FrezonProductionReaction(); + _frezonCoolantReaction = new FrezonCoolantReaction(); + _ammoniaOxygenReaction = new AmmoniaOxygenReaction(); + _n2oDecompositionReaction = new N2ODecompositionReaction(); + _waterVaporReaction = new WaterVaporReaction(); + + SetupGasMixtures(); + SetupTile(); + }); + } + + private void SetupGasMixtures() + { + // Plasma Fire: Plasma + Oxygen at high temperature + // Temperature must be > PlasmaMinimumBurnTemperature for reaction to occur + _plasmaFireMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.PlasmaMinimumBurnTemperature + 100f // ~673K + }; + _plasmaFireMixture.AdjustMoles(Gas.Plasma, 20f); + _plasmaFireMixture.AdjustMoles(Gas.Oxygen, 100f); + + // Tritium Fire: Tritium + Oxygen at high temperature + // Temperature must be > FireMinimumTemperatureToExist for reaction to occur + _tritiumFireMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.FireMinimumTemperatureToExist + 100f // ~473K + }; + _tritiumFireMixture.AdjustMoles(Gas.Tritium, 20f); + _tritiumFireMixture.AdjustMoles(Gas.Oxygen, 100f); + + // Frezon Production: Oxygen + Tritium + Nitrogen catalyst + // Optimal temperature for efficiency (80% of max efficiency temp) + _frezonProductionMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.FrezonProductionMaxEfficiencyTemperature * 0.8f // ~48K + }; + _frezonProductionMixture.AdjustMoles(Gas.Oxygen, 50f); + _frezonProductionMixture.AdjustMoles(Gas.Tritium, 50f); + _frezonProductionMixture.AdjustMoles(Gas.Nitrogen, 10f); + + // Frezon Coolant: Frezon + Nitrogen + // Temperature must be > FrezonCoolLowerTemperature (23.15K) for reaction to occur + _frezonCoolantMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 50f // ~343K + }; + _frezonCoolantMixture.AdjustMoles(Gas.Frezon, 30f); + _frezonCoolantMixture.AdjustMoles(Gas.Nitrogen, 100f); + + // Ammonia + Oxygen reaction (concentration-dependent, no temp requirement) + _ammoniaOxygenMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 100f // ~393K + }; + _ammoniaOxygenMixture.AdjustMoles(Gas.Ammonia, 40f); + _ammoniaOxygenMixture.AdjustMoles(Gas.Oxygen, 40f); + + // N2O Decomposition (no temperature requirement, just needs N2O moles) + _n2oDecompositionMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + 100f // ~393K + }; + _n2oDecompositionMixture.AdjustMoles(Gas.NitrousOxide, 100f); + + // Water Vapor - needs water vapor to condense + _waterVaporMixture = new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + }; + _waterVaporMixture.AdjustMoles(Gas.WaterVapor, 50f); + } + + private void SetupTile() + { + // Create a tile atmosphere to use as holder for all reactions + var testIndices = new Vector2i(0, 0); + _testTile = new TileAtmosphere(_testGrid, testIndices, new GasMixture(Atmospherics.CellVolume) + { + Temperature = Atmospherics.T20C + }); + } + + private static GasMixture CloneMixture(GasMixture original) + { + return new GasMixture(original); + } + + [Benchmark] + public async Task PlasmaFireReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_plasmaFireMixture); + _plasmaFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task TritiumFireReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_tritiumFireMixture); + _tritiumFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task FrezonProductionReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_frezonProductionMixture); + _frezonProductionReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task FrezonCoolantReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_frezonCoolantMixture); + _frezonCoolantReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task AmmoniaOxygenReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_ammoniaOxygenMixture); + _ammoniaOxygenReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task N2ODecompositionReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_n2oDecompositionMixture); + _n2oDecompositionReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [Benchmark] + public async Task WaterVaporReaction() + { + await _pair.Server.WaitPost(() => + { + for (var i = 0; i < Iterations; i++) + { + var mixture = CloneMixture(_waterVaporMixture); + _waterVaporReaction.React(mixture, _testTile, _atmosphereSystem, 1f); + } + }); + } + + [GlobalCleanup] + public async Task CleanupAsync() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } +} diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs index ea96b13376..2b8ebf53b7 100644 --- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs +++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs @@ -9,7 +9,6 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; using System.Numerics; using System.Linq; -using Content.Client.Stylesheets; namespace Content.Client.Access.UI { diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index 7566942506..cb2839f5d0 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -294,19 +294,19 @@ public sealed partial class BanPanel : DefaultWindow } /// - /// Adds a check button specifically for one "role" in a "group" + /// Adds a toggle button specifically for one "role" in a "group" /// E.g. it would add the Chief Medical Officer "role" into the "Medical" group. /// private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox) { var roleCheckboxContainer = new BoxContainer(); - var roleCheckButton = new Button + var roleToggleButton = new Button { Name = role, Text = role, ToggleMode = true, }; - roleCheckButton.OnToggled += args => + roleToggleButton.OnToggled += args => { // Checks the role group checkbox if all the children are pressed if (args.Pressed && _roleCheckboxes[group].All(e => e.Item1.Pressed)) @@ -343,12 +343,12 @@ public sealed partial class BanPanel : DefaultWindow roleCheckboxContainer.AddChild(jobIconTexture); } - roleCheckboxContainer.AddChild(roleCheckButton); + roleCheckboxContainer.AddChild(roleToggleButton); roleGroupInnerContainer.AddChild(roleCheckboxContainer); _roleCheckboxes.TryAdd(group, []); - _roleCheckboxes[group].Add((roleCheckButton, rolePrototype)); + _roleCheckboxes[group].Add((roleToggleButton, rolePrototype)); } public void UpdateBanFlag(bool newFlag) diff --git a/Content.Client/Atmos/EntitySystems/DeltaPressureSystem.cs b/Content.Client/Atmos/EntitySystems/DeltaPressureSystem.cs new file mode 100644 index 0000000000..3d9893ac62 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/DeltaPressureSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Atmos.EntitySystems; + +namespace Content.Client.Atmos.EntitySystems; + +public sealed class DeltaPressureSystem : SharedDeltaPressureSystem; diff --git a/Content.Client/Changelog/ChangelogManager.cs b/Content.Client/Changelog/ChangelogManager.cs index 657d0cb3ac..545d001aab 100644 --- a/Content.Client/Changelog/ChangelogManager.cs +++ b/Content.Client/Changelog/ChangelogManager.cs @@ -52,6 +52,7 @@ namespace Content.Client.Changelog // Open changelog purely to compare to the last viewed date. var changelogs = await LoadChangelog(); UpdateChangelogs(changelogs); + _configManager.OnValueChanged(CCVars.ServerId, OnServerIdCVarChanged); } private void UpdateChangelogs(List changelogs) @@ -81,6 +82,11 @@ namespace Content.Client.Changelog MaxId = changelog.Entries.Max(c => c.Id); + CheckLastSeenEntry(); + } + + private void CheckLastSeenEntry() + { var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}"); if (_resource.UserData.TryReadAllText(path, out var lastReadIdText)) { @@ -92,6 +98,11 @@ namespace Content.Client.Changelog NewChangelogEntriesChanged?.Invoke(); } + private void OnServerIdCVarChanged(string newValue) + { + CheckLastSeenEntry(); + } + public Task> LoadChangelog() { return Task.Run(() => diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 68707e021c..1b66bf8732 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -31,6 +31,11 @@ internal sealed class ChatManager : IChatManager // See server-side manager. This just exists for shared code. } + public void SendAdminAlertNoFormatOrEscape(string message) + { + // See server-side manager. This just exists for shared code. + } + public void SendMessage(string text, ChatSelectChannel channel) { var str = text.ToString(); diff --git a/Content.Client/Damage/DamageVisualsSystem.cs b/Content.Client/Damage/DamageVisualsSystem.cs index 065bf628bc..ac3ff819aa 100644 --- a/Content.Client/Damage/DamageVisualsSystem.cs +++ b/Content.Client/Damage/DamageVisualsSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.Damage; +using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Robust.Client.GameObjects; diff --git a/Content.Client/Disposal/Mailing/MailingUnitWindow.xaml b/Content.Client/Disposal/Mailing/MailingUnitWindow.xaml index 0acd300895..ab910d2db4 100644 --- a/Content.Client/Disposal/Mailing/MailingUnitWindow.xaml +++ b/Content.Client/Disposal/Mailing/MailingUnitWindow.xaml @@ -47,7 +47,7 @@ Access="Public" Text="{Loc 'ui-disposal-unit-button-eject'}" StyleClasses="OpenBoth" /> - diff --git a/Content.Client/Disposal/Unit/DisposalUnitWindow.xaml b/Content.Client/Disposal/Unit/DisposalUnitWindow.xaml index 60ca7ba0db..312f80c176 100644 --- a/Content.Client/Disposal/Unit/DisposalUnitWindow.xaml +++ b/Content.Client/Disposal/Unit/DisposalUnitWindow.xaml @@ -34,7 +34,7 @@ Access="Public" Text="{Loc 'ui-disposal-unit-button-eject'}" StyleClasses="OpenBoth" /> - diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs index 225619b031..533a8b9f2c 100644 --- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs +++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs @@ -1,28 +1,22 @@ using System.Linq; using System.Numerics; -using Content.Client.Message; using Content.Shared.Atmos; using Content.Client.UserInterface.Controls; -using Content.Shared.Alert; -using Content.Shared.Damage; +using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory; using Content.Shared.MedicalScanner; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Nutrition.Components; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.ResourceManagement; -using Robust.Client.UserInterface; using Robust.Shared.Prototypes; using Robust.Shared.Utility; diff --git a/Content.Client/Instruments/UI/ChannelsMenu.xaml b/Content.Client/Instruments/UI/ChannelsMenu.xaml index 20e4a3e923..e98b03e6b3 100644 --- a/Content.Client/Instruments/UI/ChannelsMenu.xaml +++ b/Content.Client/Instruments/UI/ChannelsMenu.xaml @@ -7,7 +7,7 @@