diff --git a/.editorconfig b/.editorconfig index 0409377f1d..59ca35cc9a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -196,7 +196,7 @@ csharp_preserve_single_line_blocks = true #dotnet_naming_style.begins_with_i.word_separator = #dotnet_naming_style.begins_with_i.capitalization = pascal_case -dotnet_diagnostic.IDE0055.severity = warning +dotnet_diagnostic.ide0055.severity = warning dotnet_naming_rule.constants_rule.severity = warning dotnet_naming_rule.constants_rule.style = upper_camel_case_style @@ -336,6 +336,10 @@ dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter # ReSharper properties resharper_braces_for_ifelse = required_for_multiline +resharper_keep_existing_attribute_arrangement = true -[*.{csproj,xml,yml,dll.config,msbuildproj,targets}] +[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets}] indent_size = 2 + +[{*.yaml,*.yml}] +ij_yaml_indent_sequence_value = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fce9ef824d..1adc0b803d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,7 @@ # Last match in file takes precedence. # Sorting by path instead of by who added it one day :( +# this isn't how codeowners rules work pls read the first comment instead of trying to force a sorting order /Resources/ConfigPresets/WizardsDen/ @Chief-Engineer # Moony's Gargantuan List Of Things She Cares About, or MGLOTSCA for short. @@ -8,7 +9,7 @@ /Content.*/Toolshed/ @moonheart08 **/Toolshed/** @moonheart08 *Command.cs @moonheart08 -/Content.*/Administration/ @moonheart08 @PaulRitter @DrSmugleaf @Chief-Engineer +/Content.*/Administration/ @moonheart08 @DrSmugleaf @Chief-Engineer /Content.*/Station/ @moonheart08 /Content.*/Maps/ @moonheart08 /Content.*/GameTicking/ @moonheart08 @EmoGarbage404 @@ -16,19 +17,14 @@ /Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404 /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer -/Resources/Prototypes/species.yml @moonheart08 -/Resources/Prototypes/Body/ @moonheart08 @mirrorcult @DrSmugleaf # suffering -/Resources/Prototypes/Maps/ @moonheart08 @Emisse -/Resources/Prototypes/Entities/Mobs/Player/ @moonheart08 @mirrorcult @DrSmugleaf -/Resources/Prototypes/Entities/Mobs/Species/ @moonheart08 @mirrorcult @DrSmugleaf -/Content.*/Body/ @mirrorcult @DrSmugleaf -/Content.*/Chemistry/ @mirrorcult -/Content.*/StatusEffect/ @mirrorcult -/Content.*/Whitelist/ @mirrorcult -/Resources/Prototypes/Reagents/ @mirrorcult -/Resources/Prototypes/Recipes/Reactions @mirrorcult -/Resources/Prototypes/Chemistry @mirrorcult +/Resources/Prototypes/Maps/ @Emisse + +/Resources/Prototypes/Body/ @DrSmugleaf # suffering +/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf +/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf +/Content.*/Body/ @DrSmugleaf +/Content.YAMLLinter @DrSmugleaf /Content.*/Verbs/ @ElectroJr /Content.Client/ContextMenu/ @ElectroJr @@ -54,21 +50,16 @@ /Content.*/Forensics/ @ficcialfaint -/Content.YAMLLinter @PaulRitter @DrSmugleaf -/Content.*/Inventory @PaulRitter -/Content.*/Arcade @PaulRitter -/Content.*/Conveyor @PaulRitter -/Content.*/Decals @PaulRitter -/Content.*/Objectives @PaulRitter -/Content.*/MachineLinking @PaulRitter -/Content.*/Crayon @PaulRitter -/Content.*/Clothing @PaulRitter - -# Be they Fluent translations or Freemarker templates, I know them both! -*.ftl @RemieRichards - # SKREEEE /Content.*.Database/ @PJB3005 @DrSmugleaf /Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @Chief-Engineer /Pow3r/ @PJB3005 /Content.Server/Power/Pow3r/ @PJB3005 + +# notafet +/Content.*/Atmos/ @Partmedia +/Content.*/Botany/ @Partmedia + +#Jezi +/Content.*/Medical @Jezithyr +/Content.*/Body @Jezithyr diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index dcedb2f8fe..3a6129c7a6 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -11,7 +11,7 @@ jobs: name: Run Benchmarks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 with: submodules: 'recursive' - name: Get Engine version diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml index 68439cb6de..1c4b543743 100644 --- a/.github/workflows/build-docfx.yml +++ b/.github/workflows/build-docfx.yml @@ -8,7 +8,7 @@ jobs: docfx: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 - name: Setup submodule run: | git submodule update --init --recursive @@ -19,9 +19,9 @@ jobs: cd RobustToolbox/ git submodule update --init --recursive - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.x + dotnet-version: 7.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml new file mode 100644 index 0000000000..e921bd2558 --- /dev/null +++ b/.github/workflows/build-map-renderer.yml @@ -0,0 +1,57 @@ +name: Build & Test Map Renderer + +on: + push: + branches: [ master, staging, trying ] + merge_group: + pull_request: + types: [ opened, reopened, synchronize, ready_for_review ] + branches: [ master ] + +jobs: + build: + if: github.actor != 'PJBot' && github.event.pull_request.draft == false + strategy: + matrix: + os: [ubuntu-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout Master + uses: actions/checkout@v3.6.0 + + - name: Setup Submodule + run: | + git submodule update --init --recursive + + - name: Pull engine updates + uses: space-wizards/submodule-dependency@v0.1.5 + + - name: Update Engine Submodules + run: | + cd RobustToolbox/ + git submodule update --init --recursive + + - name: Setup .NET Core + uses: actions/setup-dotnet@v3.2.0 + with: + dotnet-version: 7.0.x + + - name: Install dependencies + run: dotnet restore + + - name: Build Project + run: dotnet build Content.MapRenderer --configuration Release --no-restore /p:WarningsAsErrors=nullable /m + + - name: Run Map Renderer + run: dotnet run --project Content.MapRenderer Dev + + ci-success: + name: Build & Test Debug + needs: + - build + runs-on: ubuntu-latest + steps: + - name: CI succeeded + run: exit 0 diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml index 416955a644..9abd4fbe17 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -13,13 +13,13 @@ jobs: if: github.actor != 'PJBot' && github.event.pull_request.draft == false strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout Master - uses: actions/checkout@v2 + uses: actions/checkout@v3.6.0 - name: Setup Submodule run: | @@ -34,9 +34,9 @@ jobs: git submodule update --init --recursive - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.x + dotnet-version: 7.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml deleted file mode 100644 index 9ff89d47e8..0000000000 --- a/.github/workflows/build-test-release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Build & Test Release - -on: - push: - branches: [ master, staging, trying ] - merge_group: - pull_request: - types: [ opened, reopened, synchronize, ready_for_review ] - branches: [ master ] - -jobs: - build: - if: github.actor != 'PJBot' && github.event.pull_request.draft == false - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout Master - uses: actions/checkout@v2 - - - name: Setup Submodule - run: | - git submodule update --init --recursive - - - name: Pull engine updates - uses: space-wizards/submodule-dependency@v0.1.5 - - - name: Update Engine Submodules - run: | - cd RobustToolbox/ - git submodule update --init --recursive - - - name: Setup .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - - name: Install dependencies - run: dotnet restore - - - name: Build Project - run: dotnet build --configuration Tools --no-restore /p:WarningsAsErrors=nullable /m - - - name: Run Content.Tests - run: dotnet test --configuration Tools --no-build Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 - - - name: Run Content.IntegrationTests - shell: pwsh - run: | - $env:DOTNET_gcServer=1 - dotnet test --configuration Tools --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed - ci-success: - name: Build & Test Release - needs: - - build - runs-on: ubuntu-latest - steps: - - name: CI succeeded - run: exit 0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 25e75bdbf8..8283763839 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,23 +16,30 @@ jobs: - name: Install dependencies run: sudo apt-get install -y python3-paramiko - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 with: submodules: 'recursive' - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.100 + dotnet-version: 7.0.x - name: Get Engine Tag run: | cd RobustToolbox git fetch --depth=1 + - name: Install dependencies + run: dotnet restore + + - name: Build Packaging + run: dotnet build Content.Packaging --configuration Release --no-restore /m + + - name: Package server + run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64 + - name: Package client - run: | - Tools/package_server_build.py -p win-x64 linux-x64 osx-x64 linux-arm64 - Tools/package_client_build.py + run: dotnet run --project Content.Packaging client --no-wipe-release - name: Update Build Info run: Tools/gen_build_info.py diff --git a/.github/workflows/rsi-diff.yml b/.github/workflows/rsi-diff.yml index a2fef2e384..1f122526d7 100644 --- a/.github/workflows/rsi-diff.yml +++ b/.github/workflows/rsi-diff.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3.6.0 - name: Get changed files id: files diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml index 4afaabf10c..b22f307de5 100644 --- a/.github/workflows/test-packaging.yml +++ b/.github/workflows/test-packaging.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout Master - uses: actions/checkout@v2 + uses: actions/checkout@v3.6.0 - name: Setup Submodule run: | @@ -49,18 +49,22 @@ jobs: git submodule update --init --recursive - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.x + dotnet-version: 7.0.x - name: Install dependencies run: dotnet restore - - name: Package client - run: | - Tools/package_server_build.py -p win-x64 linux-x64 osx-x64 linux-arm64 - Tools/package_client_build.py + - name: Build Packaging + run: dotnet build Content.Packaging --configuration Release --no-restore /m + - name: Package server + run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64 + + - name: Package client + run: dotnet run --project Content.Packaging client --no-wipe-release + - name: Update Build Info run: Tools/gen_build_info.py diff --git a/.github/workflows/update-credits.yml b/.github/workflows/update-credits.yml new file mode 100644 index 0000000000..09844f4c19 --- /dev/null +++ b/.github/workflows/update-credits.yml @@ -0,0 +1,32 @@ +name: Update Contrib and Patreons in credits + +on: + workflow_dispatch: + schedule: + - cron: 0 0 * * 0 + +jobs: + get_credits: + runs-on: ubuntu-latest + # Hey there fork dev! If you like to include your own contributors in this then you can probably just change this to your own repo + # Do this in dump_github_contributors.ps1 too into your own repo + if: github.repository == 'space-wizards/space-station-14' + + steps: + - uses: actions/checkout@v3.6.0 + with: + ref: master + + - name: Get this week's Contributors + shell: pwsh + run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt + + # TODO + #- name: Get this week's Patreons + # run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml + + - name: Commit new credit files + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Update Credits + commit_author: PJBot diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml deleted file mode 100644 index 1d5b0a8725..0000000000 --- a/.github/workflows/update-wiki.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Update Wiki - -on: - workflow_dispatch: - push: - branches: [ master, jsondump ] - paths: - - '.github/workflows/update-wiki.yml' - - 'Content.Shared/Chemistry/**.cs' - - 'Content.Server/Chemistry/**.cs' - - 'Content.Server/GuideGenerator/**.cs' - - 'Resources/Prototypes/Reagents/**.yml' - - 'Resources/Prototypes/Chemistry/**.yml' - - 'Resources/Prototypes/Recipes/Reactions/**.yml' - - 'RobustToolbox/' - -jobs: - update-wiki: - name: Build and Publish JSON blobs to wiki - runs-on: ubuntu-latest - - steps: - - name: Checkout Master - uses: actions/checkout@v2 - - - name: Setup Submodule - run: | - git submodule update --init --recursive - - - name: Pull Engine Updates - uses: space-wizards/submodule-dependency@v0.1.5 - - - name: Update Engine Submodules - run: | - cd RobustToolbox/ - git submodule update --init --recursive - - - name: Setup .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.100 - - - name: Install Dependencies - run: dotnet restore - - - name: Build Project - run: dotnet build --configuration Release --no-restore /p:WarningsAsErrors=nullable /m - - - name: Generate JSON blobs for prototypes - run: dotnet ./bin/Content.Server/Content.Server.dll --cvar autogen.destination_file=prototypes.json - continue-on-error: true - - - name: Upload chem_prototypes.json to wiki - uses: jtmullen/mediawiki-edit-action@v0.1.1 - with: - wiki_text_file: ./bin/Content.Server/data/chem_prototypes.json - edit_summary: Update chem_prototypes.json via GitHub Actions - page_name: "${{ secrets.WIKI_PAGE_ROOT }}/chem_prototypes.json" - api_url: https://wiki.spacestation14.io/w/api.php - username: ${{ secrets.WIKI_BOT_USER }} - password: ${{ secrets.WIKI_BOT_PASS }} - - - name: Upload react_prototypes.json to wiki - uses: jtmullen/mediawiki-edit-action@v0.1.1 - with: - wiki_text_file: ./bin/Content.Server/data/react_prototypes.json - edit_summary: Update react_prototypes.json via GitHub Actions - page_name: "${{ secrets.WIKI_PAGE_ROOT }}/react_prototypes.json" - api_url: https://wiki.spacestation14.io/w/api.php - username: ${{ secrets.WIKI_BOT_USER }} - password: ${{ secrets.WIKI_BOT_PASS }} - diff --git a/.github/workflows/validate-rgas.yml b/.github/workflows/validate-rgas.yml index 539a3357d4..2c4bb40fdf 100644 --- a/.github/workflows/validate-rgas.yml +++ b/.github/workflows/validate-rgas.yml @@ -12,7 +12,7 @@ jobs: if: github.actor != 'PJBot' && github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 - name: Setup Submodule run: git submodule update --init - name: Pull engine updates diff --git a/.github/workflows/validate-rsis.yml b/.github/workflows/validate-rsis.yml index 5b9fddff33..b76df28e6a 100644 --- a/.github/workflows/validate-rsis.yml +++ b/.github/workflows/validate-rsis.yml @@ -13,7 +13,7 @@ jobs: name: Validate RSIs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 - name: Setup Submodule run: git submodule update --init - name: Pull engine updates diff --git a/.github/workflows/validate_mapfiles.yml b/.github/workflows/validate_mapfiles.yml index c47d33db71..fb11e1a469 100644 --- a/.github/workflows/validate_mapfiles.yml +++ b/.github/workflows/validate_mapfiles.yml @@ -12,7 +12,7 @@ jobs: if: github.actor != 'PJBot' && github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 - name: Setup Submodule run: git submodule update --init - name: Pull engine updates diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml index 8634dc473e..254384acff 100644 --- a/.github/workflows/yaml-linter.yml +++ b/.github/workflows/yaml-linter.yml @@ -13,7 +13,7 @@ jobs: if: github.actor != 'PJBot' && github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3.6.0 - name: Setup submodule run: | git submodule update --init --recursive @@ -24,9 +24,9 @@ jobs: cd RobustToolbox/ git submodule update --init --recursive - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 6.0.100 + dotnet-version: 7.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/.gitignore b/.gitignore index 42c93e1fc3..2dd5912047 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,7 @@ PublishScripts/ # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets +.nuget/ # Microsoft Azure Build Output csx/ diff --git a/.vscode/settings.json b/.vscode/settings.json index eed7d291a2..0e0d3ae890 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "omnisharp.analyzeOpenDocumentsOnly": true + "omnisharp.analyzeOpenDocumentsOnly": true, + "dotnet.defaultSolution": "SpaceStation14.sln" } diff --git a/Content.Benchmarks/DeviceNetworkingBenchmark.cs b/Content.Benchmarks/DeviceNetworkingBenchmark.cs index 42149aef0b..16805c2684 100644 --- a/Content.Benchmarks/DeviceNetworkingBenchmark.cs +++ b/Content.Benchmarks/DeviceNetworkingBenchmark.cs @@ -59,6 +59,7 @@ public class DeviceNetworkingBenchmark public async Task SetupAsync() { ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly); _pair = await PoolManager.GetServerClient(); var server = _pair.Server; @@ -91,6 +92,7 @@ await server.WaitPost(() => public async Task Cleanup() { await _pair.DisposeAsync(); + PoolManager.Shutdown(); } [Benchmark(Baseline = true, Description = "Entity Events")] diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 7aad0ffeb3..5d94ef85cb 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -26,6 +26,7 @@ public class MapLoadBenchmark public void Setup() { ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(null); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); var server = _pair.Server; @@ -42,6 +43,7 @@ public void Setup() public async Task Cleanup() { await _pair.DisposeAsync(); + PoolManager.Shutdown(); } public static IEnumerable MapsSource { get; set; } diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs index 5517bba6a6..65b5abaf73 100644 --- a/Content.Benchmarks/Program.cs +++ b/Content.Benchmarks/Program.cs @@ -1,11 +1,14 @@ using System; using System.Linq; using System.Threading.Tasks; -using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; using Content.IntegrationTests; using Content.Server.Maps; +#if DEBUG +using BenchmarkDotNet.Configs; +#else using Robust.Benchmarks.Configs; +#endif using Robust.Shared.Prototypes; namespace Content.Benchmarks @@ -25,6 +28,7 @@ public static async Task MainAsync(string[] args) var gameMaps = pair.Server.ResolveDependency().EnumeratePrototypes().ToList(); MapLoadBenchmark.MapsSource = gameMaps.Select(x => x.ID); await pair.CleanReturnAsync(); + PoolManager.Shutdown(); #if DEBUG Console.ForegroundColor = ConsoleColor.Red; @@ -35,8 +39,6 @@ public static async Task MainAsync(string[] args) var config = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null ? DefaultSQLConfig.Instance : null; BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); #endif - - PoolManager.Shutdown(); } } } diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs new file mode 100644 index 0000000000..de51b2fb19 --- /dev/null +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Server.Station.Systems; +using Content.Shared.Roles; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.Benchmarks; + +/// +/// This benchmarks spawns several humans, gives them captain equipment and then deletes them. +/// This measures performance for spawning, deletion, containers, and inventory code. +/// +[Virtual, MemoryDiagnoser] +public class SpawnEquipDeleteBenchmark +{ + private TestPair _pair = default!; + private StationSpawningSystem _spawnSys = default!; + private const string Mob = "MobHuman"; + private StartingGearPrototype _gear = default!; + private EntityUid _entity; + private EntityCoordinates _coords; + + [Params(1, 4, 16, 64)] + public int N; + + [GlobalSetup] + public async Task SetupAsync() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(null); + _pair = await PoolManager.GetServerClient(); + var server = _pair.Server; + + var mapData = await _pair.CreateTestMap(); + _coords = mapData.GridCoords; + _spawnSys = server.System(); + _gear = server.ProtoMan.Index("CaptainGear"); + } + + [GlobalCleanup] + public async Task Cleanup() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } + + [Benchmark] + public async Task SpawnDeletePlayer() + { + await _pair.Server.WaitPost(() => + { + var server = _pair.Server; + for (var i = 0; i < N; i++) + { + _entity = server.EntMan.SpawnAttachedTo(Mob, _coords); + _spawnSys.EquipStartingGear(_entity, _gear, null); + server.EntMan.DeleteEntity(_entity); + } + }); + } +} diff --git a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs index cb43185484..0c23542f79 100644 --- a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs +++ b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs @@ -1,7 +1,7 @@ +using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; -using Robust.Client.GameObjects; using Robust.Shared.Prototypes; using static Content.Shared.Access.Components.AccessOverriderComponent; @@ -23,7 +23,7 @@ protected override void Open() { base.Open(); - List accessLevels; + List> accessLevels; if (EntMan.TryGetComponent(Owner, out var accessOverrider)) { @@ -33,7 +33,7 @@ protected override void Open() else { - accessLevels = new List(); + accessLevels = new List>(); _accessOverriderSystem.Log.Error($"No AccessOverrider component found for {EntMan.ToPrettyString(Owner)}!"); } diff --git a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs index 6a4dcba307..2fd0057121 100644 --- a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs +++ b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs @@ -21,7 +21,7 @@ public sealed partial class AccessOverriderWindow : DefaultWindow private readonly Dictionary _accessButtons = new(); public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototypeManager prototypeManager, - List accessLevels) + List> accessLevels) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -31,7 +31,7 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype foreach (var access in accessLevels) { - if (!prototypeManager.TryIndex(access, out var accessLevel)) + if (!prototypeManager.TryIndex(access, out var accessLevel)) { _logMill.Error($"Unable to find accesslevel for {access}"); continue; diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index 292759dc87..898792aa03 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -1,10 +1,11 @@ +using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; using Content.Shared.CrewManifest; -using Robust.Client.GameObjects; using Robust.Shared.Prototypes; using static Content.Shared.Access.Components.IdCardConsoleComponent; + namespace Content.Client.Access.UI { public sealed class IdCardConsoleBoundUserInterface : BoundUserInterface @@ -22,7 +23,7 @@ public IdCardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner protected override void Open() { base.Open(); - List accessLevels; + List> accessLevels; if (EntMan.TryGetComponent(Owner, out var idCard)) { @@ -31,7 +32,7 @@ protected override void Open() } else { - accessLevels = new List(); + accessLevels = new List>(); _idCardConsoleSystem.Log.Error($"No IdCardConsole component found for {EntMan.ToPrettyString(Owner)}!"); } diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index f8450fe578..670ba08871 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -28,7 +28,7 @@ public sealed partial class IdCardConsoleWindow : DefaultWindow private string? _lastJobProto; public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, - List accessLevels) + List> accessLevels) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); diff --git a/Content.Client/Actions/ActionEvents.cs b/Content.Client/Actions/ActionEvents.cs index 36739b0dbe..2fdf25c976 100644 --- a/Content.Client/Actions/ActionEvents.cs +++ b/Content.Client/Actions/ActionEvents.cs @@ -1,11 +1,9 @@ -using Content.Shared.Actions.ActionTypes; - namespace Content.Client.Actions; /// -/// This event is raised when a user clicks on an empty action slot. Enables other systems to fill this slow. +/// This event is raised when a user clicks on an empty action slot. Enables other systems to fill this slot. /// public sealed class FillActionSlotEvent : EntityEventArgs { - public ActionType? Action; + public EntityUid? Action; } diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index ba007f35aa..b67c1bd5b9 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -1,17 +1,17 @@ using System.IO; using System.Linq; using Content.Shared.Actions; -using Content.Shared.Actions.ActionTypes; using JetBrains.Annotations; -using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.ContentPack; using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; +using Robust.Shared.Player; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -20,37 +20,98 @@ namespace Content.Client.Actions [UsedImplicitly] public sealed class ActionsSystem : SharedActionsSystem { - public delegate void OnActionReplaced(ActionType existing, ActionType action); + public delegate void OnActionReplaced(EntityUid actionId); [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IResourceManager _resources = default!; [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; - public event Action? ActionAdded; - public event Action? ActionRemoved; - public event OnActionReplaced? ActionReplaced; + public event Action? OnActionAdded; + public event Action? OnActionRemoved; public event Action? ActionsUpdated; public event Action? LinkActions; public event Action? UnlinkActions; public event Action? ClearAssignments; public event Action>? AssignSlot; - public ActionsComponent? PlayerActions { get; private set; } + private readonly List _removed = new(); + private readonly List<(EntityUid, BaseActionComponent?)> _added = new(); public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(HandleComponentState); + + SubscribeLocalEvent(OnInstantHandleState); + SubscribeLocalEvent(OnEntityTargetHandleState); + SubscribeLocalEvent(OnWorldTargetHandleState); + } + + private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args) + { + if (args.Current is not InstantActionComponentState state) + return; + + BaseHandleState(uid, component, state); } - public override void Dirty(ActionType action) + private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args) { + if (args.Current is not EntityTargetActionComponentState state) + return; + + component.Whitelist = state.Whitelist; + component.CanTargetSelf = state.CanTargetSelf; + BaseHandleState(uid, component, state); + } + + private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args) + { + if (args.Current is not WorldTargetActionComponentState state) + return; + + BaseHandleState(uid, component, state); + } + + private void BaseHandleState(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent + { + component.Icon = state.Icon; + component.IconOn = state.IconOn; + component.IconColor = state.IconColor; + component.Keywords = new HashSet(state.Keywords); + component.Enabled = state.Enabled; + component.Toggled = state.Toggled; + component.Cooldown = state.Cooldown; + component.UseDelay = state.UseDelay; + component.Charges = state.Charges; + component.Container = EnsureEntity(state.Container, uid); + component.EntityIcon = EnsureEntity(state.EntityIcon, uid); + component.CheckCanInteract = state.CheckCanInteract; + component.ClientExclusive = state.ClientExclusive; + component.Priority = state.Priority; + component.AttachedEntity = EnsureEntity(state.AttachedEntity, uid); + component.RaiseOnUser = state.RaiseOnUser; + component.AutoPopulate = state.AutoPopulate; + component.Temporary = state.Temporary; + component.ItemIconStyle = state.ItemIconStyle; + component.Sound = state.Sound; + + if (_playerManager.LocalPlayer?.ControlledEntity == component.AttachedEntity) + ActionsUpdated?.Invoke(); + } + + protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null) + { + if (!ResolveActionData(actionId, ref action)) + return; + + base.UpdateAction(actionId, action); if (_playerManager.LocalPlayer?.ControlledEntity != action.AttachedEntity) return; - base.Dirty(action); ActionsUpdated?.Invoke(); } @@ -59,124 +120,107 @@ private void HandleComponentState(EntityUid uid, ActionsComponent component, ref if (args.Current is not ActionsComponentState state) return; - state.SortedActions ??= new SortedSet(state.Actions); - var serverActions = state.SortedActions; - var removed = new List(); - - foreach (var act in component.Actions.ToList()) + _added.Clear(); + _removed.Clear(); + var stateEnts = EnsureEntitySet(state.Actions, uid); + foreach (var act in component.Actions) { - if (act.ClientExclusive) - continue; - - if (!serverActions.TryGetValue(act, out var serverAct)) - { - component.Actions.Remove(act); - if (act.AutoRemove) - removed.Add(act); - - continue; - } - - act.CopyFrom(serverAct); + if (!stateEnts.Contains(act) && !IsClientSide(act)) + _removed.Add(act); } + component.Actions.ExceptWith(_removed); - var added = new List(); - - // Anything that remains is a new action - foreach (var newAct in serverActions) + foreach (var actionId in stateEnts) { - if (component.Actions.Contains(newAct)) + if (!actionId.IsValid()) + continue; + + if (!component.Actions.Add(actionId)) continue; - // We create a new action, not just sorting a reference to the state's action. - var action = (ActionType) newAct.Clone(); - component.Actions.Add(action); - added.Add(action); + TryGetActionData(actionId, out var action); + _added.Add((actionId, action)); } if (_playerManager.LocalPlayer?.ControlledEntity != uid) return; - foreach (var action in removed) + foreach (var action in _removed) { - ActionRemoved?.Invoke(action); + OnActionRemoved?.Invoke(action); } - foreach (var action in added) + _added.Sort(ActionComparer); + + foreach (var action in _added) { - ActionAdded?.Invoke(action); + OnActionAdded?.Invoke(action.Item1); } ActionsUpdated?.Invoke(); } - protected override void AddActionInternal(ActionsComponent comp, ActionType action) + public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b) { - // Sometimes the client receives actions from the server, before predicting that newly added components will add - // their own shared actions. Just in case those systems ever decided to directly access action properties (e.g., - // action.Toggled), we will remove duplicates: - if (comp.Actions.TryGetValue(action, out var existing)) - { - comp.Actions.Remove(existing); - ActionReplaced?.Invoke(existing, action); - } - - comp.Actions.Add(action); + var priorityA = a.Item2?.Priority ?? 0; + var priorityB = b.Item2?.Priority ?? 0; + if (priorityA != priorityB) + return priorityA - priorityB; + + priorityA = a.Item2?.Container?.Id ?? 0; + priorityB = b.Item2?.Container?.Id ?? 0; + return priorityA - priorityB; } - public override void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true) + protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp, + BaseActionComponent action) { - if (!Resolve(uid, ref comp, false)) + if (_playerManager.LocalPlayer?.ControlledEntity != performer) return; - dirty &= !action.ClientExclusive; - base.AddAction(uid, action, provider, comp, dirty); - - if (uid == _playerManager.LocalPlayer?.ControlledEntity) - ActionAdded?.Invoke(action); + OnActionAdded?.Invoke(actionId); } - public override void RemoveAction(EntityUid uid, ActionType action, ActionsComponent? comp = null, bool dirty = true) + protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action) { - if (GameTiming.ApplyingState && !action.ClientExclusive) + if (_playerManager.LocalPlayer?.ControlledEntity != performer) return; - if (!Resolve(uid, ref comp, false)) - return; + OnActionRemoved?.Invoke(actionId); + } - dirty &= !action.ClientExclusive; - base.RemoveAction(uid, action, comp, dirty); + public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions() + { + if (_playerManager.LocalPlayer?.ControlledEntity is not { } user) + return Enumerable.Empty<(EntityUid, BaseActionComponent)>(); - if (action.AutoRemove && uid == _playerManager.LocalPlayer?.ControlledEntity) - ActionRemoved?.Invoke(action); + return GetActions(user); } - private void OnPlayerAttached(EntityUid uid, ActionsComponent component, PlayerAttachedEvent args) + private void OnPlayerAttached(EntityUid uid, ActionsComponent component, LocalPlayerAttachedEvent args) { LinkAllActions(component); } - private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent? args = null) + private void OnPlayerDetached(EntityUid uid, ActionsComponent component, LocalPlayerDetachedEvent? args = null) { UnlinkAllActions(); } public void UnlinkAllActions() { - PlayerActions = null; UnlinkActions?.Invoke(); } public void LinkAllActions(ActionsComponent? actions = null) { - var player = _playerManager.LocalPlayer?.ControlledEntity; - if (player == null || !Resolve(player.Value, ref actions, false)) + if (_playerManager.LocalPlayer?.ControlledEntity is not { } user || + !Resolve(user, ref actions, false)) { return; } LinkActions?.Invoke(actions); - PlayerActions = actions; } public override void Shutdown() @@ -185,61 +229,37 @@ public override void Shutdown() CommandBinds.Unregister(); } - public void TriggerAction(ActionType? action) + public void TriggerAction(EntityUid actionId, BaseActionComponent action) { - if (PlayerActions == null || action == null || _playerManager.LocalPlayer?.ControlledEntity is not { Valid: true } user) - return; - - if (action.Provider != null && Deleted(action.Provider)) - return; - - if (action is not InstantAction instantAction) + if (_playerManager.LocalPlayer?.ControlledEntity is not { } user || + !TryComp(user, out ActionsComponent? actions)) { return; } + if (action is not InstantActionComponent instantAction) + return; + if (action.ClientExclusive) { if (instantAction.Event != null) instantAction.Event.Performer = user; - PerformAction(user, PlayerActions, instantAction, instantAction.Event, GameTiming.CurTime); + PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } else { - var request = new RequestPerformActionEvent(instantAction); + var request = new RequestPerformActionEvent(GetNetEntity(actionId)); EntityManager.RaisePredictiveEvent(request); } } - /*public void SaveActionAssignments(string path) - { - - // Currently only tested with temporary innate actions (i.e., mapping actions). No guarantee it works with - // other actions. If its meant to be used for full game state saving/loading, the entity that provides - // actions needs to keep the same uid. - - var sequence = new SequenceDataNode(); - - foreach (var (action, assigns) in Assignments.Assignments) - { - var slot = new MappingDataNode(); - slot.Add("action", _serializationManager.WriteValue(action)); - slot.Add("assignments", _serializationManager.WriteValue(assigns)); - sequence.Add(slot); - } - - using var writer = _resourceManager.UserData.OpenWriteText(new ResourcePath(path).ToRootedPath()); - var stream = new YamlStream { new(sequence.ToSequenceNode()) }; - stream.Save(new YamlMappingFix(new Emitter(writer)), false); - }*/ - /// /// Load actions and their toolbar assignments from a file. /// public void LoadActionAssignments(string path, bool userData) { - if (PlayerActions == null) + if (_playerManager.LocalPlayer?.ControlledEntity is not { } user) return; var file = new ResPath(path).ToRootedPath(); @@ -265,17 +285,13 @@ public void LoadActionAssignments(string path, bool userData) if (!map.TryGet("action", out var actionNode)) continue; - var action = _serialization.Read(actionNode, notNullableOverride: true); + var action = _serialization.Read(actionNode, notNullableOverride: true); + var actionId = Spawn(null); + AddComp(actionId, action); + AddActionDirect(user, actionId); - if (PlayerActions.Actions.TryGetValue(action, out var existingAction)) - { - existingAction.CopyFrom(action); - action = existingAction; - } - else - { - PlayerActions.Actions.Add(action); - } + if (map.TryGet("name", out var nameNode)) + _metaData.SetEntityName(actionId, nameNode.Value); if (!map.TryGet("assignments", out var assignmentNode)) continue; @@ -284,7 +300,7 @@ public void LoadActionAssignments(string path, bool userData) foreach (var index in nodeAssignments) { - var assignment = new SlotAssignment(index.Hotbar, index.Slot, action); + var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId); assignments.Add(assignment); } } @@ -292,6 +308,6 @@ public void LoadActionAssignments(string path, bool userData) AssignSlot?.Invoke(assignments); } - public record struct SlotAssignment(byte Hotbar, byte Slot, ActionType Action); + public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId); } } diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index 319c682299..f48350d772 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -1,9 +1,5 @@ -using System; -using Content.Client.Stylesheets; -using Content.Shared.Actions; -using Content.Shared.Actions.ActionTypes; +using Content.Client.Stylesheets; using Robust.Client.UserInterface.Controls; -using Robust.Shared.IoC; using Robust.Shared.Timing; using Robust.Shared.Utility; using static Robust.Client.UserInterface.Controls.BoxContainer; diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index 8afee7f366..c21ba2e32c 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -35,20 +35,21 @@ protected override void Draw(in OverlayDrawArgs args) foreach (var playerInfo in _system.PlayerList) { + var entity = _entityManager.GetEntity(playerInfo.NetEntity); + // Otherwise the entity can not exist yet - if (!_entityManager.EntityExists(playerInfo.EntityUid)) + if (entity == null || !_entityManager.EntityExists(entity)) { continue; } - var entity = playerInfo.EntityUid.Value; // if not on the same map, continue - if (_entityManager.GetComponent(entity).MapID != _eyeManager.CurrentMap) + if (_entityManager.GetComponent(entity.Value).MapID != _eyeManager.CurrentMap) { continue; } - var aabb = _entityLookup.GetWorldAABB(entity); + var aabb = _entityLookup.GetWorldAABB(entity.Value); // if not on screen, continue if (!aabb.Intersects(in viewport)) diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs index 66c8b8a063..1a1366c6f2 100644 --- a/Content.Client/Administration/Managers/ClientAdminManager.cs +++ b/Content.Client/Administration/Managers/ClientAdminManager.cs @@ -4,7 +4,7 @@ using Robust.Client.Player; using Robust.Shared.ContentPack; using Robust.Shared.Network; -using Robust.Shared.Players; +using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Client.Administration.Managers @@ -130,5 +130,13 @@ void IPostInjectInit.PostInject() return null; } + + public AdminData? GetAdminData(bool includeDeAdmin = false) + { + if (_player.LocalPlayer is { Session: { } session }) + return GetAdminData(session, includeDeAdmin); + + return null; + } } } diff --git a/Content.Client/Administration/Managers/IClientAdminManager.cs b/Content.Client/Administration/Managers/IClientAdminManager.cs index 46e3a01537..b4b5b48b81 100644 --- a/Content.Client/Administration/Managers/IClientAdminManager.cs +++ b/Content.Client/Administration/Managers/IClientAdminManager.cs @@ -1,5 +1,4 @@ -using System; -using Content.Shared.Administration; +using Content.Shared.Administration; namespace Content.Client.Administration.Managers { @@ -13,6 +12,15 @@ public interface IClientAdminManager /// event Action AdminStatusUpdated; + /// + /// Gets the admin data for the client, if they are an admin. + /// + /// + /// Whether to return admin data for admins that are current de-adminned. + /// + /// if the player is not an admin. + AdminData? GetAdminData(bool includeDeAdmin = false); + /// /// Checks whether the local player is an admin. /// @@ -52,5 +60,17 @@ public interface IClientAdminManager bool CanAdminMenu(); void Initialize(); + + /// + /// Checks if the client is an admin. + /// + /// + /// Whether to return admin data for admins that are current de-adminned. + /// + /// true if the player is an admin, false otherwise. + bool IsAdmin(bool includeDeAdmin = false) + { + return GetAdminData(includeDeAdmin) != null; + } } } diff --git a/Content.Client/Administration/Systems/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs index e37b51af52..e0f84bc4f0 100644 --- a/Content.Client/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs @@ -24,12 +24,13 @@ private void AddAdminVerbs(GetVerbsEvent args) // View variables verbs if (_clientConGroupController.CanViewVar()) { - Verb verb = new(); - verb.Category = VerbCategory.Debug; - verb.Text = "View Variables"; - verb.Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")); - verb.Act = () => _clientConsoleHost.ExecuteCommand($"vv {args.Target}"); - verb.ClientExclusive = true; // opening VV window is client-side. Don't ask server to run this verb. + var verb = new VvVerb() + { + Text = Loc.GetString("view-variables"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")), + Act = () => _clientConsoleHost.ExecuteCommand($"vv {GetNetEntity(args.Target)}"), + ClientExclusive = true // opening VV window is client-side. Don't ask server to run this verb. + }; args.Verbs.Add(verb); } } diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml index 49eb9c0de6..311d67b826 100644 --- a/Content.Client/Administration/UI/AdminMenuWindow.xaml +++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml @@ -5,13 +5,15 @@ xmlns:atmosTab="clr-namespace:Content.Client.Administration.UI.Tabs.AtmosTab" xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs" xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab" - xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"> + xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab" + xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"> + diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs index d4dfcc2042..c3ea67a3ed 100644 --- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs +++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs @@ -12,7 +12,7 @@ public sealed partial class AdminMenuWindow : DefaultWindow public AdminMenuWindow() { - MinSize = new Vector2(500, 250); + MinSize = new Vector2(650, 250); Title = Loc.GetString("admin-menu-title"); RobustXamlLoader.Load(this); MasterTabContainer.SetTabTitle(0, Loc.GetString("admin-menu-admin-tab")); @@ -20,8 +20,9 @@ public AdminMenuWindow() MasterTabContainer.SetTabTitle(2, Loc.GetString("admin-menu-atmos-tab")); MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab")); MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab")); - MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-players-tab")); - MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-objects-tab")); + MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab")); + MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab")); + MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab")); } protected override void Dispose(bool disposing) diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml index eac7e37a86..311829e8b2 100644 --- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml +++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml @@ -1,13 +1,9 @@ - - - - + + + + + + - - - + + diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs index d8d0a8c0ab..3b3d90293c 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -1,6 +1,5 @@ using System.Linq; using System.Text; -using Content.Client.Stylesheets; using Content.Shared.Lathe; using Content.Shared.Materials; using Content.Shared.Research.Prototypes; @@ -21,12 +20,15 @@ public sealed partial class LatheMenu : DefaultWindow private readonly SpriteSystem _spriteSystem; private readonly LatheSystem _lathe; - public event Action? OnQueueButtonPressed; - public event Action? OnMaterialsEjectionButtonPressed; public event Action? OnServerListButtonPressed; public event Action? RecipeQueueAction; + public event Action? OnEjectPressed; + public List> Recipes = new(); - public List Recipes = new(); + /// + /// Default volume for a sheet if the material's entity prototype has no material composition. + /// + private const int DEFAULT_SHEET_VOLUME = 100; public LatheMenu(LatheBoundUserInterface owner) { @@ -47,8 +49,6 @@ public LatheMenu(LatheBoundUserInterface owner) PopulateRecipes(owner.Owner); }; - QueueButton.OnPressed += a => OnQueueButtonPressed?.Invoke(a); - MaterialsEjectionButton.OnPressed += a => OnMaterialsEjectionButtonPressed?.Invoke(a); ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a); if (_entityManager.TryGetComponent(owner.Owner, out var latheComponent)) @@ -56,13 +56,6 @@ public LatheMenu(LatheBoundUserInterface owner) if (!latheComponent.DynamicRecipes.Any()) { ServerListButton.Visible = false; - QueueButton.RemoveStyleClass(StyleBase.ButtonOpenRight); - //QueueButton.AddStyleClass(StyleBase.ButtonSquare); - } - - if (MaterialsEjectionButton != null && !latheComponent.CanEjectStoredMaterials) - { - MaterialsEjectionButton.Dispose(); } } } @@ -72,37 +65,43 @@ public void PopulateMaterials(EntityUid lathe) if (!_entityManager.TryGetComponent(lathe, out var materials)) return; - Materials.Clear(); + MaterialsList.DisposeAllChildren(); - foreach (var (id, amount) in materials.Storage) + foreach (var (materialId, volume) in materials.Storage) { - if (amount <= 0) + if (volume <= 0) continue; - if (!_prototypeManager.TryIndex(id, out MaterialPrototype? material)) + if (!_prototypeManager.TryIndex(materialId, out MaterialPrototype? material)) continue; + var sheetVolume = SheetVolume(material); + var sheets = (float) volume / sheetVolume; + var maxEjectableSheets = (int) MathF.Floor(sheets); + + var unit = Loc.GetString(material.Unit); + var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); var name = Loc.GetString(material.Name); - var mat = Loc.GetString("lathe-menu-material-display", - ("material", name), ("amount", amount)); + var mat = Loc.GetString("lathe-menu-material-display", ("material", name), ("amount", amountText)); - Materials.AddItem(mat, _spriteSystem.Frame0(material.Icon), false); - } + var row = new LatheMaterialEjector(materialId, OnEjectPressed, sheetVolume, maxEjectableSheets) + { + Icon = { Texture = _spriteSystem.Frame0(material.Icon) }, + ProductName = { Text = mat } + }; - if (MaterialsEjectionButton != null) - { - MaterialsEjectionButton.Disabled = Materials.Count == 0; + MaterialsList.AddChild(row); } - if (Materials.Count == 0) + if (MaterialsList.ChildCount == 0) { var noMaterialsMsg = Loc.GetString("lathe-menu-no-materials-message"); - Materials.AddItem(noMaterialsMsg, null, false); + var noItemRow = new Label(); + noItemRow.Text = noMaterialsMsg; + noItemRow.Align = Label.AlignMode.Center; + MaterialsList.AddChild(noItemRow); } - - PopulateRecipes(lathe); } - /// /// Populates the list of all the recipes /// @@ -115,7 +114,7 @@ public void PopulateRecipes(EntityUid lathe) var recipesToShow = new List(); foreach (var recipe in Recipes) { - if (!_prototypeManager.TryIndex(recipe, out var proto)) + if (!_prototypeManager.TryIndex(recipe, out var proto)) continue; if (SearchBar.Text.Trim().Length != 0) @@ -148,8 +147,14 @@ public void PopulateRecipes(EntityUid lathe) sb.Append('\n'); var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier); - - sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("amount", adjustedAmount), ("material", Loc.GetString(proto.Name)))); + var sheetVolume = SheetVolume(proto); + + var unit = Loc.GetString(proto.Unit); + // rounded in locale not here + var sheets = adjustedAmount / (float) sheetVolume; + var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); + var name = Loc.GetString(proto.Name); + sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText))); } var icon = prototype.Icon == null @@ -167,4 +172,46 @@ public void PopulateRecipes(EntityUid lathe) RecipeList.AddChild(control); } } + + /// + /// Populates the build queue list with all queued items + /// + /// + public void PopulateQueueList(List queue) + { + QueueList.Clear(); + var idx = 1; + foreach (var recipe in queue) + { + var icon = recipe.Icon == null + ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default + : _spriteSystem.Frame0(recipe.Icon); + QueueList.AddItem($"{idx}. {recipe.Name}", icon); + idx++; + } + } + + public void SetQueueInfo(LatheRecipePrototype? recipe) + { + FabricatingContainer.Visible = recipe != null; + if (recipe == null) + return; + Icon.Texture = recipe.Icon == null + ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default + : _spriteSystem.Frame0(recipe.Icon); + NameLabel.Text = $"{recipe.Name}"; + } + + private int SheetVolume(MaterialPrototype material) + { + if (material.StackEntity == null) + return DEFAULT_SHEET_VOLUME; + + var proto = _prototypeManager.Index(material.StackEntity); + + if (!proto.TryGetComponent(out var composition)) + return DEFAULT_SHEET_VOLUME; + + return composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == material.ID).Value; + } } diff --git a/Content.Client/Lathe/UI/LatheQueueMenu.xaml b/Content.Client/Lathe/UI/LatheQueueMenu.xaml deleted file mode 100644 index a92a0a18a1..0000000000 --- a/Content.Client/Lathe/UI/LatheQueueMenu.xaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs b/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs deleted file mode 100644 index eaf03c2fff..0000000000 --- a/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Shared.Research.Prototypes; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.UserInterface.CustomControls; -using Robust.Client.UserInterface.XAML; - -namespace Content.Client.Lathe.UI -{ - [GenerateTypedNameReferences] - public sealed partial class LatheQueueMenu : DefaultWindow - { - [Dependency] private readonly IEntityManager _entityManager = default!; - private readonly SpriteSystem _spriteSystem; - - public LatheQueueMenu() - { - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - _spriteSystem = _entityManager.EntitySysManager.GetEntitySystem(); - - SetInfo(null); - } - - public void SetInfo(LatheRecipePrototype? recipe) - { - if (recipe != null) - { - Icon.Texture = recipe.Icon == null - ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default - : _spriteSystem.Frame0(recipe.Icon); - NameLabel.Text = recipe.Name; - Description.Text = recipe.Description; - } - else - { - Icon.Texture = Texture.Transparent; - NameLabel.Text = string.Empty; - Description.Text = Loc.GetString("lathe-queue-menu-not-producing-text"); - } - } - - public void PopulateList(List queue) - { - QueueList.Clear(); - var idx = 1; - foreach (var recipe in queue) - { - var icon =recipe.Icon == null - ? _spriteSystem.GetPrototypeIcon(recipe.Result).Default - : _spriteSystem.Frame0(recipe.Icon); - QueueList.AddItem($"{idx}. {recipe.Name}", icon); - idx++; - } - } - } -} diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml b/Content.Client/Lathe/UI/RecipeControl.xaml index cacbf84ff7..2e02c8a614 100644 --- a/Content.Client/Lathe/UI/RecipeControl.xaml +++ b/Content.Client/Lathe/UI/RecipeControl.xaml @@ -2,7 +2,6 @@