diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8f37748..b43ddf6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,24 @@ version: 2 updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "github-actions" + directory: "/" schedule: - interval: "daily" \ No newline at end of file + interval: "daily" + groups: + actions-deps: + patterns: + - "*" + target-branch: "develop" + + + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" + open-pull-requests-limit: 1 + groups: + all-deps: + patterns: + - "*" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31c58a8..a0cd9f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Create NetFramework Release run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net48 --output ./releases/net48 - name: Create Net Core Release - run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net8.0 --output ./releases/net8.0 + run: dotnet publish .\src\ModVerify.CliApp\ModVerify.CliApp.csproj --configuration Release -f net9.0 --output ./releases/net9.0 - name: Upload a Build Artifact uses: actions/upload-artifact@v4 with: @@ -53,8 +53,8 @@ jobs: path: ./releases - name: Create NET Core .zip # Change into the artifacts directory to avoid including the directory itself in the zip archive - working-directory: ./releases/net8.0 - run: zip -r ../ModVerify-Net8.zip . + working-directory: ./releases/net9.0 + run: zip -r ../ModVerify-Net9.zip . - uses: dotnet/nbgv@v0.4.2 id: nbgv - name: Create GitHub release @@ -66,4 +66,4 @@ jobs: generate_release_notes: true files: | ./releases/net48/ModVerify.exe - ./releases/ModVerify-Net8.zip \ No newline at end of file + ./releases/ModVerify-Net9.zip \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20a36cc..f6c4d43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,6 @@ jobs: submodules: recursive - uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Build & Test in Release Mode run: dotnet test --configuration Release \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 25d6b29..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "PetroglyphTools"] - path = PetroglyphTools - url = https://github.com/AlamoEngine-Tools/PetroglyphTools diff --git a/Directory.Build.props b/Directory.Build.props index 1d41c28..49ca26f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,52 @@  - - - - all - 3.6.139 - - + + + + $(MSBuildThisFileDirectory) + true + true + $(RepoRootPath) + $(RepoRootPath)bin\Packages\$(Configuration)\ + + + + ModVerify + Alamo Engine Tools and Contributors + Copyright © 2025 Alamo Engine Tools and contributors. All rights reserved. + https://github.com/AlamoEngine-Tools/ModVerify + $(RepoRootPath)LICENSE + MIT + https://github.com/AlamoEngine-Tools/ModVerify + git + Alamo Engine Tools + README.md + aet.png + + + + latest + disable + enable + True + latest + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + 3.7.115 + + + + + + + + + \ No newline at end of file diff --git a/ModVerify.sln b/ModVerify.sln index baee725..d09a64e 100644 --- a/ModVerify.sln +++ b/ModVerify.sln @@ -5,12 +5,6 @@ VisualStudioVersion = 17.11.34909.67 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PetroglyphTools", "PetroglyphTools", "{15F8B753-814A-406E-9147-EB048DADAC96}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.Commons", "PetroglyphTools\PG.Commons\PG.Commons\PG.Commons.csproj", "{1A9E1B15-DD77-47E3-893E-AFADF982CEC6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.StarWarsGame.Files.DAT", "PetroglyphTools\PG.StarWarsGame.Files.DAT\PG.StarWarsGame.Files.DAT\PG.StarWarsGame.Files.DAT.csproj", "{4630F85C-D1C4-4454-9126-BE13F6901E0B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.StarWarsGame.Files.MEG", "PetroglyphTools\PG.StarWarsGame.Files.MEG\PG.StarWarsGame.Files.MEG\PG.StarWarsGame.Files.MEG.csproj", "{885291F4-E5E8-45B2-B0B4-40B2910228A3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModVerify", "src\ModVerify\ModVerify.csproj", "{22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModVerify.CliApp", "src\ModVerify.CliApp\ModVerify.CliApp.csproj", "{84479931-A329-4113-9BE5-90B71E5486E6}" @@ -29,18 +23,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A9E1B15-DD77-47E3-893E-AFADF982CEC6}.Release|Any CPU.Build.0 = Release|Any CPU - {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4630F85C-D1C4-4454-9126-BE13F6901E0B}.Release|Any CPU.Build.0 = Release|Any CPU - {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {885291F4-E5E8-45B2-B0B4-40B2910228A3}.Release|Any CPU.Build.0 = Release|Any CPU {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Debug|Any CPU.Build.0 = Debug|Any CPU {22ED0E2C-FF3B-40EB-9CE2-DCDE65CDF31B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -70,9 +52,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {1A9E1B15-DD77-47E3-893E-AFADF982CEC6} = {15F8B753-814A-406E-9147-EB048DADAC96} - {4630F85C-D1C4-4454-9126-BE13F6901E0B} = {15F8B753-814A-406E-9147-EB048DADAC96} - {885291F4-E5E8-45B2-B0B4-40B2910228A3} = {15F8B753-814A-406E-9147-EB048DADAC96} {92F2A0C8-61B6-424B-99D5-7898CDBA7CA6} = {15F8B753-814A-406E-9147-EB048DADAC96} {DF76A383-C94E-4D03-A07C-22D61ED37059} = {15F8B753-814A-406E-9147-EB048DADAC96} {418C68FA-531B-432E-8459-6433181C8AD3} = {15F8B753-814A-406E-9147-EB048DADAC96} diff --git a/PetroglyphTools b/PetroglyphTools deleted file mode 160000 index 0347671..0000000 --- a/PetroglyphTools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0347671ec7c89d2a79b167e2110df3cd495f71aa diff --git a/README.md b/README.md index 9ef3e13..70b4f91 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # ModVerify: A Mod Verification Tool -ModVerify is a command-line tool designed to verify mods for the game Star Wars: Empire at War and its expansion Forces of Corruption. +ModVerify is a command-line tool designed to analyze +mods for the game Star Wars: Empire at War and its expansion Forces of Corruption +for common errors in XML and other game files. ## Table of Contents @@ -8,6 +10,9 @@ ModVerify is a command-line tool designed to verify mods for the game Star Wars: - [Usage](#usage) - [Options](#options) - [Available Checks](#available-checks) +- [Creating a new Baseline](#creating-a-new-baseline) + +--- ## Installation @@ -18,15 +23,18 @@ Download the latest release from the [releases page](https://github.com/AlamoEng You can place the files anywhere on your system, eg. your Desktop. There is no need to place it inside a mod's directory. -*Note: Both versions have the exact same feature set. They just target a different .NET runtime. Linux and CI/CD support is not fully tested yet. Current priority is on the Windows-only version.* +***Note**: Both versions have the exact same feature set. They just target a different .NET runtime. Linux and CI/CD support is not fully tested yet. Current priority is on the Windows-only version.* + +--- ## Usage Simply run the executable file `ModVerify.exe`. -When given no specific argument through the command line, the app will ask you which game or mod you want to verify. When the tool is done, it will write the verification results into new files next to the executable. +When given no specific argument through the command line, ModVerify will ask you which game or mod you want to verify. When ModVerify is done analyzing, it will write the verification results into new files next to the executable. -A `.JSON` file lists all found issues. Into seperate `.txt` files the same errors get grouped by a category of the finding. The text files may be easier to read, while the json file is more useful for 3rd party tool processing. +A `.JSON` file contains all identified issues. The additional `.txt` files contain the same errors but are grouped by the verifier that reported the issue. +The text files may be easier to read, while the JSON file is more useful for 3rd party tool processing. ## Options @@ -49,8 +57,6 @@ Specified the output path where analysis result shall be written to. ### `--baseline` Specifies a baseline file that shall be used to filter out known errors. You can download the [FoC baseline](focBaseline.json) which includes all errors produced by the vanilla game. -### `--createBaseline` -If you want to create your own baseline, add this option with a file path such as `myModBaseline.json`. ### Example This is an example run configuration that analyzes a specific mod, uses a the FoC basline and writes the output into a dedicated directory: @@ -59,19 +65,35 @@ This is an example run configuration that analyzes a specific mod, uses a the Fo ModVerify.exe --path "C:\My Games\FoC\Mods\MyMod" --output "C:\My Games\FoC\Mods\MyMod\verifyResults" --baseline focBaseline.json ``` +--- ## Available Checks The following verifiers are currently implemented: -### For SFX Events: -- Checks whether coded preset exists +### SFX Events: +- Checks whether coded presets exist - Checks the referenced samples for validity (bit rate, sample size, channels, etc.) - Duplicates +### GUIDialogs +- Checks the referenced textures exist -### For GameObjects +### GameObjects - Checks the referenced models for validity (textures, particles and shaders) - Duplicates +### Engine +- Performs assertion checks the Debug builds of the game are also doing (not complete) +- Sports XML errors and unexpected values + +--- + +## Creating a new Baseline +If you want to create your own baseline use the `createBaseline` option. + +### Example +```bash +ModVerify.exe createBaseline -o myBaseline.json --path "C:\My Games\FoC\Mods\MyMod" +``` diff --git a/focBaseline.json b/focBaseline.json index 2b429dc..ad63da6 100644 --- a/focBaseline.json +++ b/focBaseline.json @@ -1,1632 +1,1827 @@ { + "version": "2.0", + "minSeverity": "Information", "errors": [ { "id": "XML04", - "verifier": "AET.ModVerify.Verifiers.XmlParseErrorCollector", - "message": "Expected integer but got \u002780, 20\u0027 at DATA\\XML\\SFXEVENTSWEAPONS.XML at line: 90", - "severity": 0, - "assets": [ + "verifiers": [ + "XMLError" + ], + "message": "Expected double but got value \u002737\u0060\u0027. File=\u0027DATA\\XML\\COMMANDBARCOMPONENTS.XML #11571\u0027", + "severity": "Warning", + "context": [ + "DATA\\XML\\COMMANDBARCOMPONENTS.XML", + "Size", + "parentName=\u0027bm_text_steal\u0027" + ], + "asset": "Size" + }, + { + "id": "XML04", + "verifiers": [ + "XMLError" + ], + "message": "Expected integer but got \u002780, 20\u0027. File=\u0027DATA\\XML\\SFXEVENTSWEAPONS.XML #90\u0027", + "severity": "Warning", + "context": [ "DATA\\XML\\SFXEVENTSWEAPONS.XML", "Probability", "parentName=\u0027Unit_TIE_Fighter_Fire\u0027" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_VCH.ALO references missing proxy particle: P_heat_small01.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_VCH.ALO", - "P_heat_small01.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_SCH.ALO references missing proxy particle: p_cold_tiny01.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_SCH.ALO", - "p_cold_tiny01.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO references missing proxy particle: p_smoke_small_thin2.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO", - "p_smoke_small_thin2.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO references missing proxy particle: p_explosion_smoke_small_thin5.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO", - "p_explosion_smoke_small_thin5.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027", - "severity": 0, - "assets": [ - "Cin_rv_XWingProp.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\ALTTEST.ALO references missing texture: Cin_DeathStar.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\ALTTEST.ALO", - "Cin_DeathStar.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO references missing texture: UB_girder_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO", - "UB_girder_B.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO references missing texture: NB_YsalamiriTree_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_MDU_CAGE.ALO", - "NB_YsalamiriTree_B.tga" - ] - }, - { - "id": "ALO04", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO references missing shader effect: Default.fx", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO", - "Default.fx" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO references missing proxy particle: P_mptl-2a_Die.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO", - "P_mptl-2a_Die.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027", - "severity": 0, - "assets": [ - "w_planet_volcanic.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027RV_nebulonb_D_death_00.ALO\u0027", - "severity": 0, - "assets": [ - "RV_nebulonb_D_death_00.ALO" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_STARS_LOW.ALO references missing proxy particle: Lensflare0.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_STARS_LOW.ALO", - "Lensflare0.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO references missing proxy particle: Lensflare0.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO", - "Lensflare0.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO references missing proxy particle: Lensflare0.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO", - "Lensflare0.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_STARS_CINE.ALO references missing proxy particle: Lensflare0.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_STARS_CINE.ALO", - "Lensflare0.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO references missing proxy particle: Lensflare0.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO", - "Lensflare0.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EI_MARAJADE.ALO references missing proxy particle: p_desert_ground_dust.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EI_MARAJADE.ALO", - "p_desert_ground_dust.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO references missing proxy particle: p_desert_ground_dust.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO", - "p_desert_ground_dust.alo" - ] - }, - { - "id": "ALO04", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO references missing shader effect: Default.fx", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO", - "Default.fx" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO references missing proxy particle: p_smoke_small_thin2.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO", - "p_smoke_small_thin2.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO references missing proxy particle: p_steam_small.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO", - "p_steam_small.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_prison_light.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO", - "p_prison_light.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_smoke_small_thin2.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO", - "p_smoke_small_thin2.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_PRISON.ALO references missing proxy particle: p_smoke_small_thin4.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_PRISON.ALO", - "p_smoke_small_thin4.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO references missing proxy particle: p_explosion_small_delay00.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO", - "p_explosion_small_delay00.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO references missing proxy particle: p_bomb_spin.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO", - "p_bomb_spin.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_GRENADE.ALO references missing texture: w_grenade.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_GRENADE.ALO", - "w_grenade.tga" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027", - "severity": 0, - "assets": [ - "p_splash_wake_lava.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EV_TIE_PHANTOM.ALO references missing texture: W_TE_Rock_f_02_b.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EV_TIE_PHANTOM.ALO", - "W_TE_Rock_f_02_b.tga" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO references missing proxy particle: p_hp_archammer-damage.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO", - "p_hp_archammer-damage.alo" - ] - }, - { - "id": "ALO04", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO references missing shader effect: Default.fx", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO", - "Default.fx" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\RV_BWING.ALO references missing proxy particle: pe_bwing_yellow.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\RV_BWING.ALO", - "pe_bwing_yellow.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_F9TZTRANSPORT.ALO references missing texture: W_TE_Rock_f_02_b.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_F9TZTRANSPORT.ALO", - "W_TE_Rock_f_02_b.tga" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO references missing proxy particle: p_ewok_drag_dirt.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO", - "p_ewok_drag_dirt.alo" - ] - }, - { - "id": "ALO04", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO references missing shader effect: Default.fx", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO", - "Default.fx" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_VENGEANCE.ALO references missing texture: W_TE_Rock_f_02_b.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_VENGEANCE.ALO", - "W_TE_Rock_f_02_b.tga" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO", - "p_uwstation_death.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO", - "p_uwstation_death.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO", - "p_uwstation_death.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO", - "p_uwstation_death.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO references missing proxy particle: p_uwstation_death.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO", - "p_uwstation_death.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO references missing proxy particle: lookat.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO", - "lookat.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO references missing proxy particle: p_ssd_debris.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO", - "p_ssd_debris.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO references missing proxy particle: lookat.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO", - "lookat.alo" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO references missing proxy particle: p_desert_ground_dust.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO", - "p_desert_ground_dust.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\NB_YSALAMIRI_TREE.ALO references missing texture: NB_YsalamiriTree_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\NB_YSALAMIRI_TREE.ALO", - "NB_YsalamiriTree_B.tga" - ] - }, - { - "id": "ALO03", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UI_IG88.ALO references missing proxy particle: p_desert_ground_dust.alo", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UI_IG88.ALO", - "p_desert_ground_dust.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\UV_KRAYTCLASSDESTROYER_TYBER.ALO references missing texture: W_TE_Rock_f_02_b.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\UV_KRAYTCLASSDESTROYER_TYBER.ALO", - "W_TE_Rock_f_02_b.tga" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027", - "severity": 0, - "assets": [ - "W_SwampGasEmit.ALO" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_Bush_Swmp00.ALO\u0027", - "severity": 0, - "assets": [ - "W_Bush_Swmp00.ALO" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027", - "severity": 0, - "assets": [ - "W_Volcano_Rock02.ALO" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027", - "severity": 0, - "assets": [ - "W_Vol_Steam01.ALO" - ] + ], + "asset": "Probability" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\RB_HYPERVELOCITYGUN.ALO" + ], + "asset": "p_smoke_small_thin2" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Fire_Medium.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027", - "severity": 0, - "assets": [ - "W_Kamino_Reflect.ALO" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_EV_Stardestroyer_Warp.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_EV_Stardestroyer_Warp.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027", - "severity": 0, - "assets": [ - "W_AllShaders.ALO" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Rbel_GreyGroup.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_03_STATION_D.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UB_03_STATION_D.ALO" + ], + "asset": "p_uwstation_death" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Biker_Row.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027", - "severity": 0, - "assets": [ - "W_droid_steam.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Trooper_Row.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027", - "severity": 0, - "assets": [ - "Cin_bridge.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_rv_XWingProp.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_rv_XWingProp.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DeathStar_High.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027p_splash_wake_lava.alo\u0027", + "severity": "Error", + "context": [], + "asset": "p_splash_wake_lava.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CINE_EV_StarDestroyer.ALO\u0027", - "severity": 0, - "assets": [ - "CINE_EV_StarDestroyer.ALO" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_DStar_LeverPanel.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_smoke_small_thin2" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_EI_Vader.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027", - "severity": 0, - "assets": [ - "Cin_Coruscant.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Rbel_Soldier.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027", - "severity": 0, - "assets": [ - "Cin_Planet_Alderaan_High.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Rbel_NavyRow.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027MODELS\u0027", + "severity": "Error", + "context": [], + "asset": "MODELS" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_droid_steam.alo\u0027", + "severity": "Error", + "context": [], + "asset": "W_droid_steam.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027", - "severity": 0, - "assets": [ - "Cin_Planet_Hoth_High.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_EV_lambdaShuttle_150.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_EV_Stardestroyer_Warp.alo\u0027", - "severity": 0, - "assets": [ - "Cin_EV_Stardestroyer_Warp.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_DeathStar_Hangar.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027", - "severity": 0, - "assets": [ - "Cin_Officer.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Fire_Huge.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Fire_Huge.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_STARS_CINE.ALO" + ], + "asset": "Lensflare0" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_ewok_drag_dirt\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UI_EWOK_HANDLER.ALO" + ], + "asset": "p_ewok_drag_dirt" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_steam_small\u0027 not found for model \u0027DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\RB_HEAVYVEHICLEFACTORY.ALO" + ], + "asset": "p_steam_small" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_hp_archammer-damage\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\EV_ARCHAMMER.ALO" + ], + "asset": "p_hp_archammer-damage" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_02_STATION_D.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UB_02_STATION_D.ALO" + ], + "asset": "p_uwstation_death" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Rbel_grey.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_explosion_small_delay00\u0027 not found for model \u0027DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\EB_COMMANDCENTER.ALO" + ], + "asset": "p_explosion_small_delay00" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_REb_CelebCharacters.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_IG88.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UI_IG88.ALO" + ], + "asset": "p_desert_ground_dust" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027RV_nebulonb_D_death_00.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "RV_nebulonb_D_death_00.ALO" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027Cin_DStar_Dish_close.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DStar_Dish_close.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_DStar_LeverPanel.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DStar_LeverPanel.alo" - ] + "severity": "Error", + "context": [], + "asset": "Cin_DStar_Dish_close.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\UI_SABOTEUR.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UI_SABOTEUR.ALO" + ], + "asset": "p_desert_ground_dust" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_AllShaders.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_AllShaders.ALO" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027P_heat_small01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_VCH.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_VCH.ALO" + ], + "asset": "P_heat_small01" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_NavyTrooper_Row.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027Cin_DeathStar_Wall.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DeathStar_Wall.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DStar_protons.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027", - "severity": 0, - "assets": [ - "Cin_DStar_TurretLasers.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027", - "severity": 0, - "assets": [ - "Cin_EV_TieAdvanced.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "severity": "Error", + "context": [], + "asset": "Cin_DeathStar_Wall.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_01_STATION_D.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UB_01_STATION_D.ALO" + ], + "asset": "p_uwstation_death" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027Cin_ImperialCraft.alo\u0027", - "severity": 0, - "assets": [ - "Cin_ImperialCraft.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "severity": "Error", + "context": [], + "asset": "Cin_ImperialCraft.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_05_STATION_D.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UB_05_STATION_D.ALO" + ], + "asset": "p_uwstation_death" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_ssd_debris\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UV_ECLIPSE_UC_DC.ALO" + ], + "asset": "p_ssd_debris" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_SKIPRAY.ALO\u0027.", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UV_SKIPRAY.ALO" + ], + "asset": "Default.fx" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_STARS_CINE_LUA.ALO" + ], + "asset": "Lensflare0" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027Cin_Shuttle_Tyderium.alo\u0027", - "severity": 0, - "assets": [ - "Cin_Shuttle_Tyderium.alo" - ] + "severity": "Error", + "context": [], + "asset": "Cin_Shuttle_Tyderium.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027", - "severity": 0, - "assets": [ - "CIN_p_proton_torpedo.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027w_planet_volcanic.alo\u0027", + "severity": "Error", + "context": [], + "asset": "w_planet_volcanic.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UV_ECLIPSE_UC.ALO" + ], + "asset": "lookat" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027pe_bwing_yellow\u0027 not found for model \u0027DATA\\ART\\MODELS\\RV_BWING.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\RV_BWING.ALO" + ], + "asset": "pe_bwing_yellow" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_EV_TieAdvanced.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_EV_TieAdvanced.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_DeathStar_Hangar.alo\u0027", - "severity": 0, - "assets": [ - "CIN_DeathStar_Hangar.alo" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO", - "Cin_Reb_CelebHall_Wall.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_SITH_LEFTHALL.ALO", - "Cin_Reb_CelebHall_Wall_B.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO", - "Cin_Reb_CelebHall_Wall.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_SITH_CONSOLE.ALO", - "Cin_Reb_CelebHall_Wall_B.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_TILE.ALO references missing texture: Cin_Reb_CelebHall_Wall_B.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_TILE.ALO", - "Cin_Reb_CelebHall_Wall_B.tga" - ] - }, - { - "id": "ALO02", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "DATA\\ART\\MODELS\\W_TILE.ALO references missing texture: Cin_Reb_CelebHall_Wall.tga", - "severity": 0, - "assets": [ - "DATA\\ART\\MODELS\\W_TILE.ALO", - "Cin_Reb_CelebHall_Wall.tga" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_Officer.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_Officer.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027P_mptl-2a_Die\u0027 not found for model \u0027DATA\\ART\\MODELS\\RV_MPTL-2A.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\RV_MPTL-2A.ALO" + ], + "asset": "P_mptl-2a_Die" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO\u0027.", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UV_CRUSADERCLASSCORVETTE.ALO" + ], + "asset": "Default.fx" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Reb_CelebHall.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027lookat\u0027 not found for model \u0027DATA\\ART\\MODELS\\UV_ECLIPSE.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UV_ECLIPSE.ALO" + ], + "asset": "lookat" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027w_sith_arch.alo\u0027", - "severity": 0, - "assets": [ - "w_sith_arch.alo" - ] + "severity": "Error", + "context": [], + "asset": "w_sith_arch.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Trooper_Row.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Trooper_Row.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_DeathStar_High.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_DeathStar_High.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_prison_light\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_prison_light" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_SwampGasEmit.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_SwampGasEmit.ALO" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Biker_Row.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Biker_Row.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_DStar_protons.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_DStar_protons.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027CIN_Officer_Row.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Officer_Row.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_NavyTrooper_Row.alo\u0027", - "severity": 0, - "assets": [ - "CIN_NavyTrooper_Row.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Lambda_Mouth.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Lambda_Mouth.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Lambda_Head.alo" - ] + "severity": "Error", + "context": [], + "asset": "CIN_Officer_Row.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Reb_CelebHall.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Reb_CelebHall.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_REb_CelebCharacters.alo\u0027", - "severity": 0, - "assets": [ - "CIN_REb_CelebCharacters.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Rbel_NavyRow.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Rbel_NavyRow.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Rbel_Soldier.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027CIN_Rbel_Soldier_Group.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Rbel_Soldier_Group.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Rbel_grey.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Rbel_grey.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Rbel_GreyGroup.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Rbel_GreyGroup.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_EV_lambdaShuttle_150.alo\u0027", - "severity": 0, - "assets": [ - "Cin_EV_lambdaShuttle_150.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027Cin_EI_Vader.alo\u0027", - "severity": 0, - "assets": [ - "Cin_EI_Vader.alo" - ] - }, - { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "severity": "Error", + "context": [], + "asset": "CIN_Rbel_Soldier_Group.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\EI_MARAJADE.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\EI_MARAJADE.ALO" + ], + "asset": "p_desert_ground_dust" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], "message": "Unable to find .ALO file \u0027Cin_EI_Palpatine.alo\u0027", - "severity": 0, - "assets": [ - "Cin_EI_Palpatine.alo" - ] + "severity": "Error", + "context": [], + "asset": "Cin_EI_Palpatine.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO\u0027.", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\EV_TIE_LANCET.ALO" + ], + "asset": "Default.fx" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_Planet_Alderaan_High.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_Planet_Alderaan_High.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_HIGH.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_STARS_HIGH.ALO" + ], + "asset": "Lensflare0" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_bomb_spin\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_THERMAL_DETONATOR_EMPIRE.ALO" + ], + "asset": "p_bomb_spin" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_DStar_TurretLasers.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_DStar_TurretLasers.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_smoke_small_thin4\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_PRISON.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_PRISON.ALO" + ], + "asset": "p_smoke_small_thin4" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_Vol_Steam01.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_Vol_Steam01.ALO" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_desert_ground_dust\u0027 not found for model \u0027DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\RI_KYLEKATARN.ALO" + ], + "asset": "p_desert_ground_dust" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_Planet_Hoth_High.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_Planet_Hoth_High.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_LOW.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_STARS_LOW.ALO" + ], + "asset": "Lensflare0" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_uwstation_death\u0027 not found for model \u0027DATA\\ART\\MODELS\\UB_04_STATION_D.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\UB_04_STATION_D.ALO" + ], + "asset": "p_uwstation_death" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_Coruscant.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_Coruscant.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_smoke_small_thin2\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_MONCAL_BUILDING.ALO" + ], + "asset": "p_smoke_small_thin2" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_p_proton_torpedo.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_p_proton_torpedo.alo" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Fire_Huge.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Fire_Huge.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027Cin_bridge.alo\u0027", + "severity": "Error", + "context": [], + "asset": "Cin_bridge.alo" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_explosion_smoke_small_thin5\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_NOGHRI_HUT.ALO" + ], + "asset": "p_explosion_smoke_small_thin5" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_Kamino_Reflect.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_Kamino_Reflect.ALO" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Fire_Medium.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Fire_Medium.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CINE_EV_StarDestroyer.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "CINE_EV_StarDestroyer.ALO" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027p_cold_tiny01\u0027 not found for model \u0027DATA\\ART\\MODELS\\NB_SCH.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\NB_SCH.ALO" + ], + "asset": "p_cold_tiny01" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Proxy particle \u0027Lensflare0\u0027 not found for model \u0027DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO\u0027", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\W_STARS_MEDIUM.ALO" + ], + "asset": "Lensflare0" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_Volcano_Rock02.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_Volcano_Rock02.ALO" }, { - "id": "ALO00", - "verifier": "AET.ModVerify.Verifiers.ReferencedModelsVerifier", - "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027", - "severity": 0, - "assets": [ - "CIN_Probe_Droid.alo" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Lambda_Head.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Lambda_Head.alo" }, { - "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "C000_DST0102_ENG.WAV" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027W_Bush_Swmp00.ALO\u0027", + "severity": "Error", + "context": [], + "asset": "W_Bush_Swmp00.ALO" }, { - "id": "WAV03", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027WIND_GUST_1_STEREO.WAV\u0027 is not mono audio.", - "severity": 0, - "assets": [ - "WIND_GUST_1_STEREO.WAV" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Probe_Droid.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Probe_Droid.alo" }, { - "id": "WAV03", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027WIND_GUST_2_STEREO.WAV\u0027 is not mono audio.", - "severity": 0, - "assets": [ - "WIND_GUST_2_STEREO.WAV" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Unable to find .ALO file \u0027CIN_Lambda_Mouth.alo\u0027", + "severity": "Error", + "context": [], + "asset": "CIN_Lambda_Mouth.alo" }, { - "id": "WAV03", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027WIND_GUST_3_STEREO.WAV\u0027 is not mono audio.", - "severity": 0, - "assets": [ - "WIND_GUST_3_STEREO.WAV" - ] + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.ReferencedModelsVerifier", + "AET.ModVerify.Verifiers.Commons.SingleModelVerifier" + ], + "message": "Shader effect \u0027Default.fx\u0027 not found for model \u0027DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO\u0027.", + "severity": "Error", + "context": [ + "DATA\\ART\\MODELS\\EV_MDU_SENSORNODE.ALO" + ], + "asset": "Default.fx" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "TESTUNITMOVE_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0106_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0106_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "AMB_DES_CLEAR_LOOP_1.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_MAL0503_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "AMB_URB_CLEAR_LOOP_1.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0315_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_TMC0212_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "FS_BEETLE_3.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_MAL0503_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_MAL0503_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0314_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_DEF3006_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0109_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_DEF3106_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "FS_BEETLE_2.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0101_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0202_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0102_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0204_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0103_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0304_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0104_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "EGL_STAR_VIPER_SPINNING_1.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0105_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0402_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0106_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0106_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0112_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0107_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_ARC3106_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0108_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0201_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0109_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0109_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_ARC3104_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0110_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0311_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], "message": "Audio file \u0027U000_LEI0111_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0111_ENG.WAV" - ] + "severity": "Error", + "context": [], + "asset": "U000_LEI0111_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0112_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0112_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0114_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0113_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_DEF3106_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_DEF3106_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0114_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0114_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0110_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0110_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0115_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0503_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0201_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0201_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_ARC3105_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0202_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0202_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0305_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0203_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0208_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0204_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0204_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0313_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0205_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0308_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], "message": "Audio file \u0027U000_LEI0206_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0206_ENG.WAV" - ] + "severity": "Error", + "context": [], + "asset": "U000_LEI0206_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0207_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0105_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0105_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0208_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0208_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "FS_BEETLE_1.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0209_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0209_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "FS_BEETLE_4.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0210_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0209_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0209_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0211_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0501_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0212_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027TESTUNITMOVE_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "TESTUNITMOVE_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0213_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0312_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0215_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0404_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0301_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0603_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0303_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0108_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0108_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0304_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0304_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0306_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0305_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0305_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0303_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0303_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0306_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0306_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027AMB_DES_CLEAR_LOOP_1.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "AMB_DES_CLEAR_LOOP_1.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], "message": "Audio file \u0027U000_LEI0307_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0307_ENG.WAV" - ] + "severity": "Error", + "context": [], + "asset": "U000_LEI0307_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0308_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0308_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0102_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0102_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0309_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027C000_DST0102_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "C000_DST0102_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0311_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0311_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0205_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0205_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0312_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0312_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0504_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0313_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0313_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0210_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0210_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0314_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0314_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0403_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0315_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0315_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0211_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0211_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0401_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0604_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0402_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0402_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_TMC0212_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_TMC0212_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0403_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0403_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0309_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0309_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0404_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0404_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_DEF3006_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_DEF3006_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0501_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0501_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0101_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0101_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0502_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0203_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0203_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0503_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0503_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0401_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0401_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0504_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0504_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0602_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0601_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_MCF1601_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0602_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0602_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0115_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0115_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0603_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0603_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0207_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0207_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_LEI0604_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_LEI0604_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0502_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0502_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_ARC3104_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_ARC3104_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0113_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0113_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_ARC3105_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_ARC3105_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0213_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0213_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_ARC3106_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_ARC3106_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0104_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0104_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027U000_MCF1601_ENG.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "U000_MCF1601_ENG.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0301_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0301_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027FS_BEETLE_1.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "FS_BEETLE_1.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0601_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0601_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027FS_BEETLE_2.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "FS_BEETLE_2.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0212_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0212_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027FS_BEETLE_3.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "FS_BEETLE_3.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0103_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0103_ENG.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027FS_BEETLE_4.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "FS_BEETLE_4.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027AMB_URB_CLEAR_LOOP_1.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "AMB_URB_CLEAR_LOOP_1.WAV" }, { "id": "WAV00", - "verifier": "AET.ModVerify.Verifiers.AudioFilesVerifier", - "message": "Audio file \u0027EGL_STAR_VIPER_SPINNING_1.WAV\u0027 could not be found.", - "severity": 0, - "assets": [ - "EGL_STAR_VIPER_SPINNING_1.WAV" - ] + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0107_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0107_ENG.WAV" + }, + { + "id": "WAV00", + "verifiers": [ + "AET.ModVerify.Verifiers.AudioFilesVerifier" + ], + "message": "Audio file \u0027U000_LEI0215_ENG.WAV\u0027 could not be found.", + "severity": "Error", + "context": [], + "asset": "U000_LEI0215_ENG.WAV" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + ], + "message": "Could not find GUI texture \u0027i_button_petro_sliver.tga\u0027 at location \u0027MegaTexture\u0027.", + "severity": "Error", + "context": [ + "IDC_MENU_PETRO_LOGO", + "MegaTexture" + ], + "asset": "i_button_petro_sliver.tga" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + ], + "message": "Could not find GUI texture \u0027underworld_logo_off.tga\u0027 at location \u0027MegaTexture\u0027.", + "severity": "Error", + "context": [ + "IDC_PLAY_FACTION_A_BUTTON_BIG", + "MegaTexture" + ], + "asset": "underworld_logo_off.tga" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + ], + "message": "Could not find GUI texture \u0027i_dialogue_button_large_middle_off.tga\u0027 at location \u0027Repository\u0027.", + "severity": "Error", + "context": [ + "IDC_PLAY_FACTION_B_BUTTON_BIG", + "Repository" + ], + "asset": "i_dialogue_button_large_middle_off.tga" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + ], + "message": "Could not find GUI texture \u0027underworld_logo_rollover.tga\u0027 at location \u0027MegaTexture\u0027.", + "severity": "Error", + "context": [ + "IDC_PLAY_FACTION_A_BUTTON_BIG", + "MegaTexture" + ], + "asset": "underworld_logo_rollover.tga" + }, + { + "id": "FILE00", + "verifiers": [ + "AET.ModVerify.Verifiers.GuiDialogs.GuiDialogsVerifier" + ], + "message": "Could not find GUI texture \u0027underworld_logo_selected.tga\u0027 at location \u0027MegaTexture\u0027.", + "severity": "Error", + "context": [ + "IDC_PLAY_FACTION_A_BUTTON_BIG", + "MegaTexture" + ], + "asset": "underworld_logo_selected.tga" } ] } \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props deleted file mode 100644 index 4bc8e0c..0000000 --- a/src/Directory.Build.props +++ /dev/null @@ -1,46 +0,0 @@ - - - - $(MSBuildThisFileDirectory)../ - true - true - $(RepoRootPath) - $(RepoRootPath)bin\Packages\$(Configuration)\ - - - - Alamo Engine Tools and Contributors - Copyright © 2024 Alamo Engine Tools and contributors. All rights reserved. - https://github.com/AlamoEngine-Tools/ModVerify - $(RepoRootPath)LICENSE - MIT - https://github.com/AlamoEngine-Tools/ModVerify - git - Alamo Engine Tools - README.md - aet.png - - - - latest - disable - enable - True - latest - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - \ No newline at end of file diff --git a/src/ModVerify.CliApp/ConsoleUtilities.cs b/src/ModVerify.CliApp/ConsoleUtilities.cs new file mode 100644 index 0000000..db3e741 --- /dev/null +++ b/src/ModVerify.CliApp/ConsoleUtilities.cs @@ -0,0 +1,94 @@ +using System; + +namespace AET.ModVerifyTool; + +internal static class ConsoleUtilities +{ + public delegate bool ConsoleQuestionValueFactory(string input, out T value); + + public static void WriteHorizontalLine(char lineChar = '─', int length = 20) + { + var line = new string(lineChar, length); + Console.WriteLine(line); + } + + public static void WriteHeader() + { + Console.WriteLine("***********************************"); + Console.WriteLine("***********************************"); + Console.WriteLine(Figgle.FiggleFonts.Standard.Render("Mod Verify")); + Console.WriteLine("***********************************"); + Console.WriteLine("***********************************"); + Console.WriteLine(" by AnakinRaW"); + Console.WriteLine(); + Console.WriteLine(); + } + + public static void WriteApplicationFailure() + { + Console.WriteLine(); + WriteHorizontalLine('*'); + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine(" ModVerify Failure! "); + Console.ResetColor(); + WriteHorizontalLine('*'); + Console.WriteLine(); + Console.WriteLine("The application encountered an unexpected error and will terminate now!"); + Console.WriteLine(); + } + + public static T UserQuestionOnSameLine(string question, ConsoleQuestionValueFactory inputCorrect) + { + while (true) + { + var promptLeft = 0; + var promptTop = Console.CursorTop; + + Console.SetCursorPosition(promptLeft, promptTop); + Console.Write(question); + Console.SetCursorPosition(promptLeft + question.Length, promptTop); + + var input = ReadLineInline(); + + if (!inputCorrect(input, out var result)) + { + Console.SetCursorPosition(0, promptTop); + Console.Write(new string(' ', Console.WindowWidth - 1)); + continue; + } + + Console.WriteLine(); + return result; + } + } + + private static string ReadLineInline() + { + var input = ""; + while (true) + { + var key = Console.ReadKey(intercept: true); + + if (key.Key == ConsoleKey.Enter) + break; + + if (key.Key == ConsoleKey.Backspace) + { + if (input.Length > 0) + { + input = input[..^1]; + Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); + Console.Write(' '); + Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); + } + } + else if (!char.IsControl(key.KeyChar)) + { + input += key.KeyChar; + Console.Write(key.KeyChar); + } + } + + return input; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs index fa21e3b..78fe408 100644 --- a/src/ModVerify.CliApp/GameFinder/GameFinderService.cs +++ b/src/ModVerify.CliApp/GameFinder/GameFinderService.cs @@ -8,7 +8,6 @@ using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services; -using PG.StarWarsGame.Infrastructure.Services.Dependencies; using PG.StarWarsGame.Infrastructure.Services.Detection; namespace AET.ModVerifyTool.GameFinder; @@ -69,17 +68,20 @@ public GameFinderResult FindGamesFromPathOrGlobal(string path) private bool TryDetectGame(GameType gameType, IList detectors, out GameDetectionResult result) { var gd = new CompositeGameDetector(detectors, _serviceProvider); - result = gd.Detect(new GameDetectorOptions(gameType)); - if (result.Error is not null) + try { - _logger?.LogTrace($"Unable to find game installation: {result.Error.Message}", result.Error); - return false; + result = gd.Detect(gameType); + if (result.GameLocation is null) + return false; + return true; } - if (result.GameLocation is null) + catch (Exception e) + { + result = GameDetectionResult.NotInstalled(gameType); + _logger?.LogTrace($"Unable to find game installation: {e.Message}"); return false; - - return true; + } } private GameFinderResult FindGames(IList detectors) @@ -95,7 +97,7 @@ private GameFinderResult FindGames(IList detectors) if (result.GameLocation is null) throw new GameNotFoundException("Unable to find game installation: Wrong install path?"); - _logger?.LogTrace($"Found game installation: {result.GameIdentity} at {result.GameLocation.FullName}"); + _logger?.LogInformation($"Found game installation: {result.GameIdentity} at {result.GameLocation.FullName}"); var game = _gameFactory.CreateGame(result, CultureInfo.InvariantCulture); @@ -116,7 +118,7 @@ private GameFinderResult FindGames(IList detectors) if (!TryDetectGame(GameType.Eaw, fallbackDetectors, out var fallbackResult) || fallbackResult.GameLocation is null) throw new GameNotFoundException("Unable to find fallback game installation: Wrong install path?"); - _logger?.LogTrace($"Found fallback game installation: {fallbackResult.GameIdentity} at {fallbackResult.GameLocation.FullName}"); + _logger?.LogInformation($"Found fallback game installation: {fallbackResult.GameIdentity} at {fallbackResult.GameLocation.FullName}"); fallbackGame = _gameFactory.CreateGame(fallbackResult, CultureInfo.InvariantCulture); @@ -128,15 +130,15 @@ private GameFinderResult FindGames(IList detectors) private void SetupMods(IGame game) { - var modFinder = _serviceProvider.GetRequiredService(); + var modFinder = _serviceProvider.GetRequiredService(); var modRefs = modFinder.FindMods(game); var mods = new List(); foreach (var modReference in modRefs) { - var mod = _modFactory.FromReference(game, modReference, CultureInfo.InvariantCulture); - mods.AddRange(mod); + var mod = _modFactory.CreatePhysicalMod(game, modReference, CultureInfo.InvariantCulture); + mods.Add(mod); } foreach (var mod in mods) @@ -145,9 +147,7 @@ private void SetupMods(IGame game) // Mods need to be added to the game first, before resolving their dependencies. foreach (var mod in mods) { - var resolver = _serviceProvider.GetRequiredService(); - mod.ResolveDependencies(resolver, - new DependencyResolverOptions { CheckForCycle = true, ResolveCompleteChain = true }); + mod.ResolveDependencies(); } } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs index 1d2a85f..00fc4ae 100644 --- a/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/AutomaticModSelector.cs @@ -1,11 +1,9 @@ using System; -using System.Diagnostics; using System.Globalization; using System.IO.Abstractions; +using System.Linq; using AET.ModVerifyTool.GameFinder; using AET.ModVerifyTool.Options; -using EawModinfo.Model; -using EawModinfo.Spec; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine; @@ -13,7 +11,7 @@ using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; using PG.StarWarsGame.Infrastructure.Services; -using PG.StarWarsGame.Infrastructure.Services.Dependencies; +using PG.StarWarsGame.Infrastructure.Services.Detection; namespace AET.ModVerifyTool.ModSelectors; @@ -21,10 +19,14 @@ internal class AutomaticModSelector(IServiceProvider serviceProvider) : ModSelec { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public override GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType) + public override GameLocations? Select( + GameInstallationsSettings settings, + out IPhysicalPlayableObject? targetObject, + out GameEngineType? actualEngineType) { var pathToVerify = settings.AutoPath; - Debug.Assert(pathToVerify is not null); + if (pathToVerify is null) + throw new InvalidOperationException("path to verify cannot be null."); actualEngineType = settings.EngineType; @@ -100,14 +102,18 @@ private GameLocations GetDetachedModLocations(string modPath, GameFinderResult g if (game is null) throw new GameNotFoundException($"Unable to find game of type '{settings.EngineType}'"); + var modFinder = ServiceProvider.GetRequiredService(); + var modRef = modFinder.FindMods(game, _fileSystem.DirectoryInfo.New(modPath)).FirstOrDefault(); + + if (modRef is null) + throw new NotSupportedException($"The mod at '{modPath}' is not compatible to the found game '{game}'."); + var modFactory = ServiceProvider.GetRequiredService(); - mod = modFactory.FromReference(game, new ModReference(modPath, ModType.Default), true, CultureInfo.InvariantCulture); + mod = modFactory.CreatePhysicalMod(game, modRef, CultureInfo.InvariantCulture); game.AddMod(mod); - var resolver = ServiceProvider.GetRequiredService(); - mod.ResolveDependencies(resolver, - new DependencyResolverOptions { CheckForCycle = true, ResolveCompleteChain = true }); + mod.ResolveDependencies(); return GetLocations(mod, gameResult, settings.AdditionalFallbackPaths); } diff --git a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs index c218305..7a29368 100644 --- a/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/ConsoleModSelector.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; +using AET.Modinfo.Spec; using AET.ModVerifyTool.GameFinder; using AET.ModVerifyTool.Options; -using EawModinfo.Spec; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure; +using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; namespace AET.ModVerifyTool.ModSelectors; internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) { - public override GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, + public override GameLocations Select(GameInstallationsSettings settings, out IPhysicalPlayableObject targetObject, out GameEngineType? actualEngineType) { var gameResult = GameFinderService.FindGames(); @@ -20,13 +21,15 @@ internal class ConsoleModSelector(IServiceProvider serviceProvider) : ModSelecto return GetLocations(targetObject, gameResult, settings.AdditionalFallbackPaths); } - private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult) + private static IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResult) { var list = new List(); var game = finderResult.Game; list.Add(finderResult.Game); + Console.WriteLine(); + ConsoleUtilities.WriteHorizontalLine(); Console.WriteLine($"0: {game.Name}"); var counter = 1; @@ -52,7 +55,11 @@ private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResu { var fallbackGame = finderResult.FallbackGame; list.Add(fallbackGame); - Console.WriteLine($"{counter++}: {fallbackGame.Name}"); + + ConsoleUtilities.WriteHorizontalLine('_'); + Console.WriteLine(fallbackGame.Type == GameType.Eaw + ? $"{counter++}: {fallbackGame.Name} [Not yet supported]" + : $"{counter++}: {fallbackGame.Name}"); foreach (var mod in fallbackGame.Mods) { @@ -72,22 +79,34 @@ private IPhysicalPlayableObject SelectPlayableObject(GameFinderResult finderResu } } - Console.WriteLine("Workshop Items:"); - foreach (var mod in workshopMods) + if (workshopMods.Count > 0) { - Console.WriteLine($"{counter++}:\t{mod.Name}"); - list.Add(mod); + ConsoleUtilities.WriteHorizontalLine('_'); + Console.WriteLine("Workshop Items:"); + foreach (var mod in workshopMods) + { + Console.WriteLine($"{counter++}:\t{mod.Name}"); + list.Add(mod); + } } - while (true) - { - Console.Write("Select a game or mod: "); - var numberString = Console.ReadLine(); + ConsoleUtilities.WriteHorizontalLine(); - if (!int.TryParse(numberString, out var number)) - continue; - if (number < list.Count) - return list[number]; + try + { + var selected = ConsoleUtilities.UserQuestionOnSameLine("Select a game or mod: ", + (string input, out int value) => + { + if (!int.TryParse(input, out value)) + return false; + + return value <= list.Count; + }); + return list[selected]; + } + finally + { + Console.WriteLine(); } } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs index 00f4c9e..f0b30a0 100644 --- a/src/ModVerify.CliApp/ModSelectors/IModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/IModSelector.cs @@ -6,5 +6,8 @@ namespace AET.ModVerifyTool.ModSelectors; internal interface IModSelector { - GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType); + GameLocations? Select( + GameInstallationsSettings settings, + out IPhysicalPlayableObject? targetObject, + out GameEngineType? actualEngineType); } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs index 08f6537..d5913c6 100644 --- a/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/ManualModSelector.cs @@ -7,7 +7,9 @@ namespace AET.ModVerifyTool.ModSelectors; internal class ManualModSelector(IServiceProvider serviceProvider) : ModSelectorBase(serviceProvider) { - public override GameLocations Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, + public override GameLocations Select( + GameInstallationsSettings settings, + out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType) { actualEngineType = settings.EngineType; @@ -21,7 +23,7 @@ public override GameLocations Select(GameInstallationsSettings settings, out IPh return new GameLocations( settings.ModPaths, - settings.GamePath, + settings.GamePath!, GetFallbackPaths(settings.FallbackGamePath, settings.AdditionalFallbackPaths)); } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs index 861451a..0eec285 100644 --- a/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs +++ b/src/ModVerify.CliApp/ModSelectors/ModSelectorBase.cs @@ -25,7 +25,9 @@ protected ModSelectorBase(IServiceProvider serviceProvider) GameFinderService = new GameFinderService(serviceProvider); } - public abstract GameLocations? Select(GameInstallationsSettings settings, out IPhysicalPlayableObject? targetObject, + public abstract GameLocations? Select( + GameInstallationsSettings settings, + out IPhysicalPlayableObject? targetObject, out GameEngineType? actualEngineType); protected GameLocations GetLocations(IPhysicalPlayableObject playableObject, GameFinderResult finderResult, IList additionalFallbackPaths) @@ -66,7 +68,6 @@ private IList GetModPaths(IPhysicalPlayableObject modOrGame) var traverser = ServiceProvider.GetRequiredService(); return traverser.Traverse(mod) - .Select(x => x.Mod) .OfType().Select(x => x.Directory.FullName) .ToList(); } diff --git a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs index 356e6c2..12f7861 100644 --- a/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs +++ b/src/ModVerify.CliApp/ModSelectors/SettingsBasedModSelector.cs @@ -1,24 +1,17 @@ using System; -using System.Globalization; -using System.IO.Abstractions; using System.Linq; using AET.ModVerifyTool.Options; -using EawModinfo.Model; -using EawModinfo.Spec; -using Microsoft.Extensions.DependencyInjection; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Infrastructure; -using PG.StarWarsGame.Infrastructure.Games; -using PG.StarWarsGame.Infrastructure.Services.Name; namespace AET.ModVerifyTool.ModSelectors; internal class SettingsBasedModSelector(IServiceProvider serviceProvider) { - private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstallationsSettings settings) + public VerifyInstallationInformation CreateInstallationDataFromSettings(GameInstallationsSettings settings) { - var gameLocations = new ModSelectorFactory(serviceProvider).CreateSelector(settings) + var gameLocations = new ModSelectorFactory(serviceProvider) + .CreateSelector(settings) .Select(settings, out var targetObject, out var engineType); if (gameLocations is null) @@ -27,7 +20,7 @@ public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstall if (engineType is null) throw new InvalidOperationException("Engine type not specified."); - return new VerifyGameInstallationData + return new VerifyInstallationInformation { EngineType = engineType.Value, GameLocations = gameLocations, @@ -35,30 +28,12 @@ public VerifyGameInstallationData CreateInstallationDataFromSettings(GameInstall }; } - private string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations, GameEngineType engineType) + private static string GetNameFromGameLocations(IPlayableObject? targetObject, GameLocations gameLocations, GameEngineType engineType) { if (targetObject is not null) return targetObject.Name; var mod = gameLocations.ModPaths.FirstOrDefault(); - - var name = mod is not null ? GetNameFromMod(mod) : GetNameFromGame(engineType); - - if (string.IsNullOrEmpty(name)) - throw new InvalidOperationException("Mod or game name cannot be null or empty."); - - return name; - } - - private string? GetNameFromGame(GameEngineType type) - { - var nameResolver = serviceProvider.GetRequiredService(); - return nameResolver.ResolveName(new GameIdentity(type.FromEngineType(), GamePlatform.Undefined), CultureInfo.InvariantCulture); - } - - private string? GetNameFromMod(string mod) - { - var nameResolver = serviceProvider.GetRequiredService(); - return nameResolver.ResolveName(new ModReference(_fileSystem.Path.GetFullPath(mod), ModType.Default), CultureInfo.InvariantCulture); + return mod ?? gameLocations.GamePath; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerify.CliApp.csproj b/src/ModVerify.CliApp/ModVerify.CliApp.csproj index 1fbe04f..61c0a3b 100644 --- a/src/ModVerify.CliApp/ModVerify.CliApp.csproj +++ b/src/ModVerify.CliApp/ModVerify.CliApp.csproj @@ -1,8 +1,7 @@  - false - net8.0;net48 + net9.0;net48 Exe AET.ModVerifyTool ModVerify @@ -14,48 +13,48 @@ alamo,petroglyph,glyphx - - true - true - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - + + true + - + + @@ -66,6 +65,4 @@ - - diff --git a/src/ModVerify.CliApp/ModVerifyApp.cs b/src/ModVerify.CliApp/ModVerifyApp.cs index 598aa09..1270725 100644 --- a/src/ModVerify.CliApp/ModVerifyApp.cs +++ b/src/ModVerify.CliApp/ModVerifyApp.cs @@ -1,14 +1,19 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using System.Threading.Tasks; -using AET.ModVerify; +using AET.ModVerify; using AET.ModVerify.Reporting; using AET.ModVerifyTool.ModSelectors; using AET.ModVerifyTool.Options; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Pipeline; +using AET.ModVerifyTool.Reporting; +using PG.StarWarsGame.Engine; namespace AET.ModVerifyTool; @@ -19,42 +24,132 @@ internal class ModVerifyApp(ModVerifyAppSettings settings, IServiceProvider serv public async Task RunApplication() { - var returnCode = 0; + var installData = new SettingsBasedModSelector(services) + .CreateInstallationDataFromSettings(settings.GameInstallationsSettings); + + _logger?.LogDebug($"Verify install data: {installData}"); + _logger?.LogTrace($"Verify settings: {settings}"); + + var allErrors = await Verify(installData).ConfigureAwait(false); - var installData = new SettingsBasedModSelector(services).CreateInstallationDataFromSettings(settings.GameInstallationsSettings); + try + { + await ReportErrors(allErrors).ConfigureAwait(false); + } + catch (GameVerificationException e) + { + return e.HResult; + } + + if (!settings.CreateNewBaseline) + return 0; + + await WriteBaseline(allErrors, settings.NewBaselinePath).ConfigureAwait(false); + _logger?.LogInformation("Baseline successfully created."); - _logger?.LogInformation($"Verify install data: {installData}"); + return 0; + } - var verifyPipeline = new ModVerifyPipeline(installData.EngineType, installData.GameLocations, settings.GameVerifySettings, services); + private async Task> Verify(VerifyInstallationInformation installInformation) + { + var gameEngineService = services.GetRequiredService(); + var engineErrorReporter = new ConcurrentGameEngineErrorReporter(); + + IStarWarsGameEngine gameEngine; try { - _logger?.LogInformation($"Verifying '{installData.Name}'..."); - await verifyPipeline.RunAsync().ConfigureAwait(false); - _logger?.LogInformation("Finished Verifying"); + var initProgress = new Progress(); + var initProgressReporter = new EngineInitializeProgressReporter(initProgress); + + try + { + _logger?.LogInformation($"Creating Game Engine '{installInformation.EngineType}'"); + gameEngine = await gameEngineService.InitializeAsync( + installInformation.EngineType, + installInformation.GameLocations, + engineErrorReporter, + initProgress, + false, + CancellationToken.None).ConfigureAwait(false); + _logger?.LogInformation($"Game Engine created"); + } + finally + { + initProgressReporter.Dispose(); + } } - catch (GameVerificationException e) + catch (Exception e) { - returnCode = e.HResult; + _logger?.LogError(e, $"Creating game engine failed: {e.Message}"); + throw; } + + var progressReporter = new VerifyConsoleProgressReporter(installInformation.Name); - if (settings.CreateNewBaseline) - await WriteBaseline(verifyPipeline.Errors, settings.NewBaselinePath).ConfigureAwait(false); + using var verifyPipeline = new GameVerifyPipeline( + gameEngine, + engineErrorReporter, + settings.VerifyPipelineSettings, + settings.GlobalReportSettings, + progressReporter, + services); - return returnCode; + try + { + try + { + _logger?.LogInformation($"Verifying '{installInformation.Name}'..."); + await verifyPipeline.RunAsync().ConfigureAwait(false); + progressReporter.Report(string.Empty, 1.0); + } + catch + { + progressReporter.ReportError("Verification failed", null); + throw; + } + finally + { + progressReporter.Dispose(); + } + } + catch (OperationCanceledException) + { + _logger?.LogWarning("Verification stopped due to enabled failFast setting."); + } + catch (Exception e) + { + _logger?.LogError(e, $"Verification failed: {e.Message}"); + throw; + } + + _logger?.LogInformation("Finished verification"); + return verifyPipeline.FilteredErrors; } - private async Task WriteBaseline(IEnumerable errors, string baselineFile) + private async Task ReportErrors(IReadOnlyCollection errors) { - var currentBaseline = settings.GameVerifySettings.GlobalReportSettings.Baseline; + _logger?.LogInformation("Reporting Errors..."); + + var reportBroker = new VerificationReportBroker(services); + + await reportBroker.ReportAsync(errors); - var newBaseline = currentBaseline.MergeWith(errors); + if (errors.Any(x => x.Severity >= settings.AppThrowsOnMinimumSeverity)) + throw new GameVerificationException(errors); + } + + private async Task WriteBaseline(IEnumerable errors, string baselineFile) + { + var baseline = new VerificationBaseline(settings.GlobalReportSettings.MinimumReportSeverity, errors); var fullPath = _fileSystem.Path.GetFullPath(baselineFile); + _logger?.LogInformation($"Writing Baseline to '{fullPath}'"); + #if NET await #endif using var fs = _fileSystem.FileStream.New(fullPath, FileMode.Create, FileAccess.Write, FileShare.None); - await newBaseline.ToJsonAsync(fs); + await baseline.ToJsonAsync(fs); } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/ModVerifyPipeline.cs b/src/ModVerify.CliApp/ModVerifyPipeline.cs deleted file mode 100644 index a2a798c..0000000 --- a/src/ModVerify.CliApp/ModVerifyPipeline.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using AET.ModVerify; -using AET.ModVerify.Settings; -using AET.ModVerify.Verifiers; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Engine.Database; - -namespace AET.ModVerifyTool; - -internal class ModVerifyPipeline( - GameEngineType targetType, - GameLocations gameLocations, - GameVerifySettings settings, - IServiceProvider serviceProvider) - : VerifyGamePipeline(targetType, gameLocations, settings, serviceProvider) -{ - protected override IEnumerable CreateVerificationSteps(IGameDatabase database) - { - var verifyProvider = ServiceProvider.GetRequiredService(); - return verifyProvider.GetAllDefaultVerifiers(database, Settings); - } -} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Options/ModVerifyOptions.cs b/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs similarity index 66% rename from src/ModVerify.CliApp/Options/ModVerifyOptions.cs rename to src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs index 65e3a9b..d239112 100644 --- a/src/ModVerify.CliApp/Options/ModVerifyOptions.cs +++ b/src/ModVerify.CliApp/Options/CommandLine/BaseModVerifyOptions.cs @@ -3,48 +3,33 @@ using CommandLine; using PG.StarWarsGame.Engine; -namespace AET.ModVerifyTool.Options; +namespace AET.ModVerifyTool.Options.CommandLine; -internal class ModVerifyOptions +internal abstract class BaseModVerifyOptions { - [Option('o', "output", Required = false, HelpText = "directory where result files shall be stored to.")] - public string? Output { get; set; } - - [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")] + [Option('v', "verbose", Required = false, HelpText = "Sets output to verbose messages.")] public bool Verbose { get; set; } - [Option("baseline", Required = false, HelpText = "Path to a JSON baseline file.")] - public string? Baseline { get; set; } + [Option("offline", Default = false, HelpText = "When set, the application will work in offline mode and does not need an Internet connection.")] + public bool OfflineMode { get; set; } + + [Option("minSeverity", Required = false, Default = VerificationSeverity.Information, + HelpText = "When set, only findings with at least the specified severity value are processed.")] + public VerificationSeverity MinimumSeverity { get; set; } [Option("suppressions", Required = false, HelpText = "Path to a JSON suppression file.")] public string? Suppressions { get; set; } - [Option("minFailSeverity", Required = false, Default = null, - HelpText = "When set, the application return with an error, if any finding has at least the specified severity value.")] - public VerificationSeverity? MinimumFailureSeverity { get; set; } - - [Option("failFast", Required = false, Default = false, - HelpText = "When set, the application will abort on the first failure. The option also recognized the 'MinimumFailureSeverity' setting.")] - public bool FailFast { get; set; } - - [Option("createBaseline", Required = false, - HelpText = "When a path is specified, the tools creates a new baseline file. " + - "An existing file will be overwritten. Previous baselines are merged into the new baseline.")] - public string? NewBaselineFile { get; set; } - - [Option("path", SetName = "autoDetection", Required = false, Default = null, HelpText = "Specifies the path to verify. The path may be a game or mod. The application will try to find all necessary submods or base games itself. " + "The argument cannot be combined with any of --mods, --game or --fallbackGame")] public string? AutoPath { get; set; } - [Option("mods", SetName = "manualPaths", Required = false, Default = null, Separator = ';', HelpText = "The path of the mod to verify. To support submods, multiple paths can be separated using the ';' (semicolon) character. " + "Leave empty, if you want to verify a game. If you want to use the interactive mode, leave this, --game and --fallbackGame empty.")] public IList? ModPaths { get; set; } - [Option("game", SetName = "manualPaths", Required = false, Default = null, HelpText = "The path of the base game. For FoC mods this points to the FoC installation, for EaW mods this points to the EaW installation. " + "Leave empty, if you want to auto-detect games. If you want to use the interactive mode, leave this, --mods and --fallbackGame empty. " + @@ -57,7 +42,7 @@ internal class ModVerifyOptions [Option("type", Required = false, Default = null, - HelpText = "The game type of the mod that shall be verified. Leave this value to auto-determine the type. Valid values are 'Eaw' and 'Foc'. " + + HelpText = "The game type of the mod that shall be verified. Skip this value to auto-determine the type. Valid values are 'Eaw' and 'Foc'. " + "This argument is required, if the first mod of '--mods' points to a directory outside of the common folder hierarchy (e.g, /MODS/MOD_NAME or /32470/WORKSHOP_ID")] public GameEngineType? GameType { get; set; } @@ -66,4 +51,9 @@ internal class ModVerifyOptions HelpText = "Additional fallback paths, which may contain assets that shall be included when doing the verification. Do not add EaW here. " + "Multiple paths can be separated using the ';' (semicolon) character.")] public IList? AdditionalFallbackPath { get; set; } + + [Option("parallel", Default = false, + HelpText = "When set, game verifiers will run in parallel. " + + "While this may reduce analysis time, console output might be harder to read.")] + public bool Parallel { get; set; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs b/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs new file mode 100644 index 0000000..78132cc --- /dev/null +++ b/src/ModVerify.CliApp/Options/CommandLine/CreateBaselineVerbOption.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace AET.ModVerifyTool.Options.CommandLine; + +[Verb("createBaseline", HelpText = "Verifies the specified game and creates a new baseline file at the specified location.")] +internal class CreateBaselineVerbOption : BaseModVerifyOptions +{ + [Option('o', "outFile", Required = true, HelpText = "The file path of the new baseline file.")] + public string OutputFile { get; set; } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs b/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs new file mode 100644 index 0000000..517ceda --- /dev/null +++ b/src/ModVerify.CliApp/Options/CommandLine/VerifyVerbOption.cs @@ -0,0 +1,26 @@ +using AET.ModVerify.Reporting; +using CommandLine; + +namespace AET.ModVerifyTool.Options.CommandLine; + +[Verb("verify", true, HelpText = "Verifies the specified game and reports the findings.")] +internal class VerifyVerbOption : BaseModVerifyOptions +{ + [Option('o', "outDir", Required = false, HelpText = "Directory where result files shall be stored to.")] + public string? OutputDirectory { get; set; } + + [Option("failFast", Required = false, Default = false, + HelpText = "When set, the application will abort on the first failure. The option also recognized the 'MinimumFailureSeverity' setting.")] + public bool FailFast { get; set; } + + [Option("minFailSeverity", Required = false, Default = null, + HelpText = "When set, the application return with an error, if any finding has at least the specified severity value.")] + public VerificationSeverity? MinimumFailureSeverity { get; set; } + + [Option("ignoreAsserts", Required = false, + HelpText = "When this flag is present, the application will not report engine assertions.")] + public bool IgnoreAsserts { get; set; } + + [Option("baseline", Required = false, HelpText = "Path to a JSON baseline file.")] + public string? Baseline { get; set; } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs b/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs index 6c99bf4..6a3b0bd 100644 --- a/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs +++ b/src/ModVerify.CliApp/Options/ModVerifyAppSettings.cs @@ -1,19 +1,28 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Settings; using AET.ModVerify.Settings; namespace AET.ModVerifyTool.Options; -internal record ModVerifyAppSettings +internal sealed class ModVerifyAppSettings { - public required GameVerifySettings GameVerifySettings { get; init; } + public bool Interactive => GameInstallationsSettings.Interactive; + + public required VerifyPipelineSettings VerifyPipelineSettings { get; init; } + + public required GlobalVerifyReportSettings GlobalReportSettings { get; init; } public required GameInstallationsSettings GameInstallationsSettings { get; init; } - public string Output { get; init; } = Environment.CurrentDirectory; + public VerificationSeverity? AppThrowsOnMinimumSeverity { get; init; } + + public string? ReportOutput { get; init; } [MemberNotNullWhen(true, nameof(NewBaselinePath))] public bool CreateNewBaseline => !string.IsNullOrEmpty(NewBaselinePath); public string? NewBaselinePath { get; init; } + + public bool Offline { get; init; } } \ No newline at end of file diff --git a/src/ModVerify.CliApp/Program.cs b/src/ModVerify.CliApp/Program.cs index ea5fff9..7c88edb 100644 --- a/src/ModVerify.CliApp/Program.cs +++ b/src/ModVerify.CliApp/Program.cs @@ -1,96 +1,154 @@ -using System; -using System.Collections.Generic; -using System.IO.Abstractions; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using AET.ModVerify; +using AET.ModVerify; +using AET.ModVerify.Reporting; using AET.ModVerify.Reporting.Reporters; using AET.ModVerify.Reporting.Reporters.JSON; using AET.ModVerify.Reporting.Reporters.Text; +using AET.ModVerify.Reporting.Settings; using AET.ModVerifyTool.Options; +using AET.ModVerifyTool.Options.CommandLine; +using AET.ModVerifyTool.Updates; using AET.SteamAbstraction; using AnakinRaW.CommonUtilities.Hashing; using AnakinRaW.CommonUtilities.Registry; using AnakinRaW.CommonUtilities.Registry.Windows; using CommandLine; +using CommandLine.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; -using PG.Commons.Extensibility; +using PG.Commons; using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Engine.Xml.Parsers; using PG.StarWarsGame.Files.ALO; -using PG.StarWarsGame.Files.DAT.Services.Builder; -using PG.StarWarsGame.Files.MEG.Data.Archives; +using PG.StarWarsGame.Files.MEG; +using PG.StarWarsGame.Files.MTD; using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Parsers; using PG.StarWarsGame.Infrastructure; -using PG.StarWarsGame.Infrastructure.Clients; using PG.StarWarsGame.Infrastructure.Services.Detection; using PG.StarWarsGame.Infrastructure.Services.Name; using Serilog; using Serilog.Events; using Serilog.Filters; +using Serilog.Sinks.SystemConsole.Themes; +using System; +using System.IO.Abstractions; +using System.Threading.Tasks; +using Testably.Abstractions; +using ILogger = Serilog.ILogger; namespace AET.ModVerifyTool; internal class Program { - private const string EngineParserNamespace = "PG.StarWarsGame.Engine.Xml.Parsers"; - private const string ParserNamespace = "PG.StarWarsGame.Files.XML.Parsers"; + private static readonly string EngineParserNamespace = typeof(XmlObjectParser<>).Namespace!; + private static readonly string ParserNamespace = typeof(PetroglyphXmlFileParser<>).Namespace!; + private static readonly string ModVerifyRootNameSpace = typeof(Program).Namespace!; private static async Task Main(string[] args) { + ConsoleUtilities.WriteHeader(); + var result = 0; - var parseResult = Parser.Default.ParseArguments(args); + Type[] programVerbs = + [ + typeof(VerifyVerbOption), + typeof(CreateBaselineVerbOption), + ]; - ModVerifyOptions? verifyOptions = null!; - await parseResult.WithParsedAsync(o => - { - verifyOptions = o; - return Task.CompletedTask; - }).ConfigureAwait(false); + var parseResult = Parser.Default.ParseArguments(args, programVerbs); + await parseResult.WithParsedAsync(async o => + { + result = await Run((BaseModVerifyOptions)o); + }); await parseResult.WithNotParsedAsync(e => { + Console.WriteLine(HelpText.AutoBuild(parseResult).ToString()); result = 0xA0; return Task.CompletedTask; - }).ConfigureAwait(false); + }); - if (verifyOptions is null) - { - if (result != 0) - return result; - throw new InvalidOperationException("Mod verify was executed with the wrong arguments."); - } + return result; + } - var coreServiceCollection = CreateCoreServices(verifyOptions.Verbose); + private static async Task Run(BaseModVerifyOptions options) + { + var coreServiceCollection = CreateCoreServices(options.Verbose); var coreServices = coreServiceCollection.BuildServiceProvider(); var logger = coreServices.GetService()?.CreateLogger(typeof(Program)); logger?.LogDebug($"Raw command line: {Environment.CommandLine}"); + var interactive = false; try { - var settings = new SettingsBuilder(coreServices) - .BuildSettings(verifyOptions); - + var settings = new SettingsBuilder(coreServices).BuildSettings(options); + interactive = settings.Interactive; var services = CreateAppServices(coreServiceCollection, settings); + if (!settings.Offline) + await CheckForUpdate(services, logger); + var verifier = new ModVerifyApp(settings, services); - return await verifier.RunApplication().ConfigureAwait(false); } catch (Exception e) { - Console.WriteLine($"Error: {e.Message}"); + ConsoleUtilities.WriteApplicationFailure(); logger?.LogCritical(e, e.Message); return e.HResult; } + finally + { +#if NET + await Log.CloseAndFlushAsync(); +#else + Log.CloseAndFlush(); +#endif + if (interactive) + { + Console.WriteLine(); + ConsoleUtilities.WriteHorizontalLine('-'); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } + } } - + + private static async Task CheckForUpdate(IServiceProvider services, Microsoft.Extensions.Logging.ILogger? logger) + { + var updateChecker = new ModVerifyUpdaterChecker(services); + + logger?.LogDebug("Checking for available update"); + + try + { + var updateInfo = await updateChecker.CheckForUpdateAsync().ConfigureAwait(false); + if (updateInfo.IsUpdateAvailable) + { + ConsoleUtilities.WriteHorizontalLine(); + + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine("New Update Available!"); + Console.ResetColor(); + + Console.WriteLine($"Version: {updateInfo.NewVersion}, Download here: {updateInfo.DownloadLink}"); + ConsoleUtilities.WriteHorizontalLine(); + Console.WriteLine(); + + } + } + catch(Exception e) + { + logger?.LogWarning($"Unable to check for updates due to an internal error: {e.Message}"); + logger?.LogTrace(e, "Checking for update failed: " + e.Message); + } + } + private static IServiceCollection CreateCoreServices(bool verboseLogging) { - var fileSystem = new FileSystem(); + var fileSystem = new RealFileSystem(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(new WindowsRegistry()); @@ -101,54 +159,59 @@ private static IServiceCollection CreateCoreServices(bool verboseLogging) return serviceCollection; } - private static IServiceProvider CreateAppServices(IServiceCollection serviceCollection, ModVerifyAppSettings settings) { serviceCollection.AddSingleton(sp => new HashingService(sp)); SteamAbstractionLayer.InitializeServices(serviceCollection); - PetroglyphGameClients.InitializeServices(serviceCollection); PetroglyphGameInfrastructure.InitializeServices(serviceCollection); - RuntimeHelpers.RunClassConstructor(typeof(IDatBuilder).TypeHandle); - RuntimeHelpers.RunClassConstructor(typeof(IMegArchive).TypeHandle); - AloServiceContribution.ContributeServices(serviceCollection); - serviceCollection.CollectPgServiceContributions(); - XmlServiceContribution.ContributeServices(serviceCollection); + serviceCollection.SupportMTD(); + serviceCollection.SupportMEG(); + serviceCollection.SupportALO(); + serviceCollection.SupportXML(); + PetroglyphCommons.ContributeServices(serviceCollection); PetroglyphEngineServiceContribution.ContributeServices(serviceCollection); + serviceCollection.RegisterVerifierCache(); - ModVerifyServiceContribution.ContributeServices(serviceCollection); - - SetupReporting(serviceCollection, settings); + SetupVerifyReporting(serviceCollection, settings); - serviceCollection.AddSingleton(sp => new CompositeModNameResolver(sp, s => - new List - { - new OfflineWorkshopNameResolver(s), - new OnlineWorkshopNameResolver(s), - new DirectoryModNameResolver(s) - })); - - serviceCollection.AddSingleton(sp => new OfflineModGameTypeResolver(sp)); + if (settings.Offline) + { + serviceCollection.AddSingleton(sp => new OfflineModNameResolver(sp)); + serviceCollection.AddSingleton(sp => new OfflineModGameTypeResolver(sp)); + } + else + { + serviceCollection.AddSingleton(sp => new OnlineModNameResolver(sp)); + serviceCollection.AddSingleton(sp => new OnlineModGameTypeResolver(sp)); + } return serviceCollection.BuildServiceProvider(); } - private static void SetupReporting(IServiceCollection serviceCollection, ModVerifyAppSettings settings) + private static void SetupVerifyReporting(IServiceCollection serviceCollection, ModVerifyAppSettings settings) { - serviceCollection.RegisterConsoleReporter(); + var printOnlySummary = settings.CreateNewBaseline; + serviceCollection.RegisterConsoleReporter(new VerifyReportSettings + { + MinimumReportSeverity = VerificationSeverity.Error + }, printOnlySummary); + + if (string.IsNullOrEmpty(settings.ReportOutput)) + return; serviceCollection.RegisterJsonReporter(new JsonReporterSettings { - OutputDirectory = settings.Output, - MinimumReportSeverity = settings.GameVerifySettings.GlobalReportSettings.MinimumReportSeverity + OutputDirectory = settings.ReportOutput!, + MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity }); serviceCollection.RegisterTextFileReporter(new TextFileReporterSettings { - OutputDirectory = settings.Output, - MinimumReportSeverity = settings.GameVerifySettings.GlobalReportSettings.MinimumReportSeverity + OutputDirectory = settings.ReportOutput!, + MinimumReportSeverity = settings.GlobalReportSettings.MinimumReportSeverity }); } @@ -157,48 +220,59 @@ private static void ConfigureLogging(ILoggingBuilder loggingBuilder, IFileSystem loggingBuilder.ClearProviders(); // ReSharper disable once RedundantAssignment - var logLevel = LogLevel.Information; + var logLevel = LogEventLevel.Information; #if DEBUG - logLevel = LogLevel.Debug; + logLevel = LogEventLevel.Debug; loggingBuilder.AddDebug(); -#else +#endif + if (verbose) { - logLevel = LogLevel.Debug; + logLevel = LogEventLevel.Verbose; loggingBuilder.AddDebug(); } -#endif - loggingBuilder.SetMinimumLevel(logLevel); - - SetupFileLogging(loggingBuilder, fileSystem); - loggingBuilder.AddFilter((category, level) => - { - if (level < logLevel) - return false; - if (string.IsNullOrEmpty(category)) - return false; - if (category.StartsWith(EngineParserNamespace) || category.StartsWith(ParserNamespace)) + var fileLogger = SetupFileLogging(fileSystem, logLevel); + loggingBuilder.AddSerilog(fileLogger); + + var cLogger = new LoggerConfiguration() + + .WriteTo.Console( + logLevel, + theme: AnsiConsoleTheme.Code, + outputTemplate: "[{Level:u3}] {Message:lj}{NewLine}{Exception}") + .Filter.ByIncludingOnly(x => + { + if (!x.Properties.TryGetValue("SourceContext", out var value)) + return true; + + var source = value.ToString().AsSpan().Trim('\"'); + + if (source.StartsWith(ModVerifyRootNameSpace.AsSpan())) + return true; + return false; - return true; - }).AddConsole(); + }) + .CreateLogger(); + loggingBuilder.AddSerilog(cLogger); } - - private static void SetupFileLogging(ILoggingBuilder loggingBuilder, IFileSystem fileSystem) + + private static ILogger SetupFileLogging(IFileSystem fileSystem, LogEventLevel minLevel) { var logPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), "ModVerify_log.txt"); - - var logger = new LoggerConfiguration() + return new LoggerConfiguration() .Enrich.FromLogContext() - .MinimumLevel.Verbose() + .MinimumLevel.Is(minLevel) .Filter.ByExcluding(IsXmlParserLogging) - .WriteTo.RollingFile( - logPath, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}") + .WriteTo.Async(c => + { + c.RollingFile( + logPath, + outputTemplate: + "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}"); + }) .CreateLogger(); - - loggingBuilder.AddSerilog(logger); } private static bool IsXmlParserLogging(LogEvent logEvent) diff --git a/src/ModVerify.CliApp/Properties/launchSettings.json b/src/ModVerify.CliApp/Properties/launchSettings.json index 51b4cdc..1e01ece 100644 --- a/src/ModVerify.CliApp/Properties/launchSettings.json +++ b/src/ModVerify.CliApp/Properties/launchSettings.json @@ -1,8 +1,12 @@ { "profiles": { - "Interactive": { + "Interactive Verify": { "commandName": "Project", - "commandLineArgs": "-o verifyResults --minFailSeverity Information --baseline focBaseline.json" + "commandLineArgs": "verify -o verifyResults --minFailSeverity Information -v --baseline focBaseline.json --offline" + }, + "Interactive Baseline": { + "commandName": "Project", + "commandLineArgs": "createBaseline -o focBaseline.json --offline" }, "FromModPath": { diff --git a/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs new file mode 100644 index 0000000..69413c0 --- /dev/null +++ b/src/ModVerify.CliApp/Reporting/EngineInitializeProgressReporter.cs @@ -0,0 +1,30 @@ +using System; + +namespace AET.ModVerifyTool.Reporting; + +internal sealed class EngineInitializeProgressReporter : IDisposable +{ + private Progress? _progress; + + public EngineInitializeProgressReporter(Progress? progress) + { + if (progress is null) + return; + progress.ProgressChanged += OnProgress; + } + + private void OnProgress(object sender, string e) + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine(e); + Console.ResetColor(); + } + + public void Dispose() + { + Console.WriteLine(); + if (_progress is not null) + _progress.ProgressChanged -= OnProgress; + _progress = null; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs new file mode 100644 index 0000000..37d3ca2 --- /dev/null +++ b/src/ModVerify.CliApp/Reporting/VerifyConsoleProgressReporter.cs @@ -0,0 +1,84 @@ +using AnakinRaW.CommonUtilities; +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; +using ShellProgressBar; +using System; +using System.Threading; +using AET.ModVerify.Pipeline.Progress; + +namespace AET.ModVerifyTool.Reporting; + +public sealed class VerifyConsoleProgressReporter(string toVerifyName) : DisposableObject, IVerifyProgressReporter +{ + private static readonly ProgressBarOptions ProgressBarOptions = new() + { + BackgroundColor = ConsoleColor.DarkGray, + ForegroundColorError = ConsoleColor.DarkRed, + ProgressCharacter = '─', + WriteQueuedMessage = WriteQueuedMessage, + }; + + private ProgressBar? _progressBar; + + public void ReportError(string message, string? errorLine) + { + var progressBar = EnsureProgressBar(); + progressBar.WriteErrorLine(errorLine); + progressBar.Message = message; + } + + public void Report(string message, double progress) + { + Report(progress, message, VerifyProgress.ProgressType, default); + } + + public void Report(double progress, string? progressText, ProgressType type, VerifyProgressInfo detailedProgress) + { + if (type != VerifyProgress.ProgressType) + return; + + var progressBar = EnsureProgressBar(); + + // TODO: Only recognize detailed mode + progressBar.Message = progressText; + + if (progress >= 1.0) + progressBar.Message = $"Verified '{toVerifyName}'"; + + var cpb = progressBar.AsProgress(); + cpb.Report(progress); + + // TODO: Only in verbose mode + //progressBar.WriteLine(progressText); + } + + protected override void DisposeResources() + { + base.DisposeResources(); + _progressBar?.Dispose(); + Console.WriteLine(); + } + + private ProgressBar EnsureProgressBar() + { + return LazyInitializer.EnsureInitialized(ref _progressBar, + () => new ProgressBar(100, $"Verifying '{toVerifyName}'", ProgressBarOptions))!; + } + + private static int WriteQueuedMessage(ConsoleOutLine arg) + { + if (string.IsNullOrEmpty(arg.Line)) + return 0; + + var writer = Console.Out; + Console.ForegroundColor = ConsoleColor.DarkGray; + try + { + writer.WriteLine(arg.Line); + return 1; + } + finally + { + Console.ResetColor(); + } + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/SettingsBuilder.cs b/src/ModVerify.CliApp/SettingsBuilder.cs index eeba013..d2ba861 100644 --- a/src/ModVerify.CliApp/SettingsBuilder.cs +++ b/src/ModVerify.CliApp/SettingsBuilder.cs @@ -1,81 +1,149 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Settings; using AET.ModVerify.Settings; using AET.ModVerifyTool.Options; using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using AET.ModVerify.Pipeline; +using AET.ModVerifyTool.Options.CommandLine; +using Microsoft.Extensions.Logging; namespace AET.ModVerifyTool; -internal class SettingsBuilder(IServiceProvider services) +internal sealed class SettingsBuilder(IServiceProvider services) { private readonly IFileSystem _fileSystem = services.GetRequiredService(); + private readonly ILogger? _logger = + services.GetRequiredService()?.CreateLogger(typeof(SettingsBuilder)); - public ModVerifyAppSettings BuildSettings(ModVerifyOptions options) + public ModVerifyAppSettings BuildSettings(BaseModVerifyOptions options) { - var output = Environment.CurrentDirectory; - if (!string.IsNullOrEmpty(options.Output)) - output = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(Environment.CurrentDirectory, options.Output)); + switch (options) + { + case VerifyVerbOption verifyVerb: + return BuildFromVerifyVerb(verifyVerb); + case CreateBaselineVerbOption baselineVerb: + return BuildFromCreateBaselineVerb(baselineVerb); + } + throw new NotSupportedException($"The option '{options.GetType().Name}' is not supported!"); + } + private ModVerifyAppSettings BuildFromVerifyVerb(VerifyVerbOption verifyOptions) + { + var output = Environment.CurrentDirectory; + var outDir = verifyOptions.OutputDirectory; + if (!string.IsNullOrEmpty(outDir)) + output = _fileSystem.Path.GetFullPath(_fileSystem.Path.Combine(Environment.CurrentDirectory, outDir!)); + return new ModVerifyAppSettings { - GameVerifySettings = BuildGameVerifySettings(options), - GameInstallationsSettings = BuildInstallationSettings(options), - Output = output, - NewBaselinePath = options.NewBaselineFile + VerifyPipelineSettings = new VerifyPipelineSettings + { + ParallelVerifiers = verifyOptions.Parallel ? 4 : 1, + VerifiersProvider = new DefaultGameVerifiersProvider(), + FailFast = verifyOptions.FailFast, + GameVerifySettings = new GameVerifySettings + { + IgnoreAsserts = verifyOptions.IgnoreAsserts, + ThrowsOnMinimumSeverity = GetVerifierMinimumThrowSeverity() + } + }, + AppThrowsOnMinimumSeverity = verifyOptions.MinimumFailureSeverity, + GameInstallationsSettings = BuildInstallationSettings(verifyOptions), + GlobalReportSettings = BuilderGlobalReportSettings(verifyOptions), + ReportOutput = output, + Offline = verifyOptions.OfflineMode }; + + VerificationSeverity? GetVerifierMinimumThrowSeverity() + { + var minFailSeverity = verifyOptions.MinimumFailureSeverity; + if (verifyOptions.FailFast) + { + if (minFailSeverity == null) + { + _logger?.LogWarning($"Verification is configured to fail fast but 'minFailSeverity' is not specified. " + + $"Using severity '{VerificationSeverity.Information}'."); + minFailSeverity = VerificationSeverity.Information; + } + + return minFailSeverity; + } + + // Only in a failFast scenario we want the verifier to throw. + // In a normal run, the verifier should simply store the error. + return null; + } } - private GameVerifySettings BuildGameVerifySettings(ModVerifyOptions options) + private ModVerifyAppSettings BuildFromCreateBaselineVerb(CreateBaselineVerbOption baselineVerb) { - var settings = GameVerifySettings.Default; - - return settings with + return new ModVerifyAppSettings { - GlobalReportSettings = BuilderGlobalReportSettings(options), - AbortSettings = BuildAbortSettings(options) + VerifyPipelineSettings = new VerifyPipelineSettings + { + ParallelVerifiers = baselineVerb.Parallel ? 4 : 1, + GameVerifySettings = new GameVerifySettings + { + IgnoreAsserts = false, + ThrowsOnMinimumSeverity = null, + }, + VerifiersProvider = new DefaultGameVerifiersProvider(), + FailFast = false, + }, + AppThrowsOnMinimumSeverity = null, + GameInstallationsSettings = BuildInstallationSettings(baselineVerb), + GlobalReportSettings = BuilderGlobalReportSettings(baselineVerb), + NewBaselinePath = baselineVerb.OutputFile, + ReportOutput = null, + Offline = baselineVerb.OfflineMode }; } - private VerificationAbortSettings BuildAbortSettings(ModVerifyOptions options) + private GlobalVerifyReportSettings BuilderGlobalReportSettings(BaseModVerifyOptions options) { - return new VerificationAbortSettings + return new GlobalVerifyReportSettings { - FailFast = options.FailFast, - MinimumAbortSeverity = options.MinimumFailureSeverity ?? VerificationSeverity.Information, - ThrowsGameVerificationException = options.MinimumFailureSeverity.HasValue || options.FailFast + Baseline = CreateBaseline(), + Suppressions = CreateSuppressions(), + MinimumReportSeverity = options.MinimumSeverity, }; - } - private GlobalVerificationReportSettings BuilderGlobalReportSettings(ModVerifyOptions options) - { - var baseline = VerificationBaseline.Empty; - var suppressions = SuppressionList.Empty; - - if (options.Baseline is not null) + VerificationBaseline CreateBaseline() { - using var fs = _fileSystem.FileStream.New(options.Baseline, FileMode.Open, FileAccess.Read); - baseline = VerificationBaseline.FromJson(fs); + // It does not make sense to create a baseline on another baseline. + if (options is not VerifyVerbOption verifyOptions || string.IsNullOrEmpty(verifyOptions.Baseline)) + return VerificationBaseline.Empty; + + using var fs = _fileSystem.FileStream.New(verifyOptions.Baseline!, FileMode.Open, FileAccess.Read); + + try + { + return VerificationBaseline.FromJson(fs); + } + catch (IncompatibleBaselineException) + { + Console.WriteLine($"The baseline '{verifyOptions.Baseline}' is not compatible with with version of ModVerify." + + $"{Environment.NewLine}Please generate a new baseline file or download the latest version." + + $"{Environment.NewLine}"); + throw; + } } - if (options.Suppressions is not null) + SuppressionList CreateSuppressions() { + if (options.Suppressions is null) + return SuppressionList.Empty; using var fs = _fileSystem.FileStream.New(options.Suppressions, FileMode.Open, FileAccess.Read); - suppressions = SuppressionList.FromJson(fs); + return SuppressionList.FromJson(fs); } - - return new GlobalVerificationReportSettings - { - Baseline = baseline, - Suppressions = suppressions, - MinimumReportSeverity = VerificationSeverity.Information, - }; } - private GameInstallationsSettings BuildInstallationSettings(ModVerifyOptions options) + private GameInstallationsSettings BuildInstallationSettings(BaseModVerifyOptions options) { var modPaths = new List(); if (options.ModPaths is not null) diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs b/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs new file mode 100644 index 0000000..968e6a9 --- /dev/null +++ b/src/ModVerify.CliApp/Updates/GithubReleaseEntry.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace AET.ModVerifyTool.Updates; + +[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] +[method: JsonConstructor] +internal sealed class GithubReleaseEntry(string tag, string branch, bool isPrerelease) +{ + [JsonPropertyName("tag_name")] + public string Tag { get; } = tag; + + [JsonPropertyName("target_commitish")] + public string Branch { get; } = branch; + + [JsonPropertyName("prerelease")] + public bool IsPrerelease { get; } = isPrerelease; +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Updates/GithubReleaseList.cs b/src/ModVerify.CliApp/Updates/GithubReleaseList.cs new file mode 100644 index 0000000..a54edfb --- /dev/null +++ b/src/ModVerify.CliApp/Updates/GithubReleaseList.cs @@ -0,0 +1,5 @@ +using System.Collections.Generic; + +namespace AET.ModVerifyTool.Updates; + +internal sealed class GithubReleaseList : List; \ No newline at end of file diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs new file mode 100644 index 0000000..d0ab59b --- /dev/null +++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterChecker.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Semver; + +namespace AET.ModVerifyTool.Updates; + +internal sealed class ModVerifyUpdaterChecker +{ + private readonly ILogger? _logger; + + public ModVerifyUpdaterChecker(IServiceProvider serviceProvider) + { + _logger = serviceProvider.GetService()?.CreateLogger(GetType()); + } + + public async Task CheckForUpdateAsync() + { + var githubReleases = await DownloadReleaseList().ConfigureAwait(false); + + var branch = ModVerifyUpdaterInformation.BranchName; + var latestRelease = githubReleases.FirstOrDefault(r => r.Branch == branch); + + if (latestRelease == null) + throw new InvalidOperationException($"Unable to find a release for branch '{branch}'."); + + if (!SemVersion.TryParse(latestRelease.Tag, SemVersionStyles.Any, out var latestVersion)) + throw new InvalidOperationException($"Cannot create a version from tag '{latestRelease.Tag}'."); + + var currentVersion = ModVerifyUpdaterInformation.CurrentVersion; + if (currentVersion is null) + throw new InvalidOperationException("Unable to get current version."); + + if (SemVersion.ComparePrecedence(currentVersion, latestVersion) >= 0) + { + _logger?.LogDebug($"No update available - [Current Version = {currentVersion}], [Available Version = {latestVersion}]"); + return default; + } + + _logger?.LogDebug($"Update available - [Current Version = {currentVersion}], [Available Version = {latestVersion}]"); + return new UpdateInfo + { + DownloadLink = ModVerifyUpdaterInformation.ModVerifyReleasesDownloadLink, + IsUpdateAvailable = true, + NewVersion = latestVersion.ToString() + }; + } + + private static async Task DownloadReleaseList() + { + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ModVerifyUpdaterInformation.UserAgent); + using var downloadStream = await httpClient.GetStreamAsync(ModVerifyUpdaterInformation.GithubReleasesApiLink).ConfigureAwait(false); + + using var jsonStream = new MemoryStream(); + await downloadStream.CopyToAsync(jsonStream).ConfigureAwait(false); + jsonStream.Seek(0, SeekOrigin.Begin); + + var releases = await JsonSerializer.DeserializeAsync(jsonStream).ConfigureAwait(false); + return releases ?? throw new InvalidOperationException("Unable to deserialize releases."); + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs new file mode 100644 index 0000000..2fa1022 --- /dev/null +++ b/src/ModVerify.CliApp/Updates/ModVerifyUpdaterInformation.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; +using Semver; + +namespace AET.ModVerifyTool.Updates; + +internal static class ModVerifyUpdaterInformation +{ + public const string BranchName = "main"; + public const string GithubReleasesApiLink = "https://api.github.com/repos/AlamoEngine-Tools/ModVerify/releases"; + public const string ModVerifyReleasesDownloadLink = "https://github.com/AlamoEngine-Tools/ModVerify/releases/latest"; + public const string UserAgent = "AET.Modifo"; + + public static readonly SemVersion? CurrentVersion; + + static ModVerifyUpdaterInformation() + { + var currentAssembly = typeof(ModVerifyUpdaterInformation).Assembly; + var fi = FileVersionInfo.GetVersionInfo(currentAssembly.Location); + SemVersion.TryParse(fi.ProductVersion, SemVersionStyles.Any, out var currentVersion); + CurrentVersion = currentVersion; + } +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/Updates/UpdateInfo.cs b/src/ModVerify.CliApp/Updates/UpdateInfo.cs new file mode 100644 index 0000000..b6a6505 --- /dev/null +++ b/src/ModVerify.CliApp/Updates/UpdateInfo.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + +namespace AET.ModVerifyTool.Updates; + +internal readonly struct UpdateInfo +{ + public string DownloadLink { get; init; } + + public string? NewVersion { get; init; } + + [MemberNotNullWhen(true, nameof(DownloadLink), nameof(NewVersion))] + public bool IsUpdateAvailable { get; init; } + +} \ No newline at end of file diff --git a/src/ModVerify.CliApp/VerifyGameInstallationData.cs b/src/ModVerify.CliApp/VerifyInstallationInformation.cs similarity index 94% rename from src/ModVerify.CliApp/VerifyGameInstallationData.cs rename to src/ModVerify.CliApp/VerifyInstallationInformation.cs index 69513ca..66816ab 100644 --- a/src/ModVerify.CliApp/VerifyGameInstallationData.cs +++ b/src/ModVerify.CliApp/VerifyInstallationInformation.cs @@ -3,7 +3,7 @@ namespace AET.ModVerifyTool; -internal class VerifyGameInstallationData +internal sealed class VerifyInstallationInformation { public required string Name { get; init; } diff --git a/src/ModVerify/GameVerificationException.cs b/src/ModVerify/GameVerificationException.cs index 64415d9..1096179 100644 --- a/src/ModVerify/GameVerificationException.cs +++ b/src/ModVerify/GameVerificationException.cs @@ -5,29 +5,37 @@ namespace AET.ModVerify; -public sealed class GameVerificationException(IEnumerable errors) : Exception +public sealed class GameVerificationException : Exception { - private readonly string? _error = null; - private readonly IEnumerable _errors = errors ?? throw new ArgumentNullException(nameof(errors)); - - public GameVerificationException(VerificationError error) : this([error]) - { - } - - /// - public override string Message => Error; - - private string Error + private readonly string? _errorMessage = null; + + public IReadOnlyCollection Errors { get; } + + private string ErrorMessage { get { - if (_error != null) - return _error; + if (_errorMessage != null) + return _errorMessage; var stringBuilder = new StringBuilder(); - foreach (var error in _errors) - stringBuilder.AppendLine($"Verification error: {error.Id}:{error.Message};"); + foreach (var error in Errors) + stringBuilder.AppendLine($"Verification error: {error.Id}: {error.Message};"); return stringBuilder.ToString().TrimEnd(';'); } } + + /// + public override string Message => ErrorMessage; + + public GameVerificationException(VerificationError error) : this([error]) + { + } + + public GameVerificationException(IEnumerable errors) + { + if (errors is null) + throw new ArgumentNullException(nameof(errors)); + Errors = [..errors]; + } } \ No newline at end of file diff --git a/src/ModVerify/IGameVerifier.cs b/src/ModVerify/IGameVerifier.cs deleted file mode 100644 index 1184c86..0000000 --- a/src/ModVerify/IGameVerifier.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using AET.ModVerify.Reporting; - -namespace AET.ModVerify; - -public interface IGameVerifier -{ - string Name { get; } - - string FriendlyName { get; } - - IReadOnlyCollection VerifyErrors { get; } -} \ No newline at end of file diff --git a/src/ModVerify/IVerificationProvider.cs b/src/ModVerify/IVerificationProvider.cs deleted file mode 100644 index 2d87ab5..0000000 --- a/src/ModVerify/IVerificationProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using AET.ModVerify.Settings; -using AET.ModVerify.Verifiers; -using PG.StarWarsGame.Engine.Database; - -namespace AET.ModVerify; - -public interface IVerificationProvider -{ - IEnumerable GetAllDefaultVerifiers(IGameDatabase database, GameVerifySettings settings); -} \ No newline at end of file diff --git a/src/ModVerify/ModVerify.csproj b/src/ModVerify/ModVerify.csproj index 4a77b42..e5f5b7d 100644 --- a/src/ModVerify/ModVerify.csproj +++ b/src/ModVerify/ModVerify.csproj @@ -22,21 +22,27 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - + + + + + + diff --git a/src/ModVerify/ModVerify.csproj.DotSettings b/src/ModVerify/ModVerify.csproj.DotSettings new file mode 100644 index 0000000..fcd6f14 --- /dev/null +++ b/src/ModVerify/ModVerify.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/src/ModVerify/ModVerifyServiceContribution.cs b/src/ModVerify/ModVerifyServiceContribution.cs deleted file mode 100644 index d223d2c..0000000 --- a/src/ModVerify/ModVerifyServiceContribution.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace AET.ModVerify; - -public static class ModVerifyServiceContribution -{ - public static void ContributeServices(IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(sp => new VerificationProvider(sp)); - } -} \ No newline at end of file diff --git a/src/ModVerify/ModVerifyServiceExtensions.cs b/src/ModVerify/ModVerifyServiceExtensions.cs new file mode 100644 index 0000000..8b46960 --- /dev/null +++ b/src/ModVerify/ModVerifyServiceExtensions.cs @@ -0,0 +1,12 @@ +using AET.ModVerify.Verifiers; +using Microsoft.Extensions.DependencyInjection; + +namespace AET.ModVerify; + +public static class ModVerifyServiceExtensions +{ + public static IServiceCollection RegisterVerifierCache(this IServiceCollection serviceCollection) + { + return serviceCollection.AddSingleton(sp => new AlreadyVerifiedCache(sp)); + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs b/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs new file mode 100644 index 0000000..26b3893 --- /dev/null +++ b/src/ModVerify/Pipeline/DefaultGameVerifiersProvider.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using AET.ModVerify.Settings; +using AET.ModVerify.Verifiers; +using AET.ModVerify.Verifiers.GuiDialogs; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Pipeline; + +public sealed class DefaultGameVerifiersProvider : IGameVerifiersProvider +{ + public IEnumerable GetVerifiers( + IStarWarsGameEngine database, + GameVerifySettings settings, + IServiceProvider serviceProvider) + { + yield return new ReferencedModelsVerifier(database, settings, serviceProvider); + yield return new DuplicateNameFinder(database, settings, serviceProvider); + yield return new AudioFilesVerifier(database, settings, serviceProvider); + yield return new GuiDialogsVerifier(database, settings, serviceProvider); + //yield return new CommandBarVerifier(database, settings, serviceProvider); + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs new file mode 100644 index 0000000..a27ee9b --- /dev/null +++ b/src/ModVerify/Pipeline/GameVerifierPipelineStep.cs @@ -0,0 +1,55 @@ +using AET.ModVerify.Verifiers; +using AnakinRaW.CommonUtilities.SimplePipeline; +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; +using AnakinRaW.CommonUtilities.SimplePipeline.Steps; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using AET.ModVerify.Pipeline.Progress; + +namespace AET.ModVerify.Pipeline; + +public sealed class GameVerifierPipelineStep( + GameVerifier verifier, + IServiceProvider serviceProvider) + : PipelineStep(serviceProvider), IProgressStep +{ + public event EventHandler>? Progress; + + internal GameVerifier GameVerifier { get; } = verifier ?? throw new ArgumentNullException(nameof(verifier)); + + public ProgressType Type => VerifyProgress.ProgressType; + + public long Size => 1; + + protected override void RunCore(CancellationToken token) + { + try + { + Logger?.LogDebug($"Running verifier '{GameVerifier.FriendlyName}'..."); + ReportProgress(new ProgressEventArgs(0.0, "Started")); + + GameVerifier.Progress += OnVerifyProgress; + GameVerifier.Verify(token); + + Logger?.LogDebug($"Finished verifier '{GameVerifier.FriendlyName}'"); + ReportProgress(new ProgressEventArgs(1.0, "Finished")); + } + finally + { + GameVerifier.Progress += OnVerifyProgress; + } + } + + private void OnVerifyProgress(object _, ProgressEventArgs e) + { + if (e.Progress > 1.0) + e = new ProgressEventArgs(1.0, e.ProgressText, e.ProgressInfo); + ReportProgress(e); + } + + private void ReportProgress(ProgressEventArgs e) + { + Progress?.Invoke(this, e); + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/GameVerifyPipeline.cs b/src/ModVerify/Pipeline/GameVerifyPipeline.cs new file mode 100644 index 0000000..b7ac00d --- /dev/null +++ b/src/ModVerify/Pipeline/GameVerifyPipeline.cs @@ -0,0 +1,133 @@ +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Settings; +using AET.ModVerify.Settings; +using AET.ModVerify.Utilities; +using AET.ModVerify.Verifiers; +using AnakinRaW.CommonUtilities.SimplePipeline; +using AnakinRaW.CommonUtilities.SimplePipeline.Runners; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AET.ModVerify.Pipeline.Progress; + +namespace AET.ModVerify.Pipeline; + +public sealed class GameVerifyPipeline : AnakinRaW.CommonUtilities.SimplePipeline.Pipeline +{ + private readonly List _verifiers = new(); + private readonly List _verificationSteps = new(); + private readonly StepRunnerBase _verifyRunner; + + private readonly IStarWarsGameEngine _gameEngine; + private readonly IGameEngineErrorCollection _engineErrors; + + private readonly VerifyPipelineSettings _pipelineSettings; + private readonly GlobalVerifyReportSettings _reportSettings; + + private readonly IVerifyProgressReporter _progressReporter; + + protected override bool FailFast { get; } + + public IReadOnlyCollection FilteredErrors { get; private set; } = []; + + public GameVerifyPipeline( + IStarWarsGameEngine gameEngine, + IGameEngineErrorCollection engineErrors, + VerifyPipelineSettings pipelineSettings, + GlobalVerifyReportSettings reportSettings, + IVerifyProgressReporter progressReporter, + IServiceProvider serviceProvider) : base(serviceProvider) + { + _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); + _engineErrors = engineErrors ?? throw new ArgumentNullException(nameof(gameEngine)); + _pipelineSettings = pipelineSettings ?? throw new ArgumentNullException(nameof(pipelineSettings)); + _reportSettings = reportSettings ?? throw new ArgumentNullException(nameof(reportSettings)); + _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); + + if (pipelineSettings.ParallelVerifiers is < 0 or > 64) + throw new ArgumentException("_pipelineSettings has invalid parallel worker number.", nameof(pipelineSettings)); + + if (pipelineSettings.ParallelVerifiers == 1) + _verifyRunner = new SequentialStepRunner(serviceProvider); + else + _verifyRunner = new ParallelStepRunner(pipelineSettings.ParallelVerifiers, serviceProvider); + + FailFast = pipelineSettings.FailFast; + } + + protected override Task PrepareCoreAsync() + { + _verifiers.Clear(); + + AddStep(new GameEngineErrorCollector(_engineErrors, _gameEngine, _pipelineSettings.GameVerifySettings, ServiceProvider)); + + foreach (var gameVerificationStep in CreateVerificationSteps(_gameEngine)) + AddStep(gameVerificationStep); + + return Task.FromResult(true); + } + + protected override async Task RunCoreAsync(CancellationToken token) + { + var aggregatedVerifyProgressReporter = new AggregatedVerifyProgressReporter(_progressReporter, _verificationSteps); + + try + { + Logger?.LogInformation("Running game verifiers..."); + _verifyRunner.Error += OnError; + await _verifyRunner.RunAsync(token); + } + finally + { + aggregatedVerifyProgressReporter.Dispose(); + _verifyRunner.Error -= OnError; + Logger?.LogDebug("Game verifiers finished."); + } + + token.ThrowIfCancellationRequested(); + + var failedSteps = _verifyRunner.ExecutedSteps.Where(p => + p.Error != null && !p.Error.IsExceptionType()).ToList(); + + if (failedSteps.Count != 0) + throw new StepFailureException(failedSteps); + + FilteredErrors = GetReportableErrors(_verifiers.SelectMany(s => s.VerifyErrors)).ToList(); + } + + protected override void OnError(object sender, StepRunnerErrorEventArgs e) + { + if (FailFast && e.Exception is GameVerificationException v) + { + if (v.Errors.All(error => _reportSettings.Baseline.Contains(error) || _reportSettings.Suppressions.Suppresses(error))) + return; + } + base.OnError(sender, e); + } + + private IEnumerable CreateVerificationSteps(IStarWarsGameEngine database) + { + return _pipelineSettings.VerifiersProvider.GetVerifiers(database, _pipelineSettings.GameVerifySettings, ServiceProvider); + } + + private void AddStep(GameVerifier verifier) + { + var verificationStep = new GameVerifierPipelineStep(verifier, ServiceProvider); + _verifyRunner.AddStep(verificationStep); + _verificationSteps.Add(verificationStep); + _verifiers.Add(verifier); + } + + private IEnumerable GetReportableErrors(IEnumerable errors) + { + Logger?.LogDebug("Applying baseline and suppressions."); + // NB: We don't filter for severity here, as the individual reporters handle that. + // This allows better control over what gets reported. + return errors.ApplyBaseline(_reportSettings.Baseline) + .ApplySuppressions(_reportSettings.Suppressions); + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/IGameVerifiersProvider.cs b/src/ModVerify/Pipeline/IGameVerifiersProvider.cs new file mode 100644 index 0000000..930afa6 --- /dev/null +++ b/src/ModVerify/Pipeline/IGameVerifiersProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using AET.ModVerify.Settings; +using AET.ModVerify.Verifiers; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Pipeline; + +public interface IGameVerifiersProvider +{ + IEnumerable GetVerifiers( + IStarWarsGameEngine database, + GameVerifySettings settings, + IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs new file mode 100644 index 0000000..361febe --- /dev/null +++ b/src/ModVerify/Pipeline/Progress/AggregatedVerifyProgressReporter.cs @@ -0,0 +1,72 @@ +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; +using System; +using System.Collections.Generic; + +namespace AET.ModVerify.Pipeline.Progress; + +internal class AggregatedVerifyProgressReporter( + IVerifyProgressReporter progressReporter, + IEnumerable steps) + : AggregatedProgressReporter(progressReporter, steps) +{ + private readonly object _syncLock = new(); + private readonly IDictionary _progressTable = new Dictionary(StringComparer.OrdinalIgnoreCase); + private double _completedSize; + private readonly HashSet _completedVerifiers = new(); + + protected override string GetProgressText(GameVerifierPipelineStep step, string? progressText) + { + return string.IsNullOrEmpty(progressText) + ? $"Verifier '{step.GameVerifier.FriendlyName}'" + : $"Verifier '{step.GameVerifier.FriendlyName}': {progressText}"; + } + + protected override ProgressEventArgs CalculateAggregatedProgress( + GameVerifierPipelineStep step, + ProgressEventArgs progress) + { + var key = step.GameVerifier.Name; + + var progressValue = progress.Progress; + var stepProgress = progressValue * step.Size; + + if (progressValue >= 1.0) + _completedVerifiers.Add(key); + + double totalProgress; + + lock (_syncLock) + { + if (!_progressTable.TryGetValue(key, out var storedProgress)) + { + _progressTable.Add(key, stepProgress); + _completedSize += stepProgress; + } + else + { + var deltaSize = stepProgress - storedProgress; + _progressTable[key] = stepProgress; + _completedSize += deltaSize; + } + + totalProgress = _completedSize / TotalSize; + totalProgress = Math.Min(totalProgress, 1.0); + } + + if (_completedVerifiers.Count >= TotalStepCount && progressValue >= 1.0) + totalProgress = 1.0; + + var progressInfo = new VerifyProgressInfo + { + TotalVerifiers = TotalStepCount, + }; + return new ProgressEventArgs(totalProgress, progress.ProgressText, progressInfo); + } + + protected override void DisposeResources() + { + base.DisposeResources(); + _progressTable.Clear(); + _completedVerifiers.Clear(); + } +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs b/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs new file mode 100644 index 0000000..e63e0f9 --- /dev/null +++ b/src/ModVerify/Pipeline/Progress/IVerifyProgressReporter.cs @@ -0,0 +1,5 @@ +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; + +namespace AET.ModVerify.Pipeline.Progress; + +public interface IVerifyProgressReporter : IProgressReporter; \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgress.cs b/src/ModVerify/Pipeline/Progress/VerifyProgress.cs new file mode 100644 index 0000000..a18f3d3 --- /dev/null +++ b/src/ModVerify/Pipeline/Progress/VerifyProgress.cs @@ -0,0 +1,12 @@ +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; + +namespace AET.ModVerify.Pipeline.Progress; + +public static class VerifyProgress +{ + public static readonly ProgressType ProgressType = new() + { + Id = "Verify", + DisplayName = "Verify" + }; +} \ No newline at end of file diff --git a/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs new file mode 100644 index 0000000..1409239 --- /dev/null +++ b/src/ModVerify/Pipeline/Progress/VerifyProgressInfo.cs @@ -0,0 +1,8 @@ +namespace AET.ModVerify.Pipeline.Progress; + +public struct VerifyProgressInfo +{ + public bool IsDetailed { get; init; } + + public int TotalVerifiers { get; internal set; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs b/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs new file mode 100644 index 0000000..3130547 --- /dev/null +++ b/src/ModVerify/Reporting/ConcurrentGameEngineErrorReporter.cs @@ -0,0 +1,34 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Engine.ErrorReporting; + +namespace AET.ModVerify.Reporting; + +public sealed class ConcurrentGameEngineErrorReporter : GameEngineErrorReporter, IGameEngineErrorCollection +{ + private readonly ConcurrentBag _xmlErrors = new(); + private readonly ConcurrentBag _initializationErrors = new(); + private readonly ConcurrentBag _asserts = new(); + + public IEnumerable XmlErrors => _xmlErrors.ToList(); + + public IEnumerable InitializationErrors => _initializationErrors.ToList(); + + public IEnumerable Asserts => _asserts.ToList(); + + public override void Report(XmlError error) + { + _xmlErrors.Add(error); + } + + public override void Report(InitializationError error) + { + _initializationErrors.Add(error); + } + + public override void Assert(EngineAssert assert) + { + _asserts.Add(assert); + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/IGameEngineErrorCollection.cs b/src/ModVerify/Reporting/IGameEngineErrorCollection.cs new file mode 100644 index 0000000..14d59d9 --- /dev/null +++ b/src/ModVerify/Reporting/IGameEngineErrorCollection.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.ErrorReporting; + +namespace AET.ModVerify.Reporting; + +public interface IGameEngineErrorCollection +{ + IEnumerable XmlErrors { get; } + IEnumerable InitializationErrors { get; } + IEnumerable Asserts { get; } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/IncompatibleBaselineException.cs b/src/ModVerify/Reporting/IncompatibleBaselineException.cs new file mode 100644 index 0000000..c9a9eb1 --- /dev/null +++ b/src/ModVerify/Reporting/IncompatibleBaselineException.cs @@ -0,0 +1,8 @@ +using System; + +namespace AET.ModVerify.Reporting; + +public sealed class IncompatibleBaselineException : Exception +{ + public override string Message => "The specified baseline is not compatible to this version of the application."; +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs b/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs index 92047d8..6d232c7 100644 --- a/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs +++ b/src/ModVerify/Reporting/Json/JsonSuppressionFilter.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace AET.ModVerify.Reporting.Json; @@ -13,7 +12,7 @@ internal class JsonSuppressionFilter(SuppressionFilter filter) [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Verifier { get; } = filter.Verifier; - [JsonPropertyName("assets")] + [JsonPropertyName("asset")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IEnumerable? Assets { get; } = filter.Assets; + public string? Asset { get; } = filter.Asset; } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs index 303e168..4688c98 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationBaseline.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; @@ -6,17 +7,28 @@ namespace AET.ModVerify.Reporting.Json; internal class JsonVerificationBaseline { + [JsonPropertyName("version")] + public Version? Version { get; } + + [JsonPropertyName("minSeverity")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public VerificationSeverity MinimumSeverity { get; } + [JsonPropertyName("errors")] public IEnumerable Errors { get; } public JsonVerificationBaseline(VerificationBaseline baseline) { Errors = baseline.Select(x => new JsonVerificationError(x)); + Version = baseline.Version; + MinimumSeverity = baseline.MinimumSeverity; } [JsonConstructor] - private JsonVerificationBaseline(IEnumerable errors) + private JsonVerificationBaseline(Version version, VerificationSeverity minimumSeverity, IEnumerable errors) { Errors = errors; + Version = version; + MinimumSeverity = minimumSeverity; } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Json/JsonVerificationError.cs b/src/ModVerify/Reporting/Json/JsonVerificationError.cs index 9c69d8d..ac80810 100644 --- a/src/ModVerify/Reporting/Json/JsonVerificationError.cs +++ b/src/ModVerify/Reporting/Json/JsonVerificationError.cs @@ -8,34 +8,46 @@ internal class JsonVerificationError [JsonPropertyName("id")] public string Id { get; } - [JsonPropertyName("verifier")] - public string Verifier { get; } + [JsonPropertyName("verifiers")] + public IReadOnlyList VerifierChain { get; } [JsonPropertyName("message")] public string Message { get; } [JsonPropertyName("severity")] + [JsonConverter(typeof(JsonStringEnumConverter))] public VerificationSeverity Severity { get; } - [JsonPropertyName("assets")] - public IEnumerable Assets { get; } + [JsonPropertyName("context")] + public IEnumerable ContextEntries { get; } + + [JsonPropertyName("asset")] + public string Asset { get; } [JsonConstructor] - private JsonVerificationError(string id, string verifier, string message, VerificationSeverity severity, IEnumerable assets) + private JsonVerificationError( + string id, + IReadOnlyList? verifierChain, + string message, + VerificationSeverity severity, + IEnumerable? contextEntries, + string asset) { Id = id; - Verifier = verifier; + VerifierChain = verifierChain ?? []; Message = message; Severity = severity; - Assets = assets; + ContextEntries = contextEntries ?? []; + Asset = asset ?? string.Empty; } public JsonVerificationError(VerificationError error) { Id = error.Id; - Verifier = error.Verifier; + VerifierChain = error.VerifierChain; Message = error.Message; Severity = error.Severity; - Assets = error.AffectedAssets; + ContextEntries = error.ContextEntries; + Asset = error.Asset; } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs index bda277f..7ee435d 100644 --- a/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs +++ b/src/ModVerify/Reporting/Reporters/ConsoleReporter.cs @@ -2,28 +2,68 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using AET.ModVerify.Reporting.Settings; namespace AET.ModVerify.Reporting.Reporters; -internal class ConsoleReporter(VerificationReportSettings settings, IServiceProvider serviceProvider) : ReporterBase(settings, serviceProvider) +internal class ConsoleReporter( + VerifyReportSettings settings, + bool summaryOnly, + IServiceProvider serviceProvider) : + ReporterBase(settings, serviceProvider) { public override Task ReportAsync(IReadOnlyCollection errors) { - var filteredErrors = FilteredErrors(errors).ToList(); + var filteredErrors = FilteredErrors(errors).OrderByDescending(x => x.Severity).ToList(); + PrintErrorStats(errors, filteredErrors); + Console.WriteLine(); + return Task.CompletedTask; + } + private void PrintErrorStats(IReadOnlyCollection errors, List filteredErrors) + { + Console.WriteLine(); Console.WriteLine(); - Console.WriteLine("GAME VERIFICATION RESULT"); - Console.WriteLine($"Errors of severity {Settings.MinimumReportSeverity}: {filteredErrors.Count}"); + Console.WriteLine("***********************"); + Console.WriteLine(" Error Report "); + Console.WriteLine("***********************"); Console.WriteLine(); + if (errors.Count == 0) + { + if (summaryOnly) + { + Console.WriteLine("No errors found."); + } + else + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("No errors! Well done :)"); + } - if (filteredErrors.Count == 0) - Console.WriteLine("No errors!"); + Console.ResetColor(); + return; + } - foreach (var error in filteredErrors) - Console.WriteLine(error); + Console.WriteLine($"TOTAL Verification Errors: {errors.Count}"); + var groupedBySeverity = errors.GroupBy(x => x.Severity); + foreach (var group in groupedBySeverity) + Console.WriteLine($" Severity {group.Key}: {group.Count()}"); Console.WriteLine(); - return Task.CompletedTask; + if (filteredErrors.Count == 0) + { + if (errors.Count != 0) + Console.WriteLine("Some errors are not displayed to the console. Please check the created output files."); + return; + } + + if (summaryOnly) + return; + + Console.WriteLine($"Below the list of errors with severity '{Settings.MinimumReportSeverity}' or higher:"); + + foreach (var error in filteredErrors) + Console.WriteLine($"[{error.Severity}] [{error.Id}] Message={error.Message}"); } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs new file mode 100644 index 0000000..5ecbb6f --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/Engine/EngineErrorReporterBase.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using AnakinRaW.CommonUtilities; +using PG.StarWarsGame.Engine.IO; + +namespace AET.ModVerify.Reporting.Reporters.Engine; + +internal abstract class EngineErrorReporterBase(IGameRepository gameRepository, IServiceProvider serviceProvider) +{ + protected readonly IGameRepository GameRepository = gameRepository ?? throw new ArgumentNullException(nameof(gameRepository)); + protected readonly IServiceProvider ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + public abstract string Name { get; } + + public IEnumerable GetErrors(IEnumerable errors) + { + foreach (var error in errors) + { + var errorData = CreateError(error); + yield return new VerificationError( + errorData.Identifier, errorData.Message, [Name], errorData.Context, errorData.Asset, errorData.Severity); + } + } + + protected abstract ErrorData CreateError(T error); + + protected readonly ref struct ErrorData + { + public string Identifier { get; } + public string Message { get; } + public IEnumerable Context { get; } + public string Asset { get; } + public VerificationSeverity Severity { get; } + + public ErrorData(string identifier, string message, IEnumerable context, string asset, VerificationSeverity severity) + { + ThrowHelper.ThrowIfNullOrEmpty(identifier); + ThrowHelper.ThrowIfNullOrEmpty(message); + Identifier = identifier; + Message = message; + Context = context; + Asset = asset; + Severity = severity; + } + + public ErrorData(string identifier, string message, string asset, VerificationSeverity severity) : this(identifier, message, [], asset, severity) + { + } + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs new file mode 100644 index 0000000..a2a00c5 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/Engine/GameAssertErrorReporter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; +using AET.ModVerify.Verifiers; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO; + +namespace AET.ModVerify.Reporting.Reporters.Engine; + +internal sealed class GameAssertErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) + : EngineErrorReporterBase(gameRepository, serviceProvider) +{ + public override string Name => "GameAsserts"; + + protected override ErrorData CreateError(EngineAssert assert) + { + var context = new List(); + + if (assert.Value is not null) + context.Add($"value='{assert.Value}'"); + if (assert.Context is not null) + context.Add($"context='{assert.Context}'"); + + // The location is the only identifiable thing of an assert. 'Value' might be null, thus we cannot use it. + var asset = GetLocation(assert); + + return new ErrorData(GetIdFromError(assert.Kind), assert.Message, asset, VerificationSeverity.Warning); + } + + private static string GetLocation(EngineAssert assert) + { + var sb = new StringBuilder("method='"); + if (assert.TypeName is not null) + { + sb.Append(assert.TypeName); + sb.Append("::"); + } + sb.Append(assert.Method); + sb.Append('\''); + return sb.ToString(); + } + + private static string GetIdFromError(EngineAssertKind assertKind) + { + return assertKind switch + { + EngineAssertKind.NullOrEmptyValue => VerifierErrorCodes.AssertValueNullOrEmpty, + EngineAssertKind.ValueOutOfRange => VerifierErrorCodes.AssertValueOutOfRange, + EngineAssertKind.InvalidValue => VerifierErrorCodes.AssertValueInvalid, + EngineAssertKind.FileNotFound => VerifierErrorCodes.FileNotFound, + _ => throw new ArgumentOutOfRangeException(nameof(assertKind), assertKind, null) + }; + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs new file mode 100644 index 0000000..fc44c66 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/Engine/InitializationErrorReporter.cs @@ -0,0 +1,21 @@ +using System; +using AET.ModVerify.Verifiers; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO; + +namespace AET.ModVerify.Reporting.Reporters.Engine; + +internal sealed class InitializationErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) + : EngineErrorReporterBase(gameRepository, serviceProvider) +{ + public override string Name => "InitializationErrors"; + + protected override ErrorData CreateError(InitializationError error) + { + return new ErrorData( + VerifierErrorCodes.InitializationError, + error.Message, + error.GameManager, + VerificationSeverity.Critical); + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs b/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs new file mode 100644 index 0000000..094a701 --- /dev/null +++ b/src/ModVerify/Reporting/Reporters/Engine/XmlParseErrorReporter.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using AET.ModVerify.Utilities; +using AET.ModVerify.Verifiers; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Files.XML.ErrorHandling; + +namespace AET.ModVerify.Reporting.Reporters.Engine; + +internal sealed class XmlParseErrorReporter(IGameRepository gameRepository, IServiceProvider serviceProvider) : + EngineErrorReporterBase(gameRepository, serviceProvider) +{ + private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); + + public override string Name => "XMLError"; + + protected override ErrorData CreateError(XmlError error) + { + var id = GetIdFromError(error.ErrorKind); + var severity = GetSeverityFromError(error.ErrorKind); + + var strippedFileName = _fileSystem.Path + .GetGameStrippedPath(GameRepository.Path.AsSpan(), error.FileLocation.XmlFile.ToUpperInvariant().AsSpan()).ToString(); + + var asset = strippedFileName; + + var context = new List + { + strippedFileName + }; + + var xmlElement = error.Element; + + if (xmlElement is not null) + { + var localName = xmlElement.Name.LocalName; + context.Add(localName); + + asset = localName; + + var parent = xmlElement.Parent; + + if (parent != null) + { + var parentName = parent.Attribute("Name"); + context.Add(parentName != null ? $"parentName='{parentName.Value}'" : $"parentTag='{parent.Name.LocalName}'"); + } + } + + var errorMessage = CreateErrorMessage(error, strippedFileName); + return new ErrorData(id, errorMessage, context, asset, severity); + } + + private static string CreateErrorMessage(XmlError error, string strippedFileName) + { + if (error.FileLocation.Line.HasValue) + return $"{error.Message} File='{strippedFileName} #{error.FileLocation.Line.Value}'"; + return $"{error.Message} File='{strippedFileName}'"; + } + + private static VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind) + { + return xmlErrorErrorKind switch + { + XmlParseErrorKind.EmptyRoot => VerificationSeverity.Critical, + XmlParseErrorKind.MissingFile => VerificationSeverity.Error, + XmlParseErrorKind.InvalidValue => VerificationSeverity.Information, + XmlParseErrorKind.MalformedValue => VerificationSeverity.Warning, + XmlParseErrorKind.MissingAttribute => VerificationSeverity.Error, + XmlParseErrorKind.MissingReference => VerificationSeverity.Error, + XmlParseErrorKind.TooLongData => VerificationSeverity.Warning, + XmlParseErrorKind.DataBeforeHeader => VerificationSeverity.Information, + XmlParseErrorKind.MissingNode => VerificationSeverity.Critical, + XmlParseErrorKind.UnknownNode => VerificationSeverity.Information, + _ => VerificationSeverity.Warning + }; + } + + private static string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind) + { + return xmlErrorErrorKind switch + { + XmlParseErrorKind.EmptyRoot => VerifierErrorCodes.EmptyXmlRoot, + XmlParseErrorKind.MissingFile => VerifierErrorCodes.FileNotFound, + XmlParseErrorKind.InvalidValue => VerifierErrorCodes.InvalidXmlValue, + XmlParseErrorKind.MalformedValue => VerifierErrorCodes.MalformedXmlValue, + XmlParseErrorKind.MissingAttribute => VerifierErrorCodes.MissingXmlAttribute, + XmlParseErrorKind.MissingReference => VerifierErrorCodes.MissingXmlReference, + XmlParseErrorKind.TooLongData => VerifierErrorCodes.XmlValueTooLong, + XmlParseErrorKind.Unknown => VerifierErrorCodes.GenericXmlError, + XmlParseErrorKind.DataBeforeHeader => VerifierErrorCodes.XmlDataBeforeHeader, + XmlParseErrorKind.MissingNode => VerifierErrorCodes.XmlMissingNode, + XmlParseErrorKind.UnknownNode => VerifierErrorCodes.XmlUnsupportedTag, + _ => throw new ArgumentOutOfRangeException(nameof(xmlErrorErrorKind), xmlErrorErrorKind, null) + }; + } +} \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs index d0213d0..1057bfa 100644 --- a/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs +++ b/src/ModVerify/Reporting/Reporters/FileBasedReporter.cs @@ -1,11 +1,13 @@ using System; using System.IO; using System.IO.Abstractions; +using AET.ModVerify.Reporting.Settings; using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Reporting.Reporters; -public abstract class FileBasedReporter(T settings, IServiceProvider serviceProvider) : ReporterBase(settings, serviceProvider) where T : FileBasedReporterSettings +public abstract class FileBasedReporter(T settings, IServiceProvider serviceProvider) + : ReporterBase(settings, serviceProvider) where T : FileBasedReporterSettings { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs index 731482a..941a9d5 100644 --- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs +++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporter.cs @@ -7,7 +7,8 @@ namespace AET.ModVerify.Reporting.Reporters.JSON; -internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serviceProvider) : FileBasedReporter(settings, serviceProvider) +internal class JsonReporter(JsonReporterSettings settings, IServiceProvider serviceProvider) + : FileBasedReporter(settings, serviceProvider) { public const string FileName = "VerificationResult.json"; diff --git a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs index fd5962d..4207b36 100644 --- a/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/JSON/JsonReporterSettings.cs @@ -1,3 +1,5 @@ -namespace AET.ModVerify.Reporting.Reporters.JSON; +using AET.ModVerify.Reporting.Settings; + +namespace AET.ModVerify.Reporting.Reporters.JSON; public record JsonReporterSettings : FileBasedReporterSettings; \ No newline at end of file diff --git a/src/ModVerify/Reporting/Reporters/ReporterBase.cs b/src/ModVerify/Reporting/Reporters/ReporterBase.cs index 9360c0e..fd86119 100644 --- a/src/ModVerify/Reporting/Reporters/ReporterBase.cs +++ b/src/ModVerify/Reporting/Reporters/ReporterBase.cs @@ -2,16 +2,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using AET.ModVerify.Reporting.Settings; namespace AET.ModVerify.Reporting.Reporters; -public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : VerificationReportSettings +public abstract class ReporterBase(T settings, IServiceProvider serviceProvider) : IVerificationReporter where T : VerifyReportSettings { protected IServiceProvider ServiceProvider { get; } = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); protected T Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings)); - public abstract Task ReportAsync(IReadOnlyCollection errors); protected IEnumerable FilteredErrors(IReadOnlyCollection errors) diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs index 9ff34dd..cfc5c91 100644 --- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs +++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporter.cs @@ -32,7 +32,7 @@ private async Task ReportWhole(IReadOnlyCollection errors) private async Task ReportByVerifier(IReadOnlyCollection errors) { - var grouped = errors.GroupBy(x => x.Verifier); + var grouped = errors.GroupBy(x => x.VerifierChain.Last()); foreach (var group in grouped) await ReportToSingleFile(group); } diff --git a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs index e6847a3..8fb833b 100644 --- a/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs +++ b/src/ModVerify/Reporting/Reporters/Text/TextFileReporterSettings.cs @@ -1,4 +1,6 @@ -namespace AET.ModVerify.Reporting.Reporters.Text; +using AET.ModVerify.Reporting.Settings; + +namespace AET.ModVerify.Reporting.Reporters.Text; public record TextFileReporterSettings : FileBasedReporterSettings { diff --git a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs index 04f7c7d..601e424 100644 --- a/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs +++ b/src/ModVerify/Reporting/Reporters/VerificationReportersExtensions.cs @@ -1,5 +1,6 @@ using AET.ModVerify.Reporting.Reporters.JSON; using AET.ModVerify.Reporting.Reporters.Text; +using AET.ModVerify.Reporting.Settings; using Microsoft.Extensions.DependencyInjection; namespace AET.ModVerify.Reporting.Reporters; @@ -22,26 +23,33 @@ public static IServiceCollection RegisterTextFileReporter(this IServiceCollectio }); } - public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection) + public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection, bool summaryOnly = false) { - return RegisterConsoleReporter(serviceCollection, new VerificationReportSettings + return RegisterConsoleReporter(serviceCollection, new VerifyReportSettings { MinimumReportSeverity = VerificationSeverity.Error - }); + }, summaryOnly); } - public static IServiceCollection RegisterJsonReporter(this IServiceCollection serviceCollection, JsonReporterSettings settings) + public static IServiceCollection RegisterJsonReporter( + this IServiceCollection serviceCollection, + JsonReporterSettings settings) { return serviceCollection.AddSingleton(sp => new JsonReporter(settings, sp)); } - public static IServiceCollection RegisterTextFileReporter(this IServiceCollection serviceCollection, TextFileReporterSettings settings) + public static IServiceCollection RegisterTextFileReporter( + this IServiceCollection serviceCollection, + TextFileReporterSettings settings) { return serviceCollection.AddSingleton(sp => new TextFileReporter(settings, sp)); } - public static IServiceCollection RegisterConsoleReporter(this IServiceCollection serviceCollection, VerificationReportSettings settings) + public static IServiceCollection RegisterConsoleReporter( + this IServiceCollection serviceCollection, + VerifyReportSettings settings, + bool summaryOnly = false) { - return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, sp)); + return serviceCollection.AddSingleton(sp => new ConsoleReporter(settings, summaryOnly, sp)); } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/FileBasedReporterSettings.cs b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs similarity index 72% rename from src/ModVerify/Reporting/FileBasedReporterSettings.cs rename to src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs index 6616258..c6233a1 100644 --- a/src/ModVerify/Reporting/FileBasedReporterSettings.cs +++ b/src/ModVerify/Reporting/Settings/FileBasedReporterSettings.cs @@ -1,8 +1,8 @@ using System; -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Settings; -public record FileBasedReporterSettings : VerificationReportSettings +public record FileBasedReporterSettings : VerifyReportSettings { private readonly string _outputDirectory = Environment.CurrentDirectory; diff --git a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs similarity index 57% rename from src/ModVerify/Reporting/GlobalVerificationReportSettings.cs rename to src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs index 3982d4d..b376fe3 100644 --- a/src/ModVerify/Reporting/GlobalVerificationReportSettings.cs +++ b/src/ModVerify/Reporting/Settings/GlobalVerifyReportSettings.cs @@ -1,8 +1,6 @@ -using System; +namespace AET.ModVerify.Reporting.Settings; -namespace AET.ModVerify.Reporting; - -public record GlobalVerificationReportSettings : VerificationReportSettings +public record GlobalVerifyReportSettings : VerifyReportSettings { public VerificationBaseline Baseline { get; init; } = VerificationBaseline.Empty; diff --git a/src/ModVerify/Reporting/VerificationReportSettings.cs b/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs similarity index 57% rename from src/ModVerify/Reporting/VerificationReportSettings.cs rename to src/ModVerify/Reporting/Settings/VerifyReportSettings.cs index fc02024..1289822 100644 --- a/src/ModVerify/Reporting/VerificationReportSettings.cs +++ b/src/ModVerify/Reporting/Settings/VerifyReportSettings.cs @@ -1,6 +1,6 @@ -namespace AET.ModVerify.Reporting; +namespace AET.ModVerify.Reporting.Settings; -public record VerificationReportSettings +public record VerifyReportSettings { public VerificationSeverity MinimumReportSeverity { get; init; } = VerificationSeverity.Information; } \ No newline at end of file diff --git a/src/ModVerify/Reporting/SuppressionFilter.cs b/src/ModVerify/Reporting/SuppressionFilter.cs index 7fb41de..efac588 100644 --- a/src/ModVerify/Reporting/SuppressionFilter.cs +++ b/src/ModVerify/Reporting/SuppressionFilter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using AET.ModVerify.Reporting.Json; @@ -7,33 +6,26 @@ namespace AET.ModVerify.Reporting; public sealed class SuppressionFilter : IEquatable { - private static readonly AssetsEqualityComparer AssetComparer = AssetsEqualityComparer.Instance; - - private readonly HashSet? _assets; - public string? Id { get; } public string? Verifier { get; } - public IReadOnlyCollection? Assets { get; } + public string? Asset { get; } - public SuppressionFilter(string? id, string? verifier, ICollection? assets) + public bool IsDisabled => Id == null && Verifier == null && Asset == null; + + public SuppressionFilter(string? id, string? verifier, string? asset) { Id = id; Verifier = verifier; - if (assets is not null) - _assets = new HashSet(assets); - Assets = _assets?.ToList() ?? null; + Asset = asset; } internal SuppressionFilter(JsonSuppressionFilter filter) { Id = filter.Id; Verifier = filter.Verifier; - - if (filter.Assets is not null) - _assets = new HashSet(filter.Assets); - Assets = _assets?.ToList() ?? null; + Asset = filter.Asset; } public bool Suppresses(VerificationError error) @@ -50,31 +42,23 @@ public bool Suppresses(VerificationError error) if (Verifier is not null) { - if (Verifier.Equals(error.Verifier)) + if (error.VerifierChain.Contains(Verifier)) suppresses = true; else return false; } - if (_assets is not null) + if (Asset is not null) { - if (_assets.Count != error.AffectedAssets.Count) - return false; - - if (!_assets.SetEquals(error.AffectedAssets)) + if (error.Asset.Equals(Asset)) + suppresses = true; + else return false; - - suppresses = true; } return suppresses; } - public bool IsDisabled() - { - return Id == null && Verifier == null && (Assets == null || !Assets.Any()); - } - public int Specificity() { var specificity = 0; @@ -82,7 +66,7 @@ public int Specificity() specificity++; if (Verifier != null) specificity++; - if (Assets != null && Assets.Any()) + if (Asset != null) specificity++; return specificity; } @@ -95,7 +79,7 @@ public bool IsSupersededBy(SuppressionFilter other) if (Verifier != null && other.Verifier != null && other.Verifier != Verifier) return false; - if (Assets != null && other.Assets != null && !AssetComparer.Equals(_assets, other._assets)) + if (Asset != null && other.Asset != null) return false; return other.Specificity() < Specificity(); @@ -112,8 +96,7 @@ public bool Equals(SuppressionFilter? other) return false; if (Verifier != other.Verifier) return false; - - return AssetComparer.Equals(_assets, other._assets); + return Asset == other.Asset; } public override bool Equals(object? obj) @@ -126,10 +109,7 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(Id); hashCode.Add(Verifier); - if (_assets is not null) - hashCode.Add(_assets, AssetComparer); - else - hashCode.Add((HashSet)null!); + hashCode.Add(Asset); return hashCode.ToHashCode(); } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/SuppressionList.cs b/src/ModVerify/Reporting/SuppressionList.cs index 23d1563..12ebab4 100644 --- a/src/ModVerify/Reporting/SuppressionList.cs +++ b/src/ModVerify/Reporting/SuppressionList.cs @@ -22,7 +22,7 @@ public SuppressionList(IEnumerable suppressionFilters) if (suppressionFilters == null) throw new ArgumentNullException(nameof(suppressionFilters)); - _filters = new List(suppressionFilters); + _filters = [..suppressionFilters]; _minimizedFilters = MinimizeSuppressions(_filters); } @@ -48,6 +48,10 @@ public static SuppressionList FromJson(Stream stream) return new SuppressionList(baselineJson); } + public IEnumerable Apply(IEnumerable errors) + { + return Count == 0 ? errors : errors.Where(e => !Suppresses(e)); + } public bool Suppresses(VerificationError error) { @@ -64,7 +68,7 @@ public bool Suppresses(VerificationError error) private static IReadOnlyCollection MinimizeSuppressions(IEnumerable filters) { - var sortedFilters = filters.Where(f => !f.IsDisabled()) + var sortedFilters = filters.Where(f => !f.IsDisabled) .OrderBy(x => x.Specificity()); var result = new List(); diff --git a/src/ModVerify/Reporting/VerificationBaseline.cs b/src/ModVerify/Reporting/VerificationBaseline.cs index 81cb8b3..55a8973 100644 --- a/src/ModVerify/Reporting/VerificationBaseline.cs +++ b/src/ModVerify/Reporting/VerificationBaseline.cs @@ -11,21 +11,41 @@ namespace AET.ModVerify.Reporting; public sealed class VerificationBaseline : IReadOnlyCollection { - public static readonly VerificationBaseline Empty = new([]); + private static readonly Version LatestVersion = new(2, 0); + + public static readonly VerificationBaseline Empty = new(VerificationSeverity.Information, []); private readonly HashSet _errors; + public Version? Version { get; } + + public VerificationSeverity MinimumSeverity { get; } + /// public int Count => _errors.Count; internal VerificationBaseline(JsonVerificationBaseline baseline) { - _errors = new HashSet(baseline.Errors.Select(x => new VerificationError(x))); + _errors = [..baseline.Errors.Select(x => new VerificationError(x))]; + Version = baseline.Version; + MinimumSeverity = baseline.MinimumSeverity; + } + + public VerificationBaseline(VerificationSeverity minimumSeverity, IEnumerable errors) + { + _errors = [..errors]; + Version = LatestVersion; + MinimumSeverity = minimumSeverity; + } + + public bool Contains(VerificationError error) + { + return _errors.Contains(error); } - public VerificationBaseline(IEnumerable errors) + public IEnumerable Apply(IEnumerable errors) { - _errors = new(errors); + return Count == 0 ? errors : errors.Where(e => !_errors.Contains(e)); } public void ToJson(Stream stream) @@ -42,13 +62,12 @@ public static VerificationBaseline FromJson(Stream stream) { var baselineJson = JsonSerializer.Deserialize(stream, JsonSerializerOptions.Default); if (baselineJson is null) - throw new InvalidOperationException("Unable to deserialize baseline"); - return new VerificationBaseline(baselineJson); - } + throw new InvalidOperationException("Unable to deserialize baseline."); - public VerificationBaseline MergeWith(IEnumerable errors) - { - return new VerificationBaseline(this.Concat(errors)); + if (baselineJson.Version is null || baselineJson.Version != LatestVersion) + throw new IncompatibleBaselineException(); + + return new VerificationBaseline(baselineJson); } /// @@ -61,4 +80,9 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + public override string ToString() + { + return $"Baseline [Version={Version}, MinSeverity={MinimumSeverity}, NumErrors={Count}]"; + } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/VerificationError.cs b/src/ModVerify/Reporting/VerificationError.cs index fa25cf3..4174f32 100644 --- a/src/ModVerify/Reporting/VerificationError.cs +++ b/src/ModVerify/Reporting/VerificationError.cs @@ -2,98 +2,89 @@ using System.Collections.Generic; using System.Linq; using AET.ModVerify.Reporting.Json; +using AET.ModVerify.Verifiers; using AnakinRaW.CommonUtilities; namespace AET.ModVerify.Reporting; public sealed class VerificationError : IEquatable { - private static readonly AssetsEqualityComparer AssetComparer = AssetsEqualityComparer.Instance; + private static readonly VerificationErrorContextEqualityComparer ContextComparer = VerificationErrorContextEqualityComparer.Instance; - private readonly HashSet _assets; + private readonly HashSet _contextEntries; public string Id { get; } public string Message { get; } - public string Verifier { get; } + public IReadOnlyList VerifierChain { get; } - public IReadOnlyCollection AffectedAssets { get; } + public IReadOnlyCollection ContextEntries { get; } public VerificationSeverity Severity { get; } - public VerificationError(string id, string message, string verifier, IEnumerable affectedAssets, VerificationSeverity severity) + public string Asset { get; } + + public VerificationError( + string id, + string message, + IReadOnlyList verifiers, + IEnumerable contextEntries, + string asset, + VerificationSeverity severity) { - if (affectedAssets == null) - throw new ArgumentNullException(nameof(affectedAssets)); + if (contextEntries == null) + throw new ArgumentNullException(nameof(contextEntries)); + if (asset is null) + throw new ArgumentNullException(nameof(asset)); ThrowHelper.ThrowIfNullOrEmpty(id); + Id = id; Message = message ?? throw new ArgumentNullException(nameof(message)); - Verifier = verifier; + VerifierChain = verifiers; Severity = severity; - _assets = new HashSet(affectedAssets); - AffectedAssets = _assets.ToList(); + _contextEntries = [.. contextEntries]; + ContextEntries = _contextEntries.ToList(); + Asset = asset; } internal VerificationError(JsonVerificationError error) { Id = error.Id; Message = error.Message; - Verifier = error.Verifier; - _assets = new HashSet(error.Assets); - AffectedAssets = _assets.ToList(); + VerifierChain = error.VerifierChain; + _contextEntries = [..error.ContextEntries]; + ContextEntries = _contextEntries.ToList(); + Asset = error.Asset; } public static VerificationError Create( - IGameVerifier verifier, + IReadOnlyList verifiers, string id, string message, - VerificationSeverity severity, - IEnumerable assets) - { - return new VerificationError(id, message, verifier.Name, assets, severity); - } - - - public static VerificationError Create( - IGameVerifier verifier, - string id, - string message, - VerificationSeverity severity, - params string[] assets) + VerificationSeverity severity, + IEnumerable context, + string asset) { - return new VerificationError(id, message, verifier.Name, assets, severity); + return new VerificationError(id, message, verifiers.Select(x => x.Name).ToList(), context, asset, severity); } public static VerificationError Create( - IGameVerifier verifier, - string id, + IReadOnlyList verifiers, + string id, string message, - VerificationSeverity severity) - { - return Create(verifier, id, message, severity, []); - } - - internal static VerificationError Create( - IGameVerifier verifier, - string id, - Exception exception, VerificationSeverity severity, - params string[] assets) + string asset) { - return new VerificationError(id, exception.Message, verifier.Name, assets, severity); + return new VerificationError( + id, + message, + verifiers.Select(x => x.Name).ToList(), + [], + asset, + severity); } - internal static VerificationError Create( - IGameVerifier verifier, - string id, - Exception exception, - VerificationSeverity severity) - { - return Create(verifier, id, exception, severity, []); - } - - public bool Equals(VerificationError? other) { if (other is null) @@ -103,10 +94,11 @@ public bool Equals(VerificationError? other) if (!Id.Equals(other.Id)) return false; - if (!Verifier.Equals(other.Verifier)) + + if (!Asset.Equals(other.Asset)) return false; - return AssetComparer.Equals(_assets, other._assets); + return ContextComparer.Equals(_contextEntries, other._contextEntries); } public override bool Equals(object? obj) @@ -118,13 +110,13 @@ public override int GetHashCode() { var hashCode = new HashCode(); hashCode.Add(Id); - hashCode.Add(Verifier); - hashCode.Add(_assets, AssetComparer); + hashCode.Add(_contextEntries, ContextComparer); + hashCode.Add(Asset); return hashCode.ToHashCode(); } public override string ToString() { - return $"[{Severity}] [{Verifier}] {Id}: Message={Message}; Affected Assets=[{string.Join(",", AffectedAssets)}];"; + return $"[{Severity}] [{string.Join(" --> ", VerifierChain)}] {Id}: Message={Message}; Asset='{Asset}'; Context=[{string.Join(",", ContextEntries)}];"; } } \ No newline at end of file diff --git a/src/ModVerify/Reporting/AssetsEqualityComparer.cs b/src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs similarity index 63% rename from src/ModVerify/Reporting/AssetsEqualityComparer.cs rename to src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs index 8f058bc..773b260 100644 --- a/src/ModVerify/Reporting/AssetsEqualityComparer.cs +++ b/src/ModVerify/Reporting/VerificationErrorContextEqualityComparer.cs @@ -2,13 +2,13 @@ namespace AET.ModVerify.Reporting; -internal class AssetsEqualityComparer : IEqualityComparer> +internal class VerificationErrorContextEqualityComparer : IEqualityComparer> { readonly IEqualityComparer> _setComparer = HashSet.CreateSetComparer(); - public static AssetsEqualityComparer Instance { get; } = new(); + public static VerificationErrorContextEqualityComparer Instance { get; } = new(); - private AssetsEqualityComparer() + private VerificationErrorContextEqualityComparer() { } diff --git a/src/ModVerify/Reporting/VerificationReportBroker.cs b/src/ModVerify/Reporting/VerificationReportBroker.cs index f9c3c68..5c1e8a3 100644 --- a/src/ModVerify/Reporting/VerificationReportBroker.cs +++ b/src/ModVerify/Reporting/VerificationReportBroker.cs @@ -1,22 +1,17 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using AET.ModVerify.Verifiers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace AET.ModVerify.Reporting; -internal class VerificationReportBroker(GlobalVerificationReportSettings reportSettings, IServiceProvider serviceProvider) +public sealed class VerificationReportBroker(IServiceProvider serviceProvider) { private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(VerificationReportBroker)); - - public async Task> ReportAsync(IEnumerable steps) + public async Task ReportAsync(IReadOnlyCollection errors) { - var suppressions = new SuppressionList(reportSettings.Suppressions); - var errors = GetReportableErrors(steps, suppressions); var reporters = serviceProvider.GetServices(); foreach (var reporter in reporters) @@ -30,28 +25,5 @@ public async Task> ReportAsync(IEnumerabl _logger?.LogError(e, "Exception while reporting verification error"); } } - - return errors; - } - - private IReadOnlyCollection GetReportableErrors(IEnumerable steps, SuppressionList suppressions) - { - var allErrors = steps.SelectMany(s => s.VerifyErrors); - - var errorsToReport = new List(); - foreach (var error in allErrors) - { - if (reportSettings.Baseline.Contains(error)) - continue; - - if (suppressions.Suppresses(error)) - continue; - - errorsToReport.Add(error); - } - - // NB: We don't filter for severity here, as that is something the individual reporters should handle. - // This allows better control over what gets reported. - return errorsToReport; } } \ No newline at end of file diff --git a/src/ModVerify/Settings/GameVerifySettings.cs b/src/ModVerify/Settings/GameVerifySettings.cs index 488bfe4..3f84670 100644 --- a/src/ModVerify/Settings/GameVerifySettings.cs +++ b/src/ModVerify/Settings/GameVerifySettings.cs @@ -6,16 +6,14 @@ public record GameVerifySettings { public static readonly GameVerifySettings Default = new() { - AbortSettings = new(), - GlobalReportSettings = new(), - LocalizationOption = VerifyLocalizationOption.English + LocalizationOption = VerifyLocalizationOption.English, + IgnoreAsserts = false, + ThrowsOnMinimumSeverity = null }; - public int ParallelVerifiers { get; init; } = 4; - - public VerificationAbortSettings AbortSettings { get; init; } - - public GlobalVerificationReportSettings GlobalReportSettings { get; init; } + public VerificationSeverity? ThrowsOnMinimumSeverity { get; init; } public VerifyLocalizationOption LocalizationOption { get; init; } + + public bool IgnoreAsserts { get; init; } } \ No newline at end of file diff --git a/src/ModVerify/Settings/VerificationAbortSettings.cs b/src/ModVerify/Settings/VerificationAbortSettings.cs deleted file mode 100644 index 767cef4..0000000 --- a/src/ModVerify/Settings/VerificationAbortSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -using AET.ModVerify.Reporting; - -namespace AET.ModVerify.Settings; - -public record VerificationAbortSettings -{ - public bool FailFast { get; init; } = false; - - public VerificationSeverity MinimumAbortSeverity { get; init; } = VerificationSeverity.Warning; - - public bool ThrowsGameVerificationException { get; init; } = false; -} \ No newline at end of file diff --git a/src/ModVerify/Settings/VerifyPipelineSettings.cs b/src/ModVerify/Settings/VerifyPipelineSettings.cs new file mode 100644 index 0000000..2e11c74 --- /dev/null +++ b/src/ModVerify/Settings/VerifyPipelineSettings.cs @@ -0,0 +1,14 @@ +using AET.ModVerify.Pipeline; + +namespace AET.ModVerify.Settings; + +public sealed class VerifyPipelineSettings +{ + public required GameVerifySettings GameVerifySettings { get; init; } + + public required IGameVerifiersProvider VerifiersProvider { get; init; } + + public bool FailFast { get; init; } + + public int ParallelVerifiers { get; init; } = 4; +} \ No newline at end of file diff --git a/src/ModVerify/Utilities/Extensions.cs b/src/ModVerify/Utilities/Extensions.cs new file mode 100644 index 0000000..996f474 --- /dev/null +++ b/src/ModVerify/Utilities/Extensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; + +namespace AET.ModVerify.Utilities; + +internal static class Extensions +{ + public static bool IsExceptionType(this Exception error) where T : Exception + { + switch (error) + { + case T _: + return true; + case AggregateException aggregateException: + return aggregateException.InnerExceptions.Any(p => p.IsExceptionType()); + default: + return false; + } + } +} \ No newline at end of file diff --git a/src/ModVerify/Utilities/PathExtensions.cs b/src/ModVerify/Utilities/PathExtensions.cs new file mode 100644 index 0000000..6ddc47a --- /dev/null +++ b/src/ModVerify/Utilities/PathExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.IO.Abstractions; +using AnakinRaW.CommonUtilities.FileSystem; + +namespace AET.ModVerify.Utilities; + +public static class PathExtensions +{ + public static ReadOnlySpan GetGameStrippedPath(this IPath path, ReadOnlySpan gamePath, ReadOnlySpan modPath) + { + if (!path.IsPathFullyQualified(modPath)) + return modPath; + + if (modPath.Length <= gamePath.Length) + return modPath; + + if (path.IsChildOf(gamePath, modPath)) + return modPath.Slice(gamePath.Length); + + return modPath; + } +} \ No newline at end of file diff --git a/src/ModVerify/Utilities/VerificationErrorExtensions.cs b/src/ModVerify/Utilities/VerificationErrorExtensions.cs new file mode 100644 index 0000000..1244b45 --- /dev/null +++ b/src/ModVerify/Utilities/VerificationErrorExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using AET.ModVerify.Reporting; + +namespace AET.ModVerify.Utilities; + +public static class VerificationErrorExtensions +{ + public static IEnumerable ApplyBaseline(this IEnumerable errors, + VerificationBaseline baseline) + { + if (errors == null) + throw new ArgumentNullException(nameof(errors)); + if (baseline == null) + throw new ArgumentNullException(nameof(baseline)); + return baseline.Apply(errors); + } + + public static IEnumerable ApplySuppressions(this IEnumerable errors, + SuppressionList suppressions) + { + if (errors == null) + throw new ArgumentNullException(nameof(errors)); + if (suppressions == null) + throw new ArgumentNullException(nameof(suppressions)); + return suppressions.Apply(errors); + } +} \ No newline at end of file diff --git a/src/ModVerify/VerificationProvider.cs b/src/ModVerify/VerificationProvider.cs deleted file mode 100644 index a1800dc..0000000 --- a/src/ModVerify/VerificationProvider.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using AET.ModVerify.Settings; -using AET.ModVerify.Verifiers; -using PG.StarWarsGame.Engine.Database; - -namespace AET.ModVerify; - -internal class VerificationProvider(IServiceProvider serviceProvider) : IVerificationProvider -{ - public IEnumerable GetAllDefaultVerifiers(IGameDatabase database, GameVerifySettings settings) - { - yield return new ReferencedModelsVerifier(database, settings, serviceProvider); - yield return new DuplicateNameFinder(database, settings, serviceProvider); - yield return new AudioFilesVerifier(database, settings, serviceProvider); - } -} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs b/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs new file mode 100644 index 0000000..530ca4e --- /dev/null +++ b/src/ModVerify/Verifiers/AlreadyVerifiedCache.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Verifiers; + +internal sealed class AlreadyVerifiedCache(IServiceProvider serviceProvider) : IAlreadyVerifiedCache +{ + private readonly ICrc32HashingService _crc32Hashing = serviceProvider.GetRequiredService(); + private readonly ConcurrentDictionary _cachedChecksums = new(); + + public bool TryAddEntry(string entry) + { + return TryAddEntry(entry.AsSpan()); + } + + public bool TryAddEntry(ReadOnlySpan entry) + { + return TryAddEntry(_crc32Hashing.GetCrc32Upper(entry, PGConstants.DefaultPGEncoding)); + } + + public bool TryAddEntry(Crc32 checksum) + { + return _cachedChecksums.TryAdd(checksum, 0); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/AudioFilesVerifier.cs b/src/ModVerify/Verifiers/AudioFilesVerifier.cs index 1f4a3e9..c5e2085 100644 --- a/src/ModVerify/Verifiers/AudioFilesVerifier.cs +++ b/src/ModVerify/Verifiers/AudioFilesVerifier.cs @@ -1,17 +1,19 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using System.Linq; +using System.Text; using System.Threading; using AET.ModVerify.Reporting; using AET.ModVerify.Settings; +using AnakinRaW.CommonUtilities.FileSystem.Normalization; using Microsoft.Extensions.DependencyInjection; using PG.Commons.Hashing; using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Language; +using PG.StarWarsGame.Engine.Audio.Sfx; +using PG.StarWarsGame.Engine.Localization; using PG.StarWarsGame.Files.MEG.Services.Builder.Normalization; #if NETSTANDARD2_0 using AnakinRaW.CommonUtilities.FileSystem; @@ -19,89 +21,121 @@ namespace AET.ModVerify.Verifiers; -public class AudioFilesVerifier : GameVerifierBase +public class AudioFilesVerifier : GameVerifier { - private readonly PetroglyphDataEntryPathNormalizer _pathNormalizer; + private static readonly PathNormalizeOptions SampleNormalizerOptions = new() + { + UnifyCase = UnifyCasingKind.UpperCaseForce, + UnifySeparatorKind = DirectorySeparatorKind.Windows, + UnifyDirectorySeparators = true + }; + + private readonly EmpireAtWarMegDataEntryPathNormalizer _pathNormalizer = EmpireAtWarMegDataEntryPathNormalizer.Instance; private readonly ICrc32HashingService _hashingService; private readonly IFileSystem _fileSystem; private readonly IGameLanguageManager _languageManager; - public AudioFilesVerifier(IGameDatabase gameDatabase, GameVerifySettings settings, IServiceProvider serviceProvider) : base(gameDatabase, settings, serviceProvider) + public AudioFilesVerifier(IStarWarsGameEngine gameEngine, GameVerifySettings settings, IServiceProvider serviceProvider) + : base(null, gameEngine, settings, serviceProvider) { - _pathNormalizer = new(serviceProvider); _hashingService = serviceProvider.GetRequiredService(); _fileSystem = serviceProvider.GetRequiredService(); _languageManager = serviceProvider.GetRequiredService() .GetLanguageManager(Repository.EngineType); } - public override string FriendlyName => "Verify Audio Files"; + public override string FriendlyName => "Audio Files"; - protected override void RunVerification(CancellationToken token) + public override void Verify(CancellationToken token) { var visitedSamples = new HashSet(); var languagesToVerify = GetLanguagesToVerify().ToList(); - foreach (var sfxEvent in Database.SfxEvents.Entries) + + + var numSamples = GameEngine.SfxGameManager.Entries.Sum(x => x.AllSamples.Count()); + double counter = 0; + + foreach (var sfxEvent in GameEngine.SfxGameManager.Entries) { foreach (var codedSample in sfxEvent.AllSamples) { - VerifySample(codedSample, sfxEvent, languagesToVerify, visitedSamples); + OnProgress(++counter / numSamples, $"Audio File - '{codedSample}'"); + VerifySample(codedSample.AsSpan(), sfxEvent, languagesToVerify, visitedSamples); } } } - private void VerifySample(string sample, SfxEvent sfxEvent, IEnumerable languagesToVerify, HashSet visitedSamples) + private void VerifySample(ReadOnlySpan sample, SfxEvent sfxEvent, IEnumerable languagesToVerify, HashSet visitedSamples) { - Span sampleNameBuffer = stackalloc char[PGConstants.MaxPathLength]; + char[]? pooledBuffer = null; - var i = _pathNormalizer.Normalize(sample.AsSpan(), sampleNameBuffer); - var normalizedSampleName = sampleNameBuffer.Slice(0, i); - var crc = _hashingService.GetCrc32(normalizedSampleName, PGConstants.PGCrc32Encoding); - if (!visitedSamples.Add(crc)) - return; + var buffer = sample.Length < PGConstants.MaxMegEntryPathLength + ? stackalloc char[PGConstants.MaxMegEntryPathLength] + : pooledBuffer = ArrayPool.Shared.Rent(sample.Length); - - if (normalizedSampleName.Length > PGConstants.MaxPathLength) + try { - AddError(VerificationError.Create( - this, - VerifierErrorCodes.FilePathTooLong, - $"Sample name '{sample}' is too long.", - VerificationSeverity.Error, - sample)); - return; - } + var length = PathNormalizer.Normalize(sample, buffer, SampleNormalizerOptions); + var sampleNameBuffer = buffer.Slice(0, length); - var normalizedSampleNameString = normalizedSampleName.ToString(); + var crc = _hashingService.GetCrc32(sampleNameBuffer, Encoding.ASCII); + if (!visitedSamples.Add(crc)) + return; - if (sfxEvent.IsLocalized) - { - foreach (var language in languagesToVerify) + if (sfxEvent.IsLocalized) { - var localizedSampleName = _languageManager.LocalizeFileName(normalizedSampleNameString, language, out var localized); - VerifySample(localizedSampleName, sfxEvent); - - if (!localized) - return; + foreach (var language in languagesToVerify) + { + VerifySampleLocalized(sfxEvent, sampleNameBuffer, language, out var localized); + if (!localized) + return; + } } + else + { + VerifySample(sampleNameBuffer, sfxEvent); + } + } + finally + { + if (pooledBuffer is not null) + ArrayPool.Shared.Return(pooledBuffer); + } + + } + + private void VerifySampleLocalized(SfxEvent sfxEvent, ReadOnlySpan sample, LanguageType language, out bool localized) + { + char[]? pooledBuffer = null; + + var buffer = sample.Length < PGConstants.MaxMegEntryPathLength + ? stackalloc char[PGConstants.MaxMegEntryPathLength] + : pooledBuffer = ArrayPool.Shared.Rent(sample.Length); + try + { + var l = _languageManager.LocalizeFileName(sample, language, buffer, out localized); + var localizedName = buffer.Slice(0, l); + VerifySample(localizedName, sfxEvent); } - else + finally { - VerifySample(normalizedSampleNameString, sfxEvent); + if (pooledBuffer is not null) + ArrayPool.Shared.Return(pooledBuffer); } } - private void VerifySample(string sample, SfxEvent sfxEvent) + private void VerifySample(ReadOnlySpan sample, SfxEvent sfxEvent) { using var sampleStream = Repository.TryOpenFile(sample); if (sampleStream is null) { + var sampleString = sample.ToString(); AddError(VerificationError.Create( - this, - VerifierErrorCodes.SampleNotFound, - $"Audio file '{sample}' could not be found.", + VerifierChain, + VerifierErrorCodes.SampleNotFound, + $"Audio file '{sampleString}' could not be found.", VerificationSeverity.Error, - sample)); + sampleString)); return; } using var binaryReader = new BinaryReader(sampleStream); @@ -121,42 +155,46 @@ private void VerifySample(string sample, SfxEvent sfxEvent) if (format != WaveFormats.PCM) { + var sampleString = sample.ToString(); AddError(VerificationError.Create( - this, + VerifierChain, VerifierErrorCodes.SampleNotPCM, - $"Audio file '{sample}' has an invalid format '{format}'. Supported is {WaveFormats.PCM}", + $"Audio file '{sampleString}' has an invalid format '{format}'. Supported is {WaveFormats.PCM}", VerificationSeverity.Error, - sample)); + sampleString)); } if (channels > 1 && !IsAmbient2D(sfxEvent)) { + var sampleString = sample.ToString(); AddError(VerificationError.Create( - this, + VerifierChain, VerifierErrorCodes.SampleNotMono, - $"Audio file '{sample}' is not mono audio.", - VerificationSeverity.Information, - sample)); + $"Audio file '{sampleString}' is not mono audio.", + VerificationSeverity.Information, + sampleString)); } if (sampleRate > 48_000) { + var sampleString = sample.ToString(); AddError(VerificationError.Create( - this, + VerifierChain, VerifierErrorCodes. InvalidSampleRate, - $"Audio file '{sample}' has a too high sample rate of {sampleRate}. Maximum is 48.000Hz.", + $"Audio file '{sampleString}' has a too high sample rate of {sampleRate}. Maximum is 48.000Hz.", VerificationSeverity.Error, - sample)); + sampleString)); } if (bitPerSecondPerChannel > 16) { + var sampleString = sample.ToString(); AddError(VerificationError.Create( - this, + VerifierChain, VerifierErrorCodes.InvalidBitsPerSeconds, - $"Audio file '{sample}' has an invalid bit size of {bitPerSecondPerChannel}. Supported are 16bit.", - VerificationSeverity.Error, - sample)); + $"Audio file '{sampleString}' has an invalid bit size of {bitPerSecondPerChannel}. Supported are 16bit.", + VerificationSeverity.Error, + sampleString)); } } @@ -192,7 +230,7 @@ private IEnumerable GetLanguagesToVerify() case VerifyLocalizationOption.CurrentSystem: return new List { _languageManager.GetLanguagesFromUser() }; case VerifyLocalizationOption.AllInstalled: - return Database.InstalledLanguages; + return GameEngine.InstalledLanguages; case VerifyLocalizationOption.All: return _languageManager.SupportedLanguages; default: diff --git a/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs b/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs new file mode 100644 index 0000000..3373f8e --- /dev/null +++ b/src/ModVerify/Verifiers/CommandBar/CommandBarVerifier.Base.cs @@ -0,0 +1,161 @@ +using System; +using System.Linq; +using System.Threading; +using AET.ModVerify.Reporting; +using AET.ModVerify.Settings; +using AnakinRaW.CommonUtilities.Collections; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Engine.CommandBar; +using PG.StarWarsGame.Engine.CommandBar.Components; + +namespace AET.ModVerify.Verifiers; + +public partial class CommandBarVerifier(IStarWarsGameEngine gameEngine, GameVerifySettings settings, IServiceProvider serviceProvider) + : GameVerifier(null, gameEngine, settings, serviceProvider) +{ + public const string CommandBarNoShellsGroup = "CMDBAR00"; + public const string CommandBarManyShellsGroup = "CMDBAR01"; + public const string CommandBarNoShellsComponentInShellGroup = "CMDBAR02"; + public const string CommandBarDuplicateComponent = "CMDBAR03"; + public const string CommandBarUnsupportedComponent = "CMDBAR04"; + public const string CommandBarShellNoModel = "CMDBAR05"; + + public override string FriendlyName => "CommandBar"; + + public override void Verify(CancellationToken token) + { + VerifyCommandBarShellsGroups(); + VerifyCommandBarComponents(); + } + + private void VerifySingleComponent(CommandBarBaseComponent component) + { + VerifyCommandBarModel(component); + VerifyComponentBone(component); + } +} + +partial class CommandBarVerifier +{ + private void VerifyCommandBarModel(CommandBarBaseComponent component) + { + if (component is not CommandBarShellComponent shellComponent) + return; + + if (shellComponent.ModelPath is null) + { + AddError(VerificationError.Create(VerifierChain, + CommandBarShellNoModel, $"The CommandBarShellComponent '{component.Name}' has no model specified.", + VerificationSeverity.Error, shellComponent.Name)); + return; + } + + var model = GameEngine.PGRender.LoadModelAndAnimations(shellComponent.ModelPath.AsSpan(), null); + if (model is null) + { + AddError(VerificationError.Create(VerifierChain, + CommandBarShellNoModel, $"Could not find model '{shellComponent.ModelPath}' for CommandBarShellComponent '{component.Name}'.", + VerificationSeverity.Error, [shellComponent.Name], shellComponent.ModelPath)); + return; + } + } + + private void VerifyComponentBone(CommandBarBaseComponent component) + { + if (component is CommandBarShellComponent) + return; + + if (component.Bone == -1) + { + AddError(VerificationError.Create(VerifierChain, + CommandBarShellNoModel, $"The CommandBar component '{component.Name}' is not connected to a shell component.", + VerificationSeverity.Warning, component.Name)); + } + } +} + +partial class CommandBarVerifier +{ + private void VerifyCommandBarComponents() + { + var occupiedComponentIds = SupportedCommandBarComponentData.GetComponentIdsForEngine(Repository.EngineType).Keys + .ToDictionary(value => value, _ => false); + + foreach (var component in GameEngine.CommandBar.Components) + { + if (!occupiedComponentIds.TryGetValue(component.Id, out var alreadyOccupied)) + { + AddError(VerificationError.Create( + VerifierChain, + CommandBarUnsupportedComponent, + $"The CommandBar component '{component.Name}' is not supported by the game.", + VerificationSeverity.Information, + component.Name)); + } + else + { + occupiedComponentIds[component.Id] = true; + } + + if (alreadyOccupied) + { + AddError(VerificationError.Create(VerifierChain, + CommandBarDuplicateComponent, + $"The CommandBar component '{component.Name}' with ID '{component.Id}' already exists.", + VerificationSeverity.Warning, + component.Name)); + } + + VerifySingleComponent(component); + } + } +} + +partial class CommandBarVerifier +{ + private void VerifyCommandBarShellsGroups() + { + var shellGroups = new FrugalList(); + foreach (var groupPair in GameEngine.CommandBar.Groups) + { + if (groupPair.Key == CommandBarConstants.ShellGroupName) + { + shellGroups.Add(groupPair.Key); + VerifyShellGroup(groupPair.Value); + } + else if (groupPair.Key.Equals(CommandBarConstants.ShellGroupName, StringComparison.OrdinalIgnoreCase)) + { + shellGroups.Add(groupPair.Key); + } + } + + if (shellGroups.Count == 0) + AddError(VerificationError.Create(VerifierChain, + CommandBarNoShellsGroup, + $"No CommandBarGroup '{CommandBarConstants.ShellGroupName}' found.", + VerificationSeverity.Error, + "GameCommandBar")); + + if (shellGroups.Count > 1) + AddError(VerificationError.Create(VerifierChain, + CommandBarManyShellsGroup, + $"Found more than one Shells CommandBarGroup. Mind that group names are case-sensitive. Correct name is '{CommandBarConstants.ShellGroupName}'", + VerificationSeverity.Warning, + shellGroups, "GameCommandBar")); + } + + private void VerifyShellGroup(CommandBarComponentGroup shellGroup) + { + foreach (var component in shellGroup.Components) + { + var shellComponent = component as CommandBarShellComponent; + if (shellComponent?.Type is not CommandBarComponentType.Shell) + { + AddError(VerificationError.Create(VerifierChain, + CommandBarNoShellsComponentInShellGroup, + $"The CommandBar component '{component.Name}' is not a shell component, but part of the '{CommandBarConstants.ShellGroupName}' group.", + VerificationSeverity.Warning, component.Name)); + } + } + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/Commons/ModelVerifier.cs b/src/ModVerify/Verifiers/Commons/ModelVerifier.cs new file mode 100644 index 0000000..25704d1 --- /dev/null +++ b/src/ModVerify/Verifiers/Commons/ModelVerifier.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AET.ModVerify.Reporting; +using AET.ModVerify.Settings; +using AET.ModVerify.Utilities; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Files; +using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.ALO.Files.Models; +using PG.StarWarsGame.Files.ALO.Files.Particles; +using PG.StarWarsGame.Files.Binary; +#if NETSTANDARD2_0 || NETFRAMEWORK +using AnakinRaW.CommonUtilities.FileSystem; +#endif + +namespace AET.ModVerify.Verifiers.Commons; + +public sealed class SingleModelVerifier : GameVerifier +{ + private const string ProxyAltIdentifier = "_ALT"; + + private readonly TextureVeifier _textureVerifier; + private readonly IAlreadyVerifiedCache? _cache; + + public SingleModelVerifier(IGameVerifierInfo? parent, + IStarWarsGameEngine engine, + GameVerifySettings settings, + IServiceProvider serviceProvider) : base(parent, engine, settings, serviceProvider) + { + _textureVerifier = new TextureVeifier(this, engine, settings, serviceProvider); + _cache = serviceProvider.GetService(); + } + + public override void Verify(string modelName, IReadOnlyCollection contextInfo, CancellationToken token) + { + try + { + _textureVerifier.Error += OnTextureError; + + var modelPath = BuildModelPath(modelName); + VerifyAlamoFile(modelPath, contextInfo, token); + } + finally + { + _textureVerifier.Error -= OnTextureError; + } + } + + private void OnTextureError(object sender, VerificationErrorEventArgs e) + { + AddError(e.Error); + } + + private void VerifyAlamoFile(string modelPath, IReadOnlyCollection contextInfo, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + var modelName = FileSystem.Path.GetFileName(modelPath.AsSpan()); + + if (_cache?.TryAddEntry(modelName) == false) + return; + + IAloFile? aloFile = null; + try + { + try + { + aloFile = GameEngine.PGRender.Load3DAsset(modelPath, true, true); + } + catch (BinaryCorruptedException e) + { + var aloFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), modelPath.AsSpan()).ToString(); + var message = $"'{aloFilePath}' is corrupted: {e.Message}"; + AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FileCorrupt, message, + VerificationSeverity.Critical, contextInfo, aloFilePath)); + return; + } + + if (aloFile is null) + { + var modelNameString = modelName.ToString(); + var error = VerificationError.Create( + VerifierChain, + VerifierErrorCodes.FileNotFound, + $"Unable to find .ALO file '{modelNameString}'", + VerificationSeverity.Error, + contextInfo, + modelNameString); + AddError(error); + return; + } + + VerifyModelOrParticle(aloFile, contextInfo, token); + } + finally + { + aloFile?.Dispose(); + } + } + + private void VerifyModelOrParticle( + IAloFile aloFile, + IReadOnlyCollection contextInfo, + CancellationToken token) + { + switch (aloFile) + { + case IAloModelFile model: + VerifyModel(model, contextInfo, token); + break; + case IAloParticleFile particle: + VerifyParticle(particle, contextInfo); + break; + default: + throw new InvalidOperationException("The data stream is neither a model nor particle."); + } + } + + private void VerifyParticle(IAloParticleFile file, IReadOnlyCollection contextInfo) + { + foreach (var texture in file.Content.Textures) + { + GuardedVerify(() => VerifyTextureExists(file, texture, contextInfo), + e => e is ArgumentException, + _ => + { + var particlePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString(); + AddError(VerificationError.Create( + VerifierChain, + VerifierErrorCodes.InvalidFilePath, + $"Invalid texture file name '{texture}' in particle '{particlePath}'", + VerificationSeverity.Error, + [particlePath], texture)); + }); + } + + var fileName = FileSystem.Path.GetFileNameWithoutExtension(file.FilePath.AsSpan()); + var name = file.Content.Name.AsSpan(); + + if (!fileName.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + var particlePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString(); + AddError(VerificationError.Create( + VerifierChain, + VerifierErrorCodes.InvalidParticleName, + $"The particle name '{file.Content.Name}' does not match file name '{particlePath}'", + VerificationSeverity.Error, + particlePath)); + } + + } + + private void VerifyModel(IAloModelFile file, IReadOnlyCollection contextInfo, CancellationToken token) + { + foreach (var texture in file.Content.Textures) + { + GuardedVerify(() => VerifyTextureExists(file, texture, contextInfo), + e => e is ArgumentException, + _ => + { + var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString(); + AddError(VerificationError.Create( + VerifierChain, + VerifierErrorCodes.InvalidFilePath, + $"Invalid texture file name '{texture}' in model '{modelFilePath}'", + VerificationSeverity.Error, + [modelFilePath], + texture)); + }); + } + + foreach (var shader in file.Content.Shaders) + { + GuardedVerify(() => VerifyShaderExists(file, shader, contextInfo), + e => e is ArgumentException, + _ => + { + var modelFilePath = + FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString(); + AddError(VerificationError.Create( + VerifierChain, + VerifierErrorCodes.InvalidFilePath, + $"Invalid shader file name '{shader}' in model '{modelFilePath}'", + VerificationSeverity.Error, + [modelFilePath], + shader)); + }); + } + + + foreach (var proxy in file.Content.Proxies) + { + GuardedVerify(() => VerifyProxyExists(file, proxy, contextInfo, token), + e => e is ArgumentException, + _ => + { + var modelFilePath = FileSystem.Path + .GetGameStrippedPath(Repository.Path.AsSpan(), file.FilePath.AsSpan()).ToString(); + AddError(VerificationError.Create( + VerifierChain, + VerifierErrorCodes.InvalidFilePath, + $"Invalid proxy file name '{proxy}' for model '{modelFilePath}'", + VerificationSeverity.Error, + [..contextInfo, modelFilePath], + proxy)); + }); + } + } + + private void VerifyTextureExists(IPetroglyphFileHolder model, string texture, IReadOnlyCollection contextInfo) + { + if (texture == "None") + return; + _textureVerifier.Verify(texture, [..contextInfo, model.FileName], CancellationToken.None); + } + + private void VerifyProxyExists(IPetroglyphFileHolder model, string proxy, IReadOnlyCollection contextInfo, CancellationToken token) + { + var proxyName = ProxyNameWithoutAlt(proxy); + var proxyPath = BuildModelPath(proxyName); + + var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), model.FilePath.AsSpan()).ToString(); + + if (!Repository.ModelRepository.FileExists(proxyPath)) + { + var message = $"Proxy particle '{proxyName}' not found for model '{modelFilePath}'"; + var error = VerificationError.Create( + VerifierChain, + VerifierErrorCodes.FileNotFound, + message, + VerificationSeverity.Error, + [..contextInfo, modelFilePath], + proxyName); + AddError(error); + return; + } + + VerifyAlamoFile(proxyPath, [..contextInfo, modelFilePath], token); + } + + private void VerifyShaderExists(IPetroglyphFileHolder model, string shader, IReadOnlyCollection contextInfo) + { + if (shader is "alDefault.fx" or "alDefault.fxo") + return; + + if (!Repository.EffectsRepository.FileExists(shader)) + { + var modelFilePath = FileSystem.Path.GetGameStrippedPath(Repository.Path.AsSpan(), model.FilePath.AsSpan()).ToString(); + var message = $"Shader effect '{shader}' not found for model '{modelFilePath}'."; + var error = VerificationError.Create( + VerifierChain, + VerifierErrorCodes.FileNotFound, + message, + VerificationSeverity.Error, + [..contextInfo, modelFilePath], + shader); + AddError(error); + } + } + + private string BuildModelPath(string fileName) + { + return FileSystem.Path.Combine("DATA\\ART\\MODELS", fileName); + } + + private static string ProxyNameWithoutAlt(string proxy) + { + var proxyName = proxy.AsSpan(); + + var altSpan = ProxyAltIdentifier.AsSpan(); + + var altIndex = proxyName.LastIndexOf(altSpan); + + if (altIndex == -1) + return proxy; + + while (altIndex != -1) + { + proxyName = proxyName.Slice(0, altIndex); + altIndex = proxyName.LastIndexOf(altSpan); + } + + return proxyName.ToString(); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/Commons/TextureVeifier.cs b/src/ModVerify/Verifiers/Commons/TextureVeifier.cs new file mode 100644 index 0000000..80bb269 --- /dev/null +++ b/src/ModVerify/Verifiers/Commons/TextureVeifier.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using AET.ModVerify.Reporting; +using AET.ModVerify.Settings; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Verifiers.Commons; + +public sealed class TextureVeifier( + IGameVerifierInfo? parent, + IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) + : GameVerifier(parent, gameEngine, settings, serviceProvider) +{ + private readonly IAlreadyVerifiedCache? _cache = serviceProvider.GetService(); + + public override void Verify(string texturePath, IReadOnlyCollection contextInfo, CancellationToken token) + { + Verify(texturePath.AsSpan(), contextInfo, token); + } + + public void Verify(ReadOnlySpan textureName, IReadOnlyCollection contextInfo, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + + if (_cache?.TryAddEntry(textureName) == false) + return; + + if (Repository.TextureRepository.FileExists(textureName, false, out var tooLongPath)) + return; + + var pathString = textureName.ToString(); + + if (tooLongPath) + { + VerificationError.Create(VerifierChain, VerifierErrorCodes.FilePathTooLong, + $"Could not find texture '{pathString}' because the engine resolved a path that is too long.", + VerificationSeverity.Error, contextInfo, pathString); + return; + } + + + var messageBuilder = new StringBuilder($"Could not find texture '{pathString}'"); + if (contextInfo.Count > 0) + { + messageBuilder.Append(" for context: ["); + messageBuilder.Append(string.Join("-->", contextInfo)); + messageBuilder.Append(']'); + } + + messageBuilder.Append('.'); + + VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound, + messageBuilder.ToString(), + VerificationSeverity.Error, contextInfo, pathString); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/DuplicateNameFinder.cs b/src/ModVerify/Verifiers/DuplicateNameFinder.cs index 14d2e67..23ab1b8 100644 --- a/src/ModVerify/Verifiers/DuplicateNameFinder.cs +++ b/src/ModVerify/Verifiers/DuplicateNameFinder.cs @@ -1,55 +1,104 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using AET.ModVerify.Reporting; using AET.ModVerify.Settings; using AnakinRaW.CommonUtilities.Collections; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Engine.DataTypes; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.MTD.Data; +using PG.StarWarsGame.Files.MTD.Files; namespace AET.ModVerify.Verifiers; public sealed class DuplicateNameFinder( - IGameDatabase gameDatabase, + IStarWarsGameEngine gameEngine, GameVerifySettings settings, IServiceProvider serviceProvider) - : GameVerifierBase(gameDatabase, settings, serviceProvider) + : GameVerifier(null, gameEngine, settings, serviceProvider) { - public override string FriendlyName => "Duplicate Definitions"; + public override string FriendlyName => "Duplicates"; - protected override void RunVerification(CancellationToken token) + public override void Verify(CancellationToken token) { - CheckDatabaseForDuplicates(Database.GameObjects, "GameObject"); - CheckDatabaseForDuplicates(Database.SfxEvents, "SFXEvent"); - } + CheckXmlObjectsForDuplicates("GameObject", GameEngine.GameObjectTypeManager); + CheckXmlObjectsForDuplicates("SFXEvent", GameEngine.SfxGameManager); + + if (GameEngine.GuiDialogManager.MtdFile is not null) + CheckMtdForDuplicates(GameEngine.GuiDialogManager.MtdFile); - private void CheckDatabaseForDuplicates(IXmlDatabase database, string databaseName) where T : XmlObject + if (GameEngine.CommandBar.MegaTextureFile is not null) + { + if (!GameEngine.CommandBar.MegaTextureFile.FilePath.Equals(GameEngine.GuiDialogManager.MtdFile?.FileName)) + CheckMtdForDuplicates(GameEngine.CommandBar.MegaTextureFile); + } + } + + private void CheckForDuplicateCrcEntries( + string sourceName, + TSource source, + Func> crcSelector, + Func> entrySelector, + Func entryToStringSelector, + Func, IEnumerable> contextSelector, + Func, string, string> errorMessageCreator) { - foreach (var key in database.EntryKeys) + foreach (var crc32 in crcSelector(source)) { - var entries = database.GetEntries(key); + var entries = entrySelector(source, crc32); if (entries.Count > 1) { - var entryNames = entries.Select(x => x.Name); + var entryNames = entryToStringSelector(entries.First()); + var context = contextSelector(entries); AddError(VerificationError.Create( - this, + VerifierChain, VerifierErrorCodes.DuplicateFound, - CreateDuplicateErrorMessage(databaseName, entries), - VerificationSeverity.Warning, + errorMessageCreator(entries, sourceName), + VerificationSeverity.Error, + context, entryNames)); } } } - private string CreateDuplicateErrorMessage(string databaseName, ReadOnlyFrugalList entries) where T : XmlObject + private void CheckMtdForDuplicates(IMtdFile mtdFile) + { + CheckForDuplicateCrcEntries( + mtdFile.FileName, + mtdFile, + mtd => mtd.Content.Select(x => x.Crc32), + (mtd, crc32) => mtd.Content.EntriesWithCrc(crc32), + entry => entry.FileName, + entries => entries.Select(x => $"'{x.FileName}' (CRC: {x.Crc32})"), + CreateDuplicateMtdErrorMessage); + } + + private void CheckXmlObjectsForDuplicates(string databaseName, IGameManager gameManager) where T : NamedXmlObject + { + CheckForDuplicateCrcEntries( + databaseName, + gameManager, + manager => manager.EntryKeys, + (manager, crc32) => manager.GetEntries(crc32), + entry => entry.Name, + entries => entries.Select(x => $"'{x.Name}' - {x.Location}"), + CreateDuplicateXmlErrorMessage); + } + + private static string CreateDuplicateMtdErrorMessage(ReadOnlyFrugalList entries, string fileName) { var firstEntry = entries.First(); + return $"MTD File '{fileName}' has duplicate definitions for CRC ({firstEntry}): {string.Join(",", entries.Select(x => x.FileName))}"; + } + private static string CreateDuplicateXmlErrorMessage(ReadOnlyFrugalList entries, string databaseName) where T : NamedXmlObject + { + var firstEntry = entries.First(); var message = $"{databaseName} '{firstEntry.Name}' ({firstEntry.Crc32}) has duplicate definitions: "; - foreach (var entry in entries) message += $"['{entry.Name}' in {entry.Location.XmlFile}:{entry.Location.Line}] "; - return message.TrimEnd(); } } \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GameEngineErrorCollector.cs b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs new file mode 100644 index 0000000..6475ea7 --- /dev/null +++ b/src/ModVerify/Verifiers/GameEngineErrorCollector.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting.Reporters.Engine; +using AET.ModVerify.Settings; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Verifiers; + +public sealed class GameEngineErrorCollector( + IGameEngineErrorCollection errorCollection, + IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) : GameVerifier(null, gameEngine, settings, serviceProvider) +{ + public override string FriendlyName => "Game Engine Initialization"; + + public override void Verify(CancellationToken token) + { + AddErrors(new InitializationErrorReporter(Repository, Services).GetErrors(errorCollection.InitializationErrors)); + AddErrors(new XmlParseErrorReporter(Repository, Services).GetErrors(errorCollection.XmlErrors)); + if (!Settings.IgnoreAsserts) + AddErrors(new GameAssertErrorReporter(Repository, Services).GetErrors(errorCollection.Asserts)); + } + + private void AddErrors(IEnumerable errors) + { + foreach (var error in errors) + AddError(error); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GameVerifier.cs b/src/ModVerify/Verifiers/GameVerifier.cs new file mode 100644 index 0000000..aebfe94 --- /dev/null +++ b/src/ModVerify/Verifiers/GameVerifier.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AET.ModVerify.Settings; +using PG.StarWarsGame.Engine; + +namespace AET.ModVerify.Verifiers; + +public abstract class GameVerifier( + IGameVerifierInfo? parent, + IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) + : GameVerifierBase(parent, gameEngine, settings, serviceProvider) where T : notnull +{ + public abstract void Verify(T toVerify, IReadOnlyCollection contextInfo, CancellationToken token); +} + +public abstract class GameVerifier( + IGameVerifierInfo? parent, + IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) + : GameVerifierBase(parent, gameEngine, settings, serviceProvider) +{ + public abstract void Verify(CancellationToken token); +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GameVerifierBase.cs b/src/ModVerify/Verifiers/GameVerifierBase.cs index a1f10cd..8c9d67d 100644 --- a/src/ModVerify/Verifiers/GameVerifierBase.cs +++ b/src/ModVerify/Verifiers/GameVerifierBase.cs @@ -1,62 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO.Abstractions; -using System.Threading; -using AET.ModVerify.Reporting; +using AET.ModVerify.Reporting; using AET.ModVerify.Settings; -using AnakinRaW.CommonUtilities.FileSystem; -using AnakinRaW.CommonUtilities.SimplePipeline.Steps; +using AnakinRaW.CommonUtilities.SimplePipeline.Progress; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Engine.Repositories; +using PG.StarWarsGame.Engine.IO; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO.Abstractions; +using AET.ModVerify.Pipeline.Progress; +using PG.StarWarsGame.Engine; namespace AET.ModVerify.Verifiers; -public abstract class GameVerifierBase( - IGameDatabase gameDatabase, - GameVerifySettings settings, - IServiceProvider serviceProvider) - : PipelineStep(serviceProvider), IGameVerifier +public abstract class GameVerifierBase : IGameVerifierInfo { + public event EventHandler? Error; - protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService(); - - private readonly HashSet _verifyErrors = new(); - - public IReadOnlyCollection VerifyErrors => _verifyErrors; + public event EventHandler>? Progress; - protected GameVerifySettings Settings { get; } = settings; + private readonly IStarWarsGameEngine _gameEngine; + private readonly ConcurrentDictionary _verifyErrors = new(); - protected IGameDatabase Database { get; } = gameDatabase ?? throw new ArgumentNullException(nameof(gameDatabase)); + protected readonly IFileSystem FileSystem; + protected readonly IServiceProvider Services; + protected readonly GameVerifySettings Settings; - protected IGameRepository Repository => gameDatabase.GameRepository; + public IReadOnlyCollection VerifyErrors => [.. _verifyErrors.Keys]; public virtual string FriendlyName => GetType().Name; public string Name => GetType().FullName; - protected sealed override void RunCore(CancellationToken token) + public IGameVerifierInfo? Parent { get; } + + protected IStarWarsGameEngine GameEngine { get; } + + protected IGameRepository Repository => _gameEngine.GameRepository; + + protected IReadOnlyList VerifierChain { get; } + + protected GameVerifierBase( + IGameVerifierInfo? parent, + IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) { - Logger?.LogInformation($"Running verifier '{FriendlyName}'..."); - try - { - RunVerification(token); - } - finally - { - Logger?.LogInformation($"Finished verifier '{FriendlyName}'"); - } + if (serviceProvider == null) + throw new ArgumentNullException(nameof(serviceProvider)); + FileSystem = serviceProvider.GetRequiredService(); + Services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _gameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); + Parent = parent; + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + GameEngine = gameEngine ?? throw new ArgumentNullException(nameof(gameEngine)); + VerifierChain = CreateVerifierChain(); } - protected abstract void RunVerification(CancellationToken token); - protected void AddError(VerificationError error) { - _verifyErrors.Add(error); - if (Settings.AbortSettings.FailFast && error.Severity >= Settings.AbortSettings.MinimumAbortSeverity) - throw new GameVerificationException(error); + if (_verifyErrors.TryAdd(error, 0)) + { + Error?.Invoke(this, new VerificationErrorEventArgs(error)); + + if (error.Severity >= Settings.ThrowsOnMinimumSeverity) + throw new GameVerificationException(error); + } } protected void GuardedVerify(Action action, Predicate exceptionFilter, Action exceptionHandler) @@ -71,17 +79,22 @@ protected void GuardedVerify(Action action, Predicate exceptionFilter } } - protected string GetGameStrippedPath(string path) + protected void OnProgress(double progress, string? message) { - if (!FileSystem.Path.IsPathFullyQualified(path)) - return path; + Progress?.Invoke(this, new(progress, message)); + } - if (path.Length <= Repository.Path.Length) - return path; + private IReadOnlyList CreateVerifierChain() + { + var verifierChain = new List { this }; - if (path.StartsWith(Repository.Path, StringComparison.OrdinalIgnoreCase)) - return path.Substring(Repository.Path.Length); + var parent = Parent; + while (parent != null) + { + verifierChain.Insert(0, parent); + parent = parent.Parent; + } - return path; + return verifierChain; } } \ No newline at end of file diff --git a/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs b/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs new file mode 100644 index 0000000..435c5de --- /dev/null +++ b/src/ModVerify/Verifiers/GuiDialogs/GuiDialogsVerifier.cs @@ -0,0 +1,164 @@ +using AET.ModVerify.Reporting; +using AET.ModVerify.Settings; +using AET.ModVerify.Verifiers.Commons; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine; +using PG.StarWarsGame.Engine.GuiDialog; +using PG.StarWarsGame.Files.MTD.Binary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace AET.ModVerify.Verifiers.GuiDialogs; + +sealed class GuiDialogsVerifier : GameVerifier +{ + internal const string DefaultComponentIdentifier = "<>"; + + private static readonly GuiComponentType[] GuiComponentTypes = + Enum.GetValues(typeof(GuiComponentType)).OfType().ToArray(); + + private readonly IAlreadyVerifiedCache? _cache; + private readonly TextureVeifier _textureVerifier; + + public GuiDialogsVerifier(IStarWarsGameEngine gameEngine, + GameVerifySettings settings, + IServiceProvider serviceProvider) : base(null, gameEngine, settings, serviceProvider) + { + _cache = serviceProvider.GetService(); + _textureVerifier = new TextureVeifier(this, gameEngine, settings, serviceProvider); + } + + public override void Verify(CancellationToken token) + { + try + { + _textureVerifier.Error += OnTextureError; + VerifyMegaTexturesExist(token); + VerifyGuiTextures(); + } + finally + { + _textureVerifier.Error -= OnTextureError; + } + } + + private void VerifyGuiTextures() + { + var components = new List + { + DefaultComponentIdentifier + }; + components.AddRange(GameEngine.GuiDialogManager.Components); + + foreach (var component in components) + VerifyGuiComponentTexturesExist(component); + + } + + private void VerifyMegaTexturesExist(CancellationToken token) + { + var megaTextureName = GameEngine.GuiDialogManager.GuiDialogsXml?.TextureData.MegaTexture; + if (GameEngine.GuiDialogManager.MtdFile is null) + { + var mtdFileName = megaTextureName ?? "<>"; + VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound, $"MtdFile '{mtdFileName}.mtd' could not be found", + VerificationSeverity.Critical, mtdFileName); + } + + if (megaTextureName is not null) + { + var megaTextureFileName = $"{megaTextureName}.tga"; + _textureVerifier.Verify(megaTextureFileName, ["GUIDIALOGS.XML"], token); + } + + + var compressedMegaTextureName = GameEngine.GuiDialogManager.GuiDialogsXml?.TextureData.CompressedMegaTexture; + if (compressedMegaTextureName is not null) + { + var compressedMegaTextureFieName = $"{compressedMegaTextureName}.dds"; + _textureVerifier.Verify(compressedMegaTextureFieName, ["GUIDIALOGS.XML"], token); + } + } + + private void VerifyGuiComponentTexturesExist(string component) + { + var middleButtonInRepoMode = false; + + + var entriesForComponent = GetTextureEntriesForComponents(component, out var defined); + if (!defined) + return; + + foreach (var componentType in GuiComponentTypes) + { + try + { + if (!entriesForComponent.TryGetValue(componentType, out var texture)) + continue; + + if (_cache?.TryAddEntry(texture.Texture) == false) + { + // If we are in a special case we don't want to skip + if (!middleButtonInRepoMode && + componentType is not GuiComponentType.ButtonMiddle && + componentType is not GuiComponentType.Scanlines && + componentType is not GuiComponentType.FrameBackground) + continue; + } + + if (!GameEngine.GuiDialogManager.TextureExists( + texture, + out var origin, + out var isNone, + middleButtonInRepoMode) + && !isNone) + { + + if (origin == GuiTextureOrigin.MegaTexture && texture.Texture.Length > MtdFileConstants.MaxFileNameSize) + { + AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FilePathTooLong, + $"The filename is too long. Max length is {MtdFileConstants.MaxFileNameSize} characters.", + VerificationSeverity.Error, texture.Texture)); + } + else + { + var message = $"Could not find GUI texture '{texture.Texture}' at location '{origin}'."; + + if (texture.Texture.Length > PGConstants.MaxMegEntryPathLength) + message += " The file name is too long."; + + AddError(VerificationError.Create(VerifierChain, VerifierErrorCodes.FileNotFound, + message, VerificationSeverity.Error, + [component, origin.ToString()], texture.Texture)); + } + } + + if (componentType is GuiComponentType.ButtonMiddle && origin is GuiTextureOrigin.Repository) + middleButtonInRepoMode = true; + } + finally + { + + if (componentType >= GuiComponentType.ButtonRightDisabled) + middleButtonInRepoMode = false; + } + } + } + + private IReadOnlyDictionary GetTextureEntriesForComponents(string component, out bool defined) + { + if (component == DefaultComponentIdentifier) + { + defined = true; + return GameEngine.GuiDialogManager.DefaultTextureEntries; + } + return GameEngine.GuiDialogManager.GetTextureEntries(component, out defined); + } + + private void OnTextureError(object sender, VerificationErrorEventArgs e) + { + AddError(e.Error); + } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs b/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs new file mode 100644 index 0000000..1fe8ad5 --- /dev/null +++ b/src/ModVerify/Verifiers/IAlreadyVerifiedCache.cs @@ -0,0 +1,11 @@ +using System; +using PG.Commons.Hashing; + +namespace AET.ModVerify.Verifiers; + +public interface IAlreadyVerifiedCache +{ + public bool TryAddEntry(string entry); + public bool TryAddEntry(ReadOnlySpan entry); + public bool TryAddEntry(Crc32 checksum); +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/IGameVerifierInfo.cs b/src/ModVerify/Verifiers/IGameVerifierInfo.cs new file mode 100644 index 0000000..cba43d1 --- /dev/null +++ b/src/ModVerify/Verifiers/IGameVerifierInfo.cs @@ -0,0 +1,10 @@ +namespace AET.ModVerify.Verifiers; + +public interface IGameVerifierInfo +{ + IGameVerifierInfo? Parent { get; } + + string Name { get; } + + string FriendlyName { get; } +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs index 8a6e7ba..a36648b 100644 --- a/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs +++ b/src/ModVerify/Verifiers/ReferencedModelsVerifier.cs @@ -1,249 +1,50 @@ -using System; -using System.Collections.Generic; -using System.IO; +using AET.ModVerify.Settings; +using AET.ModVerify.Verifiers.Commons; +using PG.StarWarsGame.Engine; +using System; using System.Linq; using System.Threading; -using AET.ModVerify.Reporting; -using AET.ModVerify.Settings; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Binary; -using PG.Commons.Files; -using PG.Commons.Utilities; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Files.ALO.Files.Models; -using PG.StarWarsGame.Files.ALO.Files.Particles; -using PG.StarWarsGame.Files.ALO.Services; -using PG.StarWarsGame.Files.ChunkFiles.Data; -using AnakinRaW.CommonUtilities.FileSystem; -using System.Reflection; namespace AET.ModVerify.Verifiers; public sealed class ReferencedModelsVerifier( - IGameDatabase database, + IStarWarsGameEngine engine, GameVerifySettings settings, IServiceProvider serviceProvider) - : GameVerifierBase(database, settings, serviceProvider) + : GameVerifier(null, engine, settings, serviceProvider) { - private const string ProxyAltIdentifier = "_ALT"; - - private readonly IAloFileService _modelFileService = serviceProvider.GetRequiredService(); - public override string FriendlyName => "Referenced Models"; - protected override void RunVerification(CancellationToken token) + public override void Verify(CancellationToken token) { - var aloQueue = new Queue(Database.GameObjects.Entries + var models = GameEngine.GameObjectTypeManager.Entries .SelectMany(x => x.Models) - .Concat(FocHardcodedConstants.HardcodedModels)); - - var visitedAloFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - - while (aloQueue.Count != 0) - { - var model = aloQueue.Dequeue(); - if (!visitedAloFiles.Add(model)) - continue; - - token.ThrowIfCancellationRequested(); - - using var modelStream = Repository.TryOpenFile(BuildModelPath(model)); + .Concat(FocHardcodedConstants.HardcodedModels).ToList(); - if (modelStream is null) - { - var error = VerificationError.Create( - this, - VerifierErrorCodes.ModelNotFound, - $"Unable to find .ALO file '{model}'", - VerificationSeverity.Error, - model); + if (models.Count == 0) + return; - AddError(error); - } - else - VerifyModelOrParticle(modelStream, aloQueue); - } - } + var numModels = models.Count; + var counter = 0; - private void VerifyModelOrParticle(Stream modelStream, Queue workingQueue) - { + var inner = new SingleModelVerifier(this, GameEngine, Settings, Services); try { - using var aloData = _modelFileService.Load(modelStream, AloLoadOptions.Assets); - switch (aloData) + inner.Error += OnModelError; + foreach (var model in models) { - case IAloModelFile model: - VerifyModel(model, workingQueue); - break; - case IAloParticleFile particle: - VerifyParticle(particle); - break; - default: - throw new InvalidOperationException("The data stream is neither a model nor particle."); + OnProgress((double)++counter / numModels, $"Model - '{model}'"); + inner.Verify(model, [], token); } } - catch (BinaryCorruptedException e) - { - var aloFile = GetGameStrippedPath(modelStream.GetFilePath()); - var message = $"{aloFile} is corrupted: {e.Message}"; - AddError(VerificationError.Create(this, VerifierErrorCodes.ModelBroken, message, VerificationSeverity.Critical, aloFile)); - } - } - - private void VerifyParticle(IAloParticleFile file) - { - foreach (var texture in file.Content.Textures) - { - GuardedVerify(() => VerifyTextureExists(file, texture), - e => e is ArgumentException, - _ => - { - var modelFilePath = GetGameStrippedPath(file.FilePath); - AddError(VerificationError.Create( - this, - VerifierErrorCodes.InvalidTexture, - $"Invalid texture file name" + - $" '{texture}' in particle 'modelFilePath'", - VerificationSeverity.Error, - texture, - modelFilePath)); - }); - } - - var fileName = FileSystem.Path.GetFileNameWithoutExtension(file.FilePath.AsSpan()); - var name = file.Content.Name.AsSpan(); - - if (!fileName.Equals(name, StringComparison.OrdinalIgnoreCase)) - { - var modelFilePath = GetGameStrippedPath(file.FilePath); - AddError(VerificationError.Create( - this, - VerifierErrorCodes.InvalidParticleName, - $"The particle name '{file.Content.Name}' does not match file name '{modelFilePath}'", - VerificationSeverity.Error, - modelFilePath)); - } - - } - - private void VerifyModel(IAloModelFile file, Queue workingQueue) - { - foreach (var texture in file.Content.Textures) - { - GuardedVerify(() => VerifyTextureExists(file, texture), - e => e is ArgumentException, - _ => - { - var modelFilePath = GetGameStrippedPath(file.FilePath); - AddError(VerificationError.Create( - this, - VerifierErrorCodes.InvalidTexture, - $"Invalid texture file name '{texture}' in model '{modelFilePath}'", - VerificationSeverity.Error, - texture, modelFilePath)); - }); - } - - foreach (var shader in file.Content.Shaders) - { - GuardedVerify(() => VerifyShaderExists(file, shader), - e => e is ArgumentException, - _ => - { - var modelFilePath = GetGameStrippedPath(file.FilePath); - AddError(VerificationError.Create( - this, - VerifierErrorCodes.InvalidShader, - $"Invalid texture file name '{shader}' in model '{modelFilePath}'", - VerificationSeverity.Error, - shader, modelFilePath)); - }); - } - - - foreach (var proxy in file.Content.Proxies) - { - GuardedVerify(() => VerifyProxyExists(file, proxy, workingQueue), - e => e is ArgumentException, - _ => - { - var modelFilePath = GetGameStrippedPath(file.FilePath); - AddError(VerificationError.Create( - this, - VerifierErrorCodes.InvalidProxy, - $"Invalid proxy file name '{proxy}' in model '{modelFilePath}'", - VerificationSeverity.Error, - proxy, modelFilePath)); - }); - } - } - - private void VerifyTextureExists(IPetroglyphFileHolder model, string texture) - { - if (texture == "None") - return; - - if (!Repository.TextureRepository.FileExists(texture)) - { - var modelFilePath = GetGameStrippedPath(model.FilePath); - var message = $"{modelFilePath} references missing texture: {texture}"; - var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingTexture, message, VerificationSeverity.Error, modelFilePath, texture); - AddError(error); - } - } - - private void VerifyProxyExists(IPetroglyphFileHolder model, string proxy, Queue workingQueue) - { - var proxyName = ProxyNameWithoutAlt(proxy); - var particle = FileSystem.Path.ChangeExtension(proxyName, "alo"); - if (!Repository.FileExists(BuildModelPath(particle))) - { - var modelFilePath = GetGameStrippedPath(model.FilePath); - var message = $"{modelFilePath} references missing proxy particle: {particle}"; - var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingProxy, message, VerificationSeverity.Error, modelFilePath, particle); - AddError(error); - } - else - workingQueue.Enqueue(particle); - } - - private string BuildModelPath(string fileName) - { - return FileSystem.Path.Combine("DATA/ART/MODELS", fileName); - } - - private void VerifyShaderExists(IPetroglyphFileHolder data, string shader) - { - if (shader is "alDefault.fx" or "alDefault.fxo") - return; - - if (!Repository.EffectsRepository.FileExists(shader)) + finally { - var modelFilePath = GetGameStrippedPath(data.FilePath); - var message = $"{modelFilePath} references missing shader effect: {shader}"; - var error = VerificationError.Create(this, VerifierErrorCodes.ModelMissingShader, message, VerificationSeverity.Error, modelFilePath, shader); - AddError(error); + inner.Error -= OnModelError; } } - private static string ProxyNameWithoutAlt(string proxy) + private void OnModelError(object sender, VerificationErrorEventArgs e) { - var proxyName = proxy.AsSpan(); - - var altSpan = ProxyAltIdentifier.AsSpan(); - - var altIndex = proxyName.LastIndexOf(altSpan); - - if (altIndex == -1) - return proxy; - - while (altIndex != -1) - { - proxyName = proxyName.Slice(0, altIndex); - altIndex = proxyName.LastIndexOf(altSpan); - } - - return proxyName.ToString(); + AddError(e.Error); } } diff --git a/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs b/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs new file mode 100644 index 0000000..dcb8566 --- /dev/null +++ b/src/ModVerify/Verifiers/VerificationErrorEventArgs.cs @@ -0,0 +1,9 @@ +using System; +using AET.ModVerify.Reporting; + +namespace AET.ModVerify.Verifiers; + +public sealed class VerificationErrorEventArgs(VerificationError error) : EventArgs +{ + public VerificationError Error { get; } = error ?? throw new ArgumentNullException(nameof(error)); +} \ No newline at end of file diff --git a/src/ModVerify/Verifiers/VerifierErrorCodes.cs b/src/ModVerify/Verifiers/VerifierErrorCodes.cs index d4b3fa0..986cbe6 100644 --- a/src/ModVerify/Verifiers/VerifierErrorCodes.cs +++ b/src/ModVerify/Verifiers/VerifierErrorCodes.cs @@ -2,34 +2,39 @@ public static class VerifierErrorCodes { + public const string InitializationError = "INIT00"; + + public const string AssertValueNullOrEmpty = "ASRT00"; + public const string AssertValueOutOfRange = "ASRT01"; + public const string AssertValueInvalid = "ASRT02"; + public const string GenericExceptionErrorCode = "MV00"; + public const string FileCorrupt = "ENG00"; + + + public const string FileNotFound = "FILE00"; + public const string FilePathTooLong = "FILE01"; + public const string InvalidFilePath = "FILE02"; + public const string DuplicateFound = "DUP00"; public const string SampleNotFound = "WAV00"; - public const string FilePathTooLong = "WAV01"; public const string SampleNotPCM = "WAV02"; public const string SampleNotMono = "WAV03"; public const string InvalidSampleRate = "WAV04"; public const string InvalidBitsPerSeconds = "WAV05"; - public const string ModelNotFound = "ALO00"; - public const string ModelBroken = "ALO01"; - public const string ModelMissingTexture = "ALO02"; - public const string ModelMissingProxy = "ALO03"; - public const string ModelMissingShader = "ALO04"; - public const string InvalidTexture = "ALO05"; - public const string InvalidShader = "ALO06"; - public const string InvalidProxy = "ALO07"; - public const string InvalidParticleName = "ALO08"; + public const string InvalidParticleName = "ALO01"; public const string GenericXmlError = "XML00"; public const string EmptyXmlRoot = "XML01"; - public const string MissingXmlFile = "XML02"; public const string InvalidXmlValue = "XML03"; public const string MalformedXmlValue = "XML04"; public const string MissingXmlAttribute = "XML05"; public const string MissingXmlReference = "XML06"; public const string XmlValueTooLong = "XML07"; public const string XmlDataBeforeHeader = "XML08"; + public const string XmlMissingNode = "XML09"; + public const string XmlUnsupportedTag = "XML10"; } \ No newline at end of file diff --git a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs b/src/ModVerify/Verifiers/XmlParseErrorCollector.cs deleted file mode 100644 index 0819eb8..0000000 --- a/src/ModVerify/Verifiers/XmlParseErrorCollector.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using AET.ModVerify.Reporting; -using AET.ModVerify.Settings; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace AET.ModVerify.Verifiers; - -public sealed class XmlParseErrorCollector( - IEnumerable xmlErrors, - IGameDatabase gameDatabase, - GameVerifySettings settings, - IServiceProvider serviceProvider) : - GameVerifierBase(gameDatabase, settings, serviceProvider) -{ - public override string FriendlyName => "XML Parsing Errors"; - - protected override void RunVerification(CancellationToken token) - { - foreach (var xmlError in xmlErrors) - AddError(ConvertXmlToVerificationError(xmlError)); - } - - private VerificationError ConvertXmlToVerificationError(XmlParseErrorEventArgs xmlError) - { - var id = GetIdFromError(xmlError.ErrorKind); - var severity = GetSeverityFromError(xmlError.ErrorKind); - - var assets = new List - { - GetGameStrippedPath(xmlError.File.ToUpperInvariant()) - }; - - var xmlElement = xmlError.Element; - - if (xmlElement is not null) - { - assets.Add(xmlElement.Name.LocalName); - - var parent = xmlElement.Parent; - - if (parent != null) - { - var parentName = parent.Attribute("Name"); - assets.Add(parentName != null ? $"parentName='{parentName.Value}'" : $"parentTag='{parent.Name.LocalName}'"); - } - - } - - return VerificationError.Create(this, id, xmlError.Message, severity, assets); - - } - - private VerificationSeverity GetSeverityFromError(XmlParseErrorKind xmlErrorErrorKind) - { - return xmlErrorErrorKind switch - { - XmlParseErrorKind.EmptyRoot => VerificationSeverity.Critical, - XmlParseErrorKind.MissingFile => VerificationSeverity.Error, - XmlParseErrorKind.InvalidValue => VerificationSeverity.Information, - XmlParseErrorKind.MalformedValue => VerificationSeverity.Warning, - XmlParseErrorKind.MissingAttribute => VerificationSeverity.Error, - XmlParseErrorKind.MissingReference => VerificationSeverity.Error, - XmlParseErrorKind.TooLongData => VerificationSeverity.Warning, - XmlParseErrorKind.DataBeforeHeader => VerificationSeverity.Information, - _ => VerificationSeverity.Warning - }; - } - - private string GetIdFromError(XmlParseErrorKind xmlErrorErrorKind) - { - return xmlErrorErrorKind switch - { - XmlParseErrorKind.EmptyRoot => VerifierErrorCodes.EmptyXmlRoot, - XmlParseErrorKind.MissingFile => VerifierErrorCodes.MissingXmlFile, - XmlParseErrorKind.InvalidValue => VerifierErrorCodes.InvalidXmlValue, - XmlParseErrorKind.MalformedValue => VerifierErrorCodes.MalformedXmlValue, - XmlParseErrorKind.MissingAttribute => VerifierErrorCodes.MissingXmlAttribute, - XmlParseErrorKind.MissingReference => VerifierErrorCodes.MissingXmlReference, - XmlParseErrorKind.TooLongData => VerifierErrorCodes.XmlValueTooLong, - XmlParseErrorKind.Unknown => VerifierErrorCodes.GenericXmlError, - XmlParseErrorKind.DataBeforeHeader => VerifierErrorCodes.XmlDataBeforeHeader, - _ => throw new ArgumentOutOfRangeException(nameof(xmlErrorErrorKind), xmlErrorErrorKind, null) - }; - } -} \ No newline at end of file diff --git a/src/ModVerify/VerifyGamePipeline.cs b/src/ModVerify/VerifyGamePipeline.cs deleted file mode 100644 index e546447..0000000 --- a/src/ModVerify/VerifyGamePipeline.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AET.ModVerify.Reporting; -using AET.ModVerify.Settings; -using AET.ModVerify.Verifiers; -using AnakinRaW.CommonUtilities.SimplePipeline; -using AnakinRaW.CommonUtilities.SimplePipeline.Runners; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers; - -namespace AET.ModVerify; - -public abstract class VerifyGamePipeline : Pipeline -{ - private readonly List _verificationSteps = new(); - private readonly GameEngineType _targetType; - private readonly GameLocations _gameLocations; - private readonly ParallelRunner _verifyRunner; - - protected GameVerifySettings Settings { get; } - - public IReadOnlyCollection Errors { get; private set; } = Array.Empty(); - - private readonly ConcurrentBag _xmlParseErrors = new(); - - protected VerifyGamePipeline(GameEngineType targetType, GameLocations gameLocations, GameVerifySettings settings, IServiceProvider serviceProvider) - : base(serviceProvider) - { - _targetType = targetType; - _gameLocations = gameLocations ?? throw new ArgumentNullException(nameof(gameLocations)); - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - - if (settings.ParallelVerifiers is < 0 or > 64) - throw new ArgumentException("Settings has invalid parallel worker number.", nameof(settings)); - - _verifyRunner = new ParallelRunner(settings.ParallelVerifiers, serviceProvider); - } - - - protected sealed override Task PrepareCoreAsync() - { - _verificationSteps.Clear(); - return Task.FromResult(true); - } - - protected sealed override async Task RunCoreAsync(CancellationToken token) - { - Logger?.LogInformation("Verifying..."); - try - { - var databaseService = ServiceProvider.GetRequiredService(); - - IGameDatabase database; - try - { - databaseService.XmlParseError += OnXmlParseError; - database = await databaseService.CreateDatabaseAsync(_targetType, _gameLocations, token); - } - finally - { - databaseService.XmlParseError -= OnXmlParseError; - databaseService.Dispose(); - } - - - AddStep(new XmlParseErrorCollector(_xmlParseErrors, database, Settings, ServiceProvider)); - - foreach (var gameVerificationStep in CreateVerificationSteps(database)) - AddStep(gameVerificationStep); - - try - { - Logger?.LogInformation("Verifying..."); - _verifyRunner.Error += OnError; - await _verifyRunner.RunAsync(token); - } - finally - { - _verifyRunner.Error -= OnError; - Logger?.LogInformation("Finished Verifying"); - } - - - Logger?.LogInformation("Reporting Errors"); - - var reportBroker = new VerificationReportBroker(Settings.GlobalReportSettings, ServiceProvider); - var errors = await reportBroker.ReportAsync(_verificationSteps); - - Errors = errors; - - if (Settings.AbortSettings.ThrowsGameVerificationException && - errors.Any(x => x.Severity >= Settings.AbortSettings.MinimumAbortSeverity)) - throw new GameVerificationException(errors); - } - finally - { - Logger?.LogInformation("Finished game verification"); - } - } - - protected abstract IEnumerable CreateVerificationSteps(IGameDatabase database); - - private void AddStep(GameVerifierBase verifier) - { - _verifyRunner.AddStep(verifier); - _verificationSteps.Add(verifier); - } - - private void OnXmlParseError(IPetroglyphXmlParser sender, XmlParseErrorEventArgs e) - { - _xmlParseErrors.Add(e); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/Directory.Build.props b/src/PetroglyphTools/Directory.Build.props index c8ba572..4b4cdb9 100644 --- a/src/PetroglyphTools/Directory.Build.props +++ b/src/PetroglyphTools/Directory.Build.props @@ -1,12 +1,11 @@ + - - + - Alamo Engine Tools and Contributors https://github.com/AlamoEngine-Tools/PetroglyphTools https://github.com/AlamoEngine-Tools/PetroglyphTools - + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs new file mode 100644 index 0000000..41c4bb8 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/ISfxEventGameManager.cs @@ -0,0 +1,3 @@ +namespace PG.StarWarsGame.Engine.Audio.Sfx; + +public interface ISfxEventGameManager : IGameManager; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs new file mode 100644 index 0000000..aed53f2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEvent.cs @@ -0,0 +1,239 @@ +using System.Collections.Generic; +using System.Linq; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.Audio.Sfx; + +public sealed class SfxEvent : NamedXmlObject +{ + private byte _minVolume = DefaultMinVolume; + private byte _maxVolume = DefaultMaxVolume; + private byte _minPitch = DefaultMinPitch; + private byte _maxPitch = DefaultMaxPitch; + private byte _minPan2D = DefaultMinPan2d; + private byte _maxPan2D = DefaultMaxPan2d; + private uint _minPredelay; + private uint _maxPredelay; + private uint _minPostdelay; + private uint _maxPostdelay; + + public const byte MaxVolumeValue = 100; + public const byte MaxPitchValue = 200; + public const byte MinPitchValue = 50; + public const byte MaxPan2dValue = 100; + public const byte MinPriorityValue = 1; + public const byte MaxPriorityValue = 5; + public const byte MaxProbability = 100; + public const sbyte MinMaxInstances = 0; + public const sbyte InfinitivePlayCount = -1; + public const float MinLoopSeconds = 0.0f; + public const float MinVolumeSaturation = 0.0f; + + // Default values which are not the default value of the type + public const byte DefaultPriority = 3; + public const bool DefaultIs3d = true; + public const byte DefaultProbability = 100; + public const sbyte DefaultPlayCount = 1; + public const sbyte DefaultMaxInstances = 1; + public const byte DefaultMinVolume = 100; + public const byte DefaultMaxVolume = 100; + public const byte DefaultMinPitch = 100; + public const byte DefaultMaxPitch = 100; + public const byte DefaultMinPan2d = 50; + public const byte DefaultMaxPan2d = 50; + public const float DefaultVolumeSaturationDistance = 300.0f; + + public bool IsPreset { get; internal set; } + + public bool Is3D { get; internal set; } = DefaultIs3d; + + public bool Is2D { get; internal set; } + + public bool IsGui { get; internal set; } + + public bool IsHudVo { get; internal set; } + + public bool IsUnitResponseVo { get; internal set; } + + public bool IsAmbientVo { get; internal set; } + + public bool IsLocalized { get; internal set; } + + public SfxEvent? Preset { get; internal set; } + + public string? UsePresetName { get; internal set; } + + public bool PlaySequentially { get; internal set; } + + public IEnumerable AllSamples => PreSamples.Concat(Samples).Concat(PostSamples); + + public IReadOnlyList PreSamples { get; internal set; } = []; + + public IReadOnlyList Samples { get; internal set; } = []; + + public IReadOnlyList PostSamples { get; internal set; } = []; + + public IReadOnlyList LocalizedTextIDs { get; internal set; } = []; + + public byte Priority { get; internal set; } = DefaultPriority; + + public byte Probability { get; internal set; } = DefaultProbability; + + public sbyte PlayCount { get; internal set; } = DefaultPlayCount; + + public float LoopFadeInSeconds { get; internal set; } + + public float LoopFadeOutSeconds { get; internal set; } + + public sbyte MaxInstances { get; internal set; } = DefaultMaxInstances; + + public float VolumeSaturationDistance { get; internal set; } = DefaultVolumeSaturationDistance; + + public bool KillsPreviousObjectsSfx { get; internal set; } + + public string? OverlapTestName { get; internal set; } + + public string? ChainedSfxEventName { get; internal set; } + + public byte MinVolume + { + get => _minVolume; + internal set => _minVolume = value; + } + + public byte MaxVolume + { + get => _maxVolume; + internal set => _maxVolume = value; + } + + public byte MinPitch + { + get => _minPitch; + internal set => _minPitch = value; + } + + public byte MaxPitch + { + get => _maxPitch; + internal set => _maxPitch = value; + } + + public byte MinPan2D + { + get => _minPan2D; + internal set => _minPan2D = value; + } + + public byte MaxPan2D + { + get => _maxPan2D; + internal set => _maxPan2D = value; + } + + public uint MinPredelay + { + get => _minPredelay; + internal set => _minPredelay = value; + } + + public uint MaxPredelay + { + get => _maxPredelay; + internal set => _maxPredelay = value; + } + + public uint MinPostdelay + { + get => _minPostdelay; + internal set => _minPostdelay = value; + } + + public uint MaxPostdelay + { + get => _maxPostdelay; + internal set => _maxPostdelay = value; + } + + + internal SfxEvent(string name, Crc32 nameCrc, XmlLocationInfo location) + : base(name, nameCrc, location) + { + } + + internal override void CoerceValues() + { + AdjustMinMaxValues(ref _minVolume, ref _maxVolume); + AdjustMinMaxValues(ref _minPitch, ref _maxPitch); + AdjustMinMaxValues(ref _minPan2D, ref _maxPan2D); + AdjustMinMaxValues(ref _minPredelay, ref _maxPredelay); + AdjustMinMaxValues(ref _minPostdelay, ref _maxPostdelay); + } + + /* + * The engine also copies the of the preset (which is usually null). + * As this would cause this SFXEvent loose the information of the coded preset, we do not copy the preset's preset value. + * Example: + * + * + * Preset Yes + * 90 + * + * + * Engine Behavior: SFXEvent instance(Name: A, Use_Preset: null, Min_Volume: 90) + * PG.StarWarsGame.Engine Behavior: SFXEvent instance(Name: A, Use_Preset: Preset, Min_Volume: 90) + */ + public void ApplyPreset(SfxEvent preset) + { + Is3D = preset.Is3D; + Is2D = preset.Is2D; + IsGui = preset.IsGui; + IsHudVo = preset.IsHudVo; + IsUnitResponseVo = preset.IsUnitResponseVo; + IsAmbientVo = preset.IsAmbientVo; + IsLocalized = preset.IsLocalized; + Preset = preset; + UsePresetName = preset.Name; + PlaySequentially = preset.PlaySequentially; + PreSamples = preset.PreSamples; + Samples = preset.Samples; + PostSamples = preset.PostSamples; + LocalizedTextIDs = preset.LocalizedTextIDs; + Priority = preset.Priority; + Probability = preset.Probability; + PlayCount = preset.PlayCount; + LoopFadeInSeconds = preset.LoopFadeInSeconds; + LoopFadeOutSeconds = preset.LoopFadeOutSeconds; + MaxInstances = preset.MaxInstances; + MinPredelay = preset.MinPredelay; + MaxPredelay = preset.MaxPredelay; + MinPostdelay = preset.MinPostdelay; + MaxPostdelay = preset.MaxPostdelay; + OverlapTestName = preset.OverlapTestName; + ChainedSfxEventName = preset.ChainedSfxEventName; + MinVolume = preset.MinVolume; + MaxVolume = preset.MaxVolume; + MinPitch = preset.MinPitch; + MaxPitch = preset.MaxPitch; + MinPan2D = preset.MinPan2D; + MaxPan2D = preset.MaxPan2D; + } + + private static void AdjustMinMaxValues(ref byte minValue, ref byte maxValue) + { + if (minValue > maxValue) + minValue = maxValue; + if (maxValue < minValue) + maxValue = minValue; + } + + private static void AdjustMinMaxValues(ref uint minValue, ref uint maxValue) + { + if (minValue > maxValue) + minValue = maxValue; + if (maxValue < minValue) + maxValue = minValue; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs new file mode 100644 index 0000000..b037939 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Audio/Sfx/SfxEventGameManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Engine.Localization; +using PG.StarWarsGame.Engine.Xml.Parsers; + +namespace PG.StarWarsGame.Engine.Audio.Sfx; + +internal class SfxEventGameManager(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), ISfxEventGameManager +{ + public IEnumerable InstalledLanguages { get; private set; } = []; + + protected override async Task InitializeCoreAsync(CancellationToken token) + { + Logger?.LogInformation("Initializing Language files..."); + InstalledLanguages = GameRepository.InitializeInstalledSfxMegFiles().ToList(); + Logger?.LogInformation("Finished initializing Language files"); + + Logger?.LogInformation("Parsing SFXEvents..."); + + var contentParser = new XmlContainerContentParser(ServiceProvider, ErrorReporter); + contentParser.XmlParseError += OnParseError; + try + { + await Task.Run(() => contentParser.ParseEntriesFromFileListXml( + "DATA\\XML\\SFXEventFiles.XML", + GameRepository, + "DATA\\XML", + NamedEntries, + VerifyFilePathLength), + token); + } + finally + { + contentParser.XmlParseError -= OnParseError; + } + } + + private void OnParseError(object sender, XmlContainerParserErrorEventArgs e) + { + if (e.ErrorInXmlFileList || e.HasException) + { + e.Continue = false; + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = GetMessage(e) + }); + } + } + + private static string GetMessage(XmlContainerParserErrorEventArgs errorEventArgs) + { + if (errorEventArgs.HasException) + return $"Error while parsing SFXEvent XML file '{errorEventArgs.File}': {errorEventArgs.Exception.Message}"; + return "Could not find SFXEventFiles.xml"; + } + + private void VerifyFilePathLength(string filePath) + { + if (filePath.Length > PGConstants.MaxSFXEventDatabaseFileName) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"SFXEvent file '{filePath}' is longer than {PGConstants.MaxSFXEventDatabaseFileName} characters." + }); + } + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs new file mode 100644 index 0000000..1799264 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentGroup.cs @@ -0,0 +1,21 @@ +using PG.StarWarsGame.Engine.CommandBar.Components; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PG.StarWarsGame.Engine.CommandBar; + +public sealed class CommandBarComponentGroup +{ + public string Name { get; } + + public IReadOnlyList Components { get; } + + internal CommandBarComponentGroup(string name, IEnumerable components) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + if (components == null) + throw new ArgumentNullException(nameof(components)); + Components = components.ToList(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs new file mode 100644 index 0000000..2ace3a0 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentId.cs @@ -0,0 +1,52 @@ +using System; + +namespace PG.StarWarsGame.Engine.CommandBar; + +public readonly struct CommandBarComponentId : IEquatable +{ + public static readonly CommandBarComponentId None = new(GameEngineType.Eaw, -1); + + public GameEngineType TargetEngine { get; } + + public int Value { get; } + + internal CommandBarComponentId(GameEngineType engine, int value) + { + TargetEngine = engine; + Value = value; + } + + public override string ToString() + { + if (Value == -1) + return "NONE (-1)"; + + var nameLookup = SupportedCommandBarComponentData.GetComponentIdsForEngine(TargetEngine); + return $"'{nameLookup[this]}' ({Value})"; + } + + public bool Equals(CommandBarComponentId other) + { + return TargetEngine == other.TargetEngine && Value == other.Value; + } + + public override bool Equals(object? obj) + { + return obj is CommandBarComponentId other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine((int)TargetEngine, Value); + } + + public static bool operator ==(CommandBarComponentId left, CommandBarComponentId right) + { + return left.Equals(right); + } + + public static bool operator !=(CommandBarComponentId left, CommandBarComponentId right) + { + return !(left == right); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs new file mode 100644 index 0000000..9a4c6ee --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarComponentType.cs @@ -0,0 +1,17 @@ +namespace PG.StarWarsGame.Engine.CommandBar; + +public enum CommandBarComponentType +{ + // Used for XML lookup + Shell = 0, + Icon = 1, + Button = 2, + Text = 3, + TextButton = 4, + Model = 5, + Bar = 6, + // Used internally by the engine only + Select = 7, + Count = 8, + None = 9, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs new file mode 100644 index 0000000..bec58ce --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarConstants.cs @@ -0,0 +1,7 @@ +namespace PG.StarWarsGame.Engine.CommandBar; + +public static class CommandBarConstants +{ + public const string MegaTextureBaseName = "MT_COMMANDBAR"; + public const string ShellGroupName = "Shells"; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs new file mode 100644 index 0000000..311c212 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/CommandBarGameManager.cs @@ -0,0 +1,293 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.CommandBar.Components; +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.GameConstants; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Engine.Rendering; +using PG.StarWarsGame.Engine.Rendering.Font; +using PG.StarWarsGame.Engine.Xml.Parsers; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.MTD.Files; +using PG.StarWarsGame.Files.MTD.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace PG.StarWarsGame.Engine.CommandBar; + +internal class CommandBarGameManager( + GameRepository repository, + PGRender pgRender, + IGameConstants gameConstants, + IFontManager fontManager, + GameEngineErrorReporterWrapper errorReporter, + IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), ICommandBarGameManager +{ + private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService(); + private readonly IMtdFileService _mtdFileService = serviceProvider.GetRequiredService(); + private readonly Dictionary _groups = new(); + private readonly PGRender _pgRender = pgRender; + + private bool _megaTextureExists; + private FontData? _defaultFont; + + public ICollection Components => Entries; + + public IReadOnlyDictionary Groups => _groups; + + public FontData? DefaultFont + { + get + { + ThrowIfNotInitialized(); + return _defaultFont; + } + private set + { + ThrowIfAlreadyInitialized(); + _defaultFont = value; + } + } + + public IMtdFile? MegaTextureFile + { + get + { + ThrowIfNotInitialized(); + return field; + } + private set + { + ThrowIfAlreadyInitialized(); + field = value; + } + } + + protected override async Task InitializeCoreAsync(CancellationToken token) + { + Logger?.LogInformation("Creating command bar components..."); + + var contentParser = new XmlContainerContentParser(ServiceProvider, ErrorReporter); + contentParser.XmlParseError += OnParseError; + + var parsedCommandBarComponents = new ValueListDictionary(); + + try + { + await Task.Run(() => contentParser.ParseEntriesFromFileListXml( + "DATA\\XML\\CommandBarComponentFiles.XML", + GameRepository, + ".\\DATA\\XML", + parsedCommandBarComponents, + VerifyFilePathLength), + token); + } + finally + { + contentParser.XmlParseError -= OnParseError; + } + + // Create Scene + // Create Camera + // Resize(true) + + foreach (var parsedCommandBarComponent in parsedCommandBarComponents.Values) + { + var component = CommandBarBaseComponent.Create(parsedCommandBarComponent, ErrorReporter); + if (component is not null) + { + var crc = _hashingService.GetCrc32(component.Name, PGConstants.DefaultPGEncoding); + NamedEntries.Add(crc, component); + } + } + + SetComponentGroup(Components); + SetMegaTexture(); + SetDefaultFont(); + + LinkComponentsToShell(); + LinkComponentsWithActions(); + } + + private void LinkComponentsWithActions() + { + var nameLookup = SupportedCommandBarComponentData.GetComponentIdsForEngine(GameRepository.EngineType); + + foreach (var idPair in nameLookup) + { + var crc = _hashingService.GetCrc32(idPair.Value, PGConstants.DefaultPGEncoding); + if (NamedEntries.TryGetFirstValue(crc, out var component)) + component.Id = idPair.Key; + } + } + + private void LinkComponentsToShell() + { + if (!Groups.TryGetValue(CommandBarConstants.ShellGroupName, out var shellGroup)) + return; + + var modelCache = new Dictionary(); + foreach (var component in Components) + { + if (component.Type == CommandBarComponentType.Shell) + continue; + + foreach (var shellComponent in shellGroup.Components) + { + if (LinkToShell(component, shellComponent as CommandBarShellComponent, modelCache)) + break; + } + } + + foreach (var model in modelCache.Values) + model?.Dispose(); + } + + private bool LinkToShell( + CommandBarBaseComponent component, + CommandBarShellComponent? shell, + IDictionary modelCache) + { + if (shell is null) + { + ErrorReporter.Assert( + EngineAssert.FromNullOrEmpty( + [component.Name], $"Cannot link component '{component}' because shell component is null.")); + return false; + } + + var componentName = component.Name; + if (string.IsNullOrEmpty(componentName)) + return false; + + var modelPath = shell.ModelPath; + if (string.IsNullOrEmpty(modelPath)) + return false; + + if (!modelCache.TryGetValue(shell.Name, out var model)) + { + model = _pgRender.LoadModelAndAnimations(modelPath.AsSpan(), null, true); + modelCache.Add(shell.Name, model); + } + + if (model is null) + { + ErrorReporter.Assert( + EngineAssert.FromNullOrEmpty( + [$"component='{component.Name}'", $"shell='{shell.Name}'"], + $"Cannot link component '{componentName}' to shell '{shell.Name}' because model '{modelPath}' could not be loaded.")); + return false; + } + + if (!model.IsModel) + { + ErrorReporter.Assert( + EngineAssert.FromNullOrEmpty( + [$"component='{component.Name}'", $"shell='{shell.Name}'"], + $"Cannot link component '{componentName}' to shell '{shell.Name}' because the loaded file '{modelPath}' is not a model.")); + return false; + } + + var boneIndex = model.IndexOfBone(componentName); + + if (boneIndex == -1) + return false; + component.Bone = boneIndex; + component.ParentShell = shell; + return true; + } + + private void SetDefaultFont() + { + // The code is only triggered iff at least one Text CommandbarBar component existed + if (Components.FirstOrDefault(x => x is CommandBarTextComponent or CommandBarTextButtonComponent) is null) + return; + + if (_defaultFont is null) + { + // TODO: From GameConstants + string fontName = PGConstants.DefaultUnicodeFontName; + int size = 11; + var font = fontManager.CreateFont(fontName, size, true, false, false, 1.0f); + if (font is null) + ErrorReporter.Assert(EngineAssert.FromNullOrEmpty([ToString()], $"Unable to create Default from name {fontName}")); + DefaultFont = font; + } + } + + private void SetMegaTexture() + { + // The code is only triggered iff at least one Shell CommandbarBar component existed + if (Components.FirstOrDefault(x => x is CommandBarShellComponent) is null) + return; + // Note: The tag is not used by the engine + var mtdPath = FileSystem.Path.Combine("DATA\\ART\\TEXTURES", $"{CommandBarConstants.MegaTextureBaseName}.mtd"); + using var megaTexture = GameRepository.TryOpenFile(mtdPath); + + try + { + MegaTextureFile = megaTexture is null ? null : _mtdFileService.Load(megaTexture); + } + catch (BinaryCorruptedException e) + { + var message = $"Failed to load MTD file '{mtdPath}': {e.Message}"; + Logger?.LogError(e, message); + ErrorReporter.Assert(EngineAssert.Create(EngineAssertKind.CorruptBinary, mtdPath, [], message)); + } + _megaTextureExists = GameRepository.TextureRepository.FileExists($"{CommandBarConstants.MegaTextureBaseName}.tga"); + } + + private void SetComponentGroup(IEnumerable components) + { + var groupData = components + .Where(x => !string.IsNullOrEmpty(x.XmlData.Group)) + .GroupBy(x => x.XmlData.Group!, StringComparer.Ordinal); + + foreach (var grouping in groupData) + { + var group = new CommandBarComponentGroup(grouping.Key, grouping); + _groups.Add(grouping.Key, group); + foreach (var component in grouping) + component.Group = group; + } + } + + private void OnParseError(object sender, XmlContainerParserErrorEventArgs e) + { + if (e.ErrorInXmlFileList || e.HasException) + { + e.Continue = false; + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = GetMessage(e) + }); + } + } + + private static string GetMessage(XmlContainerParserErrorEventArgs errorEventArgs) + { + if (errorEventArgs.HasException) + return $"Error while parsing CommandBar XML file '{errorEventArgs.File}': {errorEventArgs.Exception.Message}"; + return "Could not find CommandBarComponentFiles.xml"; + } + + private void VerifyFilePathLength(string filePath) + { + if (filePath.Length > PGConstants.MaxCommandBarDatabaseFileName) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"CommandBar file '{filePath}' is longer than {PGConstants.MaxCommandBarDatabaseFileName} characters." + }); + } + } +} diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/ButtonMode.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/ButtonMode.cs new file mode 100644 index 0000000..d18326d --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/ButtonMode.cs @@ -0,0 +1,14 @@ +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public enum ButtonMode +{ + Hidden = 0x0, + Normal = 0x1, + Mouseover = 0x2, + Selected = 0x3, + Disabled = 0x4, + Drag = 0x5, + Building = 0x6, + Ghost = 0x7, + None = 0x8 +}; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBarComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBarComponent.cs new file mode 100644 index 0000000..b6685cc --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBarComponent.cs @@ -0,0 +1,8 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarBarComponent(CommandBarComponentData xmlData) : CommandBarBaseComponent(xmlData) +{ + public override CommandBarComponentType Type => CommandBarComponentType.Bar; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBaseComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBaseComponent.cs new file mode 100644 index 0000000..a00f70e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarBaseComponent.cs @@ -0,0 +1,80 @@ +using System; +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.Rendering; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public abstract class CommandBarBaseComponent(CommandBarComponentData xmlData) +{ + internal readonly CommandBarComponentData XmlData = xmlData ?? throw new ArgumentNullException(nameof(xmlData)); + + public string Name => XmlData.Name; + public RgbaColor Color { get; } = xmlData.Color.HasValue ? new RgbaColor(xmlData.Color.Value) : new RgbaColor(255, 255, 255, 255); + public int Bone { get; internal set; } = -1; + public int BaseLayer { get; } = xmlData.BaseLayer; + public bool Hidden { get; internal set; } = xmlData.Hidden; + public bool Disabled { get; } = xmlData.Disabled; + public abstract CommandBarComponentType Type { get; } + public CommandBarComponentId Id { get; internal set; } = CommandBarComponentId.None; + public CommandBarComponentGroup? Group { get; internal set; } + public CommandBarShellComponent? ParentShell { get; internal set; } + + public static CommandBarBaseComponent? Create(CommandBarComponentData xmlData, IGameEngineErrorReporter errorReporter) + { + var type = GetTypeFromString(xmlData.Type.AsSpan()); + switch (type) + { + case CommandBarComponentType.Shell: + return new CommandBarShellComponent(xmlData); + case CommandBarComponentType.Icon: + return new CommandBarIconComponent(xmlData); + case CommandBarComponentType.Button: + return new CommandBarButtonComponentClass(xmlData); + case CommandBarComponentType.Text: + return new CommandBarTextComponent(xmlData); + case CommandBarComponentType.TextButton: + return new CommandBarTextButtonComponent(xmlData); + case CommandBarComponentType.Model: + return new CommandBarModelComponent(xmlData); + case CommandBarComponentType.Bar: + return new CommandBarBarComponent(xmlData); + case CommandBarComponentType.Select: + // I don't know how this code could ever be reached, because GetTypeFromString, + // if I understand it correctly, does not allow SELECT (and COUNT). The code to create a + // SELECT Component exists nether the less. + return new CommandBarSelectComponent(xmlData); + } + + // TODO: Verifier for any invalid type value + errorReporter.Assert( + EngineAssert.Create(EngineAssertKind.InvalidValue, type, [xmlData.Name], + $"Invalid type value '{xmlData.Type}' for CommandbarComponent '{xmlData.Name}')")); + + return null; + } + + public override string ToString() + { + return Name; + } + + private static CommandBarComponentType GetTypeFromString(ReadOnlySpan xmlValue) + { + if (xmlValue.Equals("SHELL".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Shell; + if (xmlValue.Equals("ICON".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Icon; + if (xmlValue.Equals("BUTTON".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Button; + if (xmlValue.Equals("TEXT".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Text; + if (xmlValue.Equals("TEXTBUTTON".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.TextButton; + if (xmlValue.Equals("MODEL".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Model; + if (xmlValue.Equals("BAR".AsSpan(), StringComparison.OrdinalIgnoreCase)) + return CommandBarComponentType.Bar; + return CommandBarComponentType.None; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarButtonComponentClass.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarButtonComponentClass.cs new file mode 100644 index 0000000..9876c31 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarButtonComponentClass.cs @@ -0,0 +1,10 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarButtonComponentClass(CommandBarComponentData xmlData) : CommandBarIconComponent(xmlData) +{ + public override CommandBarComponentType Type => CommandBarComponentType.Button; + + public ButtonMode Mode => ButtonMode.Normal; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarComponentTextData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarComponentTextData.cs new file mode 100644 index 0000000..d57e170 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarComponentTextData.cs @@ -0,0 +1,23 @@ +using PG.StarWarsGame.Engine.Rendering; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public sealed class CommandBarComponentTextData +{ + public CommandBarBaseComponent Parent { get; } + + public PrimRenderMode Mode { get; init; } + + public float Scale { get; init; } + + public int TempAlpha { get; init; } + + public RgbaColor Color { get; init; } + + public bool AutoPixelAlign { get; init; } = true; + + internal CommandBarComponentTextData(CommandBarBaseComponent parent) + { + Parent = parent; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarIconComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarIconComponent.cs new file mode 100644 index 0000000..792f58e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarIconComponent.cs @@ -0,0 +1,8 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarIconComponent(CommandBarComponentData xmlData) : CommandBarBaseComponent(xmlData) +{ + public override CommandBarComponentType Type => CommandBarComponentType.Icon; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarModelComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarModelComponent.cs new file mode 100644 index 0000000..a094747 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarModelComponent.cs @@ -0,0 +1,8 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarModelComponent(CommandBarComponentData xmlData) : CommandBarBaseComponent(xmlData) +{ + public override CommandBarComponentType Type => CommandBarComponentType.Model; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarSelectComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarSelectComponent.cs new file mode 100644 index 0000000..000995c --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarSelectComponent.cs @@ -0,0 +1,8 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarSelectComponent(CommandBarComponentData xmlData) : CommandBarBaseComponent(xmlData) +{ + public override CommandBarComponentType Type => CommandBarComponentType.Select; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarShellComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarShellComponent.cs new file mode 100644 index 0000000..799040e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarShellComponent.cs @@ -0,0 +1,19 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarShellComponent : CommandBarBaseComponent +{ + public override CommandBarComponentType Type => CommandBarComponentType.Shell; + + public string? ModelName { get; } + + public string? ModelPath { get; } + + public CommandBarShellComponent(CommandBarComponentData xmlData) : base(xmlData) + { + ModelName = xmlData.ModelName; + if (!string.IsNullOrEmpty(ModelName)) + ModelPath = $"DATA\\ART\\MODELS\\{ModelName}"; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextButtonComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextButtonComponent.cs new file mode 100644 index 0000000..9213f03 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextButtonComponent.cs @@ -0,0 +1,32 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.Rendering; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarTextButtonComponent : CommandBarButtonComponentClass +{ + public CommandBarComponentTextData TextData { get; } + public CommandBarComponentTextData TextData2 { get; } + public CommandBarComponentTextData FlashData { get; } + public bool ShowDraggedText { get; } = true; + public override CommandBarComponentType Type => CommandBarComponentType.TextButton; + + public CommandBarTextButtonComponent(CommandBarComponentData xmlData) : base(xmlData) + { + TextData = new CommandBarComponentTextData(this) + { + Mode = PrimRenderMode.PrimAlpha, + Scale = xmlData.Scale, + TempAlpha = -1, + Color = xmlData.TextColor.HasValue ? new RgbaColor(xmlData.TextColor.Value) : new RgbaColor(255, 255, 255, 255) + }; + TextData2 = new CommandBarComponentTextData(this) + { + Mode = PrimRenderMode.PrimAlpha, + Scale = xmlData.Scale, + TempAlpha = -1, + Color = xmlData.TextColor2.HasValue ? new RgbaColor(xmlData.TextColor2.Value) : new RgbaColor(255, 255, 255, 255) + }; + FlashData = new CommandBarComponentTextData(this); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextComponent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextComponent.cs new file mode 100644 index 0000000..d7471a5 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Components/CommandBarTextComponent.cs @@ -0,0 +1,28 @@ +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.Rendering; + +namespace PG.StarWarsGame.Engine.CommandBar.Components; + +public class CommandBarTextComponent : CommandBarBaseComponent +{ + public override CommandBarComponentType Type => CommandBarComponentType.Text; + + public CommandBarComponentTextData TextData { get; } + + public CommandBarComponentTextData FlashData { get; } + + public bool AutoPixelAlign => true; + + + public CommandBarTextComponent(CommandBarComponentData xmlData) : base(xmlData) + { + TextData = new CommandBarComponentTextData(this) + { + Scale = XmlData.Scale, + Color = Color, + TempAlpha = -1, + Mode = PrimRenderMode.PrimAlpha, + }; + FlashData = new CommandBarComponentTextData(this); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/EawCommandBarComponentIds.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/EawCommandBarComponentIds.cs new file mode 100644 index 0000000..ee47d05 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/EawCommandBarComponentIds.cs @@ -0,0 +1,665 @@ +namespace PG.StarWarsGame.Engine.CommandBar; + +public static class EawCommandBarComponentIds +{ + public static readonly CommandBarComponentId MainShell = new(GameEngineType.Eaw, 0x0); + public static readonly CommandBarComponentId ProductionOptions = new(GameEngineType.Eaw, 0x1); + public static readonly CommandBarComponentId GalacticCamera = new(GameEngineType.Eaw, 0x2); + public static readonly CommandBarComponentId PlanetName = new(GameEngineType.Eaw, 0x3); + public static readonly CommandBarComponentId PlanetValue = new(GameEngineType.Eaw, 0x4); + public static readonly CommandBarComponentId PlanetAffiliation = new(GameEngineType.Eaw, 0x5); + public static readonly CommandBarComponentId PlayerCredits = new(GameEngineType.Eaw, 0x6); + public static readonly CommandBarComponentId ReinforcementCap = new(GameEngineType.Eaw, 0x7); + public static readonly CommandBarComponentId PlanetNameTactical = new(GameEngineType.Eaw, 0x8); + public static readonly CommandBarComponentId PlanetInfo = new(GameEngineType.Eaw, 0x9); + public static readonly CommandBarComponentId Production0 = new(GameEngineType.Eaw, 0xa); + public static readonly CommandBarComponentId Production1 = new(GameEngineType.Eaw, 0xb); + public static readonly CommandBarComponentId Production2 = new(GameEngineType.Eaw, 0xc); + public static readonly CommandBarComponentId Production3 = new(GameEngineType.Eaw, 0xd); + public static readonly CommandBarComponentId Production4 = new(GameEngineType.Eaw, 0xe); + public static readonly CommandBarComponentId Production5 = new(GameEngineType.Eaw, 0xf); + public static readonly CommandBarComponentId Production6 = new(GameEngineType.Eaw, 0x10); + public static readonly CommandBarComponentId Production7 = new(GameEngineType.Eaw, 0x11); + public static readonly CommandBarComponentId Production8 = new(GameEngineType.Eaw, 0x12); + public static readonly CommandBarComponentId Production9 = new(GameEngineType.Eaw, 0x13); + public static readonly CommandBarComponentId Production10 = new(GameEngineType.Eaw, 0x14); + public static readonly CommandBarComponentId Production11 = new(GameEngineType.Eaw, 0x15); + public static readonly CommandBarComponentId Production12 = new(GameEngineType.Eaw, 0x16); + public static readonly CommandBarComponentId Production13 = new(GameEngineType.Eaw, 0x17); + public static readonly CommandBarComponentId Production14 = new(GameEngineType.Eaw, 0x18); + public static readonly CommandBarComponentId Production15 = new(GameEngineType.Eaw, 0x19); + public static readonly CommandBarComponentId Production16 = new(GameEngineType.Eaw, 0x1a); + public static readonly CommandBarComponentId Production17 = new(GameEngineType.Eaw, 0x1b); + public static readonly CommandBarComponentId Production18 = new(GameEngineType.Eaw, 0x1c); + public static readonly CommandBarComponentId Production19 = new(GameEngineType.Eaw, 0x1d); + public static readonly CommandBarComponentId Production20 = new(GameEngineType.Eaw, 0x1e); + public static readonly CommandBarComponentId Production21 = new(GameEngineType.Eaw, 0x1f); + public static readonly CommandBarComponentId Production22 = new(GameEngineType.Eaw, 0x20); + public static readonly CommandBarComponentId Production23 = new(GameEngineType.Eaw, 0x21); + public static readonly CommandBarComponentId Production24 = new(GameEngineType.Eaw, 0x22); + public static readonly CommandBarComponentId Production25 = new(GameEngineType.Eaw, 0x23); + public static readonly CommandBarComponentId Production26 = new(GameEngineType.Eaw, 0x24); + public static readonly CommandBarComponentId Production27 = new(GameEngineType.Eaw, 0x25); + public static readonly CommandBarComponentId Production28 = new(GameEngineType.Eaw, 0x26); + public static readonly CommandBarComponentId Production29 = new(GameEngineType.Eaw, 0x27); + public static readonly CommandBarComponentId Production30 = new(GameEngineType.Eaw, 0x28); + public static readonly CommandBarComponentId Production31 = new(GameEngineType.Eaw, 0x29); + public static readonly CommandBarComponentId Production32 = new(GameEngineType.Eaw, 0x2a); + public static readonly CommandBarComponentId Production33 = new(GameEngineType.Eaw, 0x2b); + public static readonly CommandBarComponentId Production34 = new(GameEngineType.Eaw, 0x2c); + public static readonly CommandBarComponentId Production35 = new(GameEngineType.Eaw, 0x2d); + public static readonly CommandBarComponentId Production36 = new(GameEngineType.Eaw, 0x2e); + public static readonly CommandBarComponentId Production37 = new(GameEngineType.Eaw, 0x2f); + public static readonly CommandBarComponentId Production38 = new(GameEngineType.Eaw, 0x30); + public static readonly CommandBarComponentId Production39 = new(GameEngineType.Eaw, 0x31); + public static readonly CommandBarComponentId Production40 = new(GameEngineType.Eaw, 0x32); + public static readonly CommandBarComponentId Production41 = new(GameEngineType.Eaw, 0x33); + public static readonly CommandBarComponentId Production42 = new(GameEngineType.Eaw, 0x34); + public static readonly CommandBarComponentId Production43 = new(GameEngineType.Eaw, 0x35); + public static readonly CommandBarComponentId Production44 = new(GameEngineType.Eaw, 0x36); + public static readonly CommandBarComponentId Production45 = new(GameEngineType.Eaw, 0x37); + public static readonly CommandBarComponentId Production46 = new(GameEngineType.Eaw, 0x38); + public static readonly CommandBarComponentId Production47 = new(GameEngineType.Eaw, 0x39); + public static readonly CommandBarComponentId DroidHelp = new(GameEngineType.Eaw, 0x3a); + public static readonly CommandBarComponentId DroidHelpTactical = new(GameEngineType.Eaw, 0x3b); + public static readonly CommandBarComponentId CurrentDay = new(GameEngineType.Eaw, 0x3c); + public static readonly CommandBarComponentId DayCredits = new(GameEngineType.Eaw, 0x3d); + public static readonly CommandBarComponentId PopulationCap = new(GameEngineType.Eaw, 0x3e); + public static readonly CommandBarComponentId Filter0 = new(GameEngineType.Eaw, 0x3f); + public static readonly CommandBarComponentId Filter1 = new(GameEngineType.Eaw, 0x40); + public static readonly CommandBarComponentId Filter2 = new(GameEngineType.Eaw, 0x41); + public static readonly CommandBarComponentId Filter3 = new(GameEngineType.Eaw, 0x42); + public static readonly CommandBarComponentId StoryArcButton = new(GameEngineType.Eaw, 0x43); + public static readonly CommandBarComponentId PlanetSummaryButton = new(GameEngineType.Eaw, 0x44); + public static readonly CommandBarComponentId SpaceTab = new(GameEngineType.Eaw, 0x45); + public static readonly CommandBarComponentId LandTab = new(GameEngineType.Eaw, 0x46); + public static readonly CommandBarComponentId Dial = new(GameEngineType.Eaw, 0x47); + public static readonly CommandBarComponentId ScrollRight = new(GameEngineType.Eaw, 0x48); + public static readonly CommandBarComponentId ScrollLeft = new(GameEngineType.Eaw, 0x49); + public static readonly CommandBarComponentId ZoomView = new(GameEngineType.Eaw, 0x4a); + public static readonly CommandBarComponentId PrevPlanet = new(GameEngineType.Eaw, 0x4b); + public static readonly CommandBarComponentId NextPlanet = new(GameEngineType.Eaw, 0x4c); + public static readonly CommandBarComponentId RadarGalactic = new(GameEngineType.Eaw, 0x4d); + public static readonly CommandBarComponentId TechLevel = new(GameEngineType.Eaw, 0x4e); + public static readonly CommandBarComponentId BalancePip = new(GameEngineType.Eaw, 0x4f); + public static readonly CommandBarComponentId BuildQueue00 = new(GameEngineType.Eaw, 0x50); + public static readonly CommandBarComponentId BuildQueue01 = new(GameEngineType.Eaw, 0x51); + public static readonly CommandBarComponentId BuildQueue02 = new(GameEngineType.Eaw, 0x52); + public static readonly CommandBarComponentId BuildQueue03 = new(GameEngineType.Eaw, 0x53); + public static readonly CommandBarComponentId BuildQueue04 = new(GameEngineType.Eaw, 0x54); + public static readonly CommandBarComponentId BuildQueue05 = new(GameEngineType.Eaw, 0x55); + public static readonly CommandBarComponentId BuildQueue06 = new(GameEngineType.Eaw, 0x56); + public static readonly CommandBarComponentId BuildQueue07 = new(GameEngineType.Eaw, 0x57); + public static readonly CommandBarComponentId BuildQueue08 = new(GameEngineType.Eaw, 0x58); + public static readonly CommandBarComponentId BuildQueue09 = new(GameEngineType.Eaw, 0x59); + public static readonly CommandBarComponentId OrganizationShell = new(GameEngineType.Eaw, 0x5a); + public static readonly CommandBarComponentId OrganizationCollision = new(GameEngineType.Eaw, 0x5b); + public static readonly CommandBarComponentId SmugglerBox = new(GameEngineType.Eaw, 0x5c); + public static readonly CommandBarComponentId SpaceStationUpgrade01 = new(GameEngineType.Eaw, 0x5d); + public static readonly CommandBarComponentId SpaceStationUpgrade02 = new(GameEngineType.Eaw, 0x5e); + public static readonly CommandBarComponentId HeroAbilitySlot = new(GameEngineType.Eaw, 0x5f); + public static readonly CommandBarComponentId PlanetOrganize0 = new(GameEngineType.Eaw, 0x60); + public static readonly CommandBarComponentId PlanetOrganize1 = new(GameEngineType.Eaw, 0x61); + public static readonly CommandBarComponentId PlanetOrganize2 = new(GameEngineType.Eaw, 0x62); + public static readonly CommandBarComponentId PlanetOrganize3 = new(GameEngineType.Eaw, 0x63); + public static readonly CommandBarComponentId PlanetOrganize4 = new(GameEngineType.Eaw, 0x64); + public static readonly CommandBarComponentId PlanetOrganize5 = new(GameEngineType.Eaw, 0x65); + public static readonly CommandBarComponentId PlanetOrganize6 = new(GameEngineType.Eaw, 0x66); + public static readonly CommandBarComponentId PlanetOrganize7 = new(GameEngineType.Eaw, 0x67); + public static readonly CommandBarComponentId PlanetOrganize8 = new(GameEngineType.Eaw, 0x68); + public static readonly CommandBarComponentId PlanetOrganize9 = new(GameEngineType.Eaw, 0x69); + public static readonly CommandBarComponentId SpecialStructureLand0 = new(GameEngineType.Eaw, 0x6a); + public static readonly CommandBarComponentId SpecialStructureLand1 = new(GameEngineType.Eaw, 0x6b); + public static readonly CommandBarComponentId SpecialStructureLand2 = new(GameEngineType.Eaw, 0x6c); + public static readonly CommandBarComponentId SpecialStructureLand3 = new(GameEngineType.Eaw, 0x6d); + public static readonly CommandBarComponentId SpecialStructureLand4 = new(GameEngineType.Eaw, 0x6e); + public static readonly CommandBarComponentId SpecialStructureLand5 = new(GameEngineType.Eaw, 0x6f); + public static readonly CommandBarComponentId SpecialStructureLand6 = new(GameEngineType.Eaw, 0x70); + public static readonly CommandBarComponentId SpecialStructureLand7 = new(GameEngineType.Eaw, 0x71); + public static readonly CommandBarComponentId SpecialStructureLand8 = new(GameEngineType.Eaw, 0x72); + public static readonly CommandBarComponentId SpecialStructureLandSell = new(GameEngineType.Eaw, 0x73); + public static readonly CommandBarComponentId BigFleet0 = new(GameEngineType.Eaw, 0x74); + public static readonly CommandBarComponentId BigFleet1 = new(GameEngineType.Eaw, 0x75); + public static readonly CommandBarComponentId BigFleet2 = new(GameEngineType.Eaw, 0x76); + public static readonly CommandBarComponentId BigFleet3 = new(GameEngineType.Eaw, 0x77); + public static readonly CommandBarComponentId Fleet0Slot0 = new(GameEngineType.Eaw, 0x78); + public static readonly CommandBarComponentId Fleet0Slot1 = new(GameEngineType.Eaw, 0x79); + public static readonly CommandBarComponentId Fleet0Slot2 = new(GameEngineType.Eaw, 0x7a); + public static readonly CommandBarComponentId Fleet0Slot3 = new(GameEngineType.Eaw, 0x7b); + public static readonly CommandBarComponentId Fleet0Slot4 = new(GameEngineType.Eaw, 0x7c); + public static readonly CommandBarComponentId Fleet0Slot5 = new(GameEngineType.Eaw, 0x7d); + public static readonly CommandBarComponentId Fleet0Slot6 = new(GameEngineType.Eaw, 0x7e); + public static readonly CommandBarComponentId Fleet0Slot7 = new(GameEngineType.Eaw, 0x7f); + public static readonly CommandBarComponentId Fleet0Slot8 = new(GameEngineType.Eaw, 0x80); + public static readonly CommandBarComponentId Fleet0Slot9 = new(GameEngineType.Eaw, 0x81); + public static readonly CommandBarComponentId Fleet0Slot10 = new(GameEngineType.Eaw, 0x82); + public static readonly CommandBarComponentId Fleet0Slot11 = new(GameEngineType.Eaw, 0x83); + public static readonly CommandBarComponentId Fleet0Slot12 = new(GameEngineType.Eaw, 0x84); + public static readonly CommandBarComponentId Fleet0Slot13 = new(GameEngineType.Eaw, 0x85); + public static readonly CommandBarComponentId Fleet0Slot14 = new(GameEngineType.Eaw, 0x86); + public static readonly CommandBarComponentId Fleet0Slot15 = new(GameEngineType.Eaw, 0x87); + public static readonly CommandBarComponentId Fleet0Slot16 = new(GameEngineType.Eaw, 0x88); + public static readonly CommandBarComponentId Fleet0Slot17 = new(GameEngineType.Eaw, 0x89); + public static readonly CommandBarComponentId Fleet0Slot18 = new(GameEngineType.Eaw, 0x8a); + public static readonly CommandBarComponentId Fleet0Slot19 = new(GameEngineType.Eaw, 0x8b); + public static readonly CommandBarComponentId Fleet0Slot20 = new(GameEngineType.Eaw, 0x8c); + public static readonly CommandBarComponentId Fleet0Slot21 = new(GameEngineType.Eaw, 0x8d); + public static readonly CommandBarComponentId Fleet0Slot22 = new(GameEngineType.Eaw, 0x8e); + public static readonly CommandBarComponentId Fleet0Slot23 = new(GameEngineType.Eaw, 0x8f); + public static readonly CommandBarComponentId Fleet0Slot24 = new(GameEngineType.Eaw, 0x90); + public static readonly CommandBarComponentId Fleet0Slot25 = new(GameEngineType.Eaw, 0x91); + public static readonly CommandBarComponentId Fleet0Slot26 = new(GameEngineType.Eaw, 0x92); + public static readonly CommandBarComponentId Fleet0Slot27 = new(GameEngineType.Eaw, 0x93); + public static readonly CommandBarComponentId Fleet0Slot28 = new(GameEngineType.Eaw, 0x94); + public static readonly CommandBarComponentId Fleet0Slot29 = new(GameEngineType.Eaw, 0x95); + public static readonly CommandBarComponentId Fleet0Slot30 = new(GameEngineType.Eaw, 0x96); + public static readonly CommandBarComponentId Fleet0Slot31 = new(GameEngineType.Eaw, 0x97); + public static readonly CommandBarComponentId Fleet0Slot32 = new(GameEngineType.Eaw, 0x98); + public static readonly CommandBarComponentId Fleet0Slot33 = new(GameEngineType.Eaw, 0x99); + public static readonly CommandBarComponentId Fleet0Slot34 = new(GameEngineType.Eaw, 0x9a); + public static readonly CommandBarComponentId Fleet1Slot0 = new(GameEngineType.Eaw, 0x9b); + public static readonly CommandBarComponentId Fleet1Slot1 = new(GameEngineType.Eaw, 0x9c); + public static readonly CommandBarComponentId Fleet1Slot2 = new(GameEngineType.Eaw, 0x9d); + public static readonly CommandBarComponentId Fleet1Slot3 = new(GameEngineType.Eaw, 0x9e); + public static readonly CommandBarComponentId Fleet1Slot4 = new(GameEngineType.Eaw, 0x9f); + public static readonly CommandBarComponentId Fleet1Slot5 = new(GameEngineType.Eaw, 0xa0); + public static readonly CommandBarComponentId Fleet1Slot6 = new(GameEngineType.Eaw, 0xa1); + public static readonly CommandBarComponentId Fleet1Slot7 = new(GameEngineType.Eaw, 0xa2); + public static readonly CommandBarComponentId Fleet1Slot8 = new(GameEngineType.Eaw, 0xa3); + public static readonly CommandBarComponentId Fleet1Slot9 = new(GameEngineType.Eaw, 0xa4); + public static readonly CommandBarComponentId Fleet1Slot10 = new(GameEngineType.Eaw, 0xa5); + public static readonly CommandBarComponentId Fleet1Slot11 = new(GameEngineType.Eaw, 0xa6); + public static readonly CommandBarComponentId Fleet1Slot12 = new(GameEngineType.Eaw, 0xa7); + public static readonly CommandBarComponentId Fleet1Slot13 = new(GameEngineType.Eaw, 0xa8); + public static readonly CommandBarComponentId Fleet1Slot14 = new(GameEngineType.Eaw, 0xa9); + public static readonly CommandBarComponentId Fleet1Slot15 = new(GameEngineType.Eaw, 0xaa); + public static readonly CommandBarComponentId Fleet1Slot16 = new(GameEngineType.Eaw, 0xab); + public static readonly CommandBarComponentId Fleet1Slot17 = new(GameEngineType.Eaw, 0xac); + public static readonly CommandBarComponentId Fleet1Slot18 = new(GameEngineType.Eaw, 0xad); + public static readonly CommandBarComponentId Fleet1Slot19 = new(GameEngineType.Eaw, 0xae); + public static readonly CommandBarComponentId Fleet1Slot20 = new(GameEngineType.Eaw, 0xaf); + public static readonly CommandBarComponentId Fleet1Slot21 = new(GameEngineType.Eaw, 0xb0); + public static readonly CommandBarComponentId Fleet1Slot22 = new(GameEngineType.Eaw, 0xb1); + public static readonly CommandBarComponentId Fleet1Slot23 = new(GameEngineType.Eaw, 0xb2); + public static readonly CommandBarComponentId Fleet1Slot24 = new(GameEngineType.Eaw, 0xb3); + public static readonly CommandBarComponentId Fleet1Slot25 = new(GameEngineType.Eaw, 0xb4); + public static readonly CommandBarComponentId Fleet1Slot26 = new(GameEngineType.Eaw, 0xb5); + public static readonly CommandBarComponentId Fleet1Slot27 = new(GameEngineType.Eaw, 0xb6); + public static readonly CommandBarComponentId Fleet1Slot28 = new(GameEngineType.Eaw, 0xb7); + public static readonly CommandBarComponentId Fleet1Slot29 = new(GameEngineType.Eaw, 0xb8); + public static readonly CommandBarComponentId Fleet1Slot30 = new(GameEngineType.Eaw, 0xb9); + public static readonly CommandBarComponentId Fleet1Slot31 = new(GameEngineType.Eaw, 0xba); + public static readonly CommandBarComponentId Fleet1Slot32 = new(GameEngineType.Eaw, 0xbb); + public static readonly CommandBarComponentId Fleet1Slot33 = new(GameEngineType.Eaw, 0xbc); + public static readonly CommandBarComponentId Fleet1Slot34 = new(GameEngineType.Eaw, 0xbd); + public static readonly CommandBarComponentId Fleet2Slot0 = new(GameEngineType.Eaw, 0xbe); + public static readonly CommandBarComponentId Fleet2Slot1 = new(GameEngineType.Eaw, 0xbf); + public static readonly CommandBarComponentId Fleet2Slot2 = new(GameEngineType.Eaw, 0xc0); + public static readonly CommandBarComponentId Fleet2Slot3 = new(GameEngineType.Eaw, 0xc1); + public static readonly CommandBarComponentId Fleet2Slot4 = new(GameEngineType.Eaw, 0xc2); + public static readonly CommandBarComponentId Fleet2Slot5 = new(GameEngineType.Eaw, 0xc3); + public static readonly CommandBarComponentId Fleet2Slot6 = new(GameEngineType.Eaw, 0xc4); + public static readonly CommandBarComponentId Fleet2Slot7 = new(GameEngineType.Eaw, 0xc5); + public static readonly CommandBarComponentId Fleet2Slot8 = new(GameEngineType.Eaw, 0xc6); + public static readonly CommandBarComponentId Fleet2Slot9 = new(GameEngineType.Eaw, 0xc7); + public static readonly CommandBarComponentId Fleet2Slot10 = new(GameEngineType.Eaw, 0xc8); + public static readonly CommandBarComponentId Fleet2Slot11 = new(GameEngineType.Eaw, 0xc9); + public static readonly CommandBarComponentId Fleet2Slot12 = new(GameEngineType.Eaw, 0xca); + public static readonly CommandBarComponentId Fleet2Slot13 = new(GameEngineType.Eaw, 0xcb); + public static readonly CommandBarComponentId Fleet2Slot14 = new(GameEngineType.Eaw, 0xcc); + public static readonly CommandBarComponentId Fleet2Slot15 = new(GameEngineType.Eaw, 0xcd); + public static readonly CommandBarComponentId Fleet2Slot16 = new(GameEngineType.Eaw, 0xce); + public static readonly CommandBarComponentId Fleet2Slot17 = new(GameEngineType.Eaw, 0xcf); + public static readonly CommandBarComponentId Fleet2Slot18 = new(GameEngineType.Eaw, 0xd0); + public static readonly CommandBarComponentId Fleet2Slot19 = new(GameEngineType.Eaw, 0xd1); + public static readonly CommandBarComponentId Fleet2Slot20 = new(GameEngineType.Eaw, 0xd2); + public static readonly CommandBarComponentId Fleet2Slot21 = new(GameEngineType.Eaw, 0xd3); + public static readonly CommandBarComponentId Fleet2Slot22 = new(GameEngineType.Eaw, 0xd4); + public static readonly CommandBarComponentId Fleet2Slot23 = new(GameEngineType.Eaw, 0xd5); + public static readonly CommandBarComponentId Fleet2Slot24 = new(GameEngineType.Eaw, 0xd6); + public static readonly CommandBarComponentId Fleet2Slot25 = new(GameEngineType.Eaw, 0xd7); + public static readonly CommandBarComponentId Fleet2Slot26 = new(GameEngineType.Eaw, 0xd8); + public static readonly CommandBarComponentId Fleet2Slot27 = new(GameEngineType.Eaw, 0xd9); + public static readonly CommandBarComponentId Fleet2Slot28 = new(GameEngineType.Eaw, 0xda); + public static readonly CommandBarComponentId Fleet2Slot29 = new(GameEngineType.Eaw, 0xdb); + public static readonly CommandBarComponentId Fleet2Slot30 = new(GameEngineType.Eaw, 0xdc); + public static readonly CommandBarComponentId Fleet2Slot31 = new(GameEngineType.Eaw, 0xdd); + public static readonly CommandBarComponentId Fleet2Slot32 = new(GameEngineType.Eaw, 0xde); + public static readonly CommandBarComponentId Fleet2Slot33 = new(GameEngineType.Eaw, 0xdf); + public static readonly CommandBarComponentId Fleet2Slot34 = new(GameEngineType.Eaw, 0xe0); + public static readonly CommandBarComponentId TacticalMain = new(GameEngineType.Eaw, 0xe1); + public static readonly CommandBarComponentId TacticalOptions = new(GameEngineType.Eaw, 0xe2); + public static readonly CommandBarComponentId TacticalCamera = new(GameEngineType.Eaw, 0xe3); + public static readonly CommandBarComponentId TacticalBeacon = new(GameEngineType.Eaw, 0xe4); + public static readonly CommandBarComponentId TacticalWeapon0 = new(GameEngineType.Eaw, 0xe5); + public static readonly CommandBarComponentId TacticalWeapon1 = new(GameEngineType.Eaw, 0xe6); + public static readonly CommandBarComponentId TacticalReinforce = new(GameEngineType.Eaw, 0xe7); + public static readonly CommandBarComponentId TacticalRetreat = new(GameEngineType.Eaw, 0xe8); + public static readonly CommandBarComponentId TacticalStoryArc = new(GameEngineType.Eaw, 0xe9); + public static readonly CommandBarComponentId TacticalTechLevel = new(GameEngineType.Eaw, 0xea); + public static readonly CommandBarComponentId TacticalAttack = new(GameEngineType.Eaw, 0xeb); + public static readonly CommandBarComponentId TacticalAttackMove = new(GameEngineType.Eaw, 0xec); + public static readonly CommandBarComponentId TacticalMove = new(GameEngineType.Eaw, 0xed); + public static readonly CommandBarComponentId TacticalWaypoint = new(GameEngineType.Eaw, 0xee); + public static readonly CommandBarComponentId TacticalStop = new(GameEngineType.Eaw, 0xef); + public static readonly CommandBarComponentId TacticalGuard = new(GameEngineType.Eaw, 0xf0); + public static readonly CommandBarComponentId SpaceTacticalRadar = new(GameEngineType.Eaw, 0xf1); + public static readonly CommandBarComponentId SpaceTacticalCredits = new(GameEngineType.Eaw, 0xf2); + public static readonly CommandBarComponentId SpaceTacticalGrabBar = new(GameEngineType.Eaw, 0xf3); + public static readonly CommandBarComponentId SpaceTacticalHealthBar = new(GameEngineType.Eaw, 0xf4); + public static readonly CommandBarComponentId SpaceTacticalBracketSmall = new(GameEngineType.Eaw, 0xf5); + public static readonly CommandBarComponentId SpaceTacticalBracketMedium = new(GameEngineType.Eaw, 0xf6); + public static readonly CommandBarComponentId SpaceTacticalBracketLarge = new(GameEngineType.Eaw, 0xf7); + public static readonly CommandBarComponentId SpaceTacticalHeroIcon = new(GameEngineType.Eaw, 0xf8); + public static readonly CommandBarComponentId SpaceTacticalHeroHealth = new(GameEngineType.Eaw, 0xf9); + public static readonly CommandBarComponentId SpaceTacticalHealth = new(GameEngineType.Eaw, 0xfa); + public static readonly CommandBarComponentId SpaceTacticalHealthMedium = new(GameEngineType.Eaw, 0xfb); + public static readonly CommandBarComponentId SpaceTacticalHealthLarge = new(GameEngineType.Eaw, 0xfc); + public static readonly CommandBarComponentId SpaceTacticalShields = new(GameEngineType.Eaw, 0xfd); + public static readonly CommandBarComponentId SpaceTacticalShieldsMedium = new(GameEngineType.Eaw, 0xfe); + public static readonly CommandBarComponentId SpaceTacticalShieldsLarge = new(GameEngineType.Eaw, 0xff); + public static readonly CommandBarComponentId SpaceTacticalPower = new(GameEngineType.Eaw, 0x100); + public static readonly CommandBarComponentId SpaceTacticalControlGroup = new(GameEngineType.Eaw, 0x101); + public static readonly CommandBarComponentId SpaceTacticalAbilityIcon = new(GameEngineType.Eaw, 0x102); + public static readonly CommandBarComponentId SpaceTacticalGarrisonIcon = new(GameEngineType.Eaw, 0x103); + public static readonly CommandBarComponentId LandTacticalWeatherIcon = new(GameEngineType.Eaw, 0x104); + public static readonly CommandBarComponentId DsFireShell = new(GameEngineType.Eaw, 0x105); + public static readonly CommandBarComponentId DsFire = new(GameEngineType.Eaw, 0x106); + public static readonly CommandBarComponentId DsCountdownTimer = new(GameEngineType.Eaw, 0x107); + public static readonly CommandBarComponentId TacticalSelect00 = new(GameEngineType.Eaw, 0x108); + public static readonly CommandBarComponentId TacticalSelect01 = new(GameEngineType.Eaw, 0x109); + public static readonly CommandBarComponentId TacticalSelect02 = new(GameEngineType.Eaw, 0x10a); + public static readonly CommandBarComponentId TacticalSelect03 = new(GameEngineType.Eaw, 0x10b); + public static readonly CommandBarComponentId TacticalSelect04 = new(GameEngineType.Eaw, 0x10c); + public static readonly CommandBarComponentId TacticalSelect05 = new(GameEngineType.Eaw, 0x10d); + public static readonly CommandBarComponentId TacticalSelect06 = new(GameEngineType.Eaw, 0x10e); + public static readonly CommandBarComponentId TacticalSelect07 = new(GameEngineType.Eaw, 0x10f); + public static readonly CommandBarComponentId TacticalSelect08 = new(GameEngineType.Eaw, 0x110); + public static readonly CommandBarComponentId TacticalSelect09 = new(GameEngineType.Eaw, 0x111); + public static readonly CommandBarComponentId TacticalSelect10 = new(GameEngineType.Eaw, 0x112); + public static readonly CommandBarComponentId TacticalSelect11 = new(GameEngineType.Eaw, 0x113); + public static readonly CommandBarComponentId TacticalSelect12 = new(GameEngineType.Eaw, 0x114); + public static readonly CommandBarComponentId TacticalSelect13 = new(GameEngineType.Eaw, 0x115); + public static readonly CommandBarComponentId TacticalSelect14 = new(GameEngineType.Eaw, 0x116); + public static readonly CommandBarComponentId TacticalSelect15 = new(GameEngineType.Eaw, 0x117); + public static readonly CommandBarComponentId TacticalSelect16 = new(GameEngineType.Eaw, 0x118); + public static readonly CommandBarComponentId TacticalSelect17 = new(GameEngineType.Eaw, 0x119); + public static readonly CommandBarComponentId TacticalSelect18 = new(GameEngineType.Eaw, 0x11a); + public static readonly CommandBarComponentId TacticalSelect19 = new(GameEngineType.Eaw, 0x11b); + public static readonly CommandBarComponentId TacticalSelect20 = new(GameEngineType.Eaw, 0x11c); + public static readonly CommandBarComponentId TacticalSelect21 = new(GameEngineType.Eaw, 0x11d); + public static readonly CommandBarComponentId TacticalSelect22 = new(GameEngineType.Eaw, 0x11e); + public static readonly CommandBarComponentId TacticalSelect23 = new(GameEngineType.Eaw, 0x11f); + public static readonly CommandBarComponentId TacticalSelect24 = new(GameEngineType.Eaw, 0x120); + public static readonly CommandBarComponentId TacticalSelect25 = new(GameEngineType.Eaw, 0x121); + public static readonly CommandBarComponentId TacticalSelect26 = new(GameEngineType.Eaw, 0x122); + public static readonly CommandBarComponentId TacticalSelect27 = new(GameEngineType.Eaw, 0x123); + public static readonly CommandBarComponentId TacticalSelect28 = new(GameEngineType.Eaw, 0x124); + public static readonly CommandBarComponentId TacticalSelect29 = new(GameEngineType.Eaw, 0x125); + public static readonly CommandBarComponentId TacticalSelect30 = new(GameEngineType.Eaw, 0x126); + public static readonly CommandBarComponentId TacticalSelect31 = new(GameEngineType.Eaw, 0x127); + public static readonly CommandBarComponentId TacticalSelect32 = new(GameEngineType.Eaw, 0x128); + public static readonly CommandBarComponentId TacticalSelect33 = new(GameEngineType.Eaw, 0x129); + public static readonly CommandBarComponentId TacticalSelect34 = new(GameEngineType.Eaw, 0x12a); + public static readonly CommandBarComponentId TacticalSelect35 = new(GameEngineType.Eaw, 0x12b); + public static readonly CommandBarComponentId TacticalSelect36 = new(GameEngineType.Eaw, 0x12c); + public static readonly CommandBarComponentId TacticalSelect37 = new(GameEngineType.Eaw, 0x12d); + public static readonly CommandBarComponentId TacticalSelect38 = new(GameEngineType.Eaw, 0x12e); + public static readonly CommandBarComponentId TacticalSelect39 = new(GameEngineType.Eaw, 0x12f); + public static readonly CommandBarComponentId TacticalSelect40 = new(GameEngineType.Eaw, 0x130); + public static readonly CommandBarComponentId TacticalSelect41 = new(GameEngineType.Eaw, 0x131); + public static readonly CommandBarComponentId TacticalSelect42 = new(GameEngineType.Eaw, 0x132); + public static readonly CommandBarComponentId TacticalSelect43 = new(GameEngineType.Eaw, 0x133); + public static readonly CommandBarComponentId TacticalSelect44 = new(GameEngineType.Eaw, 0x134); + public static readonly CommandBarComponentId TacticalSelect45 = new(GameEngineType.Eaw, 0x135); + public static readonly CommandBarComponentId TacticalSelect46 = new(GameEngineType.Eaw, 0x136); + public static readonly CommandBarComponentId TacticalSelect47 = new(GameEngineType.Eaw, 0x137); + public static readonly CommandBarComponentId TacticalSelectHealth00 = new(GameEngineType.Eaw, 0x138); + public static readonly CommandBarComponentId TacticalSelectHealth01 = new(GameEngineType.Eaw, 0x139); + public static readonly CommandBarComponentId TacticalSelectHealth02 = new(GameEngineType.Eaw, 0x13a); + public static readonly CommandBarComponentId TacticalSelectHealth03 = new(GameEngineType.Eaw, 0x13b); + public static readonly CommandBarComponentId TacticalSelectHealth04 = new(GameEngineType.Eaw, 0x13c); + public static readonly CommandBarComponentId TacticalSelectHealth05 = new(GameEngineType.Eaw, 0x13d); + public static readonly CommandBarComponentId TacticalSelectHealth06 = new(GameEngineType.Eaw, 0x13e); + public static readonly CommandBarComponentId TacticalSelectHealth07 = new(GameEngineType.Eaw, 0x13f); + public static readonly CommandBarComponentId TacticalSelectHealth08 = new(GameEngineType.Eaw, 0x140); + public static readonly CommandBarComponentId TacticalSelectHealth09 = new(GameEngineType.Eaw, 0x141); + public static readonly CommandBarComponentId TacticalSelectHealth10 = new(GameEngineType.Eaw, 0x142); + public static readonly CommandBarComponentId TacticalSelectHealth11 = new(GameEngineType.Eaw, 0x143); + public static readonly CommandBarComponentId TacticalSelectHealth12 = new(GameEngineType.Eaw, 0x144); + public static readonly CommandBarComponentId TacticalSelectHealth13 = new(GameEngineType.Eaw, 0x145); + public static readonly CommandBarComponentId TacticalSelectHealth14 = new(GameEngineType.Eaw, 0x146); + public static readonly CommandBarComponentId TacticalSelectHealth15 = new(GameEngineType.Eaw, 0x147); + public static readonly CommandBarComponentId TacticalSelectHealth16 = new(GameEngineType.Eaw, 0x148); + public static readonly CommandBarComponentId TacticalSelectHealth17 = new(GameEngineType.Eaw, 0x149); + public static readonly CommandBarComponentId TacticalSelectHealth18 = new(GameEngineType.Eaw, 0x14a); + public static readonly CommandBarComponentId TacticalSelectHealth19 = new(GameEngineType.Eaw, 0x14b); + public static readonly CommandBarComponentId TacticalSelectHealth20 = new(GameEngineType.Eaw, 0x14c); + public static readonly CommandBarComponentId TacticalSelectHealth21 = new(GameEngineType.Eaw, 0x14d); + public static readonly CommandBarComponentId TacticalSelectHealth22 = new(GameEngineType.Eaw, 0x14e); + public static readonly CommandBarComponentId TacticalSelectHealth23 = new(GameEngineType.Eaw, 0x14f); + public static readonly CommandBarComponentId TacticalSelectHealth24 = new(GameEngineType.Eaw, 0x150); + public static readonly CommandBarComponentId TacticalSelectHealth25 = new(GameEngineType.Eaw, 0x151); + public static readonly CommandBarComponentId TacticalSelectHealth26 = new(GameEngineType.Eaw, 0x152); + public static readonly CommandBarComponentId TacticalSelectHealth27 = new(GameEngineType.Eaw, 0x153); + public static readonly CommandBarComponentId TacticalSelectHealth28 = new(GameEngineType.Eaw, 0x154); + public static readonly CommandBarComponentId TacticalSelectHealth29 = new(GameEngineType.Eaw, 0x155); + public static readonly CommandBarComponentId TacticalSelectHealth30 = new(GameEngineType.Eaw, 0x156); + public static readonly CommandBarComponentId TacticalSelectHealth31 = new(GameEngineType.Eaw, 0x157); + public static readonly CommandBarComponentId TacticalSelectHealth32 = new(GameEngineType.Eaw, 0x158); + public static readonly CommandBarComponentId TacticalSelectHealth33 = new(GameEngineType.Eaw, 0x159); + public static readonly CommandBarComponentId TacticalSelectHealth34 = new(GameEngineType.Eaw, 0x15a); + public static readonly CommandBarComponentId TacticalSelectHealth35 = new(GameEngineType.Eaw, 0x15b); + public static readonly CommandBarComponentId TacticalSelectHealth36 = new(GameEngineType.Eaw, 0x15c); + public static readonly CommandBarComponentId TacticalSelectHealth37 = new(GameEngineType.Eaw, 0x15d); + public static readonly CommandBarComponentId TacticalSelectHealth38 = new(GameEngineType.Eaw, 0x15e); + public static readonly CommandBarComponentId TacticalSelectHealth39 = new(GameEngineType.Eaw, 0x15f); + public static readonly CommandBarComponentId TacticalSelectHealth40 = new(GameEngineType.Eaw, 0x160); + public static readonly CommandBarComponentId TacticalSelectHealth41 = new(GameEngineType.Eaw, 0x161); + public static readonly CommandBarComponentId TacticalSelectHealth42 = new(GameEngineType.Eaw, 0x162); + public static readonly CommandBarComponentId TacticalSelectHealth43 = new(GameEngineType.Eaw, 0x163); + public static readonly CommandBarComponentId TacticalSelectHealth44 = new(GameEngineType.Eaw, 0x164); + public static readonly CommandBarComponentId TacticalSelectHealth45 = new(GameEngineType.Eaw, 0x165); + public static readonly CommandBarComponentId TacticalSelectHealth46 = new(GameEngineType.Eaw, 0x166); + public static readonly CommandBarComponentId TacticalSelectHealth47 = new(GameEngineType.Eaw, 0x167); + public static readonly CommandBarComponentId TacticalSelectShield00 = new(GameEngineType.Eaw, 0x168); + public static readonly CommandBarComponentId TacticalSelectShield01 = new(GameEngineType.Eaw, 0x169); + public static readonly CommandBarComponentId TacticalSelectShield02 = new(GameEngineType.Eaw, 0x16a); + public static readonly CommandBarComponentId TacticalSelectShield03 = new(GameEngineType.Eaw, 0x16b); + public static readonly CommandBarComponentId TacticalSelectShield04 = new(GameEngineType.Eaw, 0x16c); + public static readonly CommandBarComponentId TacticalSelectShield05 = new(GameEngineType.Eaw, 0x16d); + public static readonly CommandBarComponentId TacticalSelectShield06 = new(GameEngineType.Eaw, 0x16e); + public static readonly CommandBarComponentId TacticalSelectShield07 = new(GameEngineType.Eaw, 0x16f); + public static readonly CommandBarComponentId TacticalSelectShield08 = new(GameEngineType.Eaw, 0x170); + public static readonly CommandBarComponentId TacticalSelectShield09 = new(GameEngineType.Eaw, 0x171); + public static readonly CommandBarComponentId TacticalSelectShield10 = new(GameEngineType.Eaw, 0x172); + public static readonly CommandBarComponentId TacticalSelectShield11 = new(GameEngineType.Eaw, 0x173); + public static readonly CommandBarComponentId TacticalSelectShield12 = new(GameEngineType.Eaw, 0x174); + public static readonly CommandBarComponentId TacticalSelectShield13 = new(GameEngineType.Eaw, 0x175); + public static readonly CommandBarComponentId TacticalSelectShield14 = new(GameEngineType.Eaw, 0x176); + public static readonly CommandBarComponentId TacticalSelectShield15 = new(GameEngineType.Eaw, 0x177); + public static readonly CommandBarComponentId TacticalSelectShield16 = new(GameEngineType.Eaw, 0x178); + public static readonly CommandBarComponentId TacticalSelectShield17 = new(GameEngineType.Eaw, 0x179); + public static readonly CommandBarComponentId TacticalSelectShield18 = new(GameEngineType.Eaw, 0x17a); + public static readonly CommandBarComponentId TacticalSelectShield19 = new(GameEngineType.Eaw, 0x17b); + public static readonly CommandBarComponentId TacticalSelectShield20 = new(GameEngineType.Eaw, 0x17c); + public static readonly CommandBarComponentId TacticalSelectShield21 = new(GameEngineType.Eaw, 0x17d); + public static readonly CommandBarComponentId TacticalSelectShield22 = new(GameEngineType.Eaw, 0x17e); + public static readonly CommandBarComponentId TacticalSelectShield23 = new(GameEngineType.Eaw, 0x17f); + public static readonly CommandBarComponentId TacticalSelectShield24 = new(GameEngineType.Eaw, 0x180); + public static readonly CommandBarComponentId TacticalSelectShield25 = new(GameEngineType.Eaw, 0x181); + public static readonly CommandBarComponentId TacticalSelectShield26 = new(GameEngineType.Eaw, 0x182); + public static readonly CommandBarComponentId TacticalSelectShield27 = new(GameEngineType.Eaw, 0x183); + public static readonly CommandBarComponentId TacticalSelectShield28 = new(GameEngineType.Eaw, 0x184); + public static readonly CommandBarComponentId TacticalSelectShield29 = new(GameEngineType.Eaw, 0x185); + public static readonly CommandBarComponentId TacticalSelectShield30 = new(GameEngineType.Eaw, 0x186); + public static readonly CommandBarComponentId TacticalSelectShield31 = new(GameEngineType.Eaw, 0x187); + public static readonly CommandBarComponentId TacticalSelectShield32 = new(GameEngineType.Eaw, 0x188); + public static readonly CommandBarComponentId TacticalSelectShield33 = new(GameEngineType.Eaw, 0x189); + public static readonly CommandBarComponentId TacticalSelectShield34 = new(GameEngineType.Eaw, 0x18a); + public static readonly CommandBarComponentId TacticalSelectShield35 = new(GameEngineType.Eaw, 0x18b); + public static readonly CommandBarComponentId TacticalSelectShield36 = new(GameEngineType.Eaw, 0x18c); + public static readonly CommandBarComponentId TacticalSelectShield37 = new(GameEngineType.Eaw, 0x18d); + public static readonly CommandBarComponentId TacticalSelectShield38 = new(GameEngineType.Eaw, 0x18e); + public static readonly CommandBarComponentId TacticalSelectShield39 = new(GameEngineType.Eaw, 0x18f); + public static readonly CommandBarComponentId TacticalSelectShield40 = new(GameEngineType.Eaw, 0x190); + public static readonly CommandBarComponentId TacticalSelectShield41 = new(GameEngineType.Eaw, 0x191); + public static readonly CommandBarComponentId TacticalSelectShield42 = new(GameEngineType.Eaw, 0x192); + public static readonly CommandBarComponentId TacticalSelectShield43 = new(GameEngineType.Eaw, 0x193); + public static readonly CommandBarComponentId TacticalSelectShield44 = new(GameEngineType.Eaw, 0x194); + public static readonly CommandBarComponentId TacticalSelectShield45 = new(GameEngineType.Eaw, 0x195); + public static readonly CommandBarComponentId TacticalSelectShield46 = new(GameEngineType.Eaw, 0x196); + public static readonly CommandBarComponentId TacticalSelectShield47 = new(GameEngineType.Eaw, 0x197); + public static readonly CommandBarComponentId TacticalBorder00 = new(GameEngineType.Eaw, 0x198); + public static readonly CommandBarComponentId TacticalBorder01 = new(GameEngineType.Eaw, 0x199); + public static readonly CommandBarComponentId TacticalBorder02 = new(GameEngineType.Eaw, 0x19a); + public static readonly CommandBarComponentId TacticalBorder03 = new(GameEngineType.Eaw, 0x19b); + public static readonly CommandBarComponentId TacticalBorder04 = new(GameEngineType.Eaw, 0x19c); + public static readonly CommandBarComponentId TacticalBorder05 = new(GameEngineType.Eaw, 0x19d); + public static readonly CommandBarComponentId TacticalBorder06 = new(GameEngineType.Eaw, 0x19e); + public static readonly CommandBarComponentId TacticalBorder07 = new(GameEngineType.Eaw, 0x19f); + public static readonly CommandBarComponentId TacticalBorder08 = new(GameEngineType.Eaw, 0x1a0); + public static readonly CommandBarComponentId TacticalBorder09 = new(GameEngineType.Eaw, 0x1a1); + public static readonly CommandBarComponentId TacticalBorder10 = new(GameEngineType.Eaw, 0x1a2); + public static readonly CommandBarComponentId TacticalSelectButton00 = new(GameEngineType.Eaw, 0x1a3); + public static readonly CommandBarComponentId TacticalSelectButton01 = new(GameEngineType.Eaw, 0x1a4); + public static readonly CommandBarComponentId TacticalSelectButton02 = new(GameEngineType.Eaw, 0x1a5); + public static readonly CommandBarComponentId TacticalSelectButton03 = new(GameEngineType.Eaw, 0x1a6); + public static readonly CommandBarComponentId TacticalSelectButton04 = new(GameEngineType.Eaw, 0x1a7); + public static readonly CommandBarComponentId TacticalSelectButton05 = new(GameEngineType.Eaw, 0x1a8); + public static readonly CommandBarComponentId TacticalSelectButton06 = new(GameEngineType.Eaw, 0x1a9); + public static readonly CommandBarComponentId TacticalSelectButton07 = new(GameEngineType.Eaw, 0x1aa); + public static readonly CommandBarComponentId TacticalSelectButton08 = new(GameEngineType.Eaw, 0x1ab); + public static readonly CommandBarComponentId TacticalSelectButton09 = new(GameEngineType.Eaw, 0x1ac); + public static readonly CommandBarComponentId TacticalSelectButton10 = new(GameEngineType.Eaw, 0x1ad); + public static readonly CommandBarComponentId TacticalSelectButton11 = new(GameEngineType.Eaw, 0x1ae); + public static readonly CommandBarComponentId TacticalSelectButton12 = new(GameEngineType.Eaw, 0x1af); + public static readonly CommandBarComponentId TacticalSelectButton13 = new(GameEngineType.Eaw, 0x1b0); + public static readonly CommandBarComponentId TacticalSelectButton14 = new(GameEngineType.Eaw, 0x1b1); + public static readonly CommandBarComponentId TacticalSelectButton15 = new(GameEngineType.Eaw, 0x1b2); + public static readonly CommandBarComponentId TacticalSelectButton16 = new(GameEngineType.Eaw, 0x1b3); + public static readonly CommandBarComponentId TacticalSelectButton17 = new(GameEngineType.Eaw, 0x1b4); + public static readonly CommandBarComponentId TacticalSelectButton18 = new(GameEngineType.Eaw, 0x1b5); + public static readonly CommandBarComponentId TacticalSelectButton19 = new(GameEngineType.Eaw, 0x1b6); + public static readonly CommandBarComponentId TacticalSelectButton20 = new(GameEngineType.Eaw, 0x1b7); + public static readonly CommandBarComponentId TacticalSelectButton21 = new(GameEngineType.Eaw, 0x1b8); + public static readonly CommandBarComponentId TacticalSelectButton22 = new(GameEngineType.Eaw, 0x1b9); + public static readonly CommandBarComponentId TacticalSelectButton23 = new(GameEngineType.Eaw, 0x1ba); + public static readonly CommandBarComponentId TacticalSelectButton24 = new(GameEngineType.Eaw, 0x1bb); + public static readonly CommandBarComponentId TacticalSelectButton25 = new(GameEngineType.Eaw, 0x1bc); + public static readonly CommandBarComponentId TacticalSelectButton26 = new(GameEngineType.Eaw, 0x1bd); + public static readonly CommandBarComponentId TacticalSelectButton27 = new(GameEngineType.Eaw, 0x1be); + public static readonly CommandBarComponentId TacticalSelectButton28 = new(GameEngineType.Eaw, 0x1bf); + public static readonly CommandBarComponentId TacticalSelectButton29 = new(GameEngineType.Eaw, 0x1c0); + public static readonly CommandBarComponentId TacticalSelectButton30 = new(GameEngineType.Eaw, 0x1c1); + public static readonly CommandBarComponentId TacticalSelectButton31 = new(GameEngineType.Eaw, 0x1c2); + public static readonly CommandBarComponentId TacticalSelectButton32 = new(GameEngineType.Eaw, 0x1c3); + public static readonly CommandBarComponentId TacticalSelectButton33 = new(GameEngineType.Eaw, 0x1c4); + public static readonly CommandBarComponentId TacticalSelectButton34 = new(GameEngineType.Eaw, 0x1c5); + public static readonly CommandBarComponentId TacticalSelectButton35 = new(GameEngineType.Eaw, 0x1c6); + public static readonly CommandBarComponentId TacticalSelectButton36 = new(GameEngineType.Eaw, 0x1c7); + public static readonly CommandBarComponentId TacticalSelectButton37 = new(GameEngineType.Eaw, 0x1c8); + public static readonly CommandBarComponentId TacticalSelectButton38 = new(GameEngineType.Eaw, 0x1c9); + public static readonly CommandBarComponentId TacticalSelectButton39 = new(GameEngineType.Eaw, 0x1ca); + public static readonly CommandBarComponentId TacticalSelectButton40 = new(GameEngineType.Eaw, 0x1cb); + public static readonly CommandBarComponentId TacticalSelectButton41 = new(GameEngineType.Eaw, 0x1cc); + public static readonly CommandBarComponentId TacticalSelectButton42 = new(GameEngineType.Eaw, 0x1cd); + public static readonly CommandBarComponentId TacticalSelectButton43 = new(GameEngineType.Eaw, 0x1ce); + public static readonly CommandBarComponentId TacticalSelectButton44 = new(GameEngineType.Eaw, 0x1cf); + public static readonly CommandBarComponentId TacticalSelectButton45 = new(GameEngineType.Eaw, 0x1d0); + public static readonly CommandBarComponentId TacticalSelectButton46 = new(GameEngineType.Eaw, 0x1d1); + public static readonly CommandBarComponentId TacticalSelectButton47 = new(GameEngineType.Eaw, 0x1d2); + public static readonly CommandBarComponentId TacticalBuildQueue00 = new(GameEngineType.Eaw, 0x1d3); + public static readonly CommandBarComponentId TacticalBuildQueue01 = new(GameEngineType.Eaw, 0x1d4); + public static readonly CommandBarComponentId TacticalBuildQueue02 = new(GameEngineType.Eaw, 0x1d5); + public static readonly CommandBarComponentId TacticalBuildQueue03 = new(GameEngineType.Eaw, 0x1d6); + public static readonly CommandBarComponentId TacticalBuildQueue04 = new(GameEngineType.Eaw, 0x1d7); + public static readonly CommandBarComponentId TacticalBuildQueue05 = new(GameEngineType.Eaw, 0x1d8); + public static readonly CommandBarComponentId TacticalBuildQueue06 = new(GameEngineType.Eaw, 0x1d9); + public static readonly CommandBarComponentId TacticalBuildQueue07 = new(GameEngineType.Eaw, 0x1da); + public static readonly CommandBarComponentId TacticalBuildQueue08 = new(GameEngineType.Eaw, 0x1db); + public static readonly CommandBarComponentId TacticalBuildQueue09 = new(GameEngineType.Eaw, 0x1dc); + public static readonly CommandBarComponentId TooltipBack = new(GameEngineType.Eaw, 0x1dd); + public static readonly CommandBarComponentId TooltipName = new(GameEngineType.Eaw, 0x1de); + public static readonly CommandBarComponentId TooltipPrice = new(GameEngineType.Eaw, 0x1df); + public static readonly CommandBarComponentId TooltipIcon = new(GameEngineType.Eaw, 0x1e0); + public static readonly CommandBarComponentId TooltipIconLand = new(GameEngineType.Eaw, 0x1e1); + public static readonly CommandBarComponentId TooltipLeftJustified = new(GameEngineType.Eaw, 0x1e2); + public static readonly CommandBarComponentId EncyclopediaBack = new(GameEngineType.Eaw, 0x1e3); + public static readonly CommandBarComponentId EncyclopediaHeaderText = new(GameEngineType.Eaw, 0x1e4); + public static readonly CommandBarComponentId EncyclopediaText = new(GameEngineType.Eaw, 0x1e5); + public static readonly CommandBarComponentId EncyclopediaRightText = new(GameEngineType.Eaw, 0x1e6); + public static readonly CommandBarComponentId EncyclopediaCenterText = new(GameEngineType.Eaw, 0x1e7); + public static readonly CommandBarComponentId EncyclopediaIcon = new(GameEngineType.Eaw, 0x1e8); + public static readonly CommandBarComponentId EncyclopediaCostText = new(GameEngineType.Eaw, 0x1e9); + public static readonly CommandBarComponentId ZoomedBack = new(GameEngineType.Eaw, 0x1ea); + public static readonly CommandBarComponentId ZoomedHeaderText = new(GameEngineType.Eaw, 0x1eb); + public static readonly CommandBarComponentId ZoomedText = new(GameEngineType.Eaw, 0x1ec); + public static readonly CommandBarComponentId ZoomedRightText = new(GameEngineType.Eaw, 0x1ed); + public static readonly CommandBarComponentId ZoomedCenterText = new(GameEngineType.Eaw, 0x1ee); + public static readonly CommandBarComponentId ZoomedCostText = new(GameEngineType.Eaw, 0x1ef); + public static readonly CommandBarComponentId GPlanetFleet = new(GameEngineType.Eaw, 0x1f0); + public static readonly CommandBarComponentId GPlanetName = new(GameEngineType.Eaw, 0x1f1); + public static readonly CommandBarComponentId GPlanetValue = new(GameEngineType.Eaw, 0x1f2); + public static readonly CommandBarComponentId GPoliticalControl = new(GameEngineType.Eaw, 0x1f3); + public static readonly CommandBarComponentId GSpaceLevel = new(GameEngineType.Eaw, 0x1f4); + public static readonly CommandBarComponentId GSpaceIcon = new(GameEngineType.Eaw, 0x1f5); + public static readonly CommandBarComponentId GSpaceLevelPips = new(GameEngineType.Eaw, 0x1f6); + public static readonly CommandBarComponentId GGroundLevel = new(GameEngineType.Eaw, 0x1f7); + public static readonly CommandBarComponentId GGroundIcon = new(GameEngineType.Eaw, 0x1f8); + public static readonly CommandBarComponentId GGroundLevelPips = new(GameEngineType.Eaw, 0x1f9); + public static readonly CommandBarComponentId GConflict = new(GameEngineType.Eaw, 0x1fa); + public static readonly CommandBarComponentId GHero = new(GameEngineType.Eaw, 0x1fb); + public static readonly CommandBarComponentId GEnemyHero = new(GameEngineType.Eaw, 0x1fc); + public static readonly CommandBarComponentId GBuild = new(GameEngineType.Eaw, 0x1fd); + public static readonly CommandBarComponentId GSmuggler = new(GameEngineType.Eaw, 0x2fe); + public static readonly CommandBarComponentId GBountyHunter = new(GameEngineType.Eaw, 0x2ff); + public static readonly CommandBarComponentId GPlanetLandForces = new(GameEngineType.Eaw, 0x200); + public static readonly CommandBarComponentId GGalacticRadarBlip = new(GameEngineType.Eaw, 0x201); + public static readonly CommandBarComponentId GGalacticRadarView = new(GameEngineType.Eaw, 0x202); + public static readonly CommandBarComponentId GSmuggled = new(GameEngineType.Eaw, 0x203); + public static readonly CommandBarComponentId GSpecialAbility = new(GameEngineType.Eaw, 0x204); + public static readonly CommandBarComponentId GHeroIcon = new(GameEngineType.Eaw, 0x205); + public static readonly CommandBarComponentId GPlanetRing = new(GameEngineType.Eaw, 0x206); + public static readonly CommandBarComponentId GWeather = new(GameEngineType.Eaw, 0x207); + public static readonly CommandBarComponentId GPlanetAbility = new(GameEngineType.Eaw, 0x208); + public static readonly CommandBarComponentId TutorialText = new(GameEngineType.Eaw, 0x209); + public static readonly CommandBarComponentId TutorialTextBack = new(GameEngineType.Eaw, 0x20a); + public static readonly CommandBarComponentId RadarBlip = new(GameEngineType.Eaw, 0x20b); + public static readonly CommandBarComponentId TacticalBuildButtonShell = new(GameEngineType.Eaw, 0x20c); + public static readonly CommandBarComponentId TacticalBuildButton0 = new(GameEngineType.Eaw, 0x20d); + public static readonly CommandBarComponentId TacticalBuildButton1 = new(GameEngineType.Eaw, 0x20e); + public static readonly CommandBarComponentId TacticalBuildButton2 = new(GameEngineType.Eaw, 0x20f); + public static readonly CommandBarComponentId TacticalBuildButton3 = new(GameEngineType.Eaw, 0x210); + public static readonly CommandBarComponentId TacticalBuildButton4 = new(GameEngineType.Eaw, 0x211); + public static readonly CommandBarComponentId TacticalBuildButton5 = new(GameEngineType.Eaw, 0x212); + public static readonly CommandBarComponentId TacticalSellButton = new(GameEngineType.Eaw, 0x213); + public static readonly CommandBarComponentId ReinforcementShell = new(GameEngineType.Eaw, 0x214); + public static readonly CommandBarComponentId ReinforcementCancel = new(GameEngineType.Eaw, 0x215); + public static readonly CommandBarComponentId ReinforcementSlot00 = new(GameEngineType.Eaw, 0x216); + public static readonly CommandBarComponentId ReinforcementSlot01 = new(GameEngineType.Eaw, 0x217); + public static readonly CommandBarComponentId ReinforcementSlot02 = new(GameEngineType.Eaw, 0x218); + public static readonly CommandBarComponentId ReinforcementSlot03 = new(GameEngineType.Eaw, 0x219); + public static readonly CommandBarComponentId ReinforcementSlot04 = new(GameEngineType.Eaw, 0x21a); + public static readonly CommandBarComponentId ReinforcementSlot05 = new(GameEngineType.Eaw, 0x21b); + public static readonly CommandBarComponentId ReinforcementSlot06 = new(GameEngineType.Eaw, 0x21c); + public static readonly CommandBarComponentId ReinforcementSlot07 = new(GameEngineType.Eaw, 0x21d); + public static readonly CommandBarComponentId ReinforcementSlot08 = new(GameEngineType.Eaw, 0x21e); + public static readonly CommandBarComponentId ReinforcementSlot09 = new(GameEngineType.Eaw, 0x21f); + public static readonly CommandBarComponentId ReinforcementSlot10 = new(GameEngineType.Eaw, 0x220); + public static readonly CommandBarComponentId ReinforcementSlot11 = new(GameEngineType.Eaw, 0x221); + public static readonly CommandBarComponentId ReinforcementSlot12 = new(GameEngineType.Eaw, 0x222); + public static readonly CommandBarComponentId ReinforcementSlot13 = new(GameEngineType.Eaw, 0x223); + public static readonly CommandBarComponentId ReinforcementSlot14 = new(GameEngineType.Eaw, 0x224); + public static readonly CommandBarComponentId ReinforcementCap2 = new(GameEngineType.Eaw, 0x225); + public static readonly CommandBarComponentId ReinforcementCap2Text = new(GameEngineType.Eaw, 0x226); + public static readonly CommandBarComponentId ReinforcementCounter = new(GameEngineType.Eaw, 0x227); + public static readonly CommandBarComponentId GarrisonRespawnCounter = new(GameEngineType.Eaw, 0x228); + public static readonly CommandBarComponentId SkirmishUpgrade = new(GameEngineType.Eaw, 0x229); + public static readonly CommandBarComponentId PendingBattleShell = new(GameEngineType.Eaw, 0x22a); + public static readonly CommandBarComponentId PendingBattleText = new(GameEngineType.Eaw, 0x22b); + public static readonly CommandBarComponentId PendingBattleButton = new(GameEngineType.Eaw, 0x22c); + public static readonly CommandBarComponentId PendingBattleAutoresolve = new(GameEngineType.Eaw, 0x22d); + public static readonly CommandBarComponentId PendingBattleGraphLeft = new(GameEngineType.Eaw, 0x22e); + public static readonly CommandBarComponentId PendingBattleGraphRight = new(GameEngineType.Eaw, 0x22f); + public static readonly CommandBarComponentId TatcicalAutoresolveShell = new(GameEngineType.Eaw, 0x230); + public static readonly CommandBarComponentId TacticalAutoresolveButton = new(GameEngineType.Eaw, 0x231); + public static readonly CommandBarComponentId TacticalAutoresolveGraphLeft = new(GameEngineType.Eaw, 0x232); + public static readonly CommandBarComponentId TacticalAutoresolveGraphRight = new(GameEngineType.Eaw, 0x233); + public static readonly CommandBarComponentId ObjectiveBack = new(GameEngineType.Eaw, 0x234); + public static readonly CommandBarComponentId ObjectiveHeaderText = new(GameEngineType.Eaw, 0x235); + public static readonly CommandBarComponentId ObjectiveText = new(GameEngineType.Eaw, 0x236); + public static readonly CommandBarComponentId ObjectiveIcon = new(GameEngineType.Eaw, 0x237); + public static readonly CommandBarComponentId GuiDialogTooltip = new(GameEngineType.Eaw, 0x238); + public static readonly CommandBarComponentId TargetUnitTypeShell = new(GameEngineType.Eaw, 0x239); + public static readonly CommandBarComponentId TargetUnitTypeTitle = new(GameEngineType.Eaw, 0x23a); + public static readonly CommandBarComponentId TargetUnitTypeCancel = new(GameEngineType.Eaw, 0x23b); + public static readonly CommandBarComponentId TargetUnitTypeDescription = new(GameEngineType.Eaw, 0x23c); + public static readonly CommandBarComponentId TargetUnitTypeSlot00 = new(GameEngineType.Eaw, 0x23d); + public static readonly CommandBarComponentId TargetUnitTypeSlot01 = new(GameEngineType.Eaw, 0x23e); + public static readonly CommandBarComponentId TargetUnitTypeSlot02 = new(GameEngineType.Eaw, 0x23f); + public static readonly CommandBarComponentId TargetUnitTypeSlot03 = new(GameEngineType.Eaw, 0x240); + public static readonly CommandBarComponentId TargetUnitTypeSlot04 = new(GameEngineType.Eaw, 0x241); + public static readonly CommandBarComponentId TargetUnitTypeSlot05 = new(GameEngineType.Eaw, 0x242); + public static readonly CommandBarComponentId TargetUnitTypeSlot06 = new(GameEngineType.Eaw, 0x243); + public static readonly CommandBarComponentId TargetUnitTypeSlot07 = new(GameEngineType.Eaw, 0x244); + public static readonly CommandBarComponentId TargetUnitTypeSlot08 = new(GameEngineType.Eaw, 0x245); + public static readonly CommandBarComponentId TargetUnitTypeSlot09 = new(GameEngineType.Eaw, 0x246); + public static readonly CommandBarComponentId TargetUnitTypeSlot10 = new(GameEngineType.Eaw, 0x247); + public static readonly CommandBarComponentId TargetUnitTypeSlot11 = new(GameEngineType.Eaw, 0x248); + public static readonly CommandBarComponentId TargetUnitTypeSlot12 = new(GameEngineType.Eaw, 0x249); + public static readonly CommandBarComponentId TargetUnitTypeSlot13 = new(GameEngineType.Eaw, 0x24a); + public static readonly CommandBarComponentId TargetUnitTypeSlot14 = new(GameEngineType.Eaw, 0x24b); + public static readonly CommandBarComponentId TargetUnitTypeSlot15 = new(GameEngineType.Eaw, 0x24c); + public static readonly CommandBarComponentId VcrButtonPlayPause = new(GameEngineType.Eaw, 0x24d); + public static readonly CommandBarComponentId VcrButtonFastForward = new(GameEngineType.Eaw, 0x24e); + public static readonly CommandBarComponentId VcrButtonFastForwardTactical = new(GameEngineType.Eaw, 0x24f); + public static readonly CommandBarComponentId VcrButtonPlayPauseTactical = new(GameEngineType.Eaw, 0x250); + public static readonly CommandBarComponentId AdvisorHintPopupGalactic = new(GameEngineType.Eaw, 0x251); + public static readonly CommandBarComponentId AdvisorHintPopupTactical = new(GameEngineType.Eaw, 0x252); + public static readonly CommandBarComponentId AdvisorHintBack = new(GameEngineType.Eaw, 0x253); + public static readonly CommandBarComponentId TextOrganizeFleet00 = new(GameEngineType.Eaw, 0x254); + public static readonly CommandBarComponentId TextOrganizeFleet01 = new(GameEngineType.Eaw, 0x255); + public static readonly CommandBarComponentId TextOrganizeFleet02 = new(GameEngineType.Eaw, 0x256); + public static readonly CommandBarComponentId IconOrganizeFleet00 = new(GameEngineType.Eaw, 0x257); + public static readonly CommandBarComponentId IconOrganizeFleet01 = new(GameEngineType.Eaw, 0x258); + public static readonly CommandBarComponentId IconOrganizeFleet02 = new(GameEngineType.Eaw, 0x259); + public static readonly CommandBarComponentId TextOrganizeLandFleet = new(GameEngineType.Eaw, 0x25a); + public static readonly CommandBarComponentId CsAbilityButton = new(GameEngineType.Eaw, 0x25b); + public static readonly CommandBarComponentId CsAbilityText = new(GameEngineType.Eaw, 0x25c); + public static readonly CommandBarComponentId MovieBoneGalactic = new(GameEngineType.Eaw, 0x25d); + public static readonly CommandBarComponentId MovieBoneTactical = new(GameEngineType.Eaw, 0x25e); + public static readonly CommandBarComponentId GenericCollision = new(GameEngineType.Eaw, 0x25f); + public static readonly CommandBarComponentId GoodHeroShell = new(GameEngineType.Eaw, 0x260); + public static readonly CommandBarComponentId GoodHeroSlot00 = new(GameEngineType.Eaw, 0x261); + public static readonly CommandBarComponentId GoodHeroSlot01 = new(GameEngineType.Eaw, 0x262); + public static readonly CommandBarComponentId GoodHeroSlot02 = new(GameEngineType.Eaw, 0x263); + public static readonly CommandBarComponentId GoodHeroSlot03 = new(GameEngineType.Eaw, 0x264); + public static readonly CommandBarComponentId GoodHeroSlot04 = new(GameEngineType.Eaw, 0x265); + public static readonly CommandBarComponentId GoodHeroSlot05 = new(GameEngineType.Eaw, 0x266); + public static readonly CommandBarComponentId GoodHeroSlot06 = new(GameEngineType.Eaw, 0x267); + public static readonly CommandBarComponentId GoodHeroSlot07 = new(GameEngineType.Eaw, 0x268); + public static readonly CommandBarComponentId GoodHeroSlot08 = new(GameEngineType.Eaw, 0x269); + public static readonly CommandBarComponentId GoodHeroSlot09 = new(GameEngineType.Eaw, 0x26a); + public static readonly CommandBarComponentId GoodHeroSlot10 = new(GameEngineType.Eaw, 0x26b); + public static readonly CommandBarComponentId GoodHeroHealth00 = new(GameEngineType.Eaw, 0x26c); + public static readonly CommandBarComponentId GoodHeroHealth01 = new(GameEngineType.Eaw, 0x26d); + public static readonly CommandBarComponentId GoodHeroHealth02 = new(GameEngineType.Eaw, 0x26e); + public static readonly CommandBarComponentId GoodHeroHealth03 = new(GameEngineType.Eaw, 0x26f); + public static readonly CommandBarComponentId GoodHeroHealth04 = new(GameEngineType.Eaw, 0x270); + public static readonly CommandBarComponentId GoodHeroHealth05 = new(GameEngineType.Eaw, 0x271); + public static readonly CommandBarComponentId GoodHeroHealth06 = new(GameEngineType.Eaw, 0x272); + public static readonly CommandBarComponentId GoodHeroHealth07 = new(GameEngineType.Eaw, 0x273); + public static readonly CommandBarComponentId GoodHeroHealth08 = new(GameEngineType.Eaw, 0x274); + public static readonly CommandBarComponentId GoodHeroHealth09 = new(GameEngineType.Eaw, 0x275); + public static readonly CommandBarComponentId GoodHeroHealth10 = new(GameEngineType.Eaw, 0x276); + public static readonly CommandBarComponentId EvilHeroShell = new(GameEngineType.Eaw, 0x277); + public static readonly CommandBarComponentId EvilHeroSlot00 = new(GameEngineType.Eaw, 0x278); + public static readonly CommandBarComponentId EvilHeroSlot01 = new(GameEngineType.Eaw, 0x279); + public static readonly CommandBarComponentId EvilHeroSlot02 = new(GameEngineType.Eaw, 0x27a); + public static readonly CommandBarComponentId EvilHeroSlot03 = new(GameEngineType.Eaw, 0x27b); + public static readonly CommandBarComponentId EvilHeroSlot04 = new(GameEngineType.Eaw, 0x27c); + public static readonly CommandBarComponentId EvilHeroSlot05 = new(GameEngineType.Eaw, 0x27d); + public static readonly CommandBarComponentId EvilHeroSlot06 = new(GameEngineType.Eaw, 0x27e); + public static readonly CommandBarComponentId EvilHeroSlot07 = new(GameEngineType.Eaw, 0x27f); + public static readonly CommandBarComponentId EvilHeroSlot08 = new(GameEngineType.Eaw, 0x280); + public static readonly CommandBarComponentId EvilHeroSlot09 = new(GameEngineType.Eaw, 0x281); + public static readonly CommandBarComponentId EvilHeroSlot10 = new(GameEngineType.Eaw, 0x282); + public static readonly CommandBarComponentId EvilHeroHealth00 = new(GameEngineType.Eaw, 0x283); + public static readonly CommandBarComponentId EvilHeroHealth01 = new(GameEngineType.Eaw, 0x284); + public static readonly CommandBarComponentId EvilHeroHealth02 = new(GameEngineType.Eaw, 0x285); + public static readonly CommandBarComponentId EvilHeroHealth03 = new(GameEngineType.Eaw, 0x286); + public static readonly CommandBarComponentId EvilHeroHealth04 = new(GameEngineType.Eaw, 0x287); + public static readonly CommandBarComponentId EvilHeroHealth05 = new(GameEngineType.Eaw, 0x288); + public static readonly CommandBarComponentId EvilHeroHealth06 = new(GameEngineType.Eaw, 0x289); + public static readonly CommandBarComponentId EvilHeroHealth07 = new(GameEngineType.Eaw, 0x28a); + public static readonly CommandBarComponentId EvilHeroHealth08 = new(GameEngineType.Eaw, 0x28b); + public static readonly CommandBarComponentId EvilHeroHealth09 = new(GameEngineType.Eaw, 0x28c); + public static readonly CommandBarComponentId EvilHeroHealth10 = new(GameEngineType.Eaw, 0x28d); + public static readonly CommandBarComponentId PauseShell = new(GameEngineType.Eaw, 0x28e); + public static readonly CommandBarComponentId PauseText = new(GameEngineType.Eaw, 0x28f); + public static readonly CommandBarComponentId PauseButton = new(GameEngineType.Eaw, 0x290); + public static readonly CommandBarComponentId StoryCampaignPendingBattleShell = new(GameEngineType.Eaw, 0x291); + public static readonly CommandBarComponentId StoryCampaignPendingBattleText = new(GameEngineType.Eaw, 0x292); + public static readonly CommandBarComponentId StoryCampaignPendingBattleButton = new(GameEngineType.Eaw, 0x293); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/FocCommandBarComponentIds.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/FocCommandBarComponentIds.cs new file mode 100644 index 0000000..b174d8e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/FocCommandBarComponentIds.cs @@ -0,0 +1,970 @@ +namespace PG.StarWarsGame.Engine.CommandBar; + +public static class FocCommandBarComponentIds +{ + public static readonly CommandBarComponentId MainShell = new(GameEngineType.Foc, 0x0); + public static readonly CommandBarComponentId ProductionOptions = new(GameEngineType.Foc, 0x1); + public static readonly CommandBarComponentId GalacticCamera = new(GameEngineType.Foc, 0x2); + public static readonly CommandBarComponentId PlanetName = new(GameEngineType.Foc, 0x3); + public static readonly CommandBarComponentId PlanetValue = new(GameEngineType.Foc, 0x4); + public static readonly CommandBarComponentId PlanetAffiliation = new(GameEngineType.Foc, 0x5); + public static readonly CommandBarComponentId PlayerCredits = new(GameEngineType.Foc, 0x6); + public static readonly CommandBarComponentId ReinforcementCap = new(GameEngineType.Foc, 0x7); + public static readonly CommandBarComponentId PlanetNameTactical = new(GameEngineType.Foc, 0x8); + public static readonly CommandBarComponentId PlanetInfo = new(GameEngineType.Foc, 0x9); + public static readonly CommandBarComponentId Production0 = new(GameEngineType.Foc, 0xa); + public static readonly CommandBarComponentId Production1 = new(GameEngineType.Foc, 0xb); + public static readonly CommandBarComponentId Production2 = new(GameEngineType.Foc, 0xc); + public static readonly CommandBarComponentId Production3 = new(GameEngineType.Foc, 0xd); + public static readonly CommandBarComponentId Production4 = new(GameEngineType.Foc, 0xe); + public static readonly CommandBarComponentId Production5 = new(GameEngineType.Foc, 0xf); + public static readonly CommandBarComponentId Production6 = new(GameEngineType.Foc, 0x10); + public static readonly CommandBarComponentId Production7 = new(GameEngineType.Foc, 0x11); + public static readonly CommandBarComponentId Production8 = new(GameEngineType.Foc, 0x12); + public static readonly CommandBarComponentId Production9 = new(GameEngineType.Foc, 0x13); + public static readonly CommandBarComponentId Production10 = new(GameEngineType.Foc, 0x14); + public static readonly CommandBarComponentId Production11 = new(GameEngineType.Foc, 0x15); + public static readonly CommandBarComponentId Production12 = new(GameEngineType.Foc, 0x16); + public static readonly CommandBarComponentId Production13 = new(GameEngineType.Foc, 0x17); + public static readonly CommandBarComponentId Production14 = new(GameEngineType.Foc, 0x18); + public static readonly CommandBarComponentId Production15 = new(GameEngineType.Foc, 0x19); + public static readonly CommandBarComponentId Production16 = new(GameEngineType.Foc, 0x1a); + public static readonly CommandBarComponentId Production17 = new(GameEngineType.Foc, 0x1b); + public static readonly CommandBarComponentId Production18 = new(GameEngineType.Foc, 0x1c); + public static readonly CommandBarComponentId Production19 = new(GameEngineType.Foc, 0x1d); + public static readonly CommandBarComponentId Production20 = new(GameEngineType.Foc, 0x1e); + public static readonly CommandBarComponentId Production21 = new(GameEngineType.Foc, 0x1f); + public static readonly CommandBarComponentId Production22 = new(GameEngineType.Foc, 0x20); + public static readonly CommandBarComponentId Production23 = new(GameEngineType.Foc, 0x21); + public static readonly CommandBarComponentId Production24 = new(GameEngineType.Foc, 0x22); + public static readonly CommandBarComponentId Production25 = new(GameEngineType.Foc, 0x23); + public static readonly CommandBarComponentId Production26 = new(GameEngineType.Foc, 0x24); + public static readonly CommandBarComponentId Production27 = new(GameEngineType.Foc, 0x25); + public static readonly CommandBarComponentId Production28 = new(GameEngineType.Foc, 0x26); + public static readonly CommandBarComponentId Production29 = new(GameEngineType.Foc, 0x27); + public static readonly CommandBarComponentId Production30 = new(GameEngineType.Foc, 0x28); + public static readonly CommandBarComponentId Production31 = new(GameEngineType.Foc, 0x29); + public static readonly CommandBarComponentId Production32 = new(GameEngineType.Foc, 0x2a); + public static readonly CommandBarComponentId Production33 = new(GameEngineType.Foc, 0x2b); + public static readonly CommandBarComponentId Production34 = new(GameEngineType.Foc, 0x2c); + public static readonly CommandBarComponentId Production35 = new(GameEngineType.Foc, 0x2d); + public static readonly CommandBarComponentId Production36 = new(GameEngineType.Foc, 0x2e); + public static readonly CommandBarComponentId Production37 = new(GameEngineType.Foc, 0x2f); + public static readonly CommandBarComponentId Production38 = new(GameEngineType.Foc, 0x30); + public static readonly CommandBarComponentId Production39 = new(GameEngineType.Foc, 0x31); + public static readonly CommandBarComponentId Production40 = new(GameEngineType.Foc, 0x32); + public static readonly CommandBarComponentId Production41 = new(GameEngineType.Foc, 0x33); + public static readonly CommandBarComponentId Production42 = new(GameEngineType.Foc, 0x34); + public static readonly CommandBarComponentId Production43 = new(GameEngineType.Foc, 0x35); + public static readonly CommandBarComponentId Production44 = new(GameEngineType.Foc, 0x36); + public static readonly CommandBarComponentId Production45 = new(GameEngineType.Foc, 0x37); + public static readonly CommandBarComponentId Production46 = new(GameEngineType.Foc, 0x38); + public static readonly CommandBarComponentId Production47 = new(GameEngineType.Foc, 0x39); + public static readonly CommandBarComponentId DroidHelp = new(GameEngineType.Foc, 0x3a); + public static readonly CommandBarComponentId DroidHelpTactical = new(GameEngineType.Foc, 0x3b); + public static readonly CommandBarComponentId CurrentDay = new(GameEngineType.Foc, 0x3c); + public static readonly CommandBarComponentId DayCredits = new(GameEngineType.Foc, 0x3d); + public static readonly CommandBarComponentId PopulationCap = new(GameEngineType.Foc, 0x3e); + public static readonly CommandBarComponentId Filter0 = new(GameEngineType.Foc, 0x3f); + public static readonly CommandBarComponentId Filter1 = new(GameEngineType.Foc, 0x40); + public static readonly CommandBarComponentId Filter2 = new(GameEngineType.Foc, 0x41); + public static readonly CommandBarComponentId Filter3 = new(GameEngineType.Foc, 0x42); + public static readonly CommandBarComponentId StoryArcButton = new(GameEngineType.Foc, 0x43); + public static readonly CommandBarComponentId PlanetSummaryButton = new(GameEngineType.Foc, 0x44); + public static readonly CommandBarComponentId SpaceTab = new(GameEngineType.Foc, 0x45); + public static readonly CommandBarComponentId LandTab = new(GameEngineType.Foc, 0x46); + public static readonly CommandBarComponentId Dial = new(GameEngineType.Foc, 0x47); + public static readonly CommandBarComponentId ScrollRight = new(GameEngineType.Foc, 0x48); + public static readonly CommandBarComponentId ScrollLeft = new(GameEngineType.Foc, 0x49); + public static readonly CommandBarComponentId ZoomView = new(GameEngineType.Foc, 0x4a); + public static readonly CommandBarComponentId PrevPlanet = new(GameEngineType.Foc, 0x4b); + public static readonly CommandBarComponentId NextPlanet = new(GameEngineType.Foc, 0x4c); + public static readonly CommandBarComponentId RadarGalactic = new(GameEngineType.Foc, 0x4d); + public static readonly CommandBarComponentId TechLevel = new(GameEngineType.Foc, 0x4e); + public static readonly CommandBarComponentId BalancePip = new(GameEngineType.Foc, 0x4f); + public static readonly CommandBarComponentId BuildQueue00 = new(GameEngineType.Foc, 0x50); + public static readonly CommandBarComponentId BuildQueue01 = new(GameEngineType.Foc, 0x51); + public static readonly CommandBarComponentId BuildQueue02 = new(GameEngineType.Foc, 0x52); + public static readonly CommandBarComponentId BuildQueue03 = new(GameEngineType.Foc, 0x53); + public static readonly CommandBarComponentId BuildQueue04 = new(GameEngineType.Foc, 0x54); + public static readonly CommandBarComponentId BuildQueue05 = new(GameEngineType.Foc, 0x55); + public static readonly CommandBarComponentId BuildQueue06 = new(GameEngineType.Foc, 0x56); + public static readonly CommandBarComponentId BuildQueue07 = new(GameEngineType.Foc, 0x57); + public static readonly CommandBarComponentId BuildQueue08 = new(GameEngineType.Foc, 0x58); + public static readonly CommandBarComponentId BuildQueue09 = new(GameEngineType.Foc, 0x59); + public static readonly CommandBarComponentId OrganizationShell = new(GameEngineType.Foc, 0x5a); + public static readonly CommandBarComponentId OrganizationCollision = new(GameEngineType.Foc, 0x5b); + public static readonly CommandBarComponentId SmugglerBox = new(GameEngineType.Foc, 0x5c); + public static readonly CommandBarComponentId SpaceStationUpgrade01 = new(GameEngineType.Foc, 0x5d); + public static readonly CommandBarComponentId SpaceStationUpgrade02 = new(GameEngineType.Foc, 0x5e); + public static readonly CommandBarComponentId HeroAbilitySlot = new(GameEngineType.Foc, 0x5f); + public static readonly CommandBarComponentId PlanetOrganize0 = new(GameEngineType.Foc, 0x60); + public static readonly CommandBarComponentId PlanetOrganize1 = new(GameEngineType.Foc, 0x61); + public static readonly CommandBarComponentId PlanetOrganize2 = new(GameEngineType.Foc, 0x62); + public static readonly CommandBarComponentId PlanetOrganize3 = new(GameEngineType.Foc, 0x63); + public static readonly CommandBarComponentId PlanetOrganize4 = new(GameEngineType.Foc, 0x64); + public static readonly CommandBarComponentId PlanetOrganize5 = new(GameEngineType.Foc, 0x65); + public static readonly CommandBarComponentId PlanetOrganize6 = new(GameEngineType.Foc, 0x66); + public static readonly CommandBarComponentId PlanetOrganize7 = new(GameEngineType.Foc, 0x67); + public static readonly CommandBarComponentId PlanetOrganize8 = new(GameEngineType.Foc, 0x68); + public static readonly CommandBarComponentId PlanetOrganize9 = new(GameEngineType.Foc, 0x69); + public static readonly CommandBarComponentId SpecialStructureLand0 = new(GameEngineType.Foc, 0x6a); + public static readonly CommandBarComponentId SpecialStructureLand1 = new(GameEngineType.Foc, 0x6b); + public static readonly CommandBarComponentId SpecialStructureLand2 = new(GameEngineType.Foc, 0x6c); + public static readonly CommandBarComponentId SpecialStructureLand3 = new(GameEngineType.Foc, 0x6d); + public static readonly CommandBarComponentId SpecialStructureLand4 = new(GameEngineType.Foc, 0x6e); + public static readonly CommandBarComponentId SpecialStructureLand5 = new(GameEngineType.Foc, 0x6f); + public static readonly CommandBarComponentId SpecialStructureLand6 = new(GameEngineType.Foc, 0x70); + public static readonly CommandBarComponentId SpecialStructureLand7 = new(GameEngineType.Foc, 0x71); + public static readonly CommandBarComponentId SpecialStructureLand8 = new(GameEngineType.Foc, 0x72); + public static readonly CommandBarComponentId SpecialStructureLandSell = new(GameEngineType.Foc, 0x73); + public static readonly CommandBarComponentId BigFleet0 = new(GameEngineType.Foc, 0x74); + public static readonly CommandBarComponentId BigFleet1 = new(GameEngineType.Foc, 0x75); + public static readonly CommandBarComponentId BigFleet2 = new(GameEngineType.Foc, 0x76); + public static readonly CommandBarComponentId BigFleet3 = new(GameEngineType.Foc, 0x77); + public static readonly CommandBarComponentId Fleet0Slot0 = new(GameEngineType.Foc, 0x78); + public static readonly CommandBarComponentId Fleet0Slot1 = new(GameEngineType.Foc, 0x79); + public static readonly CommandBarComponentId Fleet0Slot2 = new(GameEngineType.Foc, 0x7a); + public static readonly CommandBarComponentId Fleet0Slot3 = new(GameEngineType.Foc, 0x7b); + public static readonly CommandBarComponentId Fleet0Slot4 = new(GameEngineType.Foc, 0x7c); + public static readonly CommandBarComponentId Fleet0Slot5 = new(GameEngineType.Foc, 0x7d); + public static readonly CommandBarComponentId Fleet0Slot6 = new(GameEngineType.Foc, 0x7e); + public static readonly CommandBarComponentId Fleet0Slot7 = new(GameEngineType.Foc, 0x7f); + public static readonly CommandBarComponentId Fleet0Slot8 = new(GameEngineType.Foc, 0x80); + public static readonly CommandBarComponentId Fleet0Slot9 = new(GameEngineType.Foc, 0x81); + public static readonly CommandBarComponentId Fleet0Slot10 = new(GameEngineType.Foc, 0x82); + public static readonly CommandBarComponentId Fleet0Slot11 = new(GameEngineType.Foc, 0x83); + public static readonly CommandBarComponentId Fleet0Slot12 = new(GameEngineType.Foc, 0x84); + public static readonly CommandBarComponentId Fleet0Slot13 = new(GameEngineType.Foc, 0x85); + public static readonly CommandBarComponentId Fleet0Slot14 = new(GameEngineType.Foc, 0x86); + public static readonly CommandBarComponentId Fleet0Slot15 = new(GameEngineType.Foc, 0x87); + public static readonly CommandBarComponentId Fleet0Slot16 = new(GameEngineType.Foc, 0x88); + public static readonly CommandBarComponentId Fleet0Slot17 = new(GameEngineType.Foc, 0x89); + public static readonly CommandBarComponentId Fleet0Slot18 = new(GameEngineType.Foc, 0x8a); + public static readonly CommandBarComponentId Fleet0Slot19 = new(GameEngineType.Foc, 0x8b); + public static readonly CommandBarComponentId Fleet0Slot20 = new(GameEngineType.Foc, 0x8c); + public static readonly CommandBarComponentId Fleet0Slot21 = new(GameEngineType.Foc, 0x8d); + public static readonly CommandBarComponentId Fleet0Slot22 = new(GameEngineType.Foc, 0x8e); + public static readonly CommandBarComponentId Fleet0Slot23 = new(GameEngineType.Foc, 0x8f); + public static readonly CommandBarComponentId Fleet0Slot24 = new(GameEngineType.Foc, 0x90); + public static readonly CommandBarComponentId Fleet0Slot25 = new(GameEngineType.Foc, 0x91); + public static readonly CommandBarComponentId Fleet0Slot26 = new(GameEngineType.Foc, 0x92); + public static readonly CommandBarComponentId Fleet0Slot27 = new(GameEngineType.Foc, 0x93); + public static readonly CommandBarComponentId Fleet0Slot28 = new(GameEngineType.Foc, 0x94); + public static readonly CommandBarComponentId Fleet0Slot29 = new(GameEngineType.Foc, 0x95); + public static readonly CommandBarComponentId Fleet0Slot30 = new(GameEngineType.Foc, 0x96); + public static readonly CommandBarComponentId Fleet0Slot31 = new(GameEngineType.Foc, 0x97); + public static readonly CommandBarComponentId Fleet0Slot32 = new(GameEngineType.Foc, 0x98); + public static readonly CommandBarComponentId Fleet0Slot33 = new(GameEngineType.Foc, 0x99); + public static readonly CommandBarComponentId Fleet0Slot34 = new(GameEngineType.Foc, 0x9a); + public static readonly CommandBarComponentId Fleet1Slot0 = new(GameEngineType.Foc, 0x9b); + public static readonly CommandBarComponentId Fleet1Slot1 = new(GameEngineType.Foc, 0x9c); + public static readonly CommandBarComponentId Fleet1Slot2 = new(GameEngineType.Foc, 0x9d); + public static readonly CommandBarComponentId Fleet1Slot3 = new(GameEngineType.Foc, 0x9e); + public static readonly CommandBarComponentId Fleet1Slot4 = new(GameEngineType.Foc, 0x9f); + public static readonly CommandBarComponentId Fleet1Slot5 = new(GameEngineType.Foc, 0xa0); + public static readonly CommandBarComponentId Fleet1Slot6 = new(GameEngineType.Foc, 0xa1); + public static readonly CommandBarComponentId Fleet1Slot7 = new(GameEngineType.Foc, 0xa2); + public static readonly CommandBarComponentId Fleet1Slot8 = new(GameEngineType.Foc, 0xa3); + public static readonly CommandBarComponentId Fleet1Slot9 = new(GameEngineType.Foc, 0xa4); + public static readonly CommandBarComponentId Fleet1Slot10 = new(GameEngineType.Foc, 0xa5); + public static readonly CommandBarComponentId Fleet1Slot11 = new(GameEngineType.Foc, 0xa6); + public static readonly CommandBarComponentId Fleet1Slot12 = new(GameEngineType.Foc, 0xa7); + public static readonly CommandBarComponentId Fleet1Slot13 = new(GameEngineType.Foc, 0xa8); + public static readonly CommandBarComponentId Fleet1Slot14 = new(GameEngineType.Foc, 0xa9); + public static readonly CommandBarComponentId Fleet1Slot15 = new(GameEngineType.Foc, 0xaa); + public static readonly CommandBarComponentId Fleet1Slot16 = new(GameEngineType.Foc, 0xab); + public static readonly CommandBarComponentId Fleet1Slot17 = new(GameEngineType.Foc, 0xac); + public static readonly CommandBarComponentId Fleet1Slot18 = new(GameEngineType.Foc, 0xad); + public static readonly CommandBarComponentId Fleet1Slot19 = new(GameEngineType.Foc, 0xae); + public static readonly CommandBarComponentId Fleet1Slot20 = new(GameEngineType.Foc, 0xaf); + public static readonly CommandBarComponentId Fleet1Slot21 = new(GameEngineType.Foc, 0xb0); + public static readonly CommandBarComponentId Fleet1Slot22 = new(GameEngineType.Foc, 0xb1); + public static readonly CommandBarComponentId Fleet1Slot23 = new(GameEngineType.Foc, 0xb2); + public static readonly CommandBarComponentId Fleet1Slot24 = new(GameEngineType.Foc, 0xb3); + public static readonly CommandBarComponentId Fleet1Slot25 = new(GameEngineType.Foc, 0xb4); + public static readonly CommandBarComponentId Fleet1Slot26 = new(GameEngineType.Foc, 0xb5); + public static readonly CommandBarComponentId Fleet1Slot27 = new(GameEngineType.Foc, 0xb6); + public static readonly CommandBarComponentId Fleet1Slot28 = new(GameEngineType.Foc, 0xb7); + public static readonly CommandBarComponentId Fleet1Slot29 = new(GameEngineType.Foc, 0xb8); + public static readonly CommandBarComponentId Fleet1Slot30 = new(GameEngineType.Foc, 0xb9); + public static readonly CommandBarComponentId Fleet1Slot31 = new(GameEngineType.Foc, 0xba); + public static readonly CommandBarComponentId Fleet1Slot32 = new(GameEngineType.Foc, 0xbb); + public static readonly CommandBarComponentId Fleet1Slot33 = new(GameEngineType.Foc, 0xbc); + public static readonly CommandBarComponentId Fleet1Slot34 = new(GameEngineType.Foc, 0xbd); + public static readonly CommandBarComponentId Fleet2Slot0 = new(GameEngineType.Foc, 0xbe); + public static readonly CommandBarComponentId Fleet2Slot1 = new(GameEngineType.Foc, 0xbf); + public static readonly CommandBarComponentId Fleet2Slot2 = new(GameEngineType.Foc, 0xc0); + public static readonly CommandBarComponentId Fleet2Slot3 = new(GameEngineType.Foc, 0xc1); + public static readonly CommandBarComponentId Fleet2Slot4 = new(GameEngineType.Foc, 0xc2); + public static readonly CommandBarComponentId Fleet2Slot5 = new(GameEngineType.Foc, 0xc3); + public static readonly CommandBarComponentId Fleet2Slot6 = new(GameEngineType.Foc, 0xc4); + public static readonly CommandBarComponentId Fleet2Slot7 = new(GameEngineType.Foc, 0xc5); + public static readonly CommandBarComponentId Fleet2Slot8 = new(GameEngineType.Foc, 0xc6); + public static readonly CommandBarComponentId Fleet2Slot9 = new(GameEngineType.Foc, 0xc7); + public static readonly CommandBarComponentId Fleet2Slot10 = new(GameEngineType.Foc, 0xc8); + public static readonly CommandBarComponentId Fleet2Slot11 = new(GameEngineType.Foc, 0xc9); + public static readonly CommandBarComponentId Fleet2Slot12 = new(GameEngineType.Foc, 0xca); + public static readonly CommandBarComponentId Fleet2Slot13 = new(GameEngineType.Foc, 0xcb); + public static readonly CommandBarComponentId Fleet2Slot14 = new(GameEngineType.Foc, 0xcc); + public static readonly CommandBarComponentId Fleet2Slot15 = new(GameEngineType.Foc, 0xcd); + public static readonly CommandBarComponentId Fleet2Slot16 = new(GameEngineType.Foc, 0xce); + public static readonly CommandBarComponentId Fleet2Slot17 = new(GameEngineType.Foc, 0xcf); + public static readonly CommandBarComponentId Fleet2Slot18 = new(GameEngineType.Foc, 0xd0); + public static readonly CommandBarComponentId Fleet2Slot19 = new(GameEngineType.Foc, 0xd1); + public static readonly CommandBarComponentId Fleet2Slot20 = new(GameEngineType.Foc, 0xd2); + public static readonly CommandBarComponentId Fleet2Slot21 = new(GameEngineType.Foc, 0xd3); + public static readonly CommandBarComponentId Fleet2Slot22 = new(GameEngineType.Foc, 0xd4); + public static readonly CommandBarComponentId Fleet2Slot23 = new(GameEngineType.Foc, 0xd5); + public static readonly CommandBarComponentId Fleet2Slot24 = new(GameEngineType.Foc, 0xd6); + public static readonly CommandBarComponentId Fleet2Slot25 = new(GameEngineType.Foc, 0xd7); + public static readonly CommandBarComponentId Fleet2Slot26 = new(GameEngineType.Foc, 0xd8); + public static readonly CommandBarComponentId Fleet2Slot27 = new(GameEngineType.Foc, 0xd9); + public static readonly CommandBarComponentId Fleet2Slot28 = new(GameEngineType.Foc, 0xda); + public static readonly CommandBarComponentId Fleet2Slot29 = new(GameEngineType.Foc, 0xdb); + public static readonly CommandBarComponentId Fleet2Slot30 = new(GameEngineType.Foc, 0xdc); + public static readonly CommandBarComponentId Fleet2Slot31 = new(GameEngineType.Foc, 0xdd); + public static readonly CommandBarComponentId Fleet2Slot32 = new(GameEngineType.Foc, 0xde); + public static readonly CommandBarComponentId Fleet2Slot33 = new(GameEngineType.Foc, 0xdf); + public static readonly CommandBarComponentId Fleet2Slot34 = new(GameEngineType.Foc, 0xe0); + public static readonly CommandBarComponentId TacticalMain = new(GameEngineType.Foc, 0xe1); + public static readonly CommandBarComponentId TacticalOptions = new(GameEngineType.Foc, 0xe2); + public static readonly CommandBarComponentId TacticalCamera = new(GameEngineType.Foc, 0xe3); + public static readonly CommandBarComponentId TacticalBeacon = new(GameEngineType.Foc, 0xe4); + public static readonly CommandBarComponentId TacticalWeapon0 = new(GameEngineType.Foc, 0xe5); + public static readonly CommandBarComponentId TacticalWeapon1 = new(GameEngineType.Foc, 0xe6); + public static readonly CommandBarComponentId TacticalReinforce = new(GameEngineType.Foc, 0xe7); + public static readonly CommandBarComponentId TacticalRetreat = new(GameEngineType.Foc, 0xe8); + public static readonly CommandBarComponentId TacticalStoryArc = new(GameEngineType.Foc, 0xe9); + public static readonly CommandBarComponentId TacticalTechLevel = new(GameEngineType.Foc, 0xea); + public static readonly CommandBarComponentId TacticalAttack = new(GameEngineType.Foc, 0xeb); + public static readonly CommandBarComponentId TacticalAttackMove = new(GameEngineType.Foc, 0xec); + public static readonly CommandBarComponentId TacticalMove = new(GameEngineType.Foc, 0xed); + public static readonly CommandBarComponentId TacticalWaypoint = new(GameEngineType.Foc, 0xee); + public static readonly CommandBarComponentId TacticalStop = new(GameEngineType.Foc, 0xef); + public static readonly CommandBarComponentId TacticalGuard = new(GameEngineType.Foc, 0xf0); + public static readonly CommandBarComponentId SpaceTacticalRadar = new(GameEngineType.Foc, 0xf1); + public static readonly CommandBarComponentId SpaceTacticalCredits = new(GameEngineType.Foc, 0xf2); + public static readonly CommandBarComponentId SpaceTacticalGrabBar = new(GameEngineType.Foc, 0xf3); + public static readonly CommandBarComponentId SpaceTacticalHealthBar = new(GameEngineType.Foc, 0xf4); + public static readonly CommandBarComponentId SpaceTacticalBracketSmall = new(GameEngineType.Foc, 0xf5); + public static readonly CommandBarComponentId SpaceTacticalBracketMedium = new(GameEngineType.Foc, 0xf6); + public static readonly CommandBarComponentId SpaceTacticalBracketLarge = new(GameEngineType.Foc, 0xf7); + public static readonly CommandBarComponentId SpaceTacticalHeroIcon = new(GameEngineType.Foc, 0xf8); + public static readonly CommandBarComponentId SpaceTacticalHeroHealth = new(GameEngineType.Foc, 0xf9); + public static readonly CommandBarComponentId SpaceTacticalHealth = new(GameEngineType.Foc, 0xfa); + public static readonly CommandBarComponentId SpaceTacticalHealthMedium = new(GameEngineType.Foc, 0xfb); + public static readonly CommandBarComponentId SpaceTacticalHealthLarge = new(GameEngineType.Foc, 0xfc); + public static readonly CommandBarComponentId SpaceTacticalShields = new(GameEngineType.Foc, 0xfd); + public static readonly CommandBarComponentId SpaceTacticalShieldsMedium = new(GameEngineType.Foc, 0xfe); + public static readonly CommandBarComponentId SpaceTacticalShieldsLarge = new(GameEngineType.Foc, 0xff); + public static readonly CommandBarComponentId SpaceTacticalPower = new(GameEngineType.Foc, 0x100); + public static readonly CommandBarComponentId SpaceTacticalControlGroup = new(GameEngineType.Foc, 0x101); + public static readonly CommandBarComponentId SpaceTacticalAbilityIcon = new(GameEngineType.Foc, 0x102); + public static readonly CommandBarComponentId SpaceTacticalGarrisonIcon = new(GameEngineType.Foc, 0x103); + public static readonly CommandBarComponentId LandTacticalWeatherIcon = new(GameEngineType.Foc, 0x104); + public static readonly CommandBarComponentId GarrisonSlotIcon = new(GameEngineType.Foc, 0x105); + public static readonly CommandBarComponentId DsFireShell = new(GameEngineType.Foc, 0x106); + public static readonly CommandBarComponentId DsFire = new(GameEngineType.Foc, 0x107); + public static readonly CommandBarComponentId DsCountdownTimer = new(GameEngineType.Foc, 0x108); + public static readonly CommandBarComponentId TacticalSelect00 = new(GameEngineType.Foc, 0x109); + public static readonly CommandBarComponentId TacticalSelect01 = new(GameEngineType.Foc, 0x10a); + public static readonly CommandBarComponentId TacticalSelect02 = new(GameEngineType.Foc, 0x10b); + public static readonly CommandBarComponentId TacticalSelect03 = new(GameEngineType.Foc, 0x10c); + public static readonly CommandBarComponentId TacticalSelect04 = new(GameEngineType.Foc, 0x10d); + public static readonly CommandBarComponentId TacticalSelect05 = new(GameEngineType.Foc, 0x10e); + public static readonly CommandBarComponentId TacticalSelect06 = new(GameEngineType.Foc, 0x10f); + public static readonly CommandBarComponentId TacticalSelect07 = new(GameEngineType.Foc, 0x110); + public static readonly CommandBarComponentId TacticalSelect08 = new(GameEngineType.Foc, 0x111); + public static readonly CommandBarComponentId TacticalSelect09 = new(GameEngineType.Foc, 0x112); + public static readonly CommandBarComponentId TacticalSelect10 = new(GameEngineType.Foc, 0x113); + public static readonly CommandBarComponentId TacticalSelect11 = new(GameEngineType.Foc, 0x114); + public static readonly CommandBarComponentId TacticalSelect12 = new(GameEngineType.Foc, 0x115); + public static readonly CommandBarComponentId TacticalSelect13 = new(GameEngineType.Foc, 0x116); + public static readonly CommandBarComponentId TacticalSelect14 = new(GameEngineType.Foc, 0x117); + public static readonly CommandBarComponentId TacticalSelect15 = new(GameEngineType.Foc, 0x118); + public static readonly CommandBarComponentId TacticalSelect16 = new(GameEngineType.Foc, 0x119); + public static readonly CommandBarComponentId TacticalSelect17 = new(GameEngineType.Foc, 0x11a); + public static readonly CommandBarComponentId TacticalSelect18 = new(GameEngineType.Foc, 0x11b); + public static readonly CommandBarComponentId TacticalSelect19 = new(GameEngineType.Foc, 0x11c); + public static readonly CommandBarComponentId TacticalSelect20 = new(GameEngineType.Foc, 0x11d); + public static readonly CommandBarComponentId TacticalSelect21 = new(GameEngineType.Foc, 0x11e); + public static readonly CommandBarComponentId TacticalSelect22 = new(GameEngineType.Foc, 0x11f); + public static readonly CommandBarComponentId TacticalSelect23 = new(GameEngineType.Foc, 0x120); + public static readonly CommandBarComponentId TacticalSelect24 = new(GameEngineType.Foc, 0x121); + public static readonly CommandBarComponentId TacticalSelect25 = new(GameEngineType.Foc, 0x122); + public static readonly CommandBarComponentId TacticalSelect26 = new(GameEngineType.Foc, 0x123); + public static readonly CommandBarComponentId TacticalSelect27 = new(GameEngineType.Foc, 0x124); + public static readonly CommandBarComponentId TacticalSelect28 = new(GameEngineType.Foc, 0x125); + public static readonly CommandBarComponentId TacticalSelect29 = new(GameEngineType.Foc, 0x126); + public static readonly CommandBarComponentId TacticalSelect30 = new(GameEngineType.Foc, 0x127); + public static readonly CommandBarComponentId TacticalSelect31 = new(GameEngineType.Foc, 0x128); + public static readonly CommandBarComponentId TacticalSelect32 = new(GameEngineType.Foc, 0x129); + public static readonly CommandBarComponentId TacticalSelect33 = new(GameEngineType.Foc, 0x12a); + public static readonly CommandBarComponentId TacticalSelect34 = new(GameEngineType.Foc, 0x12b); + public static readonly CommandBarComponentId TacticalSelect35 = new(GameEngineType.Foc, 0x12c); + public static readonly CommandBarComponentId TacticalSelect36 = new(GameEngineType.Foc, 0x12d); + public static readonly CommandBarComponentId TacticalSelect37 = new(GameEngineType.Foc, 0x12e); + public static readonly CommandBarComponentId TacticalSelect38 = new(GameEngineType.Foc, 0x12f); + public static readonly CommandBarComponentId TacticalSelect39 = new(GameEngineType.Foc, 0x130); + public static readonly CommandBarComponentId TacticalSelect40 = new(GameEngineType.Foc, 0x131); + public static readonly CommandBarComponentId TacticalSelect41 = new(GameEngineType.Foc, 0x132); + public static readonly CommandBarComponentId TacticalSelect42 = new(GameEngineType.Foc, 0x133); + public static readonly CommandBarComponentId TacticalSelect43 = new(GameEngineType.Foc, 0x134); + public static readonly CommandBarComponentId TacticalSelect44 = new(GameEngineType.Foc, 0x135); + public static readonly CommandBarComponentId TacticalSelect45 = new(GameEngineType.Foc, 0x136); + public static readonly CommandBarComponentId TacticalSelect46 = new(GameEngineType.Foc, 0x137); + public static readonly CommandBarComponentId TacticalSelect47 = new(GameEngineType.Foc, 0x138); + public static readonly CommandBarComponentId TacticalSelectHealth00 = new(GameEngineType.Foc, 0x139); + public static readonly CommandBarComponentId TacticalSelectHealth01 = new(GameEngineType.Foc, 0x13a); + public static readonly CommandBarComponentId TacticalSelectHealth02 = new(GameEngineType.Foc, 0x13b); + public static readonly CommandBarComponentId TacticalSelectHealth03 = new(GameEngineType.Foc, 0x13c); + public static readonly CommandBarComponentId TacticalSelectHealth04 = new(GameEngineType.Foc, 0x13d); + public static readonly CommandBarComponentId TacticalSelectHealth05 = new(GameEngineType.Foc, 0x13e); + public static readonly CommandBarComponentId TacticalSelectHealth06 = new(GameEngineType.Foc, 0x13f); + public static readonly CommandBarComponentId TacticalSelectHealth07 = new(GameEngineType.Foc, 0x140); + public static readonly CommandBarComponentId TacticalSelectHealth08 = new(GameEngineType.Foc, 0x141); + public static readonly CommandBarComponentId TacticalSelectHealth09 = new(GameEngineType.Foc, 0x142); + public static readonly CommandBarComponentId TacticalSelectHealth10 = new(GameEngineType.Foc, 0x143); + public static readonly CommandBarComponentId TacticalSelectHealth11 = new(GameEngineType.Foc, 0x144); + public static readonly CommandBarComponentId TacticalSelectHealth12 = new(GameEngineType.Foc, 0x145); + public static readonly CommandBarComponentId TacticalSelectHealth13 = new(GameEngineType.Foc, 0x146); + public static readonly CommandBarComponentId TacticalSelectHealth14 = new(GameEngineType.Foc, 0x147); + public static readonly CommandBarComponentId TacticalSelectHealth15 = new(GameEngineType.Foc, 0x148); + public static readonly CommandBarComponentId TacticalSelectHealth16 = new(GameEngineType.Foc, 0x149); + public static readonly CommandBarComponentId TacticalSelectHealth17 = new(GameEngineType.Foc, 0x14a); + public static readonly CommandBarComponentId TacticalSelectHealth18 = new(GameEngineType.Foc, 0x14b); + public static readonly CommandBarComponentId TacticalSelectHealth19 = new(GameEngineType.Foc, 0x14c); + public static readonly CommandBarComponentId TacticalSelectHealth20 = new(GameEngineType.Foc, 0x14d); + public static readonly CommandBarComponentId TacticalSelectHealth21 = new(GameEngineType.Foc, 0x14e); + public static readonly CommandBarComponentId TacticalSelectHealth22 = new(GameEngineType.Foc, 0x14f); + public static readonly CommandBarComponentId TacticalSelectHealth23 = new(GameEngineType.Foc, 0x150); + public static readonly CommandBarComponentId TacticalSelectHealth24 = new(GameEngineType.Foc, 0x151); + public static readonly CommandBarComponentId TacticalSelectHealth25 = new(GameEngineType.Foc, 0x152); + public static readonly CommandBarComponentId TacticalSelectHealth26 = new(GameEngineType.Foc, 0x153); + public static readonly CommandBarComponentId TacticalSelectHealth27 = new(GameEngineType.Foc, 0x154); + public static readonly CommandBarComponentId TacticalSelectHealth28 = new(GameEngineType.Foc, 0x155); + public static readonly CommandBarComponentId TacticalSelectHealth29 = new(GameEngineType.Foc, 0x156); + public static readonly CommandBarComponentId TacticalSelectHealth30 = new(GameEngineType.Foc, 0x157); + public static readonly CommandBarComponentId TacticalSelectHealth31 = new(GameEngineType.Foc, 0x158); + public static readonly CommandBarComponentId TacticalSelectHealth32 = new(GameEngineType.Foc, 0x159); + public static readonly CommandBarComponentId TacticalSelectHealth33 = new(GameEngineType.Foc, 0x15a); + public static readonly CommandBarComponentId TacticalSelectHealth34 = new(GameEngineType.Foc, 0x15b); + public static readonly CommandBarComponentId TacticalSelectHealth35 = new(GameEngineType.Foc, 0x15c); + public static readonly CommandBarComponentId TacticalSelectHealth36 = new(GameEngineType.Foc, 0x15d); + public static readonly CommandBarComponentId TacticalSelectHealth37 = new(GameEngineType.Foc, 0x15e); + public static readonly CommandBarComponentId TacticalSelectHealth38 = new(GameEngineType.Foc, 0x15f); + public static readonly CommandBarComponentId TacticalSelectHealth39 = new(GameEngineType.Foc, 0x160); + public static readonly CommandBarComponentId TacticalSelectHealth40 = new(GameEngineType.Foc, 0x161); + public static readonly CommandBarComponentId TacticalSelectHealth41 = new(GameEngineType.Foc, 0x162); + public static readonly CommandBarComponentId TacticalSelectHealth42 = new(GameEngineType.Foc, 0x163); + public static readonly CommandBarComponentId TacticalSelectHealth43 = new(GameEngineType.Foc, 0x164); + public static readonly CommandBarComponentId TacticalSelectHealth44 = new(GameEngineType.Foc, 0x165); + public static readonly CommandBarComponentId TacticalSelectHealth45 = new(GameEngineType.Foc, 0x166); + public static readonly CommandBarComponentId TacticalSelectHealth46 = new(GameEngineType.Foc, 0x167); + public static readonly CommandBarComponentId TacticalSelectHealth47 = new(GameEngineType.Foc, 0x168); + public static readonly CommandBarComponentId TacticalSelectShield00 = new(GameEngineType.Foc, 0x169); + public static readonly CommandBarComponentId TacticalSelectShield01 = new(GameEngineType.Foc, 0x16a); + public static readonly CommandBarComponentId TacticalSelectShield02 = new(GameEngineType.Foc, 0x16b); + public static readonly CommandBarComponentId TacticalSelectShield03 = new(GameEngineType.Foc, 0x16c); + public static readonly CommandBarComponentId TacticalSelectShield04 = new(GameEngineType.Foc, 0x16d); + public static readonly CommandBarComponentId TacticalSelectShield05 = new(GameEngineType.Foc, 0x16e); + public static readonly CommandBarComponentId TacticalSelectShield06 = new(GameEngineType.Foc, 0x16f); + public static readonly CommandBarComponentId TacticalSelectShield07 = new(GameEngineType.Foc, 0x170); + public static readonly CommandBarComponentId TacticalSelectShield08 = new(GameEngineType.Foc, 0x171); + public static readonly CommandBarComponentId TacticalSelectShield09 = new(GameEngineType.Foc, 0x172); + public static readonly CommandBarComponentId TacticalSelectShield10 = new(GameEngineType.Foc, 0x173); + public static readonly CommandBarComponentId TacticalSelectShield11 = new(GameEngineType.Foc, 0x174); + public static readonly CommandBarComponentId TacticalSelectShield12 = new(GameEngineType.Foc, 0x175); + public static readonly CommandBarComponentId TacticalSelectShield13 = new(GameEngineType.Foc, 0x176); + public static readonly CommandBarComponentId TacticalSelectShield14 = new(GameEngineType.Foc, 0x177); + public static readonly CommandBarComponentId TacticalSelectShield15 = new(GameEngineType.Foc, 0x178); + public static readonly CommandBarComponentId TacticalSelectShield16 = new(GameEngineType.Foc, 0x179); + public static readonly CommandBarComponentId TacticalSelectShield17 = new(GameEngineType.Foc, 0x17a); + public static readonly CommandBarComponentId TacticalSelectShield18 = new(GameEngineType.Foc, 0x17b); + public static readonly CommandBarComponentId TacticalSelectShield19 = new(GameEngineType.Foc, 0x17c); + public static readonly CommandBarComponentId TacticalSelectShield20 = new(GameEngineType.Foc, 0x17d); + public static readonly CommandBarComponentId TacticalSelectShield21 = new(GameEngineType.Foc, 0x17e); + public static readonly CommandBarComponentId TacticalSelectShield22 = new(GameEngineType.Foc, 0x17f); + public static readonly CommandBarComponentId TacticalSelectShield23 = new(GameEngineType.Foc, 0x180); + public static readonly CommandBarComponentId TacticalSelectShield24 = new(GameEngineType.Foc, 0x181); + public static readonly CommandBarComponentId TacticalSelectShield25 = new(GameEngineType.Foc, 0x182); + public static readonly CommandBarComponentId TacticalSelectShield26 = new(GameEngineType.Foc, 0x183); + public static readonly CommandBarComponentId TacticalSelectShield27 = new(GameEngineType.Foc, 0x184); + public static readonly CommandBarComponentId TacticalSelectShield28 = new(GameEngineType.Foc, 0x185); + public static readonly CommandBarComponentId TacticalSelectShield29 = new(GameEngineType.Foc, 0x186); + public static readonly CommandBarComponentId TacticalSelectShield30 = new(GameEngineType.Foc, 0x187); + public static readonly CommandBarComponentId TacticalSelectShield31 = new(GameEngineType.Foc, 0x188); + public static readonly CommandBarComponentId TacticalSelectShield32 = new(GameEngineType.Foc, 0x189); + public static readonly CommandBarComponentId TacticalSelectShield33 = new(GameEngineType.Foc, 0x18a); + public static readonly CommandBarComponentId TacticalSelectShield34 = new(GameEngineType.Foc, 0x18b); + public static readonly CommandBarComponentId TacticalSelectShield35 = new(GameEngineType.Foc, 0x18c); + public static readonly CommandBarComponentId TacticalSelectShield36 = new(GameEngineType.Foc, 0x18d); + public static readonly CommandBarComponentId TacticalSelectShield37 = new(GameEngineType.Foc, 0x18e); + public static readonly CommandBarComponentId TacticalSelectShield38 = new(GameEngineType.Foc, 0x18f); + public static readonly CommandBarComponentId TacticalSelectShield39 = new(GameEngineType.Foc, 0x190); + public static readonly CommandBarComponentId TacticalSelectShield40 = new(GameEngineType.Foc, 0x191); + public static readonly CommandBarComponentId TacticalSelectShield41 = new(GameEngineType.Foc, 0x192); + public static readonly CommandBarComponentId TacticalSelectShield42 = new(GameEngineType.Foc, 0x193); + public static readonly CommandBarComponentId TacticalSelectShield43 = new(GameEngineType.Foc, 0x194); + public static readonly CommandBarComponentId TacticalSelectShield44 = new(GameEngineType.Foc, 0x195); + public static readonly CommandBarComponentId TacticalSelectShield45 = new(GameEngineType.Foc, 0x196); + public static readonly CommandBarComponentId TacticalSelectShield46 = new(GameEngineType.Foc, 0x197); + public static readonly CommandBarComponentId TacticalSelectShield47 = new(GameEngineType.Foc, 0x198); + public static readonly CommandBarComponentId TacticalBorder00 = new(GameEngineType.Foc, 0x199); + public static readonly CommandBarComponentId TacticalBorder01 = new(GameEngineType.Foc, 0x19a); + public static readonly CommandBarComponentId TacticalBorder02 = new(GameEngineType.Foc, 0x19b); + public static readonly CommandBarComponentId TacticalBorder03 = new(GameEngineType.Foc, 0x19c); + public static readonly CommandBarComponentId TacticalBorder04 = new(GameEngineType.Foc, 0x19d); + public static readonly CommandBarComponentId TacticalBorder05 = new(GameEngineType.Foc, 0x19e); + public static readonly CommandBarComponentId TacticalBorder06 = new(GameEngineType.Foc, 0x19f); + public static readonly CommandBarComponentId TacticalBorder07 = new(GameEngineType.Foc, 0x1a0); + public static readonly CommandBarComponentId TacticalBorder08 = new(GameEngineType.Foc, 0x1a1); + public static readonly CommandBarComponentId TacticalBorder09 = new(GameEngineType.Foc, 0x1a2); + public static readonly CommandBarComponentId TacticalBorder10 = new(GameEngineType.Foc, 0x1a3); + public static readonly CommandBarComponentId TacticalBorder11 = new(GameEngineType.Foc, 0x1a4); + public static readonly CommandBarComponentId TacticalSelectButton00 = new(GameEngineType.Foc, 0x1a5); + public static readonly CommandBarComponentId TacticalSelectButton01 = new(GameEngineType.Foc, 0x1a6); + public static readonly CommandBarComponentId TacticalSelectButton02 = new(GameEngineType.Foc, 0x1a7); + public static readonly CommandBarComponentId TacticalSelectButton03 = new(GameEngineType.Foc, 0x1a8); + public static readonly CommandBarComponentId TacticalSelectButton04 = new(GameEngineType.Foc, 0x1a9); + public static readonly CommandBarComponentId TacticalSelectButton05 = new(GameEngineType.Foc, 0x1aa); + public static readonly CommandBarComponentId TacticalSelectButton06 = new(GameEngineType.Foc, 0x1ab); + public static readonly CommandBarComponentId TacticalSelectButton07 = new(GameEngineType.Foc, 0x1ac); + public static readonly CommandBarComponentId TacticalSelectButton08 = new(GameEngineType.Foc, 0x1ad); + public static readonly CommandBarComponentId TacticalSelectButton09 = new(GameEngineType.Foc, 0x1ae); + public static readonly CommandBarComponentId TacticalSelectButton10 = new(GameEngineType.Foc, 0x1af); + public static readonly CommandBarComponentId TacticalSelectButton11 = new(GameEngineType.Foc, 0x1b0); + public static readonly CommandBarComponentId TacticalSelectButton12 = new(GameEngineType.Foc, 0x1b1); + public static readonly CommandBarComponentId TacticalSelectButton13 = new(GameEngineType.Foc, 0x1b2); + public static readonly CommandBarComponentId TacticalSelectButton14 = new(GameEngineType.Foc, 0x1b3); + public static readonly CommandBarComponentId TacticalSelectButton15 = new(GameEngineType.Foc, 0x1b4); + public static readonly CommandBarComponentId TacticalSelectButton16 = new(GameEngineType.Foc, 0x1b5); + public static readonly CommandBarComponentId TacticalSelectButton17 = new(GameEngineType.Foc, 0x1b6); + public static readonly CommandBarComponentId TacticalSelectButton18 = new(GameEngineType.Foc, 0x1b7); + public static readonly CommandBarComponentId TacticalSelectButton19 = new(GameEngineType.Foc, 0x1b8); + public static readonly CommandBarComponentId TacticalSelectButton20 = new(GameEngineType.Foc, 0x1b9); + public static readonly CommandBarComponentId TacticalSelectButton21 = new(GameEngineType.Foc, 0x1ba); + public static readonly CommandBarComponentId TacticalSelectButton22 = new(GameEngineType.Foc, 0x1bb); + public static readonly CommandBarComponentId TacticalSelectButton23 = new(GameEngineType.Foc, 0x1bc); + public static readonly CommandBarComponentId TacticalSelectButton24 = new(GameEngineType.Foc, 0x1bd); + public static readonly CommandBarComponentId TacticalSelectButton25 = new(GameEngineType.Foc, 0x1be); + public static readonly CommandBarComponentId TacticalSelectButton26 = new(GameEngineType.Foc, 0x1bf); + public static readonly CommandBarComponentId TacticalSelectButton27 = new(GameEngineType.Foc, 0x1c0); + public static readonly CommandBarComponentId TacticalSelectButton28 = new(GameEngineType.Foc, 0x1c1); + public static readonly CommandBarComponentId TacticalSelectButton29 = new(GameEngineType.Foc, 0x1c2); + public static readonly CommandBarComponentId TacticalSelectButton30 = new(GameEngineType.Foc, 0x1c3); + public static readonly CommandBarComponentId TacticalSelectButton31 = new(GameEngineType.Foc, 0x1c4); + public static readonly CommandBarComponentId TacticalSelectButton32 = new(GameEngineType.Foc, 0x1c5); + public static readonly CommandBarComponentId TacticalSelectButton33 = new(GameEngineType.Foc, 0x1c6); + public static readonly CommandBarComponentId TacticalSelectButton34 = new(GameEngineType.Foc, 0x1c7); + public static readonly CommandBarComponentId TacticalSelectButton35 = new(GameEngineType.Foc, 0x1c8); + public static readonly CommandBarComponentId TacticalSelectButton36 = new(GameEngineType.Foc, 0x1c9); + public static readonly CommandBarComponentId TacticalSelectButton37 = new(GameEngineType.Foc, 0x1ca); + public static readonly CommandBarComponentId TacticalSelectButton38 = new(GameEngineType.Foc, 0x1cb); + public static readonly CommandBarComponentId TacticalSelectButton39 = new(GameEngineType.Foc, 0x1cc); + public static readonly CommandBarComponentId TacticalSelectButton40 = new(GameEngineType.Foc, 0x1cd); + public static readonly CommandBarComponentId TacticalSelectButton41 = new(GameEngineType.Foc, 0x1ce); + public static readonly CommandBarComponentId TacticalSelectButton42 = new(GameEngineType.Foc, 0x1cf); + public static readonly CommandBarComponentId TacticalSelectButton43 = new(GameEngineType.Foc, 0x1d0); + public static readonly CommandBarComponentId TacticalSelectButton44 = new(GameEngineType.Foc, 0x1d1); + public static readonly CommandBarComponentId TacticalSelectButton45 = new(GameEngineType.Foc, 0x1d2); + public static readonly CommandBarComponentId TacticalSelectButton46 = new(GameEngineType.Foc, 0x1d3); + public static readonly CommandBarComponentId TacticalSelectButton47 = new(GameEngineType.Foc, 0x1d4); + public static readonly CommandBarComponentId TacticalBuildQueue00 = new(GameEngineType.Foc, 0x1d5); + public static readonly CommandBarComponentId TacticalBuildQueue01 = new(GameEngineType.Foc, 0x1d6); + public static readonly CommandBarComponentId TacticalBuildQueue02 = new(GameEngineType.Foc, 0x1d7); + public static readonly CommandBarComponentId TacticalBuildQueue03 = new(GameEngineType.Foc, 0x1d8); + public static readonly CommandBarComponentId TacticalBuildQueue04 = new(GameEngineType.Foc, 0x1d9); + public static readonly CommandBarComponentId TacticalBuildQueue05 = new(GameEngineType.Foc, 0x1da); + public static readonly CommandBarComponentId TacticalBuildQueue06 = new(GameEngineType.Foc, 0x1db); + public static readonly CommandBarComponentId TacticalBuildQueue07 = new(GameEngineType.Foc, 0x1dc); + public static readonly CommandBarComponentId TacticalBuildQueue08 = new(GameEngineType.Foc, 0x1dd); + public static readonly CommandBarComponentId TacticalBuildQueue09 = new(GameEngineType.Foc, 0x1de); + public static readonly CommandBarComponentId TooltipBack = new(GameEngineType.Foc, 0x1df); + public static readonly CommandBarComponentId TooltipName = new(GameEngineType.Foc, 0x1e0); + public static readonly CommandBarComponentId TooltipPrice = new(GameEngineType.Foc, 0x1e1); + public static readonly CommandBarComponentId TooltipIcon = new(GameEngineType.Foc, 0x1e2); + public static readonly CommandBarComponentId TooltipIconLand = new(GameEngineType.Foc, 0x1e3); + public static readonly CommandBarComponentId TooltipLeftJustified = new(GameEngineType.Foc, 0x1e4); + public static readonly CommandBarComponentId EncyclopediaBack = new(GameEngineType.Foc, 0x1e5); + public static readonly CommandBarComponentId EncyclopediaHeaderText = new(GameEngineType.Foc, 0x1e6); + public static readonly CommandBarComponentId EncyclopediaText = new(GameEngineType.Foc, 0x1e7); + public static readonly CommandBarComponentId EncyclopediaRightText = new(GameEngineType.Foc, 0x1e8); + public static readonly CommandBarComponentId EncyclopediaCenterText = new(GameEngineType.Foc, 0x1e9); + public static readonly CommandBarComponentId EncyclopediaIcon = new(GameEngineType.Foc, 0x1ea); + public static readonly CommandBarComponentId EncyclopediaCostText = new(GameEngineType.Foc, 0x1eb); + public static readonly CommandBarComponentId ZoomedBack = new(GameEngineType.Foc, 0x1ec); + public static readonly CommandBarComponentId ZoomedHeaderText = new(GameEngineType.Foc, 0x1ed); + public static readonly CommandBarComponentId ZoomedText = new(GameEngineType.Foc, 0x1ee); + public static readonly CommandBarComponentId ZoomedRightText = new(GameEngineType.Foc, 0x1ef); + public static readonly CommandBarComponentId ZoomedCenterText = new(GameEngineType.Foc, 0x1f0); + public static readonly CommandBarComponentId ZoomedCostText = new(GameEngineType.Foc, 0x1f1); + public static readonly CommandBarComponentId GPlanetFleet = new(GameEngineType.Foc, 0x1f2); + public static readonly CommandBarComponentId GPlanetName = new(GameEngineType.Foc, 0x1f3); + public static readonly CommandBarComponentId GPlanetValue = new(GameEngineType.Foc, 0x1f4); + public static readonly CommandBarComponentId GPoliticalControl = new(GameEngineType.Foc, 0x1f5); + public static readonly CommandBarComponentId GSpaceLevel = new(GameEngineType.Foc, 0x1f6); + public static readonly CommandBarComponentId GSpaceIcon = new(GameEngineType.Foc, 0x1f7); + public static readonly CommandBarComponentId GSpaceLevelPips = new(GameEngineType.Foc, 0x1f8); + public static readonly CommandBarComponentId GGroundLevel = new(GameEngineType.Foc, 0x1f9); + public static readonly CommandBarComponentId GGroundIcon = new(GameEngineType.Foc, 0x1fa); + public static readonly CommandBarComponentId GGroundLevelPips = new(GameEngineType.Foc, 0x1fb); + public static readonly CommandBarComponentId GConflict = new(GameEngineType.Foc, 0x1fc); + public static readonly CommandBarComponentId GHero = new(GameEngineType.Foc, 0x1fd); + public static readonly CommandBarComponentId GEnemyHero = new(GameEngineType.Foc, 0x1fe); + public static readonly CommandBarComponentId GBuild = new(GameEngineType.Foc, 0x1ff); + public static readonly CommandBarComponentId GSmuggler = new(GameEngineType.Foc, 0x200); + public static readonly CommandBarComponentId GBountyHunter = new(GameEngineType.Foc, 0x201); + public static readonly CommandBarComponentId GPlanetLandForces = new(GameEngineType.Foc, 0x202); + public static readonly CommandBarComponentId GGalacticRadarBlip = new(GameEngineType.Foc, 0x203); + public static readonly CommandBarComponentId GGalacticRadarView = new(GameEngineType.Foc, 0x204); + public static readonly CommandBarComponentId GSmuggled = new(GameEngineType.Foc, 0x205); + public static readonly CommandBarComponentId GSpecialAbility = new(GameEngineType.Foc, 0x206); + public static readonly CommandBarComponentId GHeroIcon = new(GameEngineType.Foc, 0x207); + public static readonly CommandBarComponentId GPlanetRing = new(GameEngineType.Foc, 0x208); + public static readonly CommandBarComponentId GWeather = new(GameEngineType.Foc, 0x209); + public static readonly CommandBarComponentId GPlanetAbility = new(GameEngineType.Foc, 0x20a); + public static readonly CommandBarComponentId GCorruptionText = new(GameEngineType.Foc, 0x20b); + public static readonly CommandBarComponentId GCorruptionIcon = new(GameEngineType.Foc, 0x20c); + public static readonly CommandBarComponentId TutorialText = new(GameEngineType.Foc, 0x20d); + public static readonly CommandBarComponentId TutorialTextBack = new(GameEngineType.Foc, 0x20e); + public static readonly CommandBarComponentId RadarBlip = new(GameEngineType.Foc, 0x20f); + public static readonly CommandBarComponentId TacticalBuildButtonShell = new(GameEngineType.Foc, 0x210); + public static readonly CommandBarComponentId TacticalBuildButton0 = new(GameEngineType.Foc, 0x211); + public static readonly CommandBarComponentId TacticalBuildButton1 = new(GameEngineType.Foc, 0x212); + public static readonly CommandBarComponentId TacticalBuildButton2 = new(GameEngineType.Foc, 0x213); + public static readonly CommandBarComponentId TacticalBuildButton3 = new(GameEngineType.Foc, 0x214); + public static readonly CommandBarComponentId TacticalBuildButton4 = new(GameEngineType.Foc, 0x215); + public static readonly CommandBarComponentId TacticalBuildButton5 = new(GameEngineType.Foc, 0x216); + public static readonly CommandBarComponentId TacticalSellButton = new(GameEngineType.Foc, 0x217); + public static readonly CommandBarComponentId ReinforcementShell = new(GameEngineType.Foc, 0x218); + public static readonly CommandBarComponentId ReinforcementCancel = new(GameEngineType.Foc, 0x219); + public static readonly CommandBarComponentId ReinforcementSlot00 = new(GameEngineType.Foc, 0x21a); + public static readonly CommandBarComponentId ReinforcementSlot01 = new(GameEngineType.Foc, 0x21b); + public static readonly CommandBarComponentId ReinforcementSlot02 = new(GameEngineType.Foc, 0x21c); + public static readonly CommandBarComponentId ReinforcementSlot03 = new(GameEngineType.Foc, 0x21d); + public static readonly CommandBarComponentId ReinforcementSlot04 = new(GameEngineType.Foc, 0x21e); + public static readonly CommandBarComponentId ReinforcementSlot05 = new(GameEngineType.Foc, 0x21f); + public static readonly CommandBarComponentId ReinforcementSlot06 = new(GameEngineType.Foc, 0x220); + public static readonly CommandBarComponentId ReinforcementSlot07 = new(GameEngineType.Foc, 0x221); + public static readonly CommandBarComponentId ReinforcementSlot08 = new(GameEngineType.Foc, 0x222); + public static readonly CommandBarComponentId ReinforcementSlot09 = new(GameEngineType.Foc, 0x223); + public static readonly CommandBarComponentId ReinforcementSlot10 = new(GameEngineType.Foc, 0x224); + public static readonly CommandBarComponentId ReinforcementSlot11 = new(GameEngineType.Foc, 0x225); + public static readonly CommandBarComponentId ReinforcementSlot12 = new(GameEngineType.Foc, 0x226); + public static readonly CommandBarComponentId ReinforcementSlot13 = new(GameEngineType.Foc, 0x227); + public static readonly CommandBarComponentId ReinforcementSlot14 = new(GameEngineType.Foc, 0x228); + public static readonly CommandBarComponentId ReinforcementSlot15 = new(GameEngineType.Foc, 0x229); + public static readonly CommandBarComponentId ReinforcementSlot16 = new(GameEngineType.Foc, 0x22a); + public static readonly CommandBarComponentId ReinforcementSlot17 = new(GameEngineType.Foc, 0x22b); + public static readonly CommandBarComponentId ReinforcementSlot18 = new(GameEngineType.Foc, 0x22c); + public static readonly CommandBarComponentId ReinforcementSlot19 = new(GameEngineType.Foc, 0x22d); + public static readonly CommandBarComponentId ReinforcementCap2 = new(GameEngineType.Foc, 0x22e); + public static readonly CommandBarComponentId ReinforcementCap2Text = new(GameEngineType.Foc, 0x22f); + public static readonly CommandBarComponentId ReinforcementCounter = new(GameEngineType.Foc, 0x230); + public static readonly CommandBarComponentId GarrisonRespawnCounter = new(GameEngineType.Foc, 0x231); + public static readonly CommandBarComponentId SkirmishUpgrade = new(GameEngineType.Foc, 0x232); + public static readonly CommandBarComponentId PendingBattleShell = new(GameEngineType.Foc, 0x233); + public static readonly CommandBarComponentId PendingBattleText = new(GameEngineType.Foc, 0x234); + public static readonly CommandBarComponentId PendingBattleButton = new(GameEngineType.Foc, 0x235); + public static readonly CommandBarComponentId PendingBattleAutoresolve = new(GameEngineType.Foc, 0x236); + public static readonly CommandBarComponentId PendingBattleGraphLeft = new(GameEngineType.Foc, 0x237); + public static readonly CommandBarComponentId PendingBattleGraphRight = new(GameEngineType.Foc, 0x238); + public static readonly CommandBarComponentId TatcicalAutoresolveShell = new(GameEngineType.Foc, 0x239); + public static readonly CommandBarComponentId TacticalAutoresolveButton = new(GameEngineType.Foc, 0x23a); + public static readonly CommandBarComponentId TacticalAutoresolveGraphLeft = new(GameEngineType.Foc, 0x23b); + public static readonly CommandBarComponentId TacticalAutoresolveGraphRight = new(GameEngineType.Foc, 0x23c); + public static readonly CommandBarComponentId ObjectiveBack = new(GameEngineType.Foc, 0x23d); + public static readonly CommandBarComponentId ObjectiveHeaderText = new(GameEngineType.Foc, 0x23e); + public static readonly CommandBarComponentId ObjectiveText = new(GameEngineType.Foc, 0x23f); + public static readonly CommandBarComponentId ObjectiveIcon = new(GameEngineType.Foc, 0x240); + public static readonly CommandBarComponentId GuiDialogTooltip = new(GameEngineType.Foc, 0x241); + public static readonly CommandBarComponentId TargetUnitTypeShell = new(GameEngineType.Foc, 0x242); + public static readonly CommandBarComponentId TargetUnitTypeTitle = new(GameEngineType.Foc, 0x243); + public static readonly CommandBarComponentId TargetUnitTypeCancel = new(GameEngineType.Foc, 0x244); + public static readonly CommandBarComponentId TargetUnitTypeDescription = new(GameEngineType.Foc, 0x245); + public static readonly CommandBarComponentId TargetUnitTypeSlot00 = new(GameEngineType.Foc, 0x246); + public static readonly CommandBarComponentId TargetUnitTypeSlot01 = new(GameEngineType.Foc, 0x247); + public static readonly CommandBarComponentId TargetUnitTypeSlot02 = new(GameEngineType.Foc, 0x248); + public static readonly CommandBarComponentId TargetUnitTypeSlot03 = new(GameEngineType.Foc, 0x249); + public static readonly CommandBarComponentId TargetUnitTypeSlot04 = new(GameEngineType.Foc, 0x24a); + public static readonly CommandBarComponentId TargetUnitTypeSlot05 = new(GameEngineType.Foc, 0x24b); + public static readonly CommandBarComponentId TargetUnitTypeSlot06 = new(GameEngineType.Foc, 0x24c); + public static readonly CommandBarComponentId TargetUnitTypeSlot07 = new(GameEngineType.Foc, 0x24d); + public static readonly CommandBarComponentId TargetUnitTypeSlot08 = new(GameEngineType.Foc, 0x24e); + public static readonly CommandBarComponentId TargetUnitTypeSlot09 = new(GameEngineType.Foc, 0x24f); + public static readonly CommandBarComponentId TargetUnitTypeSlot10 = new(GameEngineType.Foc, 0x250); + public static readonly CommandBarComponentId TargetUnitTypeSlot11 = new(GameEngineType.Foc, 0x251); + public static readonly CommandBarComponentId TargetUnitTypeName00 = new(GameEngineType.Foc, 0x252); + public static readonly CommandBarComponentId TargetUnitTypeName01 = new(GameEngineType.Foc, 0x253); + public static readonly CommandBarComponentId TargetUnitTypeName02 = new(GameEngineType.Foc, 0x254); + public static readonly CommandBarComponentId TargetUnitTypeName03 = new(GameEngineType.Foc, 0x255); + public static readonly CommandBarComponentId TargetUnitTypeName04 = new(GameEngineType.Foc, 0x256); + public static readonly CommandBarComponentId TargetUnitTypeName05 = new(GameEngineType.Foc, 0x257); + public static readonly CommandBarComponentId TargetUnitTypeName06 = new(GameEngineType.Foc, 0x258); + public static readonly CommandBarComponentId TargetUnitTypeName07 = new(GameEngineType.Foc, 0x259); + public static readonly CommandBarComponentId TargetUnitTypeName08 = new(GameEngineType.Foc, 0x25a); + public static readonly CommandBarComponentId TargetUnitTypeName09 = new(GameEngineType.Foc, 0x25b); + public static readonly CommandBarComponentId TargetUnitTypeName10 = new(GameEngineType.Foc, 0x25c); + public static readonly CommandBarComponentId TargetUnitTypeName11 = new(GameEngineType.Foc, 0x25d); + public static readonly CommandBarComponentId TargetUnitTypePrice00 = new(GameEngineType.Foc, 0x25e); + public static readonly CommandBarComponentId TargetUnitTypePrice01 = new(GameEngineType.Foc, 0x25f); + public static readonly CommandBarComponentId TargetUnitTypePrice02 = new(GameEngineType.Foc, 0x260); + public static readonly CommandBarComponentId TargetUnitTypePrice03 = new(GameEngineType.Foc, 0x261); + public static readonly CommandBarComponentId TargetUnitTypePrice04 = new(GameEngineType.Foc, 0x262); + public static readonly CommandBarComponentId TargetUnitTypePrice05 = new(GameEngineType.Foc, 0x263); + public static readonly CommandBarComponentId TargetUnitTypePrice06 = new(GameEngineType.Foc, 0x264); + public static readonly CommandBarComponentId TargetUnitTypePrice07 = new(GameEngineType.Foc, 0x265); + public static readonly CommandBarComponentId TargetUnitTypePrice08 = new(GameEngineType.Foc, 0x266); + public static readonly CommandBarComponentId TargetUnitTypePrice09 = new(GameEngineType.Foc, 0x267); + public static readonly CommandBarComponentId TargetUnitTypePrice10 = new(GameEngineType.Foc, 0x268); + public static readonly CommandBarComponentId TargetUnitTypePrice11 = new(GameEngineType.Foc, 0x269); + public static readonly CommandBarComponentId VcrButtonPlayPause = new(GameEngineType.Foc, 0x26a); + public static readonly CommandBarComponentId VcrButtonFastForward = new(GameEngineType.Foc, 0x26b); + public static readonly CommandBarComponentId VcrButtonFastForwardTactical = new(GameEngineType.Foc, 0x26c); + public static readonly CommandBarComponentId VcrButtonPlayPauseTactical = new(GameEngineType.Foc, 0x26d); + public static readonly CommandBarComponentId AdvisorHintPopupGalactic = new(GameEngineType.Foc, 0x26e); + public static readonly CommandBarComponentId AdvisorHintPopupTactical = new(GameEngineType.Foc, 0x26f); + public static readonly CommandBarComponentId AdvisorHintBack = new(GameEngineType.Foc, 0x270); + public static readonly CommandBarComponentId TextOrganizeFleet00 = new(GameEngineType.Foc, 0x271); + public static readonly CommandBarComponentId TextOrganizeFleet01 = new(GameEngineType.Foc, 0x272); + public static readonly CommandBarComponentId TextOrganizeFleet02 = new(GameEngineType.Foc, 0x273); + public static readonly CommandBarComponentId IconOrganizeFleet00 = new(GameEngineType.Foc, 0x274); + public static readonly CommandBarComponentId IconOrganizeFleet01 = new(GameEngineType.Foc, 0x275); + public static readonly CommandBarComponentId IconOrganizeFleet02 = new(GameEngineType.Foc, 0x276); + public static readonly CommandBarComponentId TextOrganizeLandFleet = new(GameEngineType.Foc, 0x277); + public static readonly CommandBarComponentId CsAbilityButton = new(GameEngineType.Foc, 0x278); + public static readonly CommandBarComponentId CsAbilityText = new(GameEngineType.Foc, 0x279); + public static readonly CommandBarComponentId MovieBoneGalactic = new(GameEngineType.Foc, 0x27a); + public static readonly CommandBarComponentId MovieBoneTactical = new(GameEngineType.Foc, 0x27b); + public static readonly CommandBarComponentId GenericCollision = new(GameEngineType.Foc, 0x27c); + public static readonly CommandBarComponentId GoodHeroShell = new(GameEngineType.Foc, 0x27d); + public static readonly CommandBarComponentId GoodHeroSlot00 = new(GameEngineType.Foc, 0x27e); + public static readonly CommandBarComponentId GoodHeroSlot01 = new(GameEngineType.Foc, 0x27f); + public static readonly CommandBarComponentId GoodHeroSlot02 = new(GameEngineType.Foc, 0x280); + public static readonly CommandBarComponentId GoodHeroSlot03 = new(GameEngineType.Foc, 0x281); + public static readonly CommandBarComponentId GoodHeroSlot04 = new(GameEngineType.Foc, 0x282); + public static readonly CommandBarComponentId GoodHeroSlot05 = new(GameEngineType.Foc, 0x283); + public static readonly CommandBarComponentId GoodHeroSlot06 = new(GameEngineType.Foc, 0x284); + public static readonly CommandBarComponentId GoodHeroSlot07 = new(GameEngineType.Foc, 0x285); + public static readonly CommandBarComponentId GoodHeroSlot08 = new(GameEngineType.Foc, 0x286); + public static readonly CommandBarComponentId GoodHeroSlot09 = new(GameEngineType.Foc, 0x287); + public static readonly CommandBarComponentId GoodHeroSlot10 = new(GameEngineType.Foc, 0x288); + public static readonly CommandBarComponentId GoodHeroHealth00 = new(GameEngineType.Foc, 0x289); + public static readonly CommandBarComponentId GoodHeroHealth01 = new(GameEngineType.Foc, 0x28a); + public static readonly CommandBarComponentId GoodHeroHealth02 = new(GameEngineType.Foc, 0x28b); + public static readonly CommandBarComponentId GoodHeroHealth03 = new(GameEngineType.Foc, 0x28c); + public static readonly CommandBarComponentId GoodHeroHealth04 = new(GameEngineType.Foc, 0x28d); + public static readonly CommandBarComponentId GoodHeroHealth05 = new(GameEngineType.Foc, 0x28e); + public static readonly CommandBarComponentId GoodHeroHealth06 = new(GameEngineType.Foc, 0x28f); + public static readonly CommandBarComponentId GoodHeroHealth07 = new(GameEngineType.Foc, 0x290); + public static readonly CommandBarComponentId GoodHeroHealth08 = new(GameEngineType.Foc, 0x291); + public static readonly CommandBarComponentId GoodHeroHealth09 = new(GameEngineType.Foc, 0x292); + public static readonly CommandBarComponentId GoodHeroHealth10 = new(GameEngineType.Foc, 0x293); + public static readonly CommandBarComponentId EvilHeroShell = new(GameEngineType.Foc, 0x294); + public static readonly CommandBarComponentId EvilHeroSlot00 = new(GameEngineType.Foc, 0x295); + public static readonly CommandBarComponentId EvilHeroSlot01 = new(GameEngineType.Foc, 0x296); + public static readonly CommandBarComponentId EvilHeroSlot02 = new(GameEngineType.Foc, 0x297); + public static readonly CommandBarComponentId EvilHeroSlot03 = new(GameEngineType.Foc, 0x298); + public static readonly CommandBarComponentId EvilHeroSlot04 = new(GameEngineType.Foc, 0x299); + public static readonly CommandBarComponentId EvilHeroSlot05 = new(GameEngineType.Foc, 0x29a); + public static readonly CommandBarComponentId EvilHeroSlot06 = new(GameEngineType.Foc, 0x29b); + public static readonly CommandBarComponentId EvilHeroSlot07 = new(GameEngineType.Foc, 0x29c); + public static readonly CommandBarComponentId EvilHeroSlot08 = new(GameEngineType.Foc, 0x29d); + public static readonly CommandBarComponentId EvilHeroSlot09 = new(GameEngineType.Foc, 0x29e); + public static readonly CommandBarComponentId EvilHeroSlot10 = new(GameEngineType.Foc, 0x29f); + public static readonly CommandBarComponentId EvilHeroHealth00 = new(GameEngineType.Foc, 0x2a0); + public static readonly CommandBarComponentId EvilHeroHealth01 = new(GameEngineType.Foc, 0x2a1); + public static readonly CommandBarComponentId EvilHeroHealth02 = new(GameEngineType.Foc, 0x2a2); + public static readonly CommandBarComponentId EvilHeroHealth03 = new(GameEngineType.Foc, 0x2a3); + public static readonly CommandBarComponentId EvilHeroHealth04 = new(GameEngineType.Foc, 0x2a4); + public static readonly CommandBarComponentId EvilHeroHealth05 = new(GameEngineType.Foc, 0x2a5); + public static readonly CommandBarComponentId EvilHeroHealth06 = new(GameEngineType.Foc, 0x2a6); + public static readonly CommandBarComponentId EvilHeroHealth07 = new(GameEngineType.Foc, 0x2a7); + public static readonly CommandBarComponentId EvilHeroHealth08 = new(GameEngineType.Foc, 0x2a8); + public static readonly CommandBarComponentId EvilHeroHealth09 = new(GameEngineType.Foc, 0x2a9); + public static readonly CommandBarComponentId EvilHeroHealth10 = new(GameEngineType.Foc, 0x2aa); + public static readonly CommandBarComponentId PauseShell = new(GameEngineType.Foc, 0x2ab); + public static readonly CommandBarComponentId PauseText = new(GameEngineType.Foc, 0x2ac); + public static readonly CommandBarComponentId PauseButton = new(GameEngineType.Foc, 0x2ad); + public static readonly CommandBarComponentId StoryCampaignPendingBattleShell = new(GameEngineType.Foc, 0x2ae); + public static readonly CommandBarComponentId StoryCampaignPendingBattleText = new(GameEngineType.Foc, 0x2af); + public static readonly CommandBarComponentId StoryCampaignPendingBattleButton = new(GameEngineType.Foc, 0x2b0); + public static readonly CommandBarComponentId MapActivate = new(GameEngineType.Foc, 0x2b1); + public static readonly CommandBarComponentId MapShell = new(GameEngineType.Foc, 0x2b2); + public static readonly CommandBarComponentId MapOverlayShell = new(GameEngineType.Foc, 0x2b3); + public static readonly CommandBarComponentId MapGroundForcesText = new(GameEngineType.Foc, 0x2b4); + public static readonly CommandBarComponentId MapDragDropText = new(GameEngineType.Foc, 0x2b5); + public static readonly CommandBarComponentId MapUnitCapText = new(GameEngineType.Foc, 0x2b6); + public static readonly CommandBarComponentId MapUnitCapNumber = new(GameEngineType.Foc, 0x2b7); + public static readonly CommandBarComponentId MapBuildingCapText = new(GameEngineType.Foc, 0x2b8); + public static readonly CommandBarComponentId MapBuildingCapNumber = new(GameEngineType.Foc, 0x2b9); + public static readonly CommandBarComponentId MapFactionIcon = new(GameEngineType.Foc, 0x2ba); + public static readonly CommandBarComponentId MapPlanetNameText = new(GameEngineType.Foc, 0x2bb); + public static readonly CommandBarComponentId MapPlanetIncomeNumber = new(GameEngineType.Foc, 0x2bc); + public static readonly CommandBarComponentId MapDayCounterText = new(GameEngineType.Foc, 0x2bd); + public static readonly CommandBarComponentId MapDayCounter = new(GameEngineType.Foc, 0x2be); + public static readonly CommandBarComponentId MapBackButton = new(GameEngineType.Foc, 0x2bf); + public static readonly CommandBarComponentId MapWeatherIcon = new(GameEngineType.Foc, 0x2c0); + public static readonly CommandBarComponentId MapPlanetBonusIcon = new(GameEngineType.Foc, 0x2c1); + public static readonly CommandBarComponentId MapRadarMap = new(GameEngineType.Foc, 0x2c2); + public static readonly CommandBarComponentId MapZoomOut = new(GameEngineType.Foc, 0x2c3); + public static readonly CommandBarComponentId MapEncyclopediaPopup = new(GameEngineType.Foc, 0x2c4); + public static readonly CommandBarComponentId AdvancedMapFilter0 = new(GameEngineType.Foc, 0x2c5); + public static readonly CommandBarComponentId AdvancedMapFilter1 = new(GameEngineType.Foc, 0x2c6); + public static readonly CommandBarComponentId AdvancedMapFilter2 = new(GameEngineType.Foc, 0x2c7); + public static readonly CommandBarComponentId AdvancedMapFilter3 = new(GameEngineType.Foc, 0x2c8); + public static readonly CommandBarComponentId AdvancedMapOptions = new(GameEngineType.Foc, 0x2c9); + public static readonly CommandBarComponentId MapMainMap = new(GameEngineType.Foc, 0x2ca); + public static readonly CommandBarComponentId MapSpaceUnit00 = new(GameEngineType.Foc, 0x2cb); + public static readonly CommandBarComponentId MapSpaceUnit01 = new(GameEngineType.Foc, 0x2cc); + public static readonly CommandBarComponentId MapSpaceUnit02 = new(GameEngineType.Foc, 0x2cd); + public static readonly CommandBarComponentId MapSpaceUnit03 = new(GameEngineType.Foc, 0x2ce); + public static readonly CommandBarComponentId MapSpaceUnit04 = new(GameEngineType.Foc, 0x2cf); + public static readonly CommandBarComponentId MapSpaceUnit05 = new(GameEngineType.Foc, 0x2d0); + public static readonly CommandBarComponentId MapSpaceUnit06 = new(GameEngineType.Foc, 0x2d1); + public static readonly CommandBarComponentId MapSpaceUnit07 = new(GameEngineType.Foc, 0x2d2); + public static readonly CommandBarComponentId MapSpaceUnit08 = new(GameEngineType.Foc, 0x2d3); + public static readonly CommandBarComponentId MapSpaceUnit09 = new(GameEngineType.Foc, 0x2d4); + public static readonly CommandBarComponentId MapSpaceUnit10 = new(GameEngineType.Foc, 0x2d5); + public static readonly CommandBarComponentId MapSpaceUnit11 = new(GameEngineType.Foc, 0x2d6); + public static readonly CommandBarComponentId MapSpaceUnit12 = new(GameEngineType.Foc, 0x2d7); + public static readonly CommandBarComponentId MapSpaceUnit13 = new(GameEngineType.Foc, 0x2d8); + public static readonly CommandBarComponentId MapSpaceUnit14 = new(GameEngineType.Foc, 0x2d9); + public static readonly CommandBarComponentId MapSpaceUnit15 = new(GameEngineType.Foc, 0x2da); + public static readonly CommandBarComponentId MapSpaceUnit16 = new(GameEngineType.Foc, 0x2db); + public static readonly CommandBarComponentId MapSpaceUnit17 = new(GameEngineType.Foc, 0x2dc); + public static readonly CommandBarComponentId MapSpaceUnit18 = new(GameEngineType.Foc, 0x2dd); + public static readonly CommandBarComponentId MapSpaceUnit19 = new(GameEngineType.Foc, 0x2de); + public static readonly CommandBarComponentId MapBuildPad00 = new(GameEngineType.Foc, 0x2df); + public static readonly CommandBarComponentId MapBuildPad01 = new(GameEngineType.Foc, 0x2e0); + public static readonly CommandBarComponentId MapBuildPad02 = new(GameEngineType.Foc, 0x2e1); + public static readonly CommandBarComponentId MapBuildPad03 = new(GameEngineType.Foc, 0x2e2); + public static readonly CommandBarComponentId MapBuildPad04 = new(GameEngineType.Foc, 0x2e3); + public static readonly CommandBarComponentId MapBuildPad05 = new(GameEngineType.Foc, 0x2e4); + public static readonly CommandBarComponentId MapBuildPad06 = new(GameEngineType.Foc, 0x2e5); + public static readonly CommandBarComponentId MapBuildPad07 = new(GameEngineType.Foc, 0x2e6); + public static readonly CommandBarComponentId MapBuildPad08 = new(GameEngineType.Foc, 0x2e7); + public static readonly CommandBarComponentId MapBuildPad09 = new(GameEngineType.Foc, 0x2e8); + public static readonly CommandBarComponentId MapBuildPad10 = new(GameEngineType.Foc, 0x2e9); + public static readonly CommandBarComponentId MapBuildPad11 = new(GameEngineType.Foc, 0x2ea); + public static readonly CommandBarComponentId MapBuildPad12 = new(GameEngineType.Foc, 0x2eb); + public static readonly CommandBarComponentId MapBuildPad13 = new(GameEngineType.Foc, 0x2ec); + public static readonly CommandBarComponentId MapBuildPad14 = new(GameEngineType.Foc, 0x2ed); + public static readonly CommandBarComponentId MapBuildPad15 = new(GameEngineType.Foc, 0x2ee); + public static readonly CommandBarComponentId MapBuildPad16 = new(GameEngineType.Foc, 0x2ef); + public static readonly CommandBarComponentId MapBuildPad17 = new(GameEngineType.Foc, 0x2f0); + public static readonly CommandBarComponentId MapBuildPad18 = new(GameEngineType.Foc, 0x2f1); + public static readonly CommandBarComponentId MapBuildPad19 = new(GameEngineType.Foc, 0x2f2); + public static readonly CommandBarComponentId MapBuildPad20 = new(GameEngineType.Foc, 0x2f3); + public static readonly CommandBarComponentId MapBuildPad21 = new(GameEngineType.Foc, 0x2f4); + public static readonly CommandBarComponentId MapBuildPad22 = new(GameEngineType.Foc, 0x2f5); + public static readonly CommandBarComponentId MapBuildPad23 = new(GameEngineType.Foc, 0x2f6); + public static readonly CommandBarComponentId MapBuildPad24 = new(GameEngineType.Foc, 0x2f7); + public static readonly CommandBarComponentId MapBuildPad25 = new(GameEngineType.Foc, 0x2f8); + public static readonly CommandBarComponentId MapBuildPad26 = new(GameEngineType.Foc, 0x2f9); + public static readonly CommandBarComponentId MapBuildPad27 = new(GameEngineType.Foc, 0x2fa); + public static readonly CommandBarComponentId MapBuildPad28 = new(GameEngineType.Foc, 0x2fb); + public static readonly CommandBarComponentId MapBuildPad29 = new(GameEngineType.Foc, 0x2fc); + public static readonly CommandBarComponentId MapBuildPad30 = new(GameEngineType.Foc, 0x2fd); + public static readonly CommandBarComponentId MapBuildPad31 = new(GameEngineType.Foc, 0x2fe); + public static readonly CommandBarComponentId MapBuildPad32 = new(GameEngineType.Foc, 0x2ff); + public static readonly CommandBarComponentId MapReinforcePad00 = new(GameEngineType.Foc, 0x300); + public static readonly CommandBarComponentId MapReinforcePad01 = new(GameEngineType.Foc, 0x301); + public static readonly CommandBarComponentId MapReinforcePad02 = new(GameEngineType.Foc, 0x302); + public static readonly CommandBarComponentId MapReinforcePad03 = new(GameEngineType.Foc, 0x303); + public static readonly CommandBarComponentId MapReinforcePad04 = new(GameEngineType.Foc, 0x304); + public static readonly CommandBarComponentId MapReinforcePad05 = new(GameEngineType.Foc, 0x305); + public static readonly CommandBarComponentId MapReinforcePad06 = new(GameEngineType.Foc, 0x306); + public static readonly CommandBarComponentId MapBunkerAndBuildPad00 = new(GameEngineType.Foc, 0x307); + public static readonly CommandBarComponentId MapBunkerAndBuildPad01 = new(GameEngineType.Foc, 0x308); + public static readonly CommandBarComponentId MapBunkerAndBuildPad02 = new(GameEngineType.Foc, 0x309); + public static readonly CommandBarComponentId MapBunkerAndBuildPad03 = new(GameEngineType.Foc, 0x30a); + public static readonly CommandBarComponentId MapBunkerAndBuildPad04 = new(GameEngineType.Foc, 0x30b); + public static readonly CommandBarComponentId MapBunkerAndBuildPad05 = new(GameEngineType.Foc, 0x30c); + public static readonly CommandBarComponentId MapBunkerAndBuildPad06 = new(GameEngineType.Foc, 0x30d); + public static readonly CommandBarComponentId MapBunkerAndBuildPad07 = new(GameEngineType.Foc, 0x30e); + public static readonly CommandBarComponentId MapBunkerAndBuildPad08 = new(GameEngineType.Foc, 0x30f); + public static readonly CommandBarComponentId MapBunkerAndBuildPad09 = new(GameEngineType.Foc, 0x310); + public static readonly CommandBarComponentId MapBunkerAndBuildPad10 = new(GameEngineType.Foc, 0x311); + public static readonly CommandBarComponentId MapBunkerAndBuildPad11 = new(GameEngineType.Foc, 0x312); + public static readonly CommandBarComponentId MapBunkerAndBuildPad12 = new(GameEngineType.Foc, 0x313); + public static readonly CommandBarComponentId MapBunkerAndBuildPad13 = new(GameEngineType.Foc, 0x314); + public static readonly CommandBarComponentId MapBunkerAndBuildPad14 = new(GameEngineType.Foc, 0x315); + public static readonly CommandBarComponentId MapBunkerAndBuildPad15 = new(GameEngineType.Foc, 0x316); + public static readonly CommandBarComponentId MapBunkerAndBuildPad16 = new(GameEngineType.Foc, 0x317); + public static readonly CommandBarComponentId MapBunkerAndBuildPad17 = new(GameEngineType.Foc, 0x318); + public static readonly CommandBarComponentId MapBunkerAndBuildPad18 = new(GameEngineType.Foc, 0x319); + public static readonly CommandBarComponentId MapBunkerAndBuildPad19 = new(GameEngineType.Foc, 0x31a); + public static readonly CommandBarComponentId MapBunkerAndBuildPad20 = new(GameEngineType.Foc, 0x31b); + public static readonly CommandBarComponentId MapBunkerAndBuildPad21 = new(GameEngineType.Foc, 0x31c); + public static readonly CommandBarComponentId MapBunkerAndBuildPad22 = new(GameEngineType.Foc, 0x31d); + public static readonly CommandBarComponentId MapBunkerAndBuildPad23 = new(GameEngineType.Foc, 0x31e); + public static readonly CommandBarComponentId MapBunkerAndBuildPad24 = new(GameEngineType.Foc, 0x31f); + public static readonly CommandBarComponentId MapBunkerAndBuildPad25 = new(GameEngineType.Foc, 0x320); + public static readonly CommandBarComponentId MapBunkerAndBuildPad26 = new(GameEngineType.Foc, 0x321); + public static readonly CommandBarComponentId MapBunkerAndBuildPad27 = new(GameEngineType.Foc, 0x322); + public static readonly CommandBarComponentId MapBunkerAndBuildPad28 = new(GameEngineType.Foc, 0x323); + public static readonly CommandBarComponentId MapBunkerAndBuildPad29 = new(GameEngineType.Foc, 0x324); + public static readonly CommandBarComponentId MapBunkerAndBuildPad30 = new(GameEngineType.Foc, 0x325); + public static readonly CommandBarComponentId MapBunkerAndBuildPad31 = new(GameEngineType.Foc, 0x326); + public static readonly CommandBarComponentId MapBunkerAndBuildPad32 = new(GameEngineType.Foc, 0x327); + public static readonly CommandBarComponentId MapBunkerAndBuildPad33 = new(GameEngineType.Foc, 0x328); + public static readonly CommandBarComponentId MapBunkerAndBuildPad34 = new(GameEngineType.Foc, 0x329); + public static readonly CommandBarComponentId MapBunkerAndBuildPad35 = new(GameEngineType.Foc, 0x32a); + public static readonly CommandBarComponentId MapBunkerAndBuildPad36 = new(GameEngineType.Foc, 0x32b); + public static readonly CommandBarComponentId MapBunkerAndBuildPad37 = new(GameEngineType.Foc, 0x32c); + public static readonly CommandBarComponentId MapBunkerAndBuildPad38 = new(GameEngineType.Foc, 0x32d); + public static readonly CommandBarComponentId MapBunkerAndBuildPad39 = new(GameEngineType.Foc, 0x32e); + public static readonly CommandBarComponentId MapUnitPad00 = new(GameEngineType.Foc, 0x32f); + public static readonly CommandBarComponentId MapUnitPad01 = new(GameEngineType.Foc, 0x330); + public static readonly CommandBarComponentId MapUnitPad02 = new(GameEngineType.Foc, 0x331); + public static readonly CommandBarComponentId MapUnitPad03 = new(GameEngineType.Foc, 0x332); + public static readonly CommandBarComponentId MapUnitPad04 = new(GameEngineType.Foc, 0x333); + public static readonly CommandBarComponentId MapUnitPad05 = new(GameEngineType.Foc, 0x334); + public static readonly CommandBarComponentId MapUnitPad06 = new(GameEngineType.Foc, 0x335); + public static readonly CommandBarComponentId MapUnitPad07 = new(GameEngineType.Foc, 0x336); + public static readonly CommandBarComponentId MapUnitPad08 = new(GameEngineType.Foc, 0x337); + public static readonly CommandBarComponentId MapUnitPad09 = new(GameEngineType.Foc, 0x338); + public static readonly CommandBarComponentId BribeDisplay = new(GameEngineType.Foc, 0x339); + public static readonly CommandBarComponentId BlackMarketShell = new(GameEngineType.Foc, 0x33a); + public static readonly CommandBarComponentId BlackMarketTitle = new(GameEngineType.Foc, 0x33b); + public static readonly CommandBarComponentId BlackMarketPlanet = new(GameEngineType.Foc, 0x33c); + public static readonly CommandBarComponentId BlackMarketTechLevelHeader = new(GameEngineType.Foc, 0x33d); + public static readonly CommandBarComponentId BlackMarketSlotHeader = new(GameEngineType.Foc, 0x33e); + public static readonly CommandBarComponentId BlackMarketCancel = new(GameEngineType.Foc, 0x33f); + public static readonly CommandBarComponentId BlackMarketEncyclopedia = new(GameEngineType.Foc, 0x340); + public static readonly CommandBarComponentId BlackMarketTechLevel00 = new(GameEngineType.Foc, 0x341); + public static readonly CommandBarComponentId BlackMarketTechLevel01 = new(GameEngineType.Foc, 0x342); + public static readonly CommandBarComponentId BlackMarketTechLevel02 = new(GameEngineType.Foc, 0x343); + public static readonly CommandBarComponentId BlackMarketTechLevel03 = new(GameEngineType.Foc, 0x344); + public static readonly CommandBarComponentId BlackMarketTechLevel04 = new(GameEngineType.Foc, 0x345); + public static readonly CommandBarComponentId BlackMarketSlot00 = new(GameEngineType.Foc, 0x346); + public static readonly CommandBarComponentId BlackMarketSlot01 = new(GameEngineType.Foc, 0x347); + public static readonly CommandBarComponentId BlackMarketSlot02 = new(GameEngineType.Foc, 0x348); + public static readonly CommandBarComponentId BlackMarketSlot03 = new(GameEngineType.Foc, 0x349); + public static readonly CommandBarComponentId BlackMarketSlot04 = new(GameEngineType.Foc, 0x34a); + public static readonly CommandBarComponentId BlackMarketSlotDescription00 = new(GameEngineType.Foc, 0x34b); + public static readonly CommandBarComponentId BlackMarketSlotDescription01 = new(GameEngineType.Foc, 0x34c); + public static readonly CommandBarComponentId BlackMarketSlotDescription02 = new(GameEngineType.Foc, 0x34d); + public static readonly CommandBarComponentId BlackMarketSlotDescription03 = new(GameEngineType.Foc, 0x34e); + public static readonly CommandBarComponentId BlackMarketSlotDescription04 = new(GameEngineType.Foc, 0x34f); + public static readonly CommandBarComponentId BlackMarketSlotPrice00 = new(GameEngineType.Foc, 0x350); + public static readonly CommandBarComponentId BlackMarketSlotPrice01 = new(GameEngineType.Foc, 0x351); + public static readonly CommandBarComponentId BlackMarketSlotPrice02 = new(GameEngineType.Foc, 0x352); + public static readonly CommandBarComponentId BlackMarketSlotPrice03 = new(GameEngineType.Foc, 0x353); + public static readonly CommandBarComponentId BlackMarketSlotPrice04 = new(GameEngineType.Foc, 0x354); + public static readonly CommandBarComponentId SabotageShell = new(GameEngineType.Foc, 0x355); + public static readonly CommandBarComponentId SabotageTitle = new(GameEngineType.Foc, 0x356); + public static readonly CommandBarComponentId SabotageTitle2 = new(GameEngineType.Foc, 0x357); + public static readonly CommandBarComponentId SabotageTechLevel = new(GameEngineType.Foc, 0x358); + public static readonly CommandBarComponentId SabotageCancel = new(GameEngineType.Foc, 0x359); + public static readonly CommandBarComponentId SabotageDescription = new(GameEngineType.Foc, 0x35a); + public static readonly CommandBarComponentId SabotageSlot00 = new(GameEngineType.Foc, 0x35b); + public static readonly CommandBarComponentId SabotageSlot01 = new(GameEngineType.Foc, 0x35c); + public static readonly CommandBarComponentId SabotageSlot02 = new(GameEngineType.Foc, 0x35d); + public static readonly CommandBarComponentId SabotageSlot03 = new(GameEngineType.Foc, 0x35e); + public static readonly CommandBarComponentId SabotageSlot04 = new(GameEngineType.Foc, 0x35f); + public static readonly CommandBarComponentId SabotageSlot05 = new(GameEngineType.Foc, 0x360); + public static readonly CommandBarComponentId SabotageSlot06 = new(GameEngineType.Foc, 0x361); + public static readonly CommandBarComponentId SabotageSlot07 = new(GameEngineType.Foc, 0x362); + public static readonly CommandBarComponentId SabotageSlot08 = new(GameEngineType.Foc, 0x363); + public static readonly CommandBarComponentId SabotageSlot09 = new(GameEngineType.Foc, 0x364); + public static readonly CommandBarComponentId SabotagePrice00 = new(GameEngineType.Foc, 0x365); + public static readonly CommandBarComponentId SabotagePrice01 = new(GameEngineType.Foc, 0x366); + public static readonly CommandBarComponentId SabotagePrice02 = new(GameEngineType.Foc, 0x367); + public static readonly CommandBarComponentId SabotagePrice03 = new(GameEngineType.Foc, 0x368); + public static readonly CommandBarComponentId SabotagePrice04 = new(GameEngineType.Foc, 0x369); + public static readonly CommandBarComponentId SabotagePrice05 = new(GameEngineType.Foc, 0x36a); + public static readonly CommandBarComponentId SabotagePrice06 = new(GameEngineType.Foc, 0x36b); + public static readonly CommandBarComponentId SabotagePrice07 = new(GameEngineType.Foc, 0x36c); + public static readonly CommandBarComponentId SabotagePrice08 = new(GameEngineType.Foc, 0x36d); + public static readonly CommandBarComponentId SabotagePrice09 = new(GameEngineType.Foc, 0x36e); + public static readonly CommandBarComponentId SabotageName00 = new(GameEngineType.Foc, 0x36f); + public static readonly CommandBarComponentId SabotageName01 = new(GameEngineType.Foc, 0x370); + public static readonly CommandBarComponentId SabotageName02 = new(GameEngineType.Foc, 0x371); + public static readonly CommandBarComponentId SabotageName03 = new(GameEngineType.Foc, 0x372); + public static readonly CommandBarComponentId SabotageName04 = new(GameEngineType.Foc, 0x373); + public static readonly CommandBarComponentId SabotageName05 = new(GameEngineType.Foc, 0x374); + public static readonly CommandBarComponentId SabotageName06 = new(GameEngineType.Foc, 0x375); + public static readonly CommandBarComponentId SabotageName07 = new(GameEngineType.Foc, 0x376); + public static readonly CommandBarComponentId SabotageName08 = new(GameEngineType.Foc, 0x377); + public static readonly CommandBarComponentId SabotageName09 = new(GameEngineType.Foc, 0x378); + public static readonly CommandBarComponentId PlanetaryBombardment = new(GameEngineType.Foc, 0x379); + public static readonly CommandBarComponentId PlanetaryBombardmentRecharge = new(GameEngineType.Foc, 0x37a); + public static readonly CommandBarComponentId SuperLaser = new(GameEngineType.Foc, 0x37b); + public static readonly CommandBarComponentId SuperLaserRecharge = new(GameEngineType.Foc, 0x37c); + public static readonly CommandBarComponentId GenericFlytext = new(GameEngineType.Foc, 0x37d); + public static readonly CommandBarComponentId BribedIcon = new(GameEngineType.Foc, 0x37e); + public static readonly CommandBarComponentId SurfaceModIcon = new(GameEngineType.Foc, 0x37f); + public static readonly CommandBarComponentId RemoteBombIcon = new(GameEngineType.Foc, 0x380); + public static readonly CommandBarComponentId CorruptionShell = new(GameEngineType.Foc, 0x381); + public static readonly CommandBarComponentId CorruptionTitle = new(GameEngineType.Foc, 0x382); + public static readonly CommandBarComponentId CorruptionPlanetName = new(GameEngineType.Foc, 0x383); + public static readonly CommandBarComponentId CorruptionEncyclopedia = new(GameEngineType.Foc, 0x384); + public static readonly CommandBarComponentId CorruptionClose = new(GameEngineType.Foc, 0x385); + public static readonly CommandBarComponentId CorruptionPlanetModel = new(GameEngineType.Foc, 0x386); + public static readonly CommandBarComponentId CorruptionInfo00 = new(GameEngineType.Foc, 0x387); + public static readonly CommandBarComponentId CorruptionInfo01 = new(GameEngineType.Foc, 0x388); + public static readonly CommandBarComponentId CorruptionChoiceIcon0 = new(GameEngineType.Foc, 0x389); + public static readonly CommandBarComponentId CorruptionChoiceIcon1 = new(GameEngineType.Foc, 0x38a); + public static readonly CommandBarComponentId CorruptionChoiceIcon2 = new(GameEngineType.Foc, 0x38b); + public static readonly CommandBarComponentId CorruptionChoiceCost0 = new(GameEngineType.Foc, 0x38c); + public static readonly CommandBarComponentId CorruptionChoiceCost1 = new(GameEngineType.Foc, 0x38d); + public static readonly CommandBarComponentId CorruptionChoiceCost2 = new(GameEngineType.Foc, 0x38e); + public static readonly CommandBarComponentId CorruptionChoiceTitle0 = new(GameEngineType.Foc, 0x38f); + public static readonly CommandBarComponentId CorruptionChoiceTitle1 = new(GameEngineType.Foc, 0x390); + public static readonly CommandBarComponentId CorruptionChoiceTitle2 = new(GameEngineType.Foc, 0x391); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText00 = new(GameEngineType.Foc, 0x392); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText01 = new(GameEngineType.Foc, 0x393); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText02 = new(GameEngineType.Foc, 0x394); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText03 = new(GameEngineType.Foc, 0x395); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText10 = new(GameEngineType.Foc, 0x396); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText11 = new(GameEngineType.Foc, 0x397); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText12 = new(GameEngineType.Foc, 0x398); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText13 = new(GameEngineType.Foc, 0x399); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText20 = new(GameEngineType.Foc, 0x39a); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText21 = new(GameEngineType.Foc, 0x39b); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText22 = new(GameEngineType.Foc, 0x39c); + public static readonly CommandBarComponentId CorruptionChoiceBenefitText23 = new(GameEngineType.Foc, 0x39d); + public static readonly CommandBarComponentId CorruptionChoiceRequirement0 = new(GameEngineType.Foc, 0x39e); + public static readonly CommandBarComponentId CorruptionChoiceRequirement1 = new(GameEngineType.Foc, 0x39f); + public static readonly CommandBarComponentId CorruptionChoiceRequirement2 = new(GameEngineType.Foc, 0x3a0); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0000 = new(GameEngineType.Foc, 0x3a1); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0001 = new(GameEngineType.Foc, 0x3a2); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0002 = new(GameEngineType.Foc, 0x3a3); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0003 = new(GameEngineType.Foc, 0x3a4); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0100 = new(GameEngineType.Foc, 0x3a5); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0101 = new(GameEngineType.Foc, 0x3a6); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0102 = new(GameEngineType.Foc, 0x3a7); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon0103 = new(GameEngineType.Foc, 0x3a8); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1000 = new(GameEngineType.Foc, 0x3a9); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1001 = new(GameEngineType.Foc, 0x3aa); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1002 = new(GameEngineType.Foc, 0x3ab); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1003 = new(GameEngineType.Foc, 0x3ac); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1100 = new(GameEngineType.Foc, 0x3ad); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1101 = new(GameEngineType.Foc, 0x3ae); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1102 = new(GameEngineType.Foc, 0x3af); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon1103 = new(GameEngineType.Foc, 0x3b0); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2000 = new(GameEngineType.Foc, 0x3b1); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2001 = new(GameEngineType.Foc, 0x3b2); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2002 = new(GameEngineType.Foc, 0x3b3); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2003 = new(GameEngineType.Foc, 0x3b4); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2100 = new(GameEngineType.Foc, 0x3b5); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2101 = new(GameEngineType.Foc, 0x3b6); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2102 = new(GameEngineType.Foc, 0x3b7); + public static readonly CommandBarComponentId CorruptionChoiceRequirementIcon2103 = new(GameEngineType.Foc, 0x3b8); + public static readonly CommandBarComponentId HackSuperWeaponShell = new(GameEngineType.Foc, 0x3b9); + public static readonly CommandBarComponentId HackSuperWeaponTitle = new(GameEngineType.Foc, 0x3ba); + public static readonly CommandBarComponentId HackSuperWeaponText00 = new(GameEngineType.Foc, 0x3bb); + public static readonly CommandBarComponentId HackSuperWeaponText01 = new(GameEngineType.Foc, 0x3bc); + public static readonly CommandBarComponentId HackSuperWeaponText02 = new(GameEngineType.Foc, 0x3bd); + public static readonly CommandBarComponentId HackSuperWeaponText03 = new(GameEngineType.Foc, 0x3be); + public static readonly CommandBarComponentId HackSuperWeaponText04 = new(GameEngineType.Foc, 0x3bf); + public static readonly CommandBarComponentId HackSuperWeaponText05 = new(GameEngineType.Foc, 0x3c0); + public static readonly CommandBarComponentId HackSuperWeaponText06 = new(GameEngineType.Foc, 0x3c1); + public static readonly CommandBarComponentId HackSuperWeaponCost = new(GameEngineType.Foc, 0x3c2); + public static readonly CommandBarComponentId HackSuperWeaponAccept = new(GameEngineType.Foc, 0x3c3); + public static readonly CommandBarComponentId HackSuperWeaponCancel = new(GameEngineType.Foc, 0x3c4); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/ICommandBarGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/ICommandBarGameManager.cs new file mode 100644 index 0000000..665d217 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/ICommandBarGameManager.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.CommandBar.Components; +using PG.StarWarsGame.Files.MTD.Files; + +namespace PG.StarWarsGame.Engine.CommandBar; + +public interface ICommandBarGameManager : IGameManager +{ + IMtdFile? MegaTextureFile { get; } + + ICollection Components { get; } + + IReadOnlyDictionary Groups { get; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/SupportedCommandBarComponentData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/SupportedCommandBarComponentData.cs new file mode 100644 index 0000000..7b6eeb6 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/SupportedCommandBarComponentData.cs @@ -0,0 +1,1652 @@ +using System; +using System.Collections.Generic; + +namespace PG.StarWarsGame.Engine.CommandBar; + +public static class SupportedCommandBarComponentData +{ + public static IReadOnlyDictionary GetComponentIdsForEngine(GameEngineType engineType) + { + return engineType switch + { + GameEngineType.Eaw => EawSupportedComponents, + GameEngineType.Foc => FocSupportedComponents, + _ => throw new NotSupportedException() + }; + } + + private static readonly IReadOnlyDictionary EawSupportedComponents = + new Dictionary + { + { EawCommandBarComponentIds.MainShell, "i_main_commandbar" }, + { EawCommandBarComponentIds.ProductionOptions, "b_option_g" }, + { EawCommandBarComponentIds.GalacticCamera, "b_camera_g" }, + { EawCommandBarComponentIds.PlanetName, "Text_Planet" }, + { EawCommandBarComponentIds.PlanetValue, "Text_Planetary_Income" }, + { EawCommandBarComponentIds.PlanetAffiliation, "Text_Allegiance_A" }, + { EawCommandBarComponentIds.PlayerCredits, "Text_Credits" }, + { EawCommandBarComponentIds.ReinforcementCap, "Text_Planetary_Pop" }, + { EawCommandBarComponentIds.PlanetNameTactical, "Text_Planet_tactical" }, + { EawCommandBarComponentIds.PlanetInfo, "planet_info" }, + { EawCommandBarComponentIds.Production0, "b_Create_00" }, + { EawCommandBarComponentIds.Production1, "b_Create_01" }, + { EawCommandBarComponentIds.Production2, "b_Create_02" }, + { EawCommandBarComponentIds.Production3, "b_Create_03" }, + { EawCommandBarComponentIds.Production4, "b_Create_04" }, + { EawCommandBarComponentIds.Production5, "b_Create_05" }, + { EawCommandBarComponentIds.Production6, "b_Create_06" }, + { EawCommandBarComponentIds.Production7, "b_Create_07" }, + { EawCommandBarComponentIds.Production8, "b_Create_08" }, + { EawCommandBarComponentIds.Production9, "b_Create_09" }, + { EawCommandBarComponentIds.Production10, "b_Create_10" }, + { EawCommandBarComponentIds.Production11, "b_Create_11" }, + { EawCommandBarComponentIds.Production12, "b_Create_12" }, + { EawCommandBarComponentIds.Production13, "b_Create_13" }, + { EawCommandBarComponentIds.Production14, "b_Create_14" }, + { EawCommandBarComponentIds.Production15, "b_Create_15" }, + { EawCommandBarComponentIds.Production16, "b_Create_16" }, + { EawCommandBarComponentIds.Production17, "b_Create_17" }, + { EawCommandBarComponentIds.Production18, "b_Create_18" }, + { EawCommandBarComponentIds.Production19, "b_Create_19" }, + { EawCommandBarComponentIds.Production20, "b_Create_20" }, + { EawCommandBarComponentIds.Production21, "b_Create_21" }, + { EawCommandBarComponentIds.Production22, "b_Create_22" }, + { EawCommandBarComponentIds.Production23, "b_Create_23" }, + { EawCommandBarComponentIds.Production24, "b_Create_24" }, + { EawCommandBarComponentIds.Production25, "b_Create_25" }, + { EawCommandBarComponentIds.Production26, "b_Create_26" }, + { EawCommandBarComponentIds.Production27, "b_Create_27" }, + { EawCommandBarComponentIds.Production28, "b_Create_28" }, + { EawCommandBarComponentIds.Production29, "b_Create_29" }, + { EawCommandBarComponentIds.Production30, "b_Create_30" }, + { EawCommandBarComponentIds.Production31, "b_Create_31" }, + { EawCommandBarComponentIds.Production32, "b_Create_32" }, + { EawCommandBarComponentIds.Production33, "b_Create_33" }, + { EawCommandBarComponentIds.Production34, "b_Create_34" }, + { EawCommandBarComponentIds.Production35, "b_Create_35" }, + { EawCommandBarComponentIds.Production36, "b_Create_36" }, + { EawCommandBarComponentIds.Production37, "b_Create_37" }, + { EawCommandBarComponentIds.Production38, "b_Create_38" }, + { EawCommandBarComponentIds.Production39, "b_Create_39" }, + { EawCommandBarComponentIds.Production40, "b_Create_40" }, + { EawCommandBarComponentIds.Production41, "b_Create_41" }, + { EawCommandBarComponentIds.Production42, "b_Create_42" }, + { EawCommandBarComponentIds.Production43, "b_Create_43" }, + { EawCommandBarComponentIds.Production44, "b_Create_44" }, + { EawCommandBarComponentIds.Production45, "b_Create_45" }, + { EawCommandBarComponentIds.Production46, "b_Create_46" }, + { EawCommandBarComponentIds.Production47, "b_Create_47" }, + { EawCommandBarComponentIds.DroidHelp, "b_droid_help" }, + { EawCommandBarComponentIds.DroidHelpTactical, "b_droid_help_tactical" }, + { EawCommandBarComponentIds.CurrentDay, "Text_Day_Counter" }, + { EawCommandBarComponentIds.DayCredits, "Text_Day_Credits" }, + { EawCommandBarComponentIds.PopulationCap, "Text_Pop_Cap" }, + { EawCommandBarComponentIds.Filter0, "b_filters0" }, + { EawCommandBarComponentIds.Filter1, "b_filters1" }, + { EawCommandBarComponentIds.Filter2, "b_filters2" }, + { EawCommandBarComponentIds.Filter3, "b_filters3" }, + { EawCommandBarComponentIds.StoryArcButton, "b_story_arc_g" }, + { EawCommandBarComponentIds.PlanetSummaryButton, "b_quick_ref" }, + { EawCommandBarComponentIds.SpaceTab, "b_space_tab" }, + { EawCommandBarComponentIds.LandTab, "b_planet_tab" }, + { EawCommandBarComponentIds.Dial, "b_dial" }, + { EawCommandBarComponentIds.ScrollRight, "b_scroll_right" }, + { EawCommandBarComponentIds.ScrollLeft, "b_scroll_left" }, + { EawCommandBarComponentIds.ZoomView, "b_zoom" }, + { EawCommandBarComponentIds.PrevPlanet, "b_planet_left" }, + { EawCommandBarComponentIds.NextPlanet, "b_planet_right" }, + { EawCommandBarComponentIds.RadarGalactic, "radar_galactic" }, + { EawCommandBarComponentIds.TechLevel, "Text_Tech_Level" }, + { EawCommandBarComponentIds.BalancePip, "balance_pip" }, + { EawCommandBarComponentIds.BuildQueue00, "queue00" }, + { EawCommandBarComponentIds.BuildQueue01, "queue01" }, + { EawCommandBarComponentIds.BuildQueue02, "queue02" }, + { EawCommandBarComponentIds.BuildQueue03, "queue03" }, + { EawCommandBarComponentIds.BuildQueue04, "queue04" }, + { EawCommandBarComponentIds.BuildQueue05, "queue05" }, + { EawCommandBarComponentIds.BuildQueue06, "queue06" }, + { EawCommandBarComponentIds.BuildQueue07, "queue07" }, + { EawCommandBarComponentIds.BuildQueue08, "queue08" }, + { EawCommandBarComponentIds.BuildQueue09, "queue09" }, + { EawCommandBarComponentIds.OrganizationShell, "i_main_organize" }, + { EawCommandBarComponentIds.OrganizationCollision, "land_art_ALT0" }, + { EawCommandBarComponentIds.SmugglerBox, "b_underground" }, + { EawCommandBarComponentIds.SpaceStationUpgrade01, "space_Pad_1" }, + { EawCommandBarComponentIds.SpaceStationUpgrade02, "space_Pad_02" }, + { EawCommandBarComponentIds.HeroAbilitySlot, "Slot_00" }, + { EawCommandBarComponentIds.PlanetOrganize0, "Pad_00" }, + { EawCommandBarComponentIds.PlanetOrganize1, "Pad_01" }, + { EawCommandBarComponentIds.PlanetOrganize2, "Pad_02" }, + { EawCommandBarComponentIds.PlanetOrganize3, "Pad_03" }, + { EawCommandBarComponentIds.PlanetOrganize4, "Pad_04" }, + { EawCommandBarComponentIds.PlanetOrganize5, "Pad_05" }, + { EawCommandBarComponentIds.PlanetOrganize6, "Pad_06" }, + { EawCommandBarComponentIds.PlanetOrganize7, "Pad_07" }, + { EawCommandBarComponentIds.PlanetOrganize8, "Pad_08" }, + { EawCommandBarComponentIds.PlanetOrganize9, "Pad_09" }, + { EawCommandBarComponentIds.SpecialStructureLand0, "Planet_structure_00" }, + { EawCommandBarComponentIds.SpecialStructureLand1, "Planet_structure_01" }, + { EawCommandBarComponentIds.SpecialStructureLand2, "Planet_structure_02" }, + { EawCommandBarComponentIds.SpecialStructureLand3, "Planet_structure_03" }, + { EawCommandBarComponentIds.SpecialStructureLand4, "Planet_structure_04" }, + { EawCommandBarComponentIds.SpecialStructureLand5, "Planet_structure_05" }, + { EawCommandBarComponentIds.SpecialStructureLand6, "Planet_structure_06" }, + { EawCommandBarComponentIds.SpecialStructureLand7, "Planet_structure_07" }, + { EawCommandBarComponentIds.SpecialStructureLand8, "Planet_structure_08" }, + { EawCommandBarComponentIds.SpecialStructureLandSell, "g_ground_sell" }, + { EawCommandBarComponentIds.BigFleet0, "q1_fleet" }, + { EawCommandBarComponentIds.BigFleet1, "q2_fleet" }, + { EawCommandBarComponentIds.BigFleet2, "q3_fleet" }, + { EawCommandBarComponentIds.BigFleet3, "q4_fleet" }, + { EawCommandBarComponentIds.Fleet0Slot0, "q1_0000" }, + { EawCommandBarComponentIds.Fleet0Slot1, "q1_0001" }, + { EawCommandBarComponentIds.Fleet0Slot2, "q1_0002" }, + { EawCommandBarComponentIds.Fleet0Slot3, "q1_0003" }, + { EawCommandBarComponentIds.Fleet0Slot4, "q1_0004" }, + { EawCommandBarComponentIds.Fleet0Slot5, "q1_0005" }, + { EawCommandBarComponentIds.Fleet0Slot6, "q1_0006" }, + { EawCommandBarComponentIds.Fleet0Slot7, "q1_0100" }, + { EawCommandBarComponentIds.Fleet0Slot8, "q1_0101" }, + { EawCommandBarComponentIds.Fleet0Slot9, "q1_0102" }, + { EawCommandBarComponentIds.Fleet0Slot10, "q1_0103" }, + { EawCommandBarComponentIds.Fleet0Slot11, "q1_0104" }, + { EawCommandBarComponentIds.Fleet0Slot12, "q1_0105" }, + { EawCommandBarComponentIds.Fleet0Slot13, "q1_0106" }, + { EawCommandBarComponentIds.Fleet0Slot14, "q1_0200" }, + { EawCommandBarComponentIds.Fleet0Slot15, "q1_0201" }, + { EawCommandBarComponentIds.Fleet0Slot16, "q1_0202" }, + { EawCommandBarComponentIds.Fleet0Slot17, "q1_0203" }, + { EawCommandBarComponentIds.Fleet0Slot18, "q1_0204" }, + { EawCommandBarComponentIds.Fleet0Slot19, "q1_0205" }, + { EawCommandBarComponentIds.Fleet0Slot20, "q1_0206" }, + { EawCommandBarComponentIds.Fleet0Slot21, "q1_0300" }, + { EawCommandBarComponentIds.Fleet0Slot22, "q1_0301" }, + { EawCommandBarComponentIds.Fleet0Slot23, "q1_0302" }, + { EawCommandBarComponentIds.Fleet0Slot24, "q1_0303" }, + { EawCommandBarComponentIds.Fleet0Slot25, "q1_0304" }, + { EawCommandBarComponentIds.Fleet0Slot26, "q1_0305" }, + { EawCommandBarComponentIds.Fleet0Slot27, "q1_0306" }, + { EawCommandBarComponentIds.Fleet0Slot28, "q1_0400" }, + { EawCommandBarComponentIds.Fleet0Slot29, "q1_0401" }, + { EawCommandBarComponentIds.Fleet0Slot30, "q1_0402" }, + { EawCommandBarComponentIds.Fleet0Slot31, "q1_0403" }, + { EawCommandBarComponentIds.Fleet0Slot32, "q1_0404" }, + { EawCommandBarComponentIds.Fleet0Slot33, "q1_0405" }, + { EawCommandBarComponentIds.Fleet0Slot34, "q1_0406" }, + { EawCommandBarComponentIds.Fleet1Slot0, "q2_0000" }, + { EawCommandBarComponentIds.Fleet1Slot1, "q2_0001" }, + { EawCommandBarComponentIds.Fleet1Slot2, "q2_0002" }, + { EawCommandBarComponentIds.Fleet1Slot3, "q2_0003" }, + { EawCommandBarComponentIds.Fleet1Slot4, "q2_0004" }, + { EawCommandBarComponentIds.Fleet1Slot5, "q2_0005" }, + { EawCommandBarComponentIds.Fleet1Slot6, "q2_0006" }, + { EawCommandBarComponentIds.Fleet1Slot7, "q2_0100" }, + { EawCommandBarComponentIds.Fleet1Slot8, "q2_0101" }, + { EawCommandBarComponentIds.Fleet1Slot9, "q2_0102" }, + { EawCommandBarComponentIds.Fleet1Slot10, "q2_0103" }, + { EawCommandBarComponentIds.Fleet1Slot11, "q2_0104" }, + { EawCommandBarComponentIds.Fleet1Slot12, "q2_0105" }, + { EawCommandBarComponentIds.Fleet1Slot13, "q2_0106" }, + { EawCommandBarComponentIds.Fleet1Slot14, "q2_0200" }, + { EawCommandBarComponentIds.Fleet1Slot15, "q2_0201" }, + { EawCommandBarComponentIds.Fleet1Slot16, "q2_0202" }, + { EawCommandBarComponentIds.Fleet1Slot17, "q2_0203" }, + { EawCommandBarComponentIds.Fleet1Slot18, "q2_0204" }, + { EawCommandBarComponentIds.Fleet1Slot19, "q2_0205" }, + { EawCommandBarComponentIds.Fleet1Slot20, "q2_0206" }, + { EawCommandBarComponentIds.Fleet1Slot21, "q2_0300" }, + { EawCommandBarComponentIds.Fleet1Slot22, "q2_0301" }, + { EawCommandBarComponentIds.Fleet1Slot23, "q2_0302" }, + { EawCommandBarComponentIds.Fleet1Slot24, "q2_0303" }, + { EawCommandBarComponentIds.Fleet1Slot25, "q2_0304" }, + { EawCommandBarComponentIds.Fleet1Slot26, "q2_0305" }, + { EawCommandBarComponentIds.Fleet1Slot27, "q2_0306" }, + { EawCommandBarComponentIds.Fleet1Slot28, "q2_0400" }, + { EawCommandBarComponentIds.Fleet1Slot29, "q2_0401" }, + { EawCommandBarComponentIds.Fleet1Slot30, "q2_0402" }, + { EawCommandBarComponentIds.Fleet1Slot31, "q2_0403" }, + { EawCommandBarComponentIds.Fleet1Slot32, "q2_0404" }, + { EawCommandBarComponentIds.Fleet1Slot33, "q2_0405" }, + { EawCommandBarComponentIds.Fleet1Slot34, "q2_0406" }, + { EawCommandBarComponentIds.Fleet2Slot0, "q3_0000" }, + { EawCommandBarComponentIds.Fleet2Slot1, "q3_0001" }, + { EawCommandBarComponentIds.Fleet2Slot2, "q3_0002" }, + { EawCommandBarComponentIds.Fleet2Slot3, "q3_0003" }, + { EawCommandBarComponentIds.Fleet2Slot4, "q3_0004" }, + { EawCommandBarComponentIds.Fleet2Slot5, "q3_0005" }, + { EawCommandBarComponentIds.Fleet2Slot6, "q3_0006" }, + { EawCommandBarComponentIds.Fleet2Slot7, "q3_0100" }, + { EawCommandBarComponentIds.Fleet2Slot8, "q3_0101" }, + { EawCommandBarComponentIds.Fleet2Slot9, "q3_0102" }, + { EawCommandBarComponentIds.Fleet2Slot10, "q3_0103" }, + { EawCommandBarComponentIds.Fleet2Slot11, "q3_0104" }, + { EawCommandBarComponentIds.Fleet2Slot12, "q3_0105" }, + { EawCommandBarComponentIds.Fleet2Slot13, "q3_0106" }, + { EawCommandBarComponentIds.Fleet2Slot14, "q3_0200" }, + { EawCommandBarComponentIds.Fleet2Slot15, "q3_0201" }, + { EawCommandBarComponentIds.Fleet2Slot16, "q3_0202" }, + { EawCommandBarComponentIds.Fleet2Slot17, "q3_0203" }, + { EawCommandBarComponentIds.Fleet2Slot18, "q3_0204" }, + { EawCommandBarComponentIds.Fleet2Slot19, "q3_0205" }, + { EawCommandBarComponentIds.Fleet2Slot20, "q3_0206" }, + { EawCommandBarComponentIds.Fleet2Slot21, "q3_0300" }, + { EawCommandBarComponentIds.Fleet2Slot22, "q3_0301" }, + { EawCommandBarComponentIds.Fleet2Slot23, "q3_0302" }, + { EawCommandBarComponentIds.Fleet2Slot24, "q3_0303" }, + { EawCommandBarComponentIds.Fleet2Slot25, "q3_0304" }, + { EawCommandBarComponentIds.Fleet2Slot26, "q3_0305" }, + { EawCommandBarComponentIds.Fleet2Slot27, "q3_0306" }, + { EawCommandBarComponentIds.Fleet2Slot28, "q3_0400" }, + { EawCommandBarComponentIds.Fleet2Slot29, "q3_0401" }, + { EawCommandBarComponentIds.Fleet2Slot30, "q3_0402" }, + { EawCommandBarComponentIds.Fleet2Slot31, "q3_0403" }, + { EawCommandBarComponentIds.Fleet2Slot32, "q3_0404" }, + { EawCommandBarComponentIds.Fleet2Slot33, "q3_0405" }, + { EawCommandBarComponentIds.Fleet2Slot34, "q3_0406" }, + { EawCommandBarComponentIds.TacticalMain, "i_main_skirmish" }, + { EawCommandBarComponentIds.TacticalOptions, "b_option_t" }, + { EawCommandBarComponentIds.TacticalCamera, "b_camera_t" }, + { EawCommandBarComponentIds.TacticalBeacon, "b_beacon_t" }, + { EawCommandBarComponentIds.TacticalWeapon0, "b_special_weapon" }, + { EawCommandBarComponentIds.TacticalWeapon1, "b_special_weapon2" }, + { EawCommandBarComponentIds.TacticalReinforce, "b_reinforcement" }, + { EawCommandBarComponentIds.TacticalRetreat, "b_retreat" }, + { EawCommandBarComponentIds.TacticalStoryArc, "b_story_arc_t" }, + { EawCommandBarComponentIds.TacticalTechLevel, "Text_Tactical_Tech" }, + { EawCommandBarComponentIds.TacticalAttack, "c_button00" }, + { EawCommandBarComponentIds.TacticalAttackMove, "c_button01" }, + { EawCommandBarComponentIds.TacticalMove, "c_button02" }, + { EawCommandBarComponentIds.TacticalWaypoint, "c_button03" }, + { EawCommandBarComponentIds.TacticalStop, "c_button04" }, + { EawCommandBarComponentIds.TacticalGuard, "c_button05" }, + { EawCommandBarComponentIds.SpaceTacticalRadar, "radar" }, + { EawCommandBarComponentIds.SpaceTacticalCredits, "Text_Credits_tactical" }, + { EawCommandBarComponentIds.SpaceTacticalGrabBar, "st_grab_bar" }, + { EawCommandBarComponentIds.SpaceTacticalHealthBar, "st_health_bar" }, + { EawCommandBarComponentIds.SpaceTacticalBracketSmall, "st_bracket_small" }, + { EawCommandBarComponentIds.SpaceTacticalBracketMedium, "st_bracket_medium" }, + { EawCommandBarComponentIds.SpaceTacticalBracketLarge, "st_bracket_large" }, + { EawCommandBarComponentIds.SpaceTacticalHeroIcon, "st_hero_icon" }, + { EawCommandBarComponentIds.SpaceTacticalHeroHealth, "st_hero_health" }, + { EawCommandBarComponentIds.SpaceTacticalHealth, "st_health" }, + { EawCommandBarComponentIds.SpaceTacticalHealthMedium, "st_health_medium" }, + { EawCommandBarComponentIds.SpaceTacticalHealthLarge, "st_health_large" }, + { EawCommandBarComponentIds.SpaceTacticalShields, "st_shields" }, + { EawCommandBarComponentIds.SpaceTacticalShieldsMedium, "st_shields_medium" }, + { EawCommandBarComponentIds.SpaceTacticalShieldsLarge, "st_shields_large" }, + { EawCommandBarComponentIds.SpaceTacticalPower, "st_power" }, + { EawCommandBarComponentIds.SpaceTacticalControlGroup, "st_control_group" }, + { EawCommandBarComponentIds.SpaceTacticalAbilityIcon, "st_ability_icon" }, + { EawCommandBarComponentIds.SpaceTacticalGarrisonIcon, "st_garrison_icon" }, + { EawCommandBarComponentIds.LandTacticalWeatherIcon, "lt_weather_icon" }, + { EawCommandBarComponentIds.DsFireShell, "ds_shell" }, + { EawCommandBarComponentIds.DsFire, "deathstar_switch" }, + { EawCommandBarComponentIds.DsCountdownTimer, "DS_Countdown_Timer" }, + { EawCommandBarComponentIds.TacticalSelect00, "s_select_00" }, + { EawCommandBarComponentIds.TacticalSelect01, "s_select_01" }, + { EawCommandBarComponentIds.TacticalSelect02, "s_select_02" }, + { EawCommandBarComponentIds.TacticalSelect03, "s_select_03" }, + { EawCommandBarComponentIds.TacticalSelect04, "s_select_04" }, + { EawCommandBarComponentIds.TacticalSelect05, "s_select_05" }, + { EawCommandBarComponentIds.TacticalSelect06, "s_select_06" }, + { EawCommandBarComponentIds.TacticalSelect07, "s_select_07" }, + { EawCommandBarComponentIds.TacticalSelect08, "s_select_08" }, + { EawCommandBarComponentIds.TacticalSelect09, "s_select_09" }, + { EawCommandBarComponentIds.TacticalSelect10, "s_select_10" }, + { EawCommandBarComponentIds.TacticalSelect11, "s_select_11" }, + { EawCommandBarComponentIds.TacticalSelect12, "s_select_12" }, + { EawCommandBarComponentIds.TacticalSelect13, "s_select_13" }, + { EawCommandBarComponentIds.TacticalSelect14, "s_select_14" }, + { EawCommandBarComponentIds.TacticalSelect15, "s_select_15" }, + { EawCommandBarComponentIds.TacticalSelect16, "s_select_16" }, + { EawCommandBarComponentIds.TacticalSelect17, "s_select_17" }, + { EawCommandBarComponentIds.TacticalSelect18, "s_select_18" }, + { EawCommandBarComponentIds.TacticalSelect19, "s_select_19" }, + { EawCommandBarComponentIds.TacticalSelect20, "s_select_20" }, + { EawCommandBarComponentIds.TacticalSelect21, "s_select_21" }, + { EawCommandBarComponentIds.TacticalSelect22, "s_select_22" }, + { EawCommandBarComponentIds.TacticalSelect23, "s_select_23" }, + { EawCommandBarComponentIds.TacticalSelect24, "s_select_24" }, + { EawCommandBarComponentIds.TacticalSelect25, "s_select_25" }, + { EawCommandBarComponentIds.TacticalSelect26, "s_select_26" }, + { EawCommandBarComponentIds.TacticalSelect27, "s_select_27" }, + { EawCommandBarComponentIds.TacticalSelect28, "s_select_28" }, + { EawCommandBarComponentIds.TacticalSelect29, "s_select_29" }, + { EawCommandBarComponentIds.TacticalSelect30, "s_select_30" }, + { EawCommandBarComponentIds.TacticalSelect31, "s_select_31" }, + { EawCommandBarComponentIds.TacticalSelect32, "s_select_32" }, + { EawCommandBarComponentIds.TacticalSelect33, "s_select_33" }, + { EawCommandBarComponentIds.TacticalSelect34, "s_select_34" }, + { EawCommandBarComponentIds.TacticalSelect35, "s_select_35" }, + { EawCommandBarComponentIds.TacticalSelect36, "s_select_36" }, + { EawCommandBarComponentIds.TacticalSelect37, "s_select_37" }, + { EawCommandBarComponentIds.TacticalSelect38, "s_select_38" }, + { EawCommandBarComponentIds.TacticalSelect39, "s_select_39" }, + { EawCommandBarComponentIds.TacticalSelect40, "s_select_40" }, + { EawCommandBarComponentIds.TacticalSelect41, "s_select_41" }, + { EawCommandBarComponentIds.TacticalSelect42, "s_select_42" }, + { EawCommandBarComponentIds.TacticalSelect43, "s_select_43" }, + { EawCommandBarComponentIds.TacticalSelect44, "s_select_44" }, + { EawCommandBarComponentIds.TacticalSelect45, "s_select_45" }, + { EawCommandBarComponentIds.TacticalSelect46, "s_select_46" }, + { EawCommandBarComponentIds.TacticalSelect47, "s_select_47" }, + { EawCommandBarComponentIds.TacticalSelectHealth00, "s_health_00" }, + { EawCommandBarComponentIds.TacticalSelectHealth01, "s_health_01" }, + { EawCommandBarComponentIds.TacticalSelectHealth02, "s_health_02" }, + { EawCommandBarComponentIds.TacticalSelectHealth03, "s_health_03" }, + { EawCommandBarComponentIds.TacticalSelectHealth04, "s_health_04" }, + { EawCommandBarComponentIds.TacticalSelectHealth05, "s_health_05" }, + { EawCommandBarComponentIds.TacticalSelectHealth06, "s_health_06" }, + { EawCommandBarComponentIds.TacticalSelectHealth07, "s_health_07" }, + { EawCommandBarComponentIds.TacticalSelectHealth08, "s_health_08" }, + { EawCommandBarComponentIds.TacticalSelectHealth09, "s_health_09" }, + { EawCommandBarComponentIds.TacticalSelectHealth10, "s_health_10" }, + { EawCommandBarComponentIds.TacticalSelectHealth11, "s_health_11" }, + { EawCommandBarComponentIds.TacticalSelectHealth12, "s_health_12" }, + { EawCommandBarComponentIds.TacticalSelectHealth13, "s_health_13" }, + { EawCommandBarComponentIds.TacticalSelectHealth14, "s_health_14" }, + { EawCommandBarComponentIds.TacticalSelectHealth15, "s_health_15" }, + { EawCommandBarComponentIds.TacticalSelectHealth16, "s_health_16" }, + { EawCommandBarComponentIds.TacticalSelectHealth17, "s_health_17" }, + { EawCommandBarComponentIds.TacticalSelectHealth18, "s_health_18" }, + { EawCommandBarComponentIds.TacticalSelectHealth19, "s_health_19" }, + { EawCommandBarComponentIds.TacticalSelectHealth20, "s_health_20" }, + { EawCommandBarComponentIds.TacticalSelectHealth21, "s_health_21" }, + { EawCommandBarComponentIds.TacticalSelectHealth22, "s_health_22" }, + { EawCommandBarComponentIds.TacticalSelectHealth23, "s_health_23" }, + { EawCommandBarComponentIds.TacticalSelectHealth24, "s_health_24" }, + { EawCommandBarComponentIds.TacticalSelectHealth25, "s_health_25" }, + { EawCommandBarComponentIds.TacticalSelectHealth26, "s_health_26" }, + { EawCommandBarComponentIds.TacticalSelectHealth27, "s_health_27" }, + { EawCommandBarComponentIds.TacticalSelectHealth28, "s_health_28" }, + { EawCommandBarComponentIds.TacticalSelectHealth29, "s_health_29" }, + { EawCommandBarComponentIds.TacticalSelectHealth30, "s_health_30" }, + { EawCommandBarComponentIds.TacticalSelectHealth31, "s_health_31" }, + { EawCommandBarComponentIds.TacticalSelectHealth32, "s_health_32" }, + { EawCommandBarComponentIds.TacticalSelectHealth33, "s_health_33" }, + { EawCommandBarComponentIds.TacticalSelectHealth34, "s_health_34" }, + { EawCommandBarComponentIds.TacticalSelectHealth35, "s_health_35" }, + { EawCommandBarComponentIds.TacticalSelectHealth36, "s_health_36" }, + { EawCommandBarComponentIds.TacticalSelectHealth37, "s_health_37" }, + { EawCommandBarComponentIds.TacticalSelectHealth38, "s_health_38" }, + { EawCommandBarComponentIds.TacticalSelectHealth39, "s_health_39" }, + { EawCommandBarComponentIds.TacticalSelectHealth40, "s_health_40" }, + { EawCommandBarComponentIds.TacticalSelectHealth41, "s_health_41" }, + { EawCommandBarComponentIds.TacticalSelectHealth42, "s_health_42" }, + { EawCommandBarComponentIds.TacticalSelectHealth43, "s_health_43" }, + { EawCommandBarComponentIds.TacticalSelectHealth44, "s_health_44" }, + { EawCommandBarComponentIds.TacticalSelectHealth45, "s_health_45" }, + { EawCommandBarComponentIds.TacticalSelectHealth46, "s_health_46" }, + { EawCommandBarComponentIds.TacticalSelectHealth47, "s_health_47" }, + { EawCommandBarComponentIds.TacticalSelectShield00, "s_shield_00" }, + { EawCommandBarComponentIds.TacticalSelectShield01, "s_shield_01" }, + { EawCommandBarComponentIds.TacticalSelectShield02, "s_shield_02" }, + { EawCommandBarComponentIds.TacticalSelectShield03, "s_shield_03" }, + { EawCommandBarComponentIds.TacticalSelectShield04, "s_shield_04" }, + { EawCommandBarComponentIds.TacticalSelectShield05, "s_shield_05" }, + { EawCommandBarComponentIds.TacticalSelectShield06, "s_shield_06" }, + { EawCommandBarComponentIds.TacticalSelectShield07, "s_shield_07" }, + { EawCommandBarComponentIds.TacticalSelectShield08, "s_shield_08" }, + { EawCommandBarComponentIds.TacticalSelectShield09, "s_shield_09" }, + { EawCommandBarComponentIds.TacticalSelectShield10, "s_shield_10" }, + { EawCommandBarComponentIds.TacticalSelectShield11, "s_shield_11" }, + { EawCommandBarComponentIds.TacticalSelectShield12, "s_shield_12" }, + { EawCommandBarComponentIds.TacticalSelectShield13, "s_shield_13" }, + { EawCommandBarComponentIds.TacticalSelectShield14, "s_shield_14" }, + { EawCommandBarComponentIds.TacticalSelectShield15, "s_shield_15" }, + { EawCommandBarComponentIds.TacticalSelectShield16, "s_shield_16" }, + { EawCommandBarComponentIds.TacticalSelectShield17, "s_shield_17" }, + { EawCommandBarComponentIds.TacticalSelectShield18, "s_shield_18" }, + { EawCommandBarComponentIds.TacticalSelectShield19, "s_shield_19" }, + { EawCommandBarComponentIds.TacticalSelectShield20, "s_shield_20" }, + { EawCommandBarComponentIds.TacticalSelectShield21, "s_shield_21" }, + { EawCommandBarComponentIds.TacticalSelectShield22, "s_shield_22" }, + { EawCommandBarComponentIds.TacticalSelectShield23, "s_shield_23" }, + { EawCommandBarComponentIds.TacticalSelectShield24, "s_shield_24" }, + { EawCommandBarComponentIds.TacticalSelectShield25, "s_shield_25" }, + { EawCommandBarComponentIds.TacticalSelectShield26, "s_shield_26" }, + { EawCommandBarComponentIds.TacticalSelectShield27, "s_shield_27" }, + { EawCommandBarComponentIds.TacticalSelectShield28, "s_shield_28" }, + { EawCommandBarComponentIds.TacticalSelectShield29, "s_shield_29" }, + { EawCommandBarComponentIds.TacticalSelectShield30, "s_shield_30" }, + { EawCommandBarComponentIds.TacticalSelectShield31, "s_shield_31" }, + { EawCommandBarComponentIds.TacticalSelectShield32, "s_shield_32" }, + { EawCommandBarComponentIds.TacticalSelectShield33, "s_shield_33" }, + { EawCommandBarComponentIds.TacticalSelectShield34, "s_shield_34" }, + { EawCommandBarComponentIds.TacticalSelectShield35, "s_shield_35" }, + { EawCommandBarComponentIds.TacticalSelectShield36, "s_shield_36" }, + { EawCommandBarComponentIds.TacticalSelectShield37, "s_shield_37" }, + { EawCommandBarComponentIds.TacticalSelectShield38, "s_shield_38" }, + { EawCommandBarComponentIds.TacticalSelectShield39, "s_shield_39" }, + { EawCommandBarComponentIds.TacticalSelectShield40, "s_shield_40" }, + { EawCommandBarComponentIds.TacticalSelectShield41, "s_shield_41" }, + { EawCommandBarComponentIds.TacticalSelectShield42, "s_shield_42" }, + { EawCommandBarComponentIds.TacticalSelectShield43, "s_shield_43" }, + { EawCommandBarComponentIds.TacticalSelectShield44, "s_shield_44" }, + { EawCommandBarComponentIds.TacticalSelectShield45, "s_shield_45" }, + { EawCommandBarComponentIds.TacticalSelectShield46, "s_shield_46" }, + { EawCommandBarComponentIds.TacticalSelectShield47, "s_shield_47" }, + { EawCommandBarComponentIds.TacticalBorder00, "special_border_00" }, + { EawCommandBarComponentIds.TacticalBorder01, "special_border_01" }, + { EawCommandBarComponentIds.TacticalBorder02, "special_border_02" }, + { EawCommandBarComponentIds.TacticalBorder03, "special_border_03" }, + { EawCommandBarComponentIds.TacticalBorder04, "special_border_04" }, + { EawCommandBarComponentIds.TacticalBorder05, "special_border_05" }, + { EawCommandBarComponentIds.TacticalBorder06, "special_border_06" }, + { EawCommandBarComponentIds.TacticalBorder07, "special_border_07" }, + { EawCommandBarComponentIds.TacticalBorder08, "special_border_08" }, + { EawCommandBarComponentIds.TacticalBorder09, "special_border_09" }, + { EawCommandBarComponentIds.TacticalBorder10, "special_border_10" }, + { EawCommandBarComponentIds.TacticalSelectButton00, "special_button_00" }, + { EawCommandBarComponentIds.TacticalSelectButton01, "special_button_01" }, + { EawCommandBarComponentIds.TacticalSelectButton02, "special_button_02" }, + { EawCommandBarComponentIds.TacticalSelectButton03, "special_button_03" }, + { EawCommandBarComponentIds.TacticalSelectButton04, "special_button_04" }, + { EawCommandBarComponentIds.TacticalSelectButton05, "special_button_05" }, + { EawCommandBarComponentIds.TacticalSelectButton06, "special_button_06" }, + { EawCommandBarComponentIds.TacticalSelectButton07, "special_button_07" }, + { EawCommandBarComponentIds.TacticalSelectButton08, "special_button_08" }, + { EawCommandBarComponentIds.TacticalSelectButton09, "special_button_09" }, + { EawCommandBarComponentIds.TacticalSelectButton10, "special_button_10" }, + { EawCommandBarComponentIds.TacticalSelectButton11, "special_button_11" }, + { EawCommandBarComponentIds.TacticalSelectButton12, "special_button_12" }, + { EawCommandBarComponentIds.TacticalSelectButton13, "special_button_13" }, + { EawCommandBarComponentIds.TacticalSelectButton14, "special_button_14" }, + { EawCommandBarComponentIds.TacticalSelectButton15, "special_button_15" }, + { EawCommandBarComponentIds.TacticalSelectButton16, "special_button_16" }, + { EawCommandBarComponentIds.TacticalSelectButton17, "special_button_17" }, + { EawCommandBarComponentIds.TacticalSelectButton18, "special_button_18" }, + { EawCommandBarComponentIds.TacticalSelectButton19, "special_button_19" }, + { EawCommandBarComponentIds.TacticalSelectButton20, "special_button_20" }, + { EawCommandBarComponentIds.TacticalSelectButton21, "special_button_21" }, + { EawCommandBarComponentIds.TacticalSelectButton22, "special_button_22" }, + { EawCommandBarComponentIds.TacticalSelectButton23, "special_button_23" }, + { EawCommandBarComponentIds.TacticalSelectButton24, "special_button_24" }, + { EawCommandBarComponentIds.TacticalSelectButton25, "special_button_25" }, + { EawCommandBarComponentIds.TacticalSelectButton26, "special_button_26" }, + { EawCommandBarComponentIds.TacticalSelectButton27, "special_button_27" }, + { EawCommandBarComponentIds.TacticalSelectButton28, "special_button_28" }, + { EawCommandBarComponentIds.TacticalSelectButton29, "special_button_29" }, + { EawCommandBarComponentIds.TacticalSelectButton30, "special_button_30" }, + { EawCommandBarComponentIds.TacticalSelectButton31, "special_button_31" }, + { EawCommandBarComponentIds.TacticalSelectButton32, "special_button_32" }, + { EawCommandBarComponentIds.TacticalSelectButton33, "special_button_33" }, + { EawCommandBarComponentIds.TacticalSelectButton34, "special_button_34" }, + { EawCommandBarComponentIds.TacticalSelectButton35, "special_button_35" }, + { EawCommandBarComponentIds.TacticalSelectButton36, "special_button_36" }, + { EawCommandBarComponentIds.TacticalSelectButton37, "special_button_37" }, + { EawCommandBarComponentIds.TacticalSelectButton38, "special_button_38" }, + { EawCommandBarComponentIds.TacticalSelectButton39, "special_button_39" }, + { EawCommandBarComponentIds.TacticalSelectButton40, "special_button_40" }, + { EawCommandBarComponentIds.TacticalSelectButton41, "special_button_41" }, + { EawCommandBarComponentIds.TacticalSelectButton42, "special_button_42" }, + { EawCommandBarComponentIds.TacticalSelectButton43, "special_button_43" }, + { EawCommandBarComponentIds.TacticalSelectButton44, "special_button_44" }, + { EawCommandBarComponentIds.TacticalSelectButton45, "special_button_45" }, + { EawCommandBarComponentIds.TacticalSelectButton46, "special_button_46" }, + { EawCommandBarComponentIds.TacticalSelectButton47, "special_button_47" }, + { EawCommandBarComponentIds.TacticalBuildQueue00, "tqueue00" }, + { EawCommandBarComponentIds.TacticalBuildQueue01, "tqueue01" }, + { EawCommandBarComponentIds.TacticalBuildQueue02, "tqueue02" }, + { EawCommandBarComponentIds.TacticalBuildQueue03, "tqueue03" }, + { EawCommandBarComponentIds.TacticalBuildQueue04, "tqueue04" }, + { EawCommandBarComponentIds.TacticalBuildQueue05, "tqueue05" }, + { EawCommandBarComponentIds.TacticalBuildQueue06, "tqueue06" }, + { EawCommandBarComponentIds.TacticalBuildQueue07, "tqueue07" }, + { EawCommandBarComponentIds.TacticalBuildQueue08, "tqueue08" }, + { EawCommandBarComponentIds.TacticalBuildQueue09, "tqueue09" }, + { EawCommandBarComponentIds.TooltipBack, "tooltip_back" }, + { EawCommandBarComponentIds.TooltipName, "tooltip_name" }, + { EawCommandBarComponentIds.TooltipPrice, "tooltip_price" }, + { EawCommandBarComponentIds.TooltipIcon, "tooltip_icon" }, + { EawCommandBarComponentIds.TooltipIconLand, "tooltip_icon_land" }, + { EawCommandBarComponentIds.TooltipLeftJustified, "tooltip_left_text" }, + { EawCommandBarComponentIds.EncyclopediaBack, "encyclopedia_back" }, + { EawCommandBarComponentIds.EncyclopediaHeaderText, "encyclopedia_header_text" }, + { EawCommandBarComponentIds.EncyclopediaText, "encyclopedia_text" }, + { EawCommandBarComponentIds.EncyclopediaRightText, "encyclopedia_right_text" }, + { EawCommandBarComponentIds.EncyclopediaCenterText, "encyclopedia_center_text" }, + { EawCommandBarComponentIds.EncyclopediaIcon, "encyclopedia_icon" }, + { EawCommandBarComponentIds.EncyclopediaCostText, "encyclopedia_cost_text" }, + { EawCommandBarComponentIds.ZoomedBack, "zoomed_back" }, + { EawCommandBarComponentIds.ZoomedHeaderText, "zoomed_header_text" }, + { EawCommandBarComponentIds.ZoomedText, "zoomed_text" }, + { EawCommandBarComponentIds.ZoomedRightText, "zoomed_right_text" }, + { EawCommandBarComponentIds.ZoomedCenterText, "zoomed_center_text" }, + { EawCommandBarComponentIds.ZoomedCostText, "zoomed_cost_text" }, + { EawCommandBarComponentIds.GPlanetFleet, "g_planet_fleet" }, + { EawCommandBarComponentIds.GPlanetName, "g_planet_name" }, + { EawCommandBarComponentIds.GPlanetValue, "g_planet_value" }, + { EawCommandBarComponentIds.GPoliticalControl, "g_political_control" }, + { EawCommandBarComponentIds.GSpaceLevel, "g_space_level" }, + { EawCommandBarComponentIds.GSpaceIcon, "g_space_icon" }, + { EawCommandBarComponentIds.GSpaceLevelPips, "g_space_level_pips" }, + { EawCommandBarComponentIds.GGroundLevel, "g_ground_level" }, + { EawCommandBarComponentIds.GGroundIcon, "g_ground_icon" }, + { EawCommandBarComponentIds.GGroundLevelPips, "g_ground_level_pips" }, + { EawCommandBarComponentIds.GConflict, "g_conflict" }, + { EawCommandBarComponentIds.GHero, "g_hero" }, + { EawCommandBarComponentIds.GEnemyHero, "g_enemy_hero" }, + { EawCommandBarComponentIds.GBuild, "g_build" }, + { EawCommandBarComponentIds.GSmuggler, "g_smuggler" }, + { EawCommandBarComponentIds.GBountyHunter, "g_bounty_hunter" }, + { EawCommandBarComponentIds.GPlanetLandForces, "g_planet_land_forces" }, + { EawCommandBarComponentIds.GGalacticRadarBlip, "g_radar_blip" }, + { EawCommandBarComponentIds.GGalacticRadarView, "g_radar_view" }, + { EawCommandBarComponentIds.GSmuggled, "g_smuggled" }, + { EawCommandBarComponentIds.GSpecialAbility, "g_special_ability" }, + { EawCommandBarComponentIds.GHeroIcon, "g_hero_icon" }, + { EawCommandBarComponentIds.GPlanetRing, "g_planet_ring" }, + { EawCommandBarComponentIds.GWeather, "g_weather" }, + { EawCommandBarComponentIds.GPlanetAbility, "g_planet_ability" }, + { EawCommandBarComponentIds.TutorialText, "tutorial_text" }, + { EawCommandBarComponentIds.TutorialTextBack, "tutorial_text_back" }, + { EawCommandBarComponentIds.RadarBlip, "radar_blip" }, + { EawCommandBarComponentIds.TacticalBuildButtonShell, "i_build_buttons" }, + { EawCommandBarComponentIds.TacticalBuildButton0, "Build_00" }, + { EawCommandBarComponentIds.TacticalBuildButton1, "Build_01" }, + { EawCommandBarComponentIds.TacticalBuildButton2, "Build_02" }, + { EawCommandBarComponentIds.TacticalBuildButton3, "Build_03" }, + { EawCommandBarComponentIds.TacticalBuildButton4, "Build_04" }, + { EawCommandBarComponentIds.TacticalBuildButton5, "Build_05" }, + { EawCommandBarComponentIds.TacticalSellButton, "tactical_sell" }, + { EawCommandBarComponentIds.ReinforcementShell, "i_main_reinforce" }, + { EawCommandBarComponentIds.ReinforcementCancel, "r_close" }, + { EawCommandBarComponentIds.ReinforcementSlot00, "r_0000" }, + { EawCommandBarComponentIds.ReinforcementSlot01, "r_0001" }, + { EawCommandBarComponentIds.ReinforcementSlot02, "r_0002" }, + { EawCommandBarComponentIds.ReinforcementSlot03, "r_0100" }, + { EawCommandBarComponentIds.ReinforcementSlot04, "r_0101" }, + { EawCommandBarComponentIds.ReinforcementSlot05, "r_0102" }, + { EawCommandBarComponentIds.ReinforcementSlot06, "r_0200" }, + { EawCommandBarComponentIds.ReinforcementSlot07, "r_0201" }, + { EawCommandBarComponentIds.ReinforcementSlot08, "r_0202" }, + { EawCommandBarComponentIds.ReinforcementSlot09, "r_0300" }, + { EawCommandBarComponentIds.ReinforcementSlot10, "r_0301" }, + { EawCommandBarComponentIds.ReinforcementSlot11, "r_0302" }, + { EawCommandBarComponentIds.ReinforcementSlot12, "r_0400" }, + { EawCommandBarComponentIds.ReinforcementSlot13, "r_0401" }, + { EawCommandBarComponentIds.ReinforcementSlot14, "r_0402" }, + { EawCommandBarComponentIds.ReinforcementCap2, "r_pop_icon" }, + { EawCommandBarComponentIds.ReinforcementCap2Text, "r_pop_text" }, + { EawCommandBarComponentIds.ReinforcementCounter, "reinforcement_counter" }, + { EawCommandBarComponentIds.GarrisonRespawnCounter, "garrison_respawn_counter" }, + { EawCommandBarComponentIds.SkirmishUpgrade, "skirmish_upgrade" }, + { EawCommandBarComponentIds.PendingBattleShell, "pending_battle_shell" }, + { EawCommandBarComponentIds.PendingBattleText, "text_attack_choice" }, + { EawCommandBarComponentIds.PendingBattleButton, "choice_button_left" }, + { EawCommandBarComponentIds.PendingBattleAutoresolve, "choice_button_right" }, + { EawCommandBarComponentIds.PendingBattleGraphLeft, "graph_left" }, + { EawCommandBarComponentIds.PendingBattleGraphRight, "graph_right" }, + { EawCommandBarComponentIds.TatcicalAutoresolveShell, "autoresolve_shell" }, + { EawCommandBarComponentIds.TacticalAutoresolveButton, "resolve_button" }, + { EawCommandBarComponentIds.TacticalAutoresolveGraphLeft, "graph_left_resolve" }, + { EawCommandBarComponentIds.TacticalAutoresolveGraphRight, "graph_right_resolve" }, + { EawCommandBarComponentIds.ObjectiveBack, "objective_back" }, + { EawCommandBarComponentIds.ObjectiveHeaderText, "objective_header_text" }, + { EawCommandBarComponentIds.ObjectiveText, "objective_text" }, + { EawCommandBarComponentIds.ObjectiveIcon, "objective_icon" }, + { EawCommandBarComponentIds.GuiDialogTooltip, "gui_dialog_tooltip" }, + { EawCommandBarComponentIds.TargetUnitTypeShell, "target_type_back" }, + { EawCommandBarComponentIds.TargetUnitTypeTitle, "s_title" }, + { EawCommandBarComponentIds.TargetUnitTypeCancel, "s_close" }, + { EawCommandBarComponentIds.TargetUnitTypeDescription, "text_steal" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot00, "s_0000" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot01, "s_0001" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot02, "s_0002" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot03, "s_0003" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot04, "s_0100" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot05, "s_0101" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot06, "s_0102" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot07, "s_0103" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot08, "s_0200" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot09, "s_0201" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot10, "s_0202" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot11, "s_0203" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot12, "s_0300" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot13, "s_0301" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot14, "s_0302" }, + { EawCommandBarComponentIds.TargetUnitTypeSlot15, "s_0303" }, + { EawCommandBarComponentIds.VcrButtonPlayPause, "b_play_pause" }, + { EawCommandBarComponentIds.VcrButtonFastForward, "b_fast_forward" }, + { EawCommandBarComponentIds.VcrButtonFastForwardTactical, "b_fast_forward_t" }, + { EawCommandBarComponentIds.VcrButtonPlayPauseTactical, "b_play_pause_t" }, + { EawCommandBarComponentIds.AdvisorHintPopupGalactic, "text_galactic_help" }, + { EawCommandBarComponentIds.AdvisorHintPopupTactical, "text_tactical_help" }, + { EawCommandBarComponentIds.AdvisorHintBack, "help_back" }, + { EawCommandBarComponentIds.TextOrganizeFleet00, "text_fleet1" }, + { EawCommandBarComponentIds.TextOrganizeFleet01, "text_fleet2" }, + { EawCommandBarComponentIds.TextOrganizeFleet02, "text_fleet3" }, + { EawCommandBarComponentIds.IconOrganizeFleet00, "icon_fleet01" }, + { EawCommandBarComponentIds.IconOrganizeFleet01, "icon_fleet02" }, + { EawCommandBarComponentIds.IconOrganizeFleet02, "icon_fleet03" }, + { EawCommandBarComponentIds.TextOrganizeLandFleet, "Text_Land_Fleet" }, + { EawCommandBarComponentIds.CsAbilityButton, "cs_ability_button" }, + { EawCommandBarComponentIds.CsAbilityText, "cs_ability_text" }, + { EawCommandBarComponentIds.MovieBoneGalactic, "Movie_galactic" }, + { EawCommandBarComponentIds.MovieBoneTactical, "Movie_tactical" }, + { EawCommandBarComponentIds.GenericCollision, "generic_collision" }, + { EawCommandBarComponentIds.GoodHeroShell, "good_hero_frame" }, + { EawCommandBarComponentIds.GoodHeroSlot00, "hero_slot_good00" }, + { EawCommandBarComponentIds.GoodHeroSlot01, "hero_slot_good01" }, + { EawCommandBarComponentIds.GoodHeroSlot02, "hero_slot_good02" }, + { EawCommandBarComponentIds.GoodHeroSlot03, "hero_slot_good03" }, + { EawCommandBarComponentIds.GoodHeroSlot04, "hero_slot_good04" }, + { EawCommandBarComponentIds.GoodHeroSlot05, "hero_slot_good05" }, + { EawCommandBarComponentIds.GoodHeroSlot06, "hero_slot_good06" }, + { EawCommandBarComponentIds.GoodHeroSlot07, "hero_slot_good07" }, + { EawCommandBarComponentIds.GoodHeroSlot08, "hero_slot_good08" }, + { EawCommandBarComponentIds.GoodHeroSlot09, "hero_slot_good09" }, + { EawCommandBarComponentIds.GoodHeroSlot10, "hero_slot_good10" }, + { EawCommandBarComponentIds.GoodHeroHealth00, "hero_hb_good00" }, + { EawCommandBarComponentIds.GoodHeroHealth01, "hero_hb_good01" }, + { EawCommandBarComponentIds.GoodHeroHealth02, "hero_hb_good02" }, + { EawCommandBarComponentIds.GoodHeroHealth03, "hero_hb_good03" }, + { EawCommandBarComponentIds.GoodHeroHealth04, "hero_hb_good04" }, + { EawCommandBarComponentIds.GoodHeroHealth05, "hero_hb_good05" }, + { EawCommandBarComponentIds.GoodHeroHealth06, "hero_hb_good06" }, + { EawCommandBarComponentIds.GoodHeroHealth07, "hero_hb_good07" }, + { EawCommandBarComponentIds.GoodHeroHealth08, "hero_hb_good08" }, + { EawCommandBarComponentIds.GoodHeroHealth09, "hero_hb_good09" }, + { EawCommandBarComponentIds.GoodHeroHealth10, "hero_hb_good10" }, + { EawCommandBarComponentIds.EvilHeroShell, "evil_hero_frame" }, + { EawCommandBarComponentIds.EvilHeroSlot00, "hero_slot_evil00" }, + { EawCommandBarComponentIds.EvilHeroSlot01, "hero_slot_evil01" }, + { EawCommandBarComponentIds.EvilHeroSlot02, "hero_slot_evil02" }, + { EawCommandBarComponentIds.EvilHeroSlot03, "hero_slot_evil03" }, + { EawCommandBarComponentIds.EvilHeroSlot04, "hero_slot_evil04" }, + { EawCommandBarComponentIds.EvilHeroSlot05, "hero_slot_evil05" }, + { EawCommandBarComponentIds.EvilHeroSlot06, "hero_slot_evil06" }, + { EawCommandBarComponentIds.EvilHeroSlot07, "hero_slot_evil07" }, + { EawCommandBarComponentIds.EvilHeroSlot08, "hero_slot_evil08" }, + { EawCommandBarComponentIds.EvilHeroSlot09, "hero_slot_evil09" }, + { EawCommandBarComponentIds.EvilHeroSlot10, "hero_slot_evil10" }, + { EawCommandBarComponentIds.EvilHeroHealth00, "hero_hb_evil00" }, + { EawCommandBarComponentIds.EvilHeroHealth01, "hero_hb_evil01" }, + { EawCommandBarComponentIds.EvilHeroHealth02, "hero_hb_evil02" }, + { EawCommandBarComponentIds.EvilHeroHealth03, "hero_hb_evil03" }, + { EawCommandBarComponentIds.EvilHeroHealth04, "hero_hb_evil04" }, + { EawCommandBarComponentIds.EvilHeroHealth05, "hero_hb_evil05" }, + { EawCommandBarComponentIds.EvilHeroHealth06, "hero_hb_evil06" }, + { EawCommandBarComponentIds.EvilHeroHealth07, "hero_hb_evil07" }, + { EawCommandBarComponentIds.EvilHeroHealth08, "hero_hb_evil08" }, + { EawCommandBarComponentIds.EvilHeroHealth09, "hero_hb_evil09" }, + { EawCommandBarComponentIds.EvilHeroHealth10, "hero_hb_evil10" }, + { EawCommandBarComponentIds.PauseShell, "pause_shell" }, + { EawCommandBarComponentIds.PauseText, "text_attack" }, + { EawCommandBarComponentIds.PauseButton, "attack_button" }, + { EawCommandBarComponentIds.StoryCampaignPendingBattleShell, "story_pending_battle_shell" }, + { EawCommandBarComponentIds.StoryCampaignPendingBattleText, "text_attack_single" }, + { EawCommandBarComponentIds.StoryCampaignPendingBattleButton, "attack_button_single" }, + }; + + private static readonly IReadOnlyDictionary FocSupportedComponents = + new Dictionary + { + { FocCommandBarComponentIds.MainShell, "i_main_commandbar" }, + { FocCommandBarComponentIds.ProductionOptions, "b_option_g" }, + { FocCommandBarComponentIds.GalacticCamera, "b_camera_g" }, + { FocCommandBarComponentIds.PlanetName, "Text_Planet" }, + { FocCommandBarComponentIds.PlanetValue, "Text_Planetary_Income" }, + { FocCommandBarComponentIds.PlanetAffiliation, "Text_Allegiance_A" }, + { FocCommandBarComponentIds.PlayerCredits, "Text_Credits" }, + { FocCommandBarComponentIds.ReinforcementCap, "Text_Planetary_Pop" }, + { FocCommandBarComponentIds.PlanetNameTactical, "Text_Planet_tactical" }, + { FocCommandBarComponentIds.PlanetInfo, "planet_info" }, + { FocCommandBarComponentIds.Production0, "b_Create_00" }, + { FocCommandBarComponentIds.Production1, "b_Create_01" }, + { FocCommandBarComponentIds.Production2, "b_Create_02" }, + { FocCommandBarComponentIds.Production3, "b_Create_03" }, + { FocCommandBarComponentIds.Production4, "b_Create_04" }, + { FocCommandBarComponentIds.Production5, "b_Create_05" }, + { FocCommandBarComponentIds.Production6, "b_Create_06" }, + { FocCommandBarComponentIds.Production7, "b_Create_07" }, + { FocCommandBarComponentIds.Production8, "b_Create_08" }, + { FocCommandBarComponentIds.Production9, "b_Create_09" }, + { FocCommandBarComponentIds.Production10, "b_Create_10" }, + { FocCommandBarComponentIds.Production11, "b_Create_11" }, + { FocCommandBarComponentIds.Production12, "b_Create_12" }, + { FocCommandBarComponentIds.Production13, "b_Create_13" }, + { FocCommandBarComponentIds.Production14, "b_Create_14" }, + { FocCommandBarComponentIds.Production15, "b_Create_15" }, + { FocCommandBarComponentIds.Production16, "b_Create_16" }, + { FocCommandBarComponentIds.Production17, "b_Create_17" }, + { FocCommandBarComponentIds.Production18, "b_Create_18" }, + { FocCommandBarComponentIds.Production19, "b_Create_19" }, + { FocCommandBarComponentIds.Production20, "b_Create_20" }, + { FocCommandBarComponentIds.Production21, "b_Create_21" }, + { FocCommandBarComponentIds.Production22, "b_Create_22" }, + { FocCommandBarComponentIds.Production23, "b_Create_23" }, + { FocCommandBarComponentIds.Production24, "b_Create_24" }, + { FocCommandBarComponentIds.Production25, "b_Create_25" }, + { FocCommandBarComponentIds.Production26, "b_Create_26" }, + { FocCommandBarComponentIds.Production27, "b_Create_27" }, + { FocCommandBarComponentIds.Production28, "b_Create_28" }, + { FocCommandBarComponentIds.Production29, "b_Create_29" }, + { FocCommandBarComponentIds.Production30, "b_Create_30" }, + { FocCommandBarComponentIds.Production31, "b_Create_31" }, + { FocCommandBarComponentIds.Production32, "b_Create_32" }, + { FocCommandBarComponentIds.Production33, "b_Create_33" }, + { FocCommandBarComponentIds.Production34, "b_Create_34" }, + { FocCommandBarComponentIds.Production35, "b_Create_35" }, + { FocCommandBarComponentIds.Production36, "b_Create_36" }, + { FocCommandBarComponentIds.Production37, "b_Create_37" }, + { FocCommandBarComponentIds.Production38, "b_Create_38" }, + { FocCommandBarComponentIds.Production39, "b_Create_39" }, + { FocCommandBarComponentIds.Production40, "b_Create_40" }, + { FocCommandBarComponentIds.Production41, "b_Create_41" }, + { FocCommandBarComponentIds.Production42, "b_Create_42" }, + { FocCommandBarComponentIds.Production43, "b_Create_43" }, + { FocCommandBarComponentIds.Production44, "b_Create_44" }, + { FocCommandBarComponentIds.Production45, "b_Create_45" }, + { FocCommandBarComponentIds.Production46, "b_Create_46" }, + { FocCommandBarComponentIds.Production47, "b_Create_47" }, + { FocCommandBarComponentIds.DroidHelp, "b_droid_help" }, + { FocCommandBarComponentIds.DroidHelpTactical, "b_droid_help_tactical" }, + { FocCommandBarComponentIds.CurrentDay, "Text_Day_Counter" }, + { FocCommandBarComponentIds.DayCredits, "Text_Day_Credits" }, + { FocCommandBarComponentIds.PopulationCap, "Text_Pop_Cap" }, + { FocCommandBarComponentIds.Filter0, "b_filters0" }, + { FocCommandBarComponentIds.Filter1, "b_filters1" }, + { FocCommandBarComponentIds.Filter2, "b_filters2" }, + { FocCommandBarComponentIds.Filter3, "b_filters3" }, + { FocCommandBarComponentIds.StoryArcButton, "b_story_arc_g" }, + { FocCommandBarComponentIds.PlanetSummaryButton, "b_quick_ref" }, + { FocCommandBarComponentIds.SpaceTab, "b_space_tab" }, + { FocCommandBarComponentIds.LandTab, "b_planet_tab" }, + { FocCommandBarComponentIds.Dial, "b_dial" }, + { FocCommandBarComponentIds.ScrollRight, "b_scroll_right" }, + { FocCommandBarComponentIds.ScrollLeft, "b_scroll_left" }, + { FocCommandBarComponentIds.ZoomView, "b_zoom" }, + { FocCommandBarComponentIds.PrevPlanet, "b_planet_left" }, + { FocCommandBarComponentIds.NextPlanet, "b_planet_right" }, + { FocCommandBarComponentIds.RadarGalactic, "radar_galactic" }, + { FocCommandBarComponentIds.TechLevel, "Text_Tech_Level" }, + { FocCommandBarComponentIds.BalancePip, "balance_pip" }, + { FocCommandBarComponentIds.BuildQueue00, "queue00" }, + { FocCommandBarComponentIds.BuildQueue01, "queue01" }, + { FocCommandBarComponentIds.BuildQueue02, "queue02" }, + { FocCommandBarComponentIds.BuildQueue03, "queue03" }, + { FocCommandBarComponentIds.BuildQueue04, "queue04" }, + { FocCommandBarComponentIds.BuildQueue05, "queue05" }, + { FocCommandBarComponentIds.BuildQueue06, "queue06" }, + { FocCommandBarComponentIds.BuildQueue07, "queue07" }, + { FocCommandBarComponentIds.BuildQueue08, "queue08" }, + { FocCommandBarComponentIds.BuildQueue09, "queue09" }, + { FocCommandBarComponentIds.OrganizationShell, "i_main_organize" }, + { FocCommandBarComponentIds.OrganizationCollision, "land_art_ALT0" }, + { FocCommandBarComponentIds.SmugglerBox, "b_underground" }, + { FocCommandBarComponentIds.SpaceStationUpgrade01, "space_Pad_1" }, + { FocCommandBarComponentIds.SpaceStationUpgrade02, "space_Pad_02" }, + { FocCommandBarComponentIds.HeroAbilitySlot, "Slot_00" }, + { FocCommandBarComponentIds.PlanetOrganize0, "Pad_00" }, + { FocCommandBarComponentIds.PlanetOrganize1, "Pad_01" }, + { FocCommandBarComponentIds.PlanetOrganize2, "Pad_02" }, + { FocCommandBarComponentIds.PlanetOrganize3, "Pad_03" }, + { FocCommandBarComponentIds.PlanetOrganize4, "Pad_04" }, + { FocCommandBarComponentIds.PlanetOrganize5, "Pad_05" }, + { FocCommandBarComponentIds.PlanetOrganize6, "Pad_06" }, + { FocCommandBarComponentIds.PlanetOrganize7, "Pad_07" }, + { FocCommandBarComponentIds.PlanetOrganize8, "Pad_08" }, + { FocCommandBarComponentIds.PlanetOrganize9, "Pad_09" }, + { FocCommandBarComponentIds.SpecialStructureLand0, "Planet_structure_00" }, + { FocCommandBarComponentIds.SpecialStructureLand1, "Planet_structure_01" }, + { FocCommandBarComponentIds.SpecialStructureLand2, "Planet_structure_02" }, + { FocCommandBarComponentIds.SpecialStructureLand3, "Planet_structure_03" }, + { FocCommandBarComponentIds.SpecialStructureLand4, "Planet_structure_04" }, + { FocCommandBarComponentIds.SpecialStructureLand5, "Planet_structure_05" }, + { FocCommandBarComponentIds.SpecialStructureLand6, "Planet_structure_06" }, + { FocCommandBarComponentIds.SpecialStructureLand7, "Planet_structure_07" }, + { FocCommandBarComponentIds.SpecialStructureLand8, "Planet_structure_08" }, + { FocCommandBarComponentIds.SpecialStructureLandSell, "g_ground_sell" }, + { FocCommandBarComponentIds.BigFleet0, "q1_fleet" }, + { FocCommandBarComponentIds.BigFleet1, "q2_fleet" }, + { FocCommandBarComponentIds.BigFleet2, "q3_fleet" }, + { FocCommandBarComponentIds.BigFleet3, "q4_fleet" }, + { FocCommandBarComponentIds.Fleet0Slot0, "q1_0000" }, + { FocCommandBarComponentIds.Fleet0Slot1, "q1_0001" }, + { FocCommandBarComponentIds.Fleet0Slot2, "q1_0002" }, + { FocCommandBarComponentIds.Fleet0Slot3, "q1_0003" }, + { FocCommandBarComponentIds.Fleet0Slot4, "q1_0004" }, + { FocCommandBarComponentIds.Fleet0Slot5, "q1_0005" }, + { FocCommandBarComponentIds.Fleet0Slot6, "q1_0006" }, + { FocCommandBarComponentIds.Fleet0Slot7, "q1_0100" }, + { FocCommandBarComponentIds.Fleet0Slot8, "q1_0101" }, + { FocCommandBarComponentIds.Fleet0Slot9, "q1_0102" }, + { FocCommandBarComponentIds.Fleet0Slot10, "q1_0103" }, + { FocCommandBarComponentIds.Fleet0Slot11, "q1_0104" }, + { FocCommandBarComponentIds.Fleet0Slot12, "q1_0105" }, + { FocCommandBarComponentIds.Fleet0Slot13, "q1_0106" }, + { FocCommandBarComponentIds.Fleet0Slot14, "q1_0200" }, + { FocCommandBarComponentIds.Fleet0Slot15, "q1_0201" }, + { FocCommandBarComponentIds.Fleet0Slot16, "q1_0202" }, + { FocCommandBarComponentIds.Fleet0Slot17, "q1_0203" }, + { FocCommandBarComponentIds.Fleet0Slot18, "q1_0204" }, + { FocCommandBarComponentIds.Fleet0Slot19, "q1_0205" }, + { FocCommandBarComponentIds.Fleet0Slot20, "q1_0206" }, + { FocCommandBarComponentIds.Fleet0Slot21, "q1_0300" }, + { FocCommandBarComponentIds.Fleet0Slot22, "q1_0301" }, + { FocCommandBarComponentIds.Fleet0Slot23, "q1_0302" }, + { FocCommandBarComponentIds.Fleet0Slot24, "q1_0303" }, + { FocCommandBarComponentIds.Fleet0Slot25, "q1_0304" }, + { FocCommandBarComponentIds.Fleet0Slot26, "q1_0305" }, + { FocCommandBarComponentIds.Fleet0Slot27, "q1_0306" }, + { FocCommandBarComponentIds.Fleet0Slot28, "q1_0400" }, + { FocCommandBarComponentIds.Fleet0Slot29, "q1_0401" }, + { FocCommandBarComponentIds.Fleet0Slot30, "q1_0402" }, + { FocCommandBarComponentIds.Fleet0Slot31, "q1_0403" }, + { FocCommandBarComponentIds.Fleet0Slot32, "q1_0404" }, + { FocCommandBarComponentIds.Fleet0Slot33, "q1_0405" }, + { FocCommandBarComponentIds.Fleet0Slot34, "q1_0406" }, + { FocCommandBarComponentIds.Fleet1Slot0, "q2_0000" }, + { FocCommandBarComponentIds.Fleet1Slot1, "q2_0001" }, + { FocCommandBarComponentIds.Fleet1Slot2, "q2_0002" }, + { FocCommandBarComponentIds.Fleet1Slot3, "q2_0003" }, + { FocCommandBarComponentIds.Fleet1Slot4, "q2_0004" }, + { FocCommandBarComponentIds.Fleet1Slot5, "q2_0005" }, + { FocCommandBarComponentIds.Fleet1Slot6, "q2_0006" }, + { FocCommandBarComponentIds.Fleet1Slot7, "q2_0100" }, + { FocCommandBarComponentIds.Fleet1Slot8, "q2_0101" }, + { FocCommandBarComponentIds.Fleet1Slot9, "q2_0102" }, + { FocCommandBarComponentIds.Fleet1Slot10, "q2_0103" }, + { FocCommandBarComponentIds.Fleet1Slot11, "q2_0104" }, + { FocCommandBarComponentIds.Fleet1Slot12, "q2_0105" }, + { FocCommandBarComponentIds.Fleet1Slot13, "q2_0106" }, + { FocCommandBarComponentIds.Fleet1Slot14, "q2_0200" }, + { FocCommandBarComponentIds.Fleet1Slot15, "q2_0201" }, + { FocCommandBarComponentIds.Fleet1Slot16, "q2_0202" }, + { FocCommandBarComponentIds.Fleet1Slot17, "q2_0203" }, + { FocCommandBarComponentIds.Fleet1Slot18, "q2_0204" }, + { FocCommandBarComponentIds.Fleet1Slot19, "q2_0205" }, + { FocCommandBarComponentIds.Fleet1Slot20, "q2_0206" }, + { FocCommandBarComponentIds.Fleet1Slot21, "q2_0300" }, + { FocCommandBarComponentIds.Fleet1Slot22, "q2_0301" }, + { FocCommandBarComponentIds.Fleet1Slot23, "q2_0302" }, + { FocCommandBarComponentIds.Fleet1Slot24, "q2_0303" }, + { FocCommandBarComponentIds.Fleet1Slot25, "q2_0304" }, + { FocCommandBarComponentIds.Fleet1Slot26, "q2_0305" }, + { FocCommandBarComponentIds.Fleet1Slot27, "q2_0306" }, + { FocCommandBarComponentIds.Fleet1Slot28, "q2_0400" }, + { FocCommandBarComponentIds.Fleet1Slot29, "q2_0401" }, + { FocCommandBarComponentIds.Fleet1Slot30, "q2_0402" }, + { FocCommandBarComponentIds.Fleet1Slot31, "q2_0403" }, + { FocCommandBarComponentIds.Fleet1Slot32, "q2_0404" }, + { FocCommandBarComponentIds.Fleet1Slot33, "q2_0405" }, + { FocCommandBarComponentIds.Fleet1Slot34, "q2_0406" }, + { FocCommandBarComponentIds.Fleet2Slot0, "q3_0000" }, + { FocCommandBarComponentIds.Fleet2Slot1, "q3_0001" }, + { FocCommandBarComponentIds.Fleet2Slot2, "q3_0002" }, + { FocCommandBarComponentIds.Fleet2Slot3, "q3_0003" }, + { FocCommandBarComponentIds.Fleet2Slot4, "q3_0004" }, + { FocCommandBarComponentIds.Fleet2Slot5, "q3_0005" }, + { FocCommandBarComponentIds.Fleet2Slot6, "q3_0006" }, + { FocCommandBarComponentIds.Fleet2Slot7, "q3_0100" }, + { FocCommandBarComponentIds.Fleet2Slot8, "q3_0101" }, + { FocCommandBarComponentIds.Fleet2Slot9, "q3_0102" }, + { FocCommandBarComponentIds.Fleet2Slot10, "q3_0103" }, + { FocCommandBarComponentIds.Fleet2Slot11, "q3_0104" }, + { FocCommandBarComponentIds.Fleet2Slot12, "q3_0105" }, + { FocCommandBarComponentIds.Fleet2Slot13, "q3_0106" }, + { FocCommandBarComponentIds.Fleet2Slot14, "q3_0200" }, + { FocCommandBarComponentIds.Fleet2Slot15, "q3_0201" }, + { FocCommandBarComponentIds.Fleet2Slot16, "q3_0202" }, + { FocCommandBarComponentIds.Fleet2Slot17, "q3_0203" }, + { FocCommandBarComponentIds.Fleet2Slot18, "q3_0204" }, + { FocCommandBarComponentIds.Fleet2Slot19, "q3_0205" }, + { FocCommandBarComponentIds.Fleet2Slot20, "q3_0206" }, + { FocCommandBarComponentIds.Fleet2Slot21, "q3_0300" }, + { FocCommandBarComponentIds.Fleet2Slot22, "q3_0301" }, + { FocCommandBarComponentIds.Fleet2Slot23, "q3_0302" }, + { FocCommandBarComponentIds.Fleet2Slot24, "q3_0303" }, + { FocCommandBarComponentIds.Fleet2Slot25, "q3_0304" }, + { FocCommandBarComponentIds.Fleet2Slot26, "q3_0305" }, + { FocCommandBarComponentIds.Fleet2Slot27, "q3_0306" }, + { FocCommandBarComponentIds.Fleet2Slot28, "q3_0400" }, + { FocCommandBarComponentIds.Fleet2Slot29, "q3_0401" }, + { FocCommandBarComponentIds.Fleet2Slot30, "q3_0402" }, + { FocCommandBarComponentIds.Fleet2Slot31, "q3_0403" }, + { FocCommandBarComponentIds.Fleet2Slot32, "q3_0404" }, + { FocCommandBarComponentIds.Fleet2Slot33, "q3_0405" }, + { FocCommandBarComponentIds.Fleet2Slot34, "q3_0406" }, + { FocCommandBarComponentIds.TacticalMain, "i_main_skirmish" }, + { FocCommandBarComponentIds.TacticalOptions, "b_option_t" }, + { FocCommandBarComponentIds.TacticalCamera, "b_camera_t" }, + { FocCommandBarComponentIds.TacticalBeacon, "b_beacon_t" }, + { FocCommandBarComponentIds.TacticalWeapon0, "b_special_weapon" }, + { FocCommandBarComponentIds.TacticalWeapon1, "b_special_weapon2" }, + { FocCommandBarComponentIds.TacticalReinforce, "b_reinforcement" }, + { FocCommandBarComponentIds.TacticalRetreat, "b_retreat" }, + { FocCommandBarComponentIds.TacticalStoryArc, "b_story_arc_t" }, + { FocCommandBarComponentIds.TacticalTechLevel, "Text_Tactical_Tech" }, + { FocCommandBarComponentIds.TacticalAttack, "c_button00" }, + { FocCommandBarComponentIds.TacticalAttackMove, "c_button01" }, + { FocCommandBarComponentIds.TacticalMove, "c_button02" }, + { FocCommandBarComponentIds.TacticalWaypoint, "c_button03" }, + { FocCommandBarComponentIds.TacticalStop, "c_button04" }, + { FocCommandBarComponentIds.TacticalGuard, "c_button05" }, + { FocCommandBarComponentIds.SpaceTacticalRadar, "radar" }, + { FocCommandBarComponentIds.SpaceTacticalCredits, "Text_Credits_tactical" }, + { FocCommandBarComponentIds.SpaceTacticalGrabBar, "st_grab_bar" }, + { FocCommandBarComponentIds.SpaceTacticalHealthBar, "st_health_bar" }, + { FocCommandBarComponentIds.SpaceTacticalBracketSmall, "st_bracket_small" }, + { FocCommandBarComponentIds.SpaceTacticalBracketMedium, "st_bracket_medium" }, + { FocCommandBarComponentIds.SpaceTacticalBracketLarge, "st_bracket_large" }, + { FocCommandBarComponentIds.SpaceTacticalHeroIcon, "st_hero_icon" }, + { FocCommandBarComponentIds.SpaceTacticalHeroHealth, "st_hero_health" }, + { FocCommandBarComponentIds.SpaceTacticalHealth, "st_health" }, + { FocCommandBarComponentIds.SpaceTacticalHealthMedium, "st_health_medium" }, + { FocCommandBarComponentIds.SpaceTacticalHealthLarge, "st_health_large" }, + { FocCommandBarComponentIds.SpaceTacticalShields, "st_shields" }, + { FocCommandBarComponentIds.SpaceTacticalShieldsMedium, "st_shields_medium" }, + { FocCommandBarComponentIds.SpaceTacticalShieldsLarge, "st_shields_large" }, + { FocCommandBarComponentIds.SpaceTacticalPower, "st_power" }, + { FocCommandBarComponentIds.SpaceTacticalControlGroup, "st_control_group" }, + { FocCommandBarComponentIds.SpaceTacticalAbilityIcon, "st_ability_icon" }, + { FocCommandBarComponentIds.SpaceTacticalGarrisonIcon, "st_garrison_icon" }, + { FocCommandBarComponentIds.LandTacticalWeatherIcon, "lt_weather_icon" }, + { FocCommandBarComponentIds.GarrisonSlotIcon, "garrison_slot_icon" }, + { FocCommandBarComponentIds.DsFireShell, "ds_shell" }, + { FocCommandBarComponentIds.DsFire, "deathstar_switch" }, + { FocCommandBarComponentIds.DsCountdownTimer, "DS_Countdown_Timer" }, + { FocCommandBarComponentIds.TacticalSelect00, "s_select_00" }, + { FocCommandBarComponentIds.TacticalSelect01, "s_select_01" }, + { FocCommandBarComponentIds.TacticalSelect02, "s_select_02" }, + { FocCommandBarComponentIds.TacticalSelect03, "s_select_03" }, + { FocCommandBarComponentIds.TacticalSelect04, "s_select_04" }, + { FocCommandBarComponentIds.TacticalSelect05, "s_select_05" }, + { FocCommandBarComponentIds.TacticalSelect06, "s_select_06" }, + { FocCommandBarComponentIds.TacticalSelect07, "s_select_07" }, + { FocCommandBarComponentIds.TacticalSelect08, "s_select_08" }, + { FocCommandBarComponentIds.TacticalSelect09, "s_select_09" }, + { FocCommandBarComponentIds.TacticalSelect10, "s_select_10" }, + { FocCommandBarComponentIds.TacticalSelect11, "s_select_11" }, + { FocCommandBarComponentIds.TacticalSelect12, "s_select_12" }, + { FocCommandBarComponentIds.TacticalSelect13, "s_select_13" }, + { FocCommandBarComponentIds.TacticalSelect14, "s_select_14" }, + { FocCommandBarComponentIds.TacticalSelect15, "s_select_15" }, + { FocCommandBarComponentIds.TacticalSelect16, "s_select_16" }, + { FocCommandBarComponentIds.TacticalSelect17, "s_select_17" }, + { FocCommandBarComponentIds.TacticalSelect18, "s_select_18" }, + { FocCommandBarComponentIds.TacticalSelect19, "s_select_19" }, + { FocCommandBarComponentIds.TacticalSelect20, "s_select_20" }, + { FocCommandBarComponentIds.TacticalSelect21, "s_select_21" }, + { FocCommandBarComponentIds.TacticalSelect22, "s_select_22" }, + { FocCommandBarComponentIds.TacticalSelect23, "s_select_23" }, + { FocCommandBarComponentIds.TacticalSelect24, "s_select_24" }, + { FocCommandBarComponentIds.TacticalSelect25, "s_select_25" }, + { FocCommandBarComponentIds.TacticalSelect26, "s_select_26" }, + { FocCommandBarComponentIds.TacticalSelect27, "s_select_27" }, + { FocCommandBarComponentIds.TacticalSelect28, "s_select_28" }, + { FocCommandBarComponentIds.TacticalSelect29, "s_select_29" }, + { FocCommandBarComponentIds.TacticalSelect30, "s_select_30" }, + { FocCommandBarComponentIds.TacticalSelect31, "s_select_31" }, + { FocCommandBarComponentIds.TacticalSelect32, "s_select_32" }, + { FocCommandBarComponentIds.TacticalSelect33, "s_select_33" }, + { FocCommandBarComponentIds.TacticalSelect34, "s_select_34" }, + { FocCommandBarComponentIds.TacticalSelect35, "s_select_35" }, + { FocCommandBarComponentIds.TacticalSelect36, "s_select_36" }, + { FocCommandBarComponentIds.TacticalSelect37, "s_select_37" }, + { FocCommandBarComponentIds.TacticalSelect38, "s_select_38" }, + { FocCommandBarComponentIds.TacticalSelect39, "s_select_39" }, + { FocCommandBarComponentIds.TacticalSelect40, "s_select_40" }, + { FocCommandBarComponentIds.TacticalSelect41, "s_select_41" }, + { FocCommandBarComponentIds.TacticalSelect42, "s_select_42" }, + { FocCommandBarComponentIds.TacticalSelect43, "s_select_43" }, + { FocCommandBarComponentIds.TacticalSelect44, "s_select_44" }, + { FocCommandBarComponentIds.TacticalSelect45, "s_select_45" }, + { FocCommandBarComponentIds.TacticalSelect46, "s_select_46" }, + { FocCommandBarComponentIds.TacticalSelect47, "s_select_47" }, + { FocCommandBarComponentIds.TacticalSelectHealth00, "s_health_00" }, + { FocCommandBarComponentIds.TacticalSelectHealth01, "s_health_01" }, + { FocCommandBarComponentIds.TacticalSelectHealth02, "s_health_02" }, + { FocCommandBarComponentIds.TacticalSelectHealth03, "s_health_03" }, + { FocCommandBarComponentIds.TacticalSelectHealth04, "s_health_04" }, + { FocCommandBarComponentIds.TacticalSelectHealth05, "s_health_05" }, + { FocCommandBarComponentIds.TacticalSelectHealth06, "s_health_06" }, + { FocCommandBarComponentIds.TacticalSelectHealth07, "s_health_07" }, + { FocCommandBarComponentIds.TacticalSelectHealth08, "s_health_08" }, + { FocCommandBarComponentIds.TacticalSelectHealth09, "s_health_09" }, + { FocCommandBarComponentIds.TacticalSelectHealth10, "s_health_10" }, + { FocCommandBarComponentIds.TacticalSelectHealth11, "s_health_11" }, + { FocCommandBarComponentIds.TacticalSelectHealth12, "s_health_12" }, + { FocCommandBarComponentIds.TacticalSelectHealth13, "s_health_13" }, + { FocCommandBarComponentIds.TacticalSelectHealth14, "s_health_14" }, + { FocCommandBarComponentIds.TacticalSelectHealth15, "s_health_15" }, + { FocCommandBarComponentIds.TacticalSelectHealth16, "s_health_16" }, + { FocCommandBarComponentIds.TacticalSelectHealth17, "s_health_17" }, + { FocCommandBarComponentIds.TacticalSelectHealth18, "s_health_18" }, + { FocCommandBarComponentIds.TacticalSelectHealth19, "s_health_19" }, + { FocCommandBarComponentIds.TacticalSelectHealth20, "s_health_20" }, + { FocCommandBarComponentIds.TacticalSelectHealth21, "s_health_21" }, + { FocCommandBarComponentIds.TacticalSelectHealth22, "s_health_22" }, + { FocCommandBarComponentIds.TacticalSelectHealth23, "s_health_23" }, + { FocCommandBarComponentIds.TacticalSelectHealth24, "s_health_24" }, + { FocCommandBarComponentIds.TacticalSelectHealth25, "s_health_25" }, + { FocCommandBarComponentIds.TacticalSelectHealth26, "s_health_26" }, + { FocCommandBarComponentIds.TacticalSelectHealth27, "s_health_27" }, + { FocCommandBarComponentIds.TacticalSelectHealth28, "s_health_28" }, + { FocCommandBarComponentIds.TacticalSelectHealth29, "s_health_29" }, + { FocCommandBarComponentIds.TacticalSelectHealth30, "s_health_30" }, + { FocCommandBarComponentIds.TacticalSelectHealth31, "s_health_31" }, + { FocCommandBarComponentIds.TacticalSelectHealth32, "s_health_32" }, + { FocCommandBarComponentIds.TacticalSelectHealth33, "s_health_33" }, + { FocCommandBarComponentIds.TacticalSelectHealth34, "s_health_34" }, + { FocCommandBarComponentIds.TacticalSelectHealth35, "s_health_35" }, + { FocCommandBarComponentIds.TacticalSelectHealth36, "s_health_36" }, + { FocCommandBarComponentIds.TacticalSelectHealth37, "s_health_37" }, + { FocCommandBarComponentIds.TacticalSelectHealth38, "s_health_38" }, + { FocCommandBarComponentIds.TacticalSelectHealth39, "s_health_39" }, + { FocCommandBarComponentIds.TacticalSelectHealth40, "s_health_40" }, + { FocCommandBarComponentIds.TacticalSelectHealth41, "s_health_41" }, + { FocCommandBarComponentIds.TacticalSelectHealth42, "s_health_42" }, + { FocCommandBarComponentIds.TacticalSelectHealth43, "s_health_43" }, + { FocCommandBarComponentIds.TacticalSelectHealth44, "s_health_44" }, + { FocCommandBarComponentIds.TacticalSelectHealth45, "s_health_45" }, + { FocCommandBarComponentIds.TacticalSelectHealth46, "s_health_46" }, + { FocCommandBarComponentIds.TacticalSelectHealth47, "s_health_47" }, + { FocCommandBarComponentIds.TacticalSelectShield00, "s_shield_00" }, + { FocCommandBarComponentIds.TacticalSelectShield01, "s_shield_01" }, + { FocCommandBarComponentIds.TacticalSelectShield02, "s_shield_02" }, + { FocCommandBarComponentIds.TacticalSelectShield03, "s_shield_03" }, + { FocCommandBarComponentIds.TacticalSelectShield04, "s_shield_04" }, + { FocCommandBarComponentIds.TacticalSelectShield05, "s_shield_05" }, + { FocCommandBarComponentIds.TacticalSelectShield06, "s_shield_06" }, + { FocCommandBarComponentIds.TacticalSelectShield07, "s_shield_07" }, + { FocCommandBarComponentIds.TacticalSelectShield08, "s_shield_08" }, + { FocCommandBarComponentIds.TacticalSelectShield09, "s_shield_09" }, + { FocCommandBarComponentIds.TacticalSelectShield10, "s_shield_10" }, + { FocCommandBarComponentIds.TacticalSelectShield11, "s_shield_11" }, + { FocCommandBarComponentIds.TacticalSelectShield12, "s_shield_12" }, + { FocCommandBarComponentIds.TacticalSelectShield13, "s_shield_13" }, + { FocCommandBarComponentIds.TacticalSelectShield14, "s_shield_14" }, + { FocCommandBarComponentIds.TacticalSelectShield15, "s_shield_15" }, + { FocCommandBarComponentIds.TacticalSelectShield16, "s_shield_16" }, + { FocCommandBarComponentIds.TacticalSelectShield17, "s_shield_17" }, + { FocCommandBarComponentIds.TacticalSelectShield18, "s_shield_18" }, + { FocCommandBarComponentIds.TacticalSelectShield19, "s_shield_19" }, + { FocCommandBarComponentIds.TacticalSelectShield20, "s_shield_20" }, + { FocCommandBarComponentIds.TacticalSelectShield21, "s_shield_21" }, + { FocCommandBarComponentIds.TacticalSelectShield22, "s_shield_22" }, + { FocCommandBarComponentIds.TacticalSelectShield23, "s_shield_23" }, + { FocCommandBarComponentIds.TacticalSelectShield24, "s_shield_24" }, + { FocCommandBarComponentIds.TacticalSelectShield25, "s_shield_25" }, + { FocCommandBarComponentIds.TacticalSelectShield26, "s_shield_26" }, + { FocCommandBarComponentIds.TacticalSelectShield27, "s_shield_27" }, + { FocCommandBarComponentIds.TacticalSelectShield28, "s_shield_28" }, + { FocCommandBarComponentIds.TacticalSelectShield29, "s_shield_29" }, + { FocCommandBarComponentIds.TacticalSelectShield30, "s_shield_30" }, + { FocCommandBarComponentIds.TacticalSelectShield31, "s_shield_31" }, + { FocCommandBarComponentIds.TacticalSelectShield32, "s_shield_32" }, + { FocCommandBarComponentIds.TacticalSelectShield33, "s_shield_33" }, + { FocCommandBarComponentIds.TacticalSelectShield34, "s_shield_34" }, + { FocCommandBarComponentIds.TacticalSelectShield35, "s_shield_35" }, + { FocCommandBarComponentIds.TacticalSelectShield36, "s_shield_36" }, + { FocCommandBarComponentIds.TacticalSelectShield37, "s_shield_37" }, + { FocCommandBarComponentIds.TacticalSelectShield38, "s_shield_38" }, + { FocCommandBarComponentIds.TacticalSelectShield39, "s_shield_39" }, + { FocCommandBarComponentIds.TacticalSelectShield40, "s_shield_40" }, + { FocCommandBarComponentIds.TacticalSelectShield41, "s_shield_41" }, + { FocCommandBarComponentIds.TacticalSelectShield42, "s_shield_42" }, + { FocCommandBarComponentIds.TacticalSelectShield43, "s_shield_43" }, + { FocCommandBarComponentIds.TacticalSelectShield44, "s_shield_44" }, + { FocCommandBarComponentIds.TacticalSelectShield45, "s_shield_45" }, + { FocCommandBarComponentIds.TacticalSelectShield46, "s_shield_46" }, + { FocCommandBarComponentIds.TacticalSelectShield47, "s_shield_47" }, + { FocCommandBarComponentIds.TacticalBorder00, "special_border_00" }, + { FocCommandBarComponentIds.TacticalBorder01, "special_border_01" }, + { FocCommandBarComponentIds.TacticalBorder02, "special_border_02" }, + { FocCommandBarComponentIds.TacticalBorder03, "special_border_03" }, + { FocCommandBarComponentIds.TacticalBorder04, "special_border_04" }, + { FocCommandBarComponentIds.TacticalBorder05, "special_border_05" }, + { FocCommandBarComponentIds.TacticalBorder06, "special_border_06" }, + { FocCommandBarComponentIds.TacticalBorder07, "special_border_07" }, + { FocCommandBarComponentIds.TacticalBorder08, "special_border_08" }, + { FocCommandBarComponentIds.TacticalBorder09, "special_border_09" }, + { FocCommandBarComponentIds.TacticalBorder10, "special_border_10" }, + { FocCommandBarComponentIds.TacticalBorder11, "special_border_11" }, + { FocCommandBarComponentIds.TacticalSelectButton00, "special_button_00" }, + { FocCommandBarComponentIds.TacticalSelectButton01, "special_button_01" }, + { FocCommandBarComponentIds.TacticalSelectButton02, "special_button_02" }, + { FocCommandBarComponentIds.TacticalSelectButton03, "special_button_03" }, + { FocCommandBarComponentIds.TacticalSelectButton04, "special_button_04" }, + { FocCommandBarComponentIds.TacticalSelectButton05, "special_button_05" }, + { FocCommandBarComponentIds.TacticalSelectButton06, "special_button_06" }, + { FocCommandBarComponentIds.TacticalSelectButton07, "special_button_07" }, + { FocCommandBarComponentIds.TacticalSelectButton08, "special_button_08" }, + { FocCommandBarComponentIds.TacticalSelectButton09, "special_button_09" }, + { FocCommandBarComponentIds.TacticalSelectButton10, "special_button_10" }, + { FocCommandBarComponentIds.TacticalSelectButton11, "special_button_11" }, + { FocCommandBarComponentIds.TacticalSelectButton12, "special_button_12" }, + { FocCommandBarComponentIds.TacticalSelectButton13, "special_button_13" }, + { FocCommandBarComponentIds.TacticalSelectButton14, "special_button_14" }, + { FocCommandBarComponentIds.TacticalSelectButton15, "special_button_15" }, + { FocCommandBarComponentIds.TacticalSelectButton16, "special_button_16" }, + { FocCommandBarComponentIds.TacticalSelectButton17, "special_button_17" }, + { FocCommandBarComponentIds.TacticalSelectButton18, "special_button_18" }, + { FocCommandBarComponentIds.TacticalSelectButton19, "special_button_19" }, + { FocCommandBarComponentIds.TacticalSelectButton20, "special_button_20" }, + { FocCommandBarComponentIds.TacticalSelectButton21, "special_button_21" }, + { FocCommandBarComponentIds.TacticalSelectButton22, "special_button_22" }, + { FocCommandBarComponentIds.TacticalSelectButton23, "special_button_23" }, + { FocCommandBarComponentIds.TacticalSelectButton24, "special_button_24" }, + { FocCommandBarComponentIds.TacticalSelectButton25, "special_button_25" }, + { FocCommandBarComponentIds.TacticalSelectButton26, "special_button_26" }, + { FocCommandBarComponentIds.TacticalSelectButton27, "special_button_27" }, + { FocCommandBarComponentIds.TacticalSelectButton28, "special_button_28" }, + { FocCommandBarComponentIds.TacticalSelectButton29, "special_button_29" }, + { FocCommandBarComponentIds.TacticalSelectButton30, "special_button_30" }, + { FocCommandBarComponentIds.TacticalSelectButton31, "special_button_31" }, + { FocCommandBarComponentIds.TacticalSelectButton32, "special_button_32" }, + { FocCommandBarComponentIds.TacticalSelectButton33, "special_button_33" }, + { FocCommandBarComponentIds.TacticalSelectButton34, "special_button_34" }, + { FocCommandBarComponentIds.TacticalSelectButton35, "special_button_35" }, + { FocCommandBarComponentIds.TacticalSelectButton36, "special_button_36" }, + { FocCommandBarComponentIds.TacticalSelectButton37, "special_button_37" }, + { FocCommandBarComponentIds.TacticalSelectButton38, "special_button_38" }, + { FocCommandBarComponentIds.TacticalSelectButton39, "special_button_39" }, + { FocCommandBarComponentIds.TacticalSelectButton40, "special_button_40" }, + { FocCommandBarComponentIds.TacticalSelectButton41, "special_button_41" }, + { FocCommandBarComponentIds.TacticalSelectButton42, "special_button_42" }, + { FocCommandBarComponentIds.TacticalSelectButton43, "special_button_43" }, + { FocCommandBarComponentIds.TacticalSelectButton44, "special_button_44" }, + { FocCommandBarComponentIds.TacticalSelectButton45, "special_button_45" }, + { FocCommandBarComponentIds.TacticalSelectButton46, "special_button_46" }, + { FocCommandBarComponentIds.TacticalSelectButton47, "special_button_47" }, + { FocCommandBarComponentIds.TacticalBuildQueue00, "tqueue00" }, + { FocCommandBarComponentIds.TacticalBuildQueue01, "tqueue01" }, + { FocCommandBarComponentIds.TacticalBuildQueue02, "tqueue02" }, + { FocCommandBarComponentIds.TacticalBuildQueue03, "tqueue03" }, + { FocCommandBarComponentIds.TacticalBuildQueue04, "tqueue04" }, + { FocCommandBarComponentIds.TacticalBuildQueue05, "tqueue05" }, + { FocCommandBarComponentIds.TacticalBuildQueue06, "tqueue06" }, + { FocCommandBarComponentIds.TacticalBuildQueue07, "tqueue07" }, + { FocCommandBarComponentIds.TacticalBuildQueue08, "tqueue08" }, + { FocCommandBarComponentIds.TacticalBuildQueue09, "tqueue09" }, + { FocCommandBarComponentIds.TooltipBack, "tooltip_back" }, + { FocCommandBarComponentIds.TooltipName, "tooltip_name" }, + { FocCommandBarComponentIds.TooltipPrice, "tooltip_price" }, + { FocCommandBarComponentIds.TooltipIcon, "tooltip_icon" }, + { FocCommandBarComponentIds.TooltipIconLand, "tooltip_icon_land" }, + { FocCommandBarComponentIds.TooltipLeftJustified, "tooltip_left_text" }, + { FocCommandBarComponentIds.EncyclopediaBack, "encyclopedia_back" }, + { FocCommandBarComponentIds.EncyclopediaHeaderText, "encyclopedia_header_text" }, + { FocCommandBarComponentIds.EncyclopediaText, "encyclopedia_text" }, + { FocCommandBarComponentIds.EncyclopediaRightText, "encyclopedia_right_text" }, + { FocCommandBarComponentIds.EncyclopediaCenterText, "encyclopedia_center_text" }, + { FocCommandBarComponentIds.EncyclopediaIcon, "encyclopedia_icon" }, + { FocCommandBarComponentIds.EncyclopediaCostText, "encyclopedia_cost_text" }, + { FocCommandBarComponentIds.ZoomedBack, "zoomed_back" }, + { FocCommandBarComponentIds.ZoomedHeaderText, "zoomed_header_text" }, + { FocCommandBarComponentIds.ZoomedText, "zoomed_text" }, + { FocCommandBarComponentIds.ZoomedRightText, "zoomed_right_text" }, + { FocCommandBarComponentIds.ZoomedCenterText, "zoomed_center_text" }, + { FocCommandBarComponentIds.ZoomedCostText, "zoomed_cost_text" }, + { FocCommandBarComponentIds.GPlanetFleet, "g_planet_fleet" }, + { FocCommandBarComponentIds.GPlanetName, "g_planet_name" }, + { FocCommandBarComponentIds.GPlanetValue, "g_planet_value" }, + { FocCommandBarComponentIds.GPoliticalControl, "g_political_control" }, + { FocCommandBarComponentIds.GSpaceLevel, "g_space_level" }, + { FocCommandBarComponentIds.GSpaceIcon, "g_space_icon" }, + { FocCommandBarComponentIds.GSpaceLevelPips, "g_space_level_pips" }, + { FocCommandBarComponentIds.GGroundLevel, "g_ground_level" }, + { FocCommandBarComponentIds.GGroundIcon, "g_ground_icon" }, + { FocCommandBarComponentIds.GGroundLevelPips, "g_ground_level_pips" }, + { FocCommandBarComponentIds.GConflict, "g_conflict" }, + { FocCommandBarComponentIds.GHero, "g_hero" }, + { FocCommandBarComponentIds.GEnemyHero, "g_enemy_hero" }, + { FocCommandBarComponentIds.GBuild, "g_build" }, + { FocCommandBarComponentIds.GSmuggler, "g_smuggler" }, + { FocCommandBarComponentIds.GBountyHunter, "g_bounty_hunter" }, + { FocCommandBarComponentIds.GPlanetLandForces, "g_planet_land_forces" }, + { FocCommandBarComponentIds.GGalacticRadarBlip, "g_radar_blip" }, + { FocCommandBarComponentIds.GGalacticRadarView, "g_radar_view" }, + { FocCommandBarComponentIds.GSmuggled, "g_smuggled" }, + { FocCommandBarComponentIds.GSpecialAbility, "g_special_ability" }, + { FocCommandBarComponentIds.GHeroIcon, "g_hero_icon" }, + { FocCommandBarComponentIds.GPlanetRing, "g_planet_ring" }, + { FocCommandBarComponentIds.GWeather, "g_weather" }, + { FocCommandBarComponentIds.GPlanetAbility, "g_planet_ability" }, + { FocCommandBarComponentIds.GCorruptionText, "g_corruption_text" }, + { FocCommandBarComponentIds.GCorruptionIcon, "g_corruption_icon" }, + { FocCommandBarComponentIds.TutorialText, "tutorial_text" }, + { FocCommandBarComponentIds.TutorialTextBack, "tutorial_text_back" }, + { FocCommandBarComponentIds.RadarBlip, "radar_blip" }, + { FocCommandBarComponentIds.TacticalBuildButtonShell, "i_build_buttons" }, + { FocCommandBarComponentIds.TacticalBuildButton0, "Build_00" }, + { FocCommandBarComponentIds.TacticalBuildButton1, "Build_01" }, + { FocCommandBarComponentIds.TacticalBuildButton2, "Build_02" }, + { FocCommandBarComponentIds.TacticalBuildButton3, "Build_03" }, + { FocCommandBarComponentIds.TacticalBuildButton4, "Build_04" }, + { FocCommandBarComponentIds.TacticalBuildButton5, "Build_05" }, + { FocCommandBarComponentIds.TacticalSellButton, "tactical_sell" }, + { FocCommandBarComponentIds.ReinforcementShell, "i_main_reinforce" }, + { FocCommandBarComponentIds.ReinforcementCancel, "r_close" }, + { FocCommandBarComponentIds.ReinforcementSlot00, "r_0000" }, + { FocCommandBarComponentIds.ReinforcementSlot01, "r_0001" }, + { FocCommandBarComponentIds.ReinforcementSlot02, "r_0002" }, + { FocCommandBarComponentIds.ReinforcementSlot03, "r_0003" }, + { FocCommandBarComponentIds.ReinforcementSlot04, "r_0100" }, + { FocCommandBarComponentIds.ReinforcementSlot05, "r_0101" }, + { FocCommandBarComponentIds.ReinforcementSlot06, "r_0102" }, + { FocCommandBarComponentIds.ReinforcementSlot07, "r_0103" }, + { FocCommandBarComponentIds.ReinforcementSlot08, "r_0200" }, + { FocCommandBarComponentIds.ReinforcementSlot09, "r_0201" }, + { FocCommandBarComponentIds.ReinforcementSlot10, "r_0202" }, + { FocCommandBarComponentIds.ReinforcementSlot11, "r_0203" }, + { FocCommandBarComponentIds.ReinforcementSlot12, "r_0300" }, + { FocCommandBarComponentIds.ReinforcementSlot13, "r_0301" }, + { FocCommandBarComponentIds.ReinforcementSlot14, "r_0302" }, + { FocCommandBarComponentIds.ReinforcementSlot15, "r_0303" }, + { FocCommandBarComponentIds.ReinforcementSlot16, "r_0400" }, + { FocCommandBarComponentIds.ReinforcementSlot17, "r_0401" }, + { FocCommandBarComponentIds.ReinforcementSlot18, "r_0402" }, + { FocCommandBarComponentIds.ReinforcementSlot19, "r_0403" }, + { FocCommandBarComponentIds.ReinforcementCap2, "r_pop_icon" }, + { FocCommandBarComponentIds.ReinforcementCap2Text, "r_pop_text" }, + { FocCommandBarComponentIds.ReinforcementCounter, "reinforcement_counter" }, + { FocCommandBarComponentIds.GarrisonRespawnCounter, "garrison_respawn_counter" }, + { FocCommandBarComponentIds.SkirmishUpgrade, "skirmish_upgrade" }, + { FocCommandBarComponentIds.PendingBattleShell, "pending_battle_shell" }, + { FocCommandBarComponentIds.PendingBattleText, "text_attack_choice" }, + { FocCommandBarComponentIds.PendingBattleButton, "choice_button_left" }, + { FocCommandBarComponentIds.PendingBattleAutoresolve, "choice_button_right" }, + { FocCommandBarComponentIds.PendingBattleGraphLeft, "graph_left" }, + { FocCommandBarComponentIds.PendingBattleGraphRight, "graph_right" }, + { FocCommandBarComponentIds.TatcicalAutoresolveShell, "autoresolve_shell" }, + { FocCommandBarComponentIds.TacticalAutoresolveButton, "resolve_button" }, + { FocCommandBarComponentIds.TacticalAutoresolveGraphLeft, "graph_left_resolve" }, + { FocCommandBarComponentIds.TacticalAutoresolveGraphRight, "graph_right_resolve" }, + { FocCommandBarComponentIds.ObjectiveBack, "objective_back" }, + { FocCommandBarComponentIds.ObjectiveHeaderText, "objective_header_text" }, + { FocCommandBarComponentIds.ObjectiveText, "objective_text" }, + { FocCommandBarComponentIds.ObjectiveIcon, "objective_icon" }, + { FocCommandBarComponentIds.GuiDialogTooltip, "gui_dialog_tooltip" }, + { FocCommandBarComponentIds.TargetUnitTypeShell, "target_type_back" }, + { FocCommandBarComponentIds.TargetUnitTypeTitle, "h_title" }, + { FocCommandBarComponentIds.TargetUnitTypeCancel, "h_close" }, + { FocCommandBarComponentIds.TargetUnitTypeDescription, "text_hero" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot00, "h_0000" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot01, "h_0001" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot02, "h_0100" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot03, "h_0101" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot04, "h_0200" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot05, "h_0201" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot06, "h_0300" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot07, "h_0301" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot08, "h_0400" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot09, "h_0401" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot10, "h_0500" }, + { FocCommandBarComponentIds.TargetUnitTypeSlot11, "h_0501" }, + { FocCommandBarComponentIds.TargetUnitTypeName00, "h_name_00" }, + { FocCommandBarComponentIds.TargetUnitTypeName01, "h_name_01" }, + { FocCommandBarComponentIds.TargetUnitTypeName02, "h_name_02" }, + { FocCommandBarComponentIds.TargetUnitTypeName03, "h_name_03" }, + { FocCommandBarComponentIds.TargetUnitTypeName04, "h_name_04" }, + { FocCommandBarComponentIds.TargetUnitTypeName05, "h_name_05" }, + { FocCommandBarComponentIds.TargetUnitTypeName06, "h_name_06" }, + { FocCommandBarComponentIds.TargetUnitTypeName07, "h_name_07" }, + { FocCommandBarComponentIds.TargetUnitTypeName08, "h_name_08" }, + { FocCommandBarComponentIds.TargetUnitTypeName09, "h_name_09" }, + { FocCommandBarComponentIds.TargetUnitTypeName10, "h_name_10" }, + { FocCommandBarComponentIds.TargetUnitTypeName11, "h_name_11" }, + { FocCommandBarComponentIds.TargetUnitTypePrice00, "h_price_00" }, + { FocCommandBarComponentIds.TargetUnitTypePrice01, "h_price_01" }, + { FocCommandBarComponentIds.TargetUnitTypePrice02, "h_price_02" }, + { FocCommandBarComponentIds.TargetUnitTypePrice03, "h_price_03" }, + { FocCommandBarComponentIds.TargetUnitTypePrice04, "h_price_04" }, + { FocCommandBarComponentIds.TargetUnitTypePrice05, "h_price_05" }, + { FocCommandBarComponentIds.TargetUnitTypePrice06, "h_price_06" }, + { FocCommandBarComponentIds.TargetUnitTypePrice07, "h_price_07" }, + { FocCommandBarComponentIds.TargetUnitTypePrice08, "h_price_08" }, + { FocCommandBarComponentIds.TargetUnitTypePrice09, "h_price_09" }, + { FocCommandBarComponentIds.TargetUnitTypePrice10, "h_price_10" }, + { FocCommandBarComponentIds.TargetUnitTypePrice11, "h_price_11" }, + { FocCommandBarComponentIds.VcrButtonPlayPause, "b_play_pause" }, + { FocCommandBarComponentIds.VcrButtonFastForward, "b_fast_forward" }, + { FocCommandBarComponentIds.VcrButtonFastForwardTactical, "b_fast_forward_t" }, + { FocCommandBarComponentIds.VcrButtonPlayPauseTactical, "b_play_pause_t" }, + { FocCommandBarComponentIds.AdvisorHintPopupGalactic, "text_galactic_help" }, + { FocCommandBarComponentIds.AdvisorHintPopupTactical, "text_tactical_help" }, + { FocCommandBarComponentIds.AdvisorHintBack, "help_back" }, + { FocCommandBarComponentIds.TextOrganizeFleet00, "text_fleet1" }, + { FocCommandBarComponentIds.TextOrganizeFleet01, "text_fleet2" }, + { FocCommandBarComponentIds.TextOrganizeFleet02, "text_fleet3" }, + { FocCommandBarComponentIds.IconOrganizeFleet00, "icon_fleet01" }, + { FocCommandBarComponentIds.IconOrganizeFleet01, "icon_fleet02" }, + { FocCommandBarComponentIds.IconOrganizeFleet02, "icon_fleet03" }, + { FocCommandBarComponentIds.TextOrganizeLandFleet, "Text_Land_Fleet" }, + { FocCommandBarComponentIds.CsAbilityButton, "cs_ability_button" }, + { FocCommandBarComponentIds.CsAbilityText, "cs_ability_text" }, + { FocCommandBarComponentIds.MovieBoneGalactic, "Movie_galactic" }, + { FocCommandBarComponentIds.MovieBoneTactical, "Movie_tactical" }, + { FocCommandBarComponentIds.GenericCollision, "generic_collision" }, + { FocCommandBarComponentIds.GoodHeroShell, "good_hero_frame" }, + { FocCommandBarComponentIds.GoodHeroSlot00, "hero_slot_good00" }, + { FocCommandBarComponentIds.GoodHeroSlot01, "hero_slot_good01" }, + { FocCommandBarComponentIds.GoodHeroSlot02, "hero_slot_good02" }, + { FocCommandBarComponentIds.GoodHeroSlot03, "hero_slot_good03" }, + { FocCommandBarComponentIds.GoodHeroSlot04, "hero_slot_good04" }, + { FocCommandBarComponentIds.GoodHeroSlot05, "hero_slot_good05" }, + { FocCommandBarComponentIds.GoodHeroSlot06, "hero_slot_good06" }, + { FocCommandBarComponentIds.GoodHeroSlot07, "hero_slot_good07" }, + { FocCommandBarComponentIds.GoodHeroSlot08, "hero_slot_good08" }, + { FocCommandBarComponentIds.GoodHeroSlot09, "hero_slot_good09" }, + { FocCommandBarComponentIds.GoodHeroSlot10, "hero_slot_good10" }, + { FocCommandBarComponentIds.GoodHeroHealth00, "hero_hb_good00" }, + { FocCommandBarComponentIds.GoodHeroHealth01, "hero_hb_good01" }, + { FocCommandBarComponentIds.GoodHeroHealth02, "hero_hb_good02" }, + { FocCommandBarComponentIds.GoodHeroHealth03, "hero_hb_good03" }, + { FocCommandBarComponentIds.GoodHeroHealth04, "hero_hb_good04" }, + { FocCommandBarComponentIds.GoodHeroHealth05, "hero_hb_good05" }, + { FocCommandBarComponentIds.GoodHeroHealth06, "hero_hb_good06" }, + { FocCommandBarComponentIds.GoodHeroHealth07, "hero_hb_good07" }, + { FocCommandBarComponentIds.GoodHeroHealth08, "hero_hb_good08" }, + { FocCommandBarComponentIds.GoodHeroHealth09, "hero_hb_good09" }, + { FocCommandBarComponentIds.GoodHeroHealth10, "hero_hb_good10" }, + { FocCommandBarComponentIds.EvilHeroShell, "evil_hero_frame" }, + { FocCommandBarComponentIds.EvilHeroSlot00, "hero_slot_evil00" }, + { FocCommandBarComponentIds.EvilHeroSlot01, "hero_slot_evil01" }, + { FocCommandBarComponentIds.EvilHeroSlot02, "hero_slot_evil02" }, + { FocCommandBarComponentIds.EvilHeroSlot03, "hero_slot_evil03" }, + { FocCommandBarComponentIds.EvilHeroSlot04, "hero_slot_evil04" }, + { FocCommandBarComponentIds.EvilHeroSlot05, "hero_slot_evil05" }, + { FocCommandBarComponentIds.EvilHeroSlot06, "hero_slot_evil06" }, + { FocCommandBarComponentIds.EvilHeroSlot07, "hero_slot_evil07" }, + { FocCommandBarComponentIds.EvilHeroSlot08, "hero_slot_evil08" }, + { FocCommandBarComponentIds.EvilHeroSlot09, "hero_slot_evil09" }, + { FocCommandBarComponentIds.EvilHeroSlot10, "hero_slot_evil10" }, + { FocCommandBarComponentIds.EvilHeroHealth00, "hero_hb_evil00" }, + { FocCommandBarComponentIds.EvilHeroHealth01, "hero_hb_evil01" }, + { FocCommandBarComponentIds.EvilHeroHealth02, "hero_hb_evil02" }, + { FocCommandBarComponentIds.EvilHeroHealth03, "hero_hb_evil03" }, + { FocCommandBarComponentIds.EvilHeroHealth04, "hero_hb_evil04" }, + { FocCommandBarComponentIds.EvilHeroHealth05, "hero_hb_evil05" }, + { FocCommandBarComponentIds.EvilHeroHealth06, "hero_hb_evil06" }, + { FocCommandBarComponentIds.EvilHeroHealth07, "hero_hb_evil07" }, + { FocCommandBarComponentIds.EvilHeroHealth08, "hero_hb_evil08" }, + { FocCommandBarComponentIds.EvilHeroHealth09, "hero_hb_evil09" }, + { FocCommandBarComponentIds.EvilHeroHealth10, "hero_hb_evil10" }, + { FocCommandBarComponentIds.PauseShell, "pause_shell" }, + { FocCommandBarComponentIds.PauseText, "text_attack" }, + { FocCommandBarComponentIds.PauseButton, "attack_button" }, + { FocCommandBarComponentIds.StoryCampaignPendingBattleShell, "story_pending_battle_shell" }, + { FocCommandBarComponentIds.StoryCampaignPendingBattleText, "text_attack_single" }, + { FocCommandBarComponentIds.StoryCampaignPendingBattleButton, "attack_button_single" }, + { FocCommandBarComponentIds.MapActivate, "advanced_planetary" }, + { FocCommandBarComponentIds.MapShell, "map_shell" }, + { FocCommandBarComponentIds.MapOverlayShell, "map_overlay_shell" }, + { FocCommandBarComponentIds.MapGroundForcesText, "ground_forces_text" }, + { FocCommandBarComponentIds.MapDragDropText, "drage_drop_text" }, + { FocCommandBarComponentIds.MapUnitCapText, "unit_cap_text" }, + { FocCommandBarComponentIds.MapUnitCapNumber, "unit_cap_number" }, + { FocCommandBarComponentIds.MapBuildingCapText, "building_cap_text" }, + { FocCommandBarComponentIds.MapBuildingCapNumber, "building_cap_number" }, + { FocCommandBarComponentIds.MapFactionIcon, "Allegiance_A" }, + { FocCommandBarComponentIds.MapPlanetNameText, "Planet_name_text" }, + { FocCommandBarComponentIds.MapPlanetIncomeNumber, "Planetary_Income_number" }, + { FocCommandBarComponentIds.MapDayCounterText, "Day_Counter_text" }, + { FocCommandBarComponentIds.MapDayCounter, "day_clock" }, + { FocCommandBarComponentIds.MapBackButton, "back_button" }, + { FocCommandBarComponentIds.MapWeatherIcon, "weather" }, + { FocCommandBarComponentIds.MapPlanetBonusIcon, "planet_bonus" }, + { FocCommandBarComponentIds.MapRadarMap, "radar_galactic_adv_map" }, + { FocCommandBarComponentIds.MapZoomOut, "b_zoom_adv_map" }, + { FocCommandBarComponentIds.MapEncyclopediaPopup, "Text_adv_help" }, + { FocCommandBarComponentIds.AdvancedMapFilter0, "c_filters0" }, + { FocCommandBarComponentIds.AdvancedMapFilter1, "c_filters1" }, + { FocCommandBarComponentIds.AdvancedMapFilter2, "c_filters2" }, + { FocCommandBarComponentIds.AdvancedMapFilter3, "c_filters3" }, + { FocCommandBarComponentIds.AdvancedMapOptions, "c_option_g" }, + { FocCommandBarComponentIds.MapMainMap, "map_window" }, + { FocCommandBarComponentIds.MapSpaceUnit00, "land_fleet_00" }, + { FocCommandBarComponentIds.MapSpaceUnit01, "land_fleet_01" }, + { FocCommandBarComponentIds.MapSpaceUnit02, "land_fleet_02" }, + { FocCommandBarComponentIds.MapSpaceUnit03, "land_fleet_03" }, + { FocCommandBarComponentIds.MapSpaceUnit04, "land_fleet_04" }, + { FocCommandBarComponentIds.MapSpaceUnit05, "land_fleet_05" }, + { FocCommandBarComponentIds.MapSpaceUnit06, "land_fleet_06" }, + { FocCommandBarComponentIds.MapSpaceUnit07, "land_fleet_07" }, + { FocCommandBarComponentIds.MapSpaceUnit08, "land_fleet_08" }, + { FocCommandBarComponentIds.MapSpaceUnit09, "land_fleet_09" }, + { FocCommandBarComponentIds.MapSpaceUnit10, "land_fleet_10" }, + { FocCommandBarComponentIds.MapSpaceUnit11, "land_fleet_11" }, + { FocCommandBarComponentIds.MapSpaceUnit12, "land_fleet_12" }, + { FocCommandBarComponentIds.MapSpaceUnit13, "land_fleet_13" }, + { FocCommandBarComponentIds.MapSpaceUnit14, "land_fleet_14" }, + { FocCommandBarComponentIds.MapSpaceUnit15, "land_fleet_15" }, + { FocCommandBarComponentIds.MapSpaceUnit16, "land_fleet_16" }, + { FocCommandBarComponentIds.MapSpaceUnit17, "land_fleet_17" }, + { FocCommandBarComponentIds.MapSpaceUnit18, "land_fleet_18" }, + { FocCommandBarComponentIds.MapSpaceUnit19, "land_fleet_19" }, + { FocCommandBarComponentIds.MapBuildPad00, "build_pad00" }, + { FocCommandBarComponentIds.MapBuildPad01, "build_pad01" }, + { FocCommandBarComponentIds.MapBuildPad02, "build_pad02" }, + { FocCommandBarComponentIds.MapBuildPad03, "build_pad03" }, + { FocCommandBarComponentIds.MapBuildPad04, "build_pad04" }, + { FocCommandBarComponentIds.MapBuildPad05, "build_pad05" }, + { FocCommandBarComponentIds.MapBuildPad06, "build_pad06" }, + { FocCommandBarComponentIds.MapBuildPad07, "build_pad07" }, + { FocCommandBarComponentIds.MapBuildPad08, "build_pad08" }, + { FocCommandBarComponentIds.MapBuildPad09, "build_pad09" }, + { FocCommandBarComponentIds.MapBuildPad10, "build_pad10" }, + { FocCommandBarComponentIds.MapBuildPad11, "build_pad11" }, + { FocCommandBarComponentIds.MapBuildPad12, "build_pad12" }, + { FocCommandBarComponentIds.MapBuildPad13, "build_pad13" }, + { FocCommandBarComponentIds.MapBuildPad14, "build_pad14" }, + { FocCommandBarComponentIds.MapBuildPad15, "build_pad15" }, + { FocCommandBarComponentIds.MapBuildPad16, "build_pad16" }, + { FocCommandBarComponentIds.MapBuildPad17, "build_pad17" }, + { FocCommandBarComponentIds.MapBuildPad18, "build_pad18" }, + { FocCommandBarComponentIds.MapBuildPad19, "build_pad19" }, + { FocCommandBarComponentIds.MapBuildPad20, "build_pad20" }, + { FocCommandBarComponentIds.MapBuildPad21, "build_pad21" }, + { FocCommandBarComponentIds.MapBuildPad22, "build_pad22" }, + { FocCommandBarComponentIds.MapBuildPad23, "build_pad23" }, + { FocCommandBarComponentIds.MapBuildPad24, "build_pad24" }, + { FocCommandBarComponentIds.MapBuildPad25, "build_pad25" }, + { FocCommandBarComponentIds.MapBuildPad26, "build_pad26" }, + { FocCommandBarComponentIds.MapBuildPad27, "build_pad27" }, + { FocCommandBarComponentIds.MapBuildPad28, "build_pad28" }, + { FocCommandBarComponentIds.MapBuildPad29, "build_pad29" }, + { FocCommandBarComponentIds.MapBuildPad30, "build_pad30" }, + { FocCommandBarComponentIds.MapBuildPad31, "build_pad31" }, + { FocCommandBarComponentIds.MapBuildPad32, "build_pad32" }, + { FocCommandBarComponentIds.MapReinforcePad00, "build_pad33" }, + { FocCommandBarComponentIds.MapReinforcePad01, "build_pad34" }, + { FocCommandBarComponentIds.MapReinforcePad02, "build_pad35" }, + { FocCommandBarComponentIds.MapReinforcePad03, "build_pad36" }, + { FocCommandBarComponentIds.MapReinforcePad04, "build_pad37" }, + { FocCommandBarComponentIds.MapReinforcePad05, "build_pad38" }, + { FocCommandBarComponentIds.MapReinforcePad06, "build_pad39" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad00, "build_pad40" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad01, "build_pad41" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad02, "build_pad42" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad03, "build_pad43" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad04, "build_pad44" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad05, "build_pad45" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad06, "build_pad46" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad07, "build_pad47" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad08, "build_pad48" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad09, "build_pad49" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad10, "build_pad50" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad11, "build_pad51" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad12, "build_pad52" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad13, "build_pad53" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad14, "build_pad54" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad15, "build_pad55" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad16, "build_pad56" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad17, "build_pad57" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad18, "build_pad58" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad19, "build_pad59" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad20, "build_pad60" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad21, "build_pad61" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad22, "build_pad62" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad23, "build_pad63" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad24, "build_pad64" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad25, "build_pad65" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad26, "build_pad66" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad27, "build_pad67" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad28, "build_pad68" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad29, "build_pad69" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad30, "build_pad70" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad31, "build_pad71" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad32, "build_pad72" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad33, "build_pad73" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad34, "build_pad74" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad35, "build_pad75" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad36, "build_pad76" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad37, "build_pad77" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad38, "build_pad78" }, + { FocCommandBarComponentIds.MapBunkerAndBuildPad39, "build_pad79" }, + { FocCommandBarComponentIds.MapUnitPad00, "unit_pad00" }, + { FocCommandBarComponentIds.MapUnitPad01, "unit_pad01" }, + { FocCommandBarComponentIds.MapUnitPad02, "unit_pad02" }, + { FocCommandBarComponentIds.MapUnitPad03, "unit_pad03" }, + { FocCommandBarComponentIds.MapUnitPad04, "unit_pad04" }, + { FocCommandBarComponentIds.MapUnitPad05, "unit_pad05" }, + { FocCommandBarComponentIds.MapUnitPad06, "unit_pad06" }, + { FocCommandBarComponentIds.MapUnitPad07, "unit_pad07" }, + { FocCommandBarComponentIds.MapUnitPad08, "unit_pad08" }, + { FocCommandBarComponentIds.MapUnitPad09, "unit_pad09" }, + { FocCommandBarComponentIds.BribeDisplay, "bribe_display" }, + { FocCommandBarComponentIds.BlackMarketShell, "market_frame" }, + { FocCommandBarComponentIds.BlackMarketTitle, "market_title00" }, + { FocCommandBarComponentIds.BlackMarketPlanet, "market_title01" }, + { FocCommandBarComponentIds.BlackMarketTechLevelHeader, "market_title02" }, + { FocCommandBarComponentIds.BlackMarketSlotHeader, "market_title03" }, + { FocCommandBarComponentIds.BlackMarketCancel, "market_close" }, + { FocCommandBarComponentIds.BlackMarketEncyclopedia, "i_blackmarket_encycl" }, + { FocCommandBarComponentIds.BlackMarketTechLevel00, "market_tech_level00" }, + { FocCommandBarComponentIds.BlackMarketTechLevel01, "market_tech_level01" }, + { FocCommandBarComponentIds.BlackMarketTechLevel02, "market_tech_level02" }, + { FocCommandBarComponentIds.BlackMarketTechLevel03, "market_tech_level03" }, + { FocCommandBarComponentIds.BlackMarketTechLevel04, "market_tech_level04" }, + { FocCommandBarComponentIds.BlackMarketSlot00, "market_00" }, + { FocCommandBarComponentIds.BlackMarketSlot01, "market_01" }, + { FocCommandBarComponentIds.BlackMarketSlot02, "market_02" }, + { FocCommandBarComponentIds.BlackMarketSlot03, "market_03" }, + { FocCommandBarComponentIds.BlackMarketSlot04, "market_04" }, + { FocCommandBarComponentIds.BlackMarketSlotDescription00, "market_title04" }, + { FocCommandBarComponentIds.BlackMarketSlotDescription01, "market_title06" }, + { FocCommandBarComponentIds.BlackMarketSlotDescription02, "market_title08" }, + { FocCommandBarComponentIds.BlackMarketSlotDescription03, "market_title10" }, + { FocCommandBarComponentIds.BlackMarketSlotDescription04, "market_title12" }, + { FocCommandBarComponentIds.BlackMarketSlotPrice00, "market_title05" }, + { FocCommandBarComponentIds.BlackMarketSlotPrice01, "market_title07" }, + { FocCommandBarComponentIds.BlackMarketSlotPrice02, "market_title09" }, + { FocCommandBarComponentIds.BlackMarketSlotPrice03, "market_title11" }, + { FocCommandBarComponentIds.BlackMarketSlotPrice04, "market_title13" }, + { FocCommandBarComponentIds.SabotageShell, "bm_frame" }, + { FocCommandBarComponentIds.SabotageTitle, "bm_title" }, + { FocCommandBarComponentIds.SabotageTitle2, "bm_title01" }, + { FocCommandBarComponentIds.SabotageTechLevel, "bm_tech_level" }, + { FocCommandBarComponentIds.SabotageCancel, "bm_close" }, + { FocCommandBarComponentIds.SabotageDescription, "bm_text_steal" }, + { FocCommandBarComponentIds.SabotageSlot00, "bm_0000" }, + { FocCommandBarComponentIds.SabotageSlot01, "bm_0001" }, + { FocCommandBarComponentIds.SabotageSlot02, "bm_0100" }, + { FocCommandBarComponentIds.SabotageSlot03, "bm_0101" }, + { FocCommandBarComponentIds.SabotageSlot04, "bm_0200" }, + { FocCommandBarComponentIds.SabotageSlot05, "bm_0201" }, + { FocCommandBarComponentIds.SabotageSlot06, "bm_0300" }, + { FocCommandBarComponentIds.SabotageSlot07, "bm_0301" }, + { FocCommandBarComponentIds.SabotageSlot08, "bm_0400" }, + { FocCommandBarComponentIds.SabotageSlot09, "bm_0401" }, + { FocCommandBarComponentIds.SabotagePrice00, "bm_title_000" }, + { FocCommandBarComponentIds.SabotagePrice01, "bm_title_001" }, + { FocCommandBarComponentIds.SabotagePrice02, "bm_title_100" }, + { FocCommandBarComponentIds.SabotagePrice03, "bm_title_101" }, + { FocCommandBarComponentIds.SabotagePrice04, "bm_title_200" }, + { FocCommandBarComponentIds.SabotagePrice05, "bm_title_201" }, + { FocCommandBarComponentIds.SabotagePrice06, "bm_title_300" }, + { FocCommandBarComponentIds.SabotagePrice07, "bm_title_301" }, + { FocCommandBarComponentIds.SabotagePrice08, "bm_title_400" }, + { FocCommandBarComponentIds.SabotagePrice09, "bm_title_401" }, + { FocCommandBarComponentIds.SabotageName00, "bm_title_402" }, + { FocCommandBarComponentIds.SabotageName01, "bm_title_403" }, + { FocCommandBarComponentIds.SabotageName02, "bm_title_404" }, + { FocCommandBarComponentIds.SabotageName03, "bm_title_405" }, + { FocCommandBarComponentIds.SabotageName04, "bm_title_406" }, + { FocCommandBarComponentIds.SabotageName05, "bm_title_407" }, + { FocCommandBarComponentIds.SabotageName06, "bm_title_408" }, + { FocCommandBarComponentIds.SabotageName07, "bm_title_409" }, + { FocCommandBarComponentIds.SabotageName08, "bm_title_4010" }, + { FocCommandBarComponentIds.SabotageName09, "bm_title_4011" }, + { FocCommandBarComponentIds.PlanetaryBombardment, "pb_switch" }, + { FocCommandBarComponentIds.PlanetaryBombardmentRecharge, "pb_switch_recharge" }, + { FocCommandBarComponentIds.SuperLaser, "sl_switch" }, + { FocCommandBarComponentIds.SuperLaserRecharge, "sl_switch_recharge" }, + { FocCommandBarComponentIds.GenericFlytext, "generic_flytext" }, + { FocCommandBarComponentIds.BribedIcon, "bribed_icon" }, + { FocCommandBarComponentIds.SurfaceModIcon, "surface_mod_icon" }, + { FocCommandBarComponentIds.RemoteBombIcon, "remote_bomb_icon" }, + { FocCommandBarComponentIds.CorruptionShell, "corruption_shell" }, + { FocCommandBarComponentIds.CorruptionTitle, "corrupt_planet_title00" }, + { FocCommandBarComponentIds.CorruptionPlanetName, "corrupt_planet_title01" }, + { FocCommandBarComponentIds.CorruptionEncyclopedia, "i_corrupt_encycl" }, + { FocCommandBarComponentIds.CorruptionClose, "corrupt_close" }, + { FocCommandBarComponentIds.CorruptionPlanetModel, "i_planet_image" }, + { FocCommandBarComponentIds.CorruptionInfo00, "corrupt_planet_defiler_00" }, + { FocCommandBarComponentIds.CorruptionInfo01, "corrupt_planet_defiler_01" }, + { FocCommandBarComponentIds.CorruptionChoiceIcon0, "corrupt_button_icon_a" }, + { FocCommandBarComponentIds.CorruptionChoiceIcon1, "corrupt_button_icon_b" }, + { FocCommandBarComponentIds.CorruptionChoiceIcon2, "corrupt_button_icon_c" }, + { FocCommandBarComponentIds.CorruptionChoiceCost0, "corrupt_cost_a" }, + { FocCommandBarComponentIds.CorruptionChoiceCost1, "corrupt_cost_b" }, + { FocCommandBarComponentIds.CorruptionChoiceCost2, "corrupt_cost_c" }, + { FocCommandBarComponentIds.CorruptionChoiceTitle0, "corrupt_title_a" }, + { FocCommandBarComponentIds.CorruptionChoiceTitle1, "corrupt_title_b" }, + { FocCommandBarComponentIds.CorruptionChoiceTitle2, "corrupt_title_c" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText00, "corrupt_text00a" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText01, "corrupt_text01a" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText02, "corrupt_text02a" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText03, "corrupt_text03a" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText10, "corrupt_text00b" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText11, "corrupt_text01b" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText12, "corrupt_text02b" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText13, "corrupt_text03b" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText20, "corrupt_text00c" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText21, "corrupt_text01c" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText22, "corrupt_text02c" }, + { FocCommandBarComponentIds.CorruptionChoiceBenefitText23, "corrupt_text03c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirement0, "corrupt_text04a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirement1, "corrupt_text04b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirement2, "corrupt_text04c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0001, "corrupt_icon_00a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0002, "corrupt_icon_01a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0003, "corrupt_icon_02a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0000, "corrupt_icon_03a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0100, "corrupt_icon_04a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0101, "corrupt_icon_05a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0102, "corrupt_icon_06a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon0103, "corrupt_icon_07a" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1000, "corrupt_icon_00b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1001, "corrupt_icon_01b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1002, "corrupt_icon_02b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1003, "corrupt_icon_03b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1100, "corrupt_icon_04b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1101, "corrupt_icon_05b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1102, "corrupt_icon_06b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon1103, "corrupt_icon_07b" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2000, "corrupt_icon_00c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2001, "corrupt_icon_01c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2002, "corrupt_icon_02c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2003, "corrupt_icon_03c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2100, "corrupt_icon_04c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2101, "corrupt_icon_05c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2102, "corrupt_icon_06c" }, + { FocCommandBarComponentIds.CorruptionChoiceRequirementIcon2103, "corrupt_icon_07c" }, + { FocCommandBarComponentIds.HackSuperWeaponShell, "hack_shell" }, + { FocCommandBarComponentIds.HackSuperWeaponTitle, "hack_title01" }, + { FocCommandBarComponentIds.HackSuperWeaponText00, "hack_text_00" }, + { FocCommandBarComponentIds.HackSuperWeaponText01, "hack_text_01" }, + { FocCommandBarComponentIds.HackSuperWeaponText02, "hack_text_02" }, + { FocCommandBarComponentIds.HackSuperWeaponText03, "hack_text_03" }, + { FocCommandBarComponentIds.HackSuperWeaponText04, "hack_text_04" }, + { FocCommandBarComponentIds.HackSuperWeaponText05, "hack_text_05" }, + { FocCommandBarComponentIds.HackSuperWeaponText06, "hack_text_06" }, + { FocCommandBarComponentIds.HackSuperWeaponCost, "hack_cost" }, + { FocCommandBarComponentIds.HackSuperWeaponAccept, "hack_hack" }, + { FocCommandBarComponentIds.HackSuperWeaponCancel, "hack_close" }, + }; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs new file mode 100644 index 0000000..057745e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/CommandBar/Xml/CommandBarComponentData.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Numerics; +using PG.Commons.Hashing; +using PG.Commons.Numerics; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.CommandBar.Xml; + +public sealed class CommandBarComponentData(string name, Crc32 crc, XmlLocationInfo location) : NamedXmlObject(name, crc, location) +{ + public const float DefaultScale = 1.0f; + public const float DefaultBlinkRate = 0.2f; + public const int DefaultBaseLayer = 2; + public static readonly Vector2 DefaultOffsetWidescreenValue = new(9.9999998e17f, 9.9999998e17f); + public static readonly Vector4Int WhiteColor = new(255, 255, 255, 255); + + public IReadOnlyList SelectedTextureNames { get; internal set; } = []; + public IReadOnlyList BlankTextureNames { get; internal set; } = []; + public IReadOnlyList IconAlternateTextureNames { get; internal set; } = []; + public IReadOnlyList MouseOverTextureNames { get; internal set; } = []; + public IReadOnlyList BarTextureNames { get; internal set; } = []; + public IReadOnlyList BarOverlayNames { get; internal set; } = []; + public IReadOnlyList AlternateFontNames { get; internal set; } = []; + public IReadOnlyList TooltipTexts { get; internal set; } = []; + public IReadOnlyList LowerEffectTextureNames { get; internal set; } = []; + public IReadOnlyList UpperEffectTextureNames { get; internal set; } = []; + public IReadOnlyList OverlayTextureNames { get; internal set; } = []; + public IReadOnlyList Overlay2TextureNames { get; internal set; } = []; + + public string? IconTextureName { get; internal set; } + public string? DisabledTextureName { get; internal set; } + public string? FlashTextureName { get; internal set; } + public string? BuildTextureName { get; internal set; } + public string? ModelName { get; internal set; } + public string? BoneName { get; internal set; } + public string? CursorTextureName { get; internal set; } + public string? FontName { get; internal set; } + public string? ClickSfx { get; internal set; } + public string? MouseOverSfx { get; internal set; } + public string? RightClickSfx { get; internal set; } + public string? Type { get; internal set; } + public string? Group { get; internal set; } + public string? AssociatedText { get; internal set; } + + public bool DragAndDrop { get; internal set; } + public bool DragSelect { get; internal set; } + public bool Receptor { get; internal set; } + public bool Toggle { get; internal set; } + public bool Tab { get; internal set; } + public bool Hidden { get; internal set; } + public bool ClearColor { get; internal set; } + public bool Disabled { get; internal set; } + public bool SwapTexture { get; internal set; } + public bool DrawAdditive { get; internal set; } + public bool Editable { get; internal set; } + public bool TextOutline { get; internal set; } + public bool Stackable { get; internal set; } + public bool ModelOffsetX { get; internal set; } + public bool ModelOffsetY { get; internal set; } + public bool ScaleModelX { get; internal set; } + public bool ScaleModelY { get; internal set; } + public bool Collideable { get; internal set; } + public bool TextEmboss { get; internal set; } + public bool ShouldGhost { get; internal set; } + public bool GhostBaseOnly { get; internal set; } + public bool CrossFade { get; internal set; } + public bool LeftJustified { get; internal set; } + public bool RightJustified { get; internal set; } + public bool NoShell { get; internal set; } + public bool SnapDrag { get; internal set; } + public bool SnapLocation { get; internal set; } + public bool OffsetRender { get; internal set; } + public bool BlinkFade { get; internal set; } + public bool NoHiddenCollision { get; internal set; } + public bool ManualOffset { get; internal set; } + public bool SelectedAlpha { get; internal set; } + public bool PixelAlign { get; internal set; } + public bool CanDragStack { get; internal set; } + public bool CanAnimate { get; internal set; } + public bool LoopAnim { get; internal set; } + public bool SmoothBar { get; internal set; } + public bool OutlinedBar { get; internal set; } + public bool DragBack { get; internal set; } + public bool LowerEffectAdditive { get; internal set; } + public bool UpperEffectAdditive { get; internal set; } + public bool ClickShift { get; internal set; } + public bool TutorialScene { get; internal set; } + public bool DialogScene { get; internal set; } + public bool ShouldRenderAtDragPos { get; internal set; } + public bool DisableDarken { get; internal set; } + public bool AnimateBack { get; internal set; } + public bool AnimateUpperEffect { get; internal set; } + + public int BaseLayer { get; internal set; } = DefaultBaseLayer; + public int MaxBarLevel { get; internal set; } + + public uint MaxTextLength { get; internal set; } + public int FontPointSize { get; internal set; } + + public float ScaleDuration { get; internal set; } + public float BlinkDuration { get; internal set; } + public float MaxTextWidth { get; internal set; } + public float BlinkRate { get; internal set; } = DefaultBlinkRate; + public float Scale { get; internal set; } = DefaultScale; + public float AnimFps { get; internal set; } + + public Vector2 Size { get; internal set; } + public Vector2 TextOffset { get; internal set; } + public Vector2 TextOffset2 { get; internal set; } + public Vector2 Offset { get; internal set; } + public Vector2 DefaultOffset { get; internal set; } + public Vector2 DefaultOffsetWidescreen { get; internal set; } = DefaultOffsetWidescreenValue; + public Vector2 IconOffset { get; internal set; } + public Vector2 MouseOverOffset { get; internal set; } + public Vector2 DisabledOffset { get; internal set; } + public Vector2 BuildDialOffset { get; internal set; } + public Vector2 BuildDial2Offset { get; internal set; } + public Vector2 LowerEffectOffset { get; internal set; } + public Vector2 UpperEffectOffset { get; internal set; } + public Vector2 OverlayOffset { get; internal set; } + public Vector2 Overlay2Offset { get; internal set; } + + public Vector4Int? Color { get; internal set; } = WhiteColor; + public Vector4Int? TextColor { get; internal set; } + public Vector4Int? TextColor2 { get; internal set; } + public Vector4Int? MaxBarColor { get; internal set; } = WhiteColor; + + internal override void CoerceValues() + { + base.CoerceValues(); + if (AlternateFontNames.Count == 0) + return; + var newFontNames = new string[AlternateFontNames.Count]; + for (var i = 0; i < AlternateFontNames.Count; i++) + { + var current = AlternateFontNames[i]; + + if (current.AsSpan().IndexOf('_') != -1) + newFontNames[i] = current.Replace('_', ' '); + else + newFontNames[i] = current; + } + AlternateFontNames = new ReadOnlyCollection(newFontNames); + } +} + diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs deleted file mode 100644 index a2a4498..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameConstants.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PG.StarWarsGame.Engine.DataTypes; - -public class GameConstants -{ - -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs deleted file mode 100644 index 6cfd4c0..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using PG.Commons.Hashing; -using PG.StarWarsGame.Files.XML; - -namespace PG.StarWarsGame.Engine.DataTypes; - -public sealed class GameObject : XmlObject -{ - internal GameObject( - string type, - string name, - Crc32 nameCrc, - GameObjectType estimatedType, - IReadOnlyValueListDictionary properties, - XmlLocationInfo location) - : base(name, nameCrc, properties, location) - { - Type = type ?? throw new ArgumentNullException(nameof(type)); - EstimatedType = estimatedType; - } - - public string Type { get; } - - - public GameObjectType EstimatedType { get; } - - /// - /// Gets all model files (including particles) the game object references. - /// - public ISet Models - { - get - { - var models = XmlProperties.AggregateValues - (new HashSet - { - "Galactic_Model_Name", - "Destroyed_Galactic_Model_Name", - "Land_Model_Name", - "Space_Model_Name", - "Model_Name", - "Tactical_Model_Name", - "Galactic_Fleet_Override_Model_Name", - "GUI_Model_Name", - "GUI_Model", - // This can either be a model or a unit reference - "Land_Model_Anim_Override_Name", - "xxxSpace_Model_Name", - "Damaged_Smoke_Asset_Name" - }, v => v.EndsWith(".alo", StringComparison.OrdinalIgnoreCase), - ValueListDictionaryExtensions.AggregateStrategy.LastValuePerKey); - - var terrainMappedModels = LandTerrainModelMapping?.Select(x => x.Model); - if (terrainMappedModels is null) - return new HashSet(models, StringComparer.OrdinalIgnoreCase); - - return new HashSet(models.Concat(terrainMappedModels), StringComparer.OrdinalIgnoreCase); - } - } - - public IList<(string Terrain, string Model)>? LandTerrainModelMapping => - GetLastPropertyOrDefault>("Land_Terrain_Model_Mapping"); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs deleted file mode 100644 index 9920449..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.Xml.Tags; -using PG.StarWarsGame.Files.XML; - -namespace PG.StarWarsGame.Engine.DataTypes; - -public sealed class SfxEvent : XmlObject -{ - public const byte MaxVolumeValue = 100; - public const byte MaxPitchValue = 200; - public const byte MinPitchValue = 50; - public const byte MaxPan2dValue = 100; - public const byte MinPriorityValue = 1; - public const byte MaxPriorityValue = 5; - public const byte MaxProbability = 100; - public const sbyte MinMaxInstances = 0; - public const sbyte InfinitivePlayCount = -1; - public const float MinLoopSeconds = 0.0f; - public const float MinVolumeSaturation = 0.0f; - - // Default values which are not the default value of the type - public const byte DefaultPriority = 3; - public const bool DefaultIs3d = true; - public const byte DefaultProbability = 100; - public const sbyte DefaultPlayCount = 1; - public const sbyte DefaultMaxInstances = 1; - public const byte DefaultMinVolume = 100; - public const byte DefaultMaxVolume = 100; - public const byte DefaultMinPitch = 100; - public const byte DefaultMaxPitch = 100; - public const byte DefaultMinPan2d = 50; - public const byte DefaultMaxPan2d = 50; - public const float DefaultVolumeSaturationDistance = 300.0f; - - private SfxEvent? _preset; - private string? _presetName; - private string? _overlapTestName; - private string? _chainedSfxEvent; - private IReadOnlyList? _textIds; - private IReadOnlyList? _preSamples; - private IReadOnlyList? _samples; - private IReadOnlyList? _postSamples; - private bool? _isPreset; - private bool? _is2D; - private bool? _is3D; - private bool? _isGui; - private bool? _isHudVo; - private bool? _isUnitResponseVo; - private bool? _isAmbientVo; - private bool? _isLocalized; - private bool? _playSequentially; - private bool? _killsPreviousObjectsSfx; - private byte? _priority; - private byte? _probability; - private sbyte? _playCount; - private sbyte? _maxInstances; - private uint? _minPredelay; - private uint? _maxPredelay; - private uint? _minPostdelay; - private uint? _maxPostdelay; - private float? _loopFadeInSeconds; - private float? _loopFadeOutSeconds; - private float? _volumeSaturationDistance; - - private static readonly Func PriorityCoercion = priority => - { - if (!priority.HasValue) - return DefaultPriority; - if (priority < MinPriorityValue) - return MinPriorityValue; - if (priority > MaxPriorityValue) - return MaxPriorityValue; - return priority; - }; - - private static readonly Func MaxInstancesCoercion = maxInstances => - { - if (!maxInstances.HasValue) - return DefaultMaxInstances; - if (maxInstances < MinMaxInstances) - return MinMaxInstances; - return maxInstances; - }; - - private static readonly Func ProbabilityCoercion = probability => - { - if (!probability.HasValue) - return DefaultProbability; - if (probability > MaxProbability) - return MaxProbability; - return probability; - }; - - private static readonly Func PlayCountCoercion = playCount => - { - if (!playCount.HasValue) - return DefaultPlayCount; - if (playCount < InfinitivePlayCount) - return InfinitivePlayCount; - return playCount; - }; - - private static readonly Func LoopAndSaturationCoercion = loopSeconds => - { - if (!loopSeconds.HasValue) - return DefaultVolumeSaturationDistance; - if (loopSeconds < MinLoopSeconds) - return MinLoopSeconds; - return loopSeconds; - }; - - public bool IsPreset => LazyInitValue(ref _isPreset, SfxEventXmlTags.IsPreset, false)!.Value; - - public bool Is3D => LazyInitValue(ref _is3D, SfxEventXmlTags.Is3D, DefaultIs3d)!.Value; - - public bool Is2D => LazyInitValue(ref _is2D, SfxEventXmlTags.Is2D, false)!.Value; - - public bool IsGui => LazyInitValue(ref _isGui, SfxEventXmlTags.IsGui, false)!.Value; - - public bool IsHudVo => LazyInitValue(ref _isHudVo, SfxEventXmlTags.IsHudVo, false)!.Value; - - public bool IsUnitResponseVo => LazyInitValue(ref _isUnitResponseVo, SfxEventXmlTags.IsUnitResponseVo, false)!.Value; - - public bool IsAmbientVo => LazyInitValue(ref _isAmbientVo, SfxEventXmlTags.IsAmbientVo, false)!.Value; - - public bool IsLocalized => LazyInitValue(ref _isLocalized, SfxEventXmlTags.Localize, false)!.Value; - - public SfxEvent? Preset => LazyInitValue(ref _preset, SfxEventXmlTags.PresetXRef, null); - - public string? UsePresetName => LazyInitValue(ref _presetName, SfxEventXmlTags.UsePreset, null); - - public bool PlaySequentially => LazyInitValue(ref _playSequentially, SfxEventXmlTags.PlaySequentially, false)!.Value; - - public IEnumerable AllSamples => PreSamples.Concat(Samples).Concat(PostSamples); - - public IReadOnlyList PreSamples => LazyInitValue(ref _preSamples, SfxEventXmlTags.PreSamples, Array.Empty()); - - public IReadOnlyList Samples => LazyInitValue(ref _samples, SfxEventXmlTags.Samples, Array.Empty()); - - public IReadOnlyList PostSamples => LazyInitValue(ref _postSamples, SfxEventXmlTags.PostSamples, Array.Empty()); - - public IReadOnlyList LocalizedTextIDs => LazyInitValue(ref _textIds, SfxEventXmlTags.TextID, Array.Empty()); - - public byte Priority => LazyInitValue(ref _priority, SfxEventXmlTags.Priority, DefaultPriority, PriorityCoercion)!.Value; - - public byte Probability => LazyInitValue(ref _probability, SfxEventXmlTags.Probability, DefaultProbability, ProbabilityCoercion)!.Value; - - public sbyte PlayCount => LazyInitValue(ref _playCount, SfxEventXmlTags.PlayCount, DefaultPlayCount, PlayCountCoercion)!.Value; - - public float LoopFadeInSeconds => LazyInitValue(ref _loopFadeInSeconds, SfxEventXmlTags.LoopFadeInSeconds, 0f, LoopAndSaturationCoercion)!.Value; - - public float LoopFadeOutSeconds => LazyInitValue(ref _loopFadeOutSeconds, SfxEventXmlTags.LoopFadeOutSeconds, 0f, LoopAndSaturationCoercion)!.Value; - - public sbyte MaxInstances => LazyInitValue(ref _maxInstances, SfxEventXmlTags.MaxInstances, DefaultMaxInstances, MaxInstancesCoercion)!.Value; - - public uint MinPredelay => LazyInitValue(ref _minPredelay, SfxEventXmlTags.MinPredelay, 0u)!.Value; - - public uint MaxPredelay => LazyInitValue(ref _maxPredelay, SfxEventXmlTags.MaxPredelay, 0u)!.Value; - - public uint MinPostdelay => LazyInitValue(ref _minPostdelay, SfxEventXmlTags.MinPostdelay, 0u)!.Value; - - public uint MaxPostdelay => LazyInitValue(ref _maxPostdelay, SfxEventXmlTags.MaxPostdelay, 0u)!.Value; - - public float VolumeSaturationDistance => LazyInitValue(ref _volumeSaturationDistance, - SfxEventXmlTags.VolumeSaturationDistance, DefaultVolumeSaturationDistance, LoopAndSaturationCoercion)!.Value; - - public bool KillsPreviousObjectsSfx => LazyInitValue(ref _killsPreviousObjectsSfx, SfxEventXmlTags.KillsPreviousObjectSFX, false)!.Value; - - public string? OverlapTestName => LazyInitValue(ref _overlapTestName, SfxEventXmlTags.OverlapTest, null); - - public string? ChainedSfxEventName => LazyInitValue(ref _chainedSfxEvent, SfxEventXmlTags.ChainedSfxEvent, null); - - public byte MinVolume { get; } - - public byte MaxVolume { get; } - - public byte MinPitch { get; } - - public byte MaxPitch { get; } - - public byte MinPan2D { get; } - - public byte MaxPan2D { get; } - - internal SfxEvent(string name, Crc32 nameCrc, IReadOnlyValueListDictionary properties, - XmlLocationInfo location) - : base(name, nameCrc, properties, location) - { - var minMaxVolume = GetMinMaxVolume(properties); - MinVolume = minMaxVolume.min; - MaxVolume = minMaxVolume.max; - - var minMaxPitch = GetMinMaxPitch(properties); - MinPitch = minMaxPitch.min; - MaxPitch = minMaxPitch.max; - - var minMaxPan = GetMinMaxPan2d(properties); - MinPan2D = minMaxPan.min; - MaxPan2D = minMaxPan.max; - } - - private static (byte min, byte max) GetMinMaxVolume(IReadOnlyValueListDictionary properties) - { - return GetMinMaxValues(properties, SfxEventXmlTags.MinVolume, SfxEventXmlTags.MaxVolume, DefaultMinVolume, - DefaultMaxVolume, null, MaxVolumeValue); - } - - private static (byte min, byte max) GetMinMaxPitch(IReadOnlyValueListDictionary properties) - { - return GetMinMaxValues(properties, SfxEventXmlTags.MinPitch, SfxEventXmlTags.MaxPitch, DefaultMinPitch, - DefaultMaxPitch, MinPitchValue, MaxPitchValue); - } - - private static (byte min, byte max) GetMinMaxPan2d(IReadOnlyValueListDictionary properties) - { - return GetMinMaxValues(properties, SfxEventXmlTags.MinPan2D, SfxEventXmlTags.MaxPan2D, DefaultMinPan2d, - DefaultMaxPan2d, null, MaxPan2dValue); - } - - - private static (byte min, byte max) GetMinMaxValues( - IReadOnlyValueListDictionary properties, - string minTag, - string maxTag, - byte defaultMin, - byte defaultMax, - byte? totalMinValue, - byte? totalMaxValue) - { - var minValue = !properties.TryGetLastValue(minTag, out var minObj) ? defaultMin : Convert.ToByte(minObj); - var maxValue = !properties.TryGetLastValue(maxTag, out var maxObj) ? defaultMax : Convert.ToByte(maxObj); - - if (totalMaxValue.HasValue) - { - if (minValue > totalMaxValue) - minValue = totalMaxValue.Value; - if (maxValue > totalMaxValue) - maxValue = totalMaxValue.Value; - } - - if (totalMinValue.HasValue) - { - if (minValue < totalMinValue) - minValue = totalMinValue.Value; - if (maxValue < totalMinValue) - maxValue = totalMinValue.Value; - } - - if (minValue > maxValue) - minValue = maxValue; - - if (maxValue < minValue) - maxValue = minValue; - - return (minValue, maxValue); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs deleted file mode 100644 index b8161b1..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using PG.Commons.DataTypes; -using PG.Commons.Hashing; -using PG.StarWarsGame.Files.XML; - -namespace PG.StarWarsGame.Engine.DataTypes; - -public abstract class XmlObject( - string name, - Crc32 nameCrc, - IReadOnlyValueListDictionary properties, - XmlLocationInfo location) - : IHasCrc32 -{ - public XmlLocationInfo Location { get; } = location; - - public Crc32 Crc32 { get; } = nameCrc; - - public IReadOnlyValueListDictionary XmlProperties { get; } = properties ?? throw new ArgumentNullException(nameof(properties)); - - public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); - - public T? GetLastPropertyOrDefault(string tagName, T? defaultValue = default) - { - if (!XmlProperties.TryGetLastValue(tagName, out var value)) - return defaultValue; - return (T)value; - } - - protected T LazyInitValue(ref T? field, string tag, T defaultValue, Func? coerceFunc = null) - { - if (field is null) - { - if (XmlProperties.TryGetLastValue(tag, out var value)) - { - var tValue = (T)value; - if (coerceFunc is not null) - tValue = coerceFunc(tValue); - field = tValue; - } - else - field = defaultValue; - } - - return field; - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs deleted file mode 100644 index f32f347..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Language; -using PG.StarWarsGame.Engine.Repositories; - -namespace PG.StarWarsGame.Engine.Database; - -internal class GameDatabase : IGameDatabase -{ - public required IGameRepository GameRepository { get; init; } - - public required GameConstants GameConstants { get; init; } - - public required IXmlDatabase GameObjects { get; init; } - - public required IXmlDatabase SfxEvents { get; init; } - - public IEnumerable InstalledLanguages { get; init; } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs deleted file mode 100644 index fc1f5ca..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using AnakinRaW.CommonUtilities; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Engine.Database.Initialization; -using PG.StarWarsGame.Engine.Repositories; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Engine.Database; - -internal class GameDatabaseService : DisposableObject, IXmlParserErrorListener, IGameDatabaseService -{ - public event XmlErrorEventHandler? XmlParseError; - - private readonly IServiceProvider _serviceProvider; - private IPrimitiveXmlErrorParserProvider _primitiveXmlParserErrorProvider; - - public GameDatabaseService(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - _primitiveXmlParserErrorProvider = serviceProvider.GetRequiredService(); - _primitiveXmlParserErrorProvider.XmlParseError += OnXmlParseError; - } - - public async Task CreateDatabaseAsync( - GameEngineType targetEngineType, - GameLocations locations, - CancellationToken cancellationToken = default) - { - var repoFactory = _serviceProvider.GetRequiredService(); - var repository = repoFactory.Create(targetEngineType, locations, this); - - var pipeline = new GameDatabaseCreationPipeline(repository, this, _serviceProvider); - await pipeline.RunAsync(cancellationToken); - return pipeline.GameDatabase; - } - - protected override void DisposeManagedResources() - { - base.DisposeManagedResources(); - _primitiveXmlParserErrorProvider.XmlParseError -= OnXmlParseError; - _primitiveXmlParserErrorProvider = null!; - } - - public void OnXmlParseError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) - { - XmlParseError?.Invoke(parser, error); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs deleted file mode 100644 index 15dd8a1..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Language; -using PG.StarWarsGame.Engine.Repositories; - -namespace PG.StarWarsGame.Engine.Database; - -public interface IGameDatabase -{ - IGameRepository GameRepository { get; } - - GameConstants GameConstants { get; } - - IXmlDatabase GameObjects { get; } - - IXmlDatabase SfxEvents { get; } - - IEnumerable InstalledLanguages { get; } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs deleted file mode 100644 index 575c521..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Engine.Database; - -public interface IGameDatabaseService : IXmlParserErrorProvider, IDisposable -{ - Task CreateDatabaseAsync( - GameEngineType targetEngineType, - GameLocations locations, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs deleted file mode 100644 index 2c1f20e..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading; -using AnakinRaW.CommonUtilities.SimplePipeline.Steps; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine.Repositories; - -namespace PG.StarWarsGame.Engine.Database.Initialization; - -internal abstract class CreateDatabaseStep(IGameRepository repository, IServiceProvider serviceProvider) - : PipelineStep(serviceProvider) where T : class -{ - public T Database { get; private set; } = null!; - - protected abstract string Name { get; } - - protected IGameRepository GameRepository { get; } = repository; - - protected sealed override void RunCore(CancellationToken token) - { - Logger?.LogDebug($"Creating {Name} database..."); - Database = CreateDatabase(); - } - - protected abstract T CreateDatabase(); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs deleted file mode 100644 index 05f6e21..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AnakinRaW.CommonUtilities.SimplePipeline; -using AnakinRaW.CommonUtilities.SimplePipeline.Runners; -using AnakinRaW.CommonUtilities.SimplePipeline.Steps; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Repositories; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Engine.Database.Initialization; - -internal class GameDatabaseCreationPipeline(GameRepository repository, IXmlParserErrorListener xmlParserErrorListener, IServiceProvider serviceProvider) - : Pipeline(serviceProvider) -{ - private ParseSingletonXmlStep _parseGameConstants = null!; - private ParseXmlDatabaseFromContainerStep _parseGameObjects = null!; - private ParseXmlDatabaseFromContainerStep _parseSfxEvents = null!; - - private StepRunner _parseXmlRunner = null!; - - public GameDatabase GameDatabase { get; private set; } = null!; - - protected override Task PrepareCoreAsync() - { - _parseXmlRunner = new ParallelRunner(4, ServiceProvider); - foreach (var xmlParserStep in CreateXmlParserSteps()) - _parseXmlRunner.AddStep(xmlParserStep); - - return Task.FromResult(true); - } - - private IEnumerable CreateXmlParserSteps() - { - // TODO: Use same load order as the engine! - - yield return _parseGameConstants = new ParseSingletonXmlStep("GameConstants", - "DATA\\XML\\GAMECONSTANTS.XML", repository, xmlParserErrorListener, ServiceProvider); - - yield return _parseGameObjects = new ParseXmlDatabaseFromContainerStep("GameObjects", - "DATA\\XML\\GAMEOBJECTFILES.XML", repository, xmlParserErrorListener, ServiceProvider); - - yield return _parseSfxEvents = new ParseXmlDatabaseFromContainerStep("SFXEvents", - "DATA\\XML\\SFXEventFiles.XML", repository, xmlParserErrorListener, ServiceProvider); - - // GUIDialogs.xml - // LensFlares.xml - // SurfaceFX.xml - // TerrainDecalFX.xml - // GraphicDetails.xml - // DynamicTrackFX.xml - // ShadowBlobMaterials.xml - // TacticalCameras.xml - // LightSources.xml - // StarWars3DTextCrawl.xml - // MusicEvents.xml - // SpeechEvents.xml - // GameConstants.xml - // Audio.xml - // WeatherAudio.xml - // HeroClash.xml - // TradeRouteLines.xml - // RadarMap.xml - // WeatherModifiers.xml - // Movies.xml - // LightningEffectTypes.xml - // DifficultyAdjustments.xml - // WeatherScenarios.xml - // UnitAbilityTypes.xml - // BlackMarketItems.xml - // MovementClassTypeDefs.xml - // AITerrainEffectiveness.xml - - - // CONTAINER FILES: - // GameObjectFiles.xml - // CommandBarComponentFiles.xml - // TradeRouteFiles.xml - // HardPointDataFiles.xml - // CampaignFiles.xml - // FactionFiles.xml - // TargetingPrioritySetFiles.xml - // MousePointerFiles.xml - } - - protected override async Task RunCoreAsync(CancellationToken token) - { - Logger?.LogInformation("Creating Game Database..."); - - try - { - try - { - Logger?.LogInformation("Parsing XML Files..."); - _parseXmlRunner.Error += OnError; - await _parseXmlRunner.RunAsync(token); - } - finally - { - Logger?.LogInformation("Finished parsing XML Files"); - _parseXmlRunner.Error -= OnError; - } - - ThrowIfAnyStepsFailed(_parseXmlRunner.Steps); - - token.ThrowIfCancellationRequested(); - - - Logger?.LogInformation("Initializing Language files..."); - var installedLanguages = repository.InitializeInstalledSfxMegFiles(); - Logger?.LogInformation("Finished initializing Language files"); - - - repository.Seal(); - - GameDatabase = new GameDatabase - { - GameRepository = repository, - GameConstants = _parseGameConstants.Database, - GameObjects = _parseGameObjects.Database, - SfxEvents = _parseSfxEvents.Database, - InstalledLanguages = installedLanguages - }; - } - finally - { - Logger?.LogInformation("Finished creating game database"); - } - } - - private sealed class ParseSingletonXmlStep(string name, string xmlFile, IGameRepository repository, IXmlParserErrorListener? listener, IServiceProvider serviceProvider) - : ParseXmlDatabaseStep(xmlFile, repository, listener, serviceProvider) where T : class - { - protected override string Name => name; - - protected override T CreateDatabase(IList parsedDatabaseEntries) - { - if (parsedDatabaseEntries.Count != 1) - throw new InvalidOperationException($"There can be only one {Name} model."); - - return parsedDatabaseEntries.First(); - } - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs deleted file mode 100644 index 5dcde76..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.IO.Abstractions; -using System.Linq; -using System.Xml; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Repositories; -using PG.StarWarsGame.Engine.Xml; -using PG.StarWarsGame.Files.XML; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Engine.Database.Initialization; - -internal class ParseXmlDatabaseFromContainerStep( - string name, - string xmlFile, - IGameRepository repository, - IXmlParserErrorListener? listener, - IServiceProvider serviceProvider) - : CreateDatabaseStep>(repository, serviceProvider) - where T : XmlObject -{ - private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - - protected readonly IPetroglyphXmlFileParserFactory FileParserFactory = serviceProvider.GetRequiredService(); - - protected override string Name => name; - - protected sealed override IXmlDatabase CreateDatabase() - { - using var containerStream = GameRepository.OpenFile(xmlFile); - var containerParser = FileParserFactory.GetFileParser(listener); - Logger?.LogDebug($"Parsing container data '{xmlFile}'"); - var container = containerParser.ParseFile(containerStream); - - var xmlFiles = container.Files.Select(x => _fileSystem.Path.Combine("DATA\\XML", x)).ToList(); - - var parsedEntries = new ValueListDictionary(); - - foreach (var file in xmlFiles) - { - using var fileStream = GameRepository.TryOpenFile(file); - - var parser = FileParserFactory.GetFileParser(listener); - - if (fileStream is null) - { - listener?.OnXmlParseError(parser, XmlParseErrorEventArgs.FromMissingFile(file)); - Logger?.LogWarning($"Could not find XML file '{file}'"); - continue; - } - - Logger?.LogDebug($"Parsing File '{file}'"); - - try - { - parser.ParseFile(fileStream, parsedEntries); - } - catch (XmlException e) - { - listener?.OnXmlParseError(parser, new XmlParseErrorEventArgs(file, null, XmlParseErrorKind.Unknown, e.Message)); - } - } - - return new XmlDatabase(parsedEntries, Services); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs deleted file mode 100644 index 8050f02..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine.Repositories; -using PG.StarWarsGame.Engine.Xml; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Engine.Database.Initialization; - -internal abstract class ParseXmlDatabaseStep( - IList xmlFiles, - IGameRepository repository, - IXmlParserErrorListener? listener, - IServiceProvider serviceProvider) - : CreateDatabaseStep(repository, serviceProvider) - where T : class -{ - protected readonly IPetroglyphXmlFileParserFactory FileParserFactory = serviceProvider.GetRequiredService(); - - protected ParseXmlDatabaseStep(string xmlFile, IGameRepository repository, IXmlParserErrorListener? listener, IServiceProvider serviceProvider) - : this([xmlFile], repository, listener, serviceProvider) - { - } - - protected sealed override T CreateDatabase() - { - var parsedDatabaseEntries = new List(); - foreach (var xmlFile in xmlFiles) - { - using var fileStream = GameRepository.OpenFile(xmlFile); - - var parser = FileParserFactory.GetFileParser(listener); - Logger?.LogDebug($"Parsing File '{xmlFile}'"); - var parsedData = parser.ParseFile(fileStream)!; - parsedDatabaseEntries.Add(parsedData); - } - return CreateDatabase(parsedDatabaseEntries); - } - - protected abstract T CreateDatabase(IList parsedDatabaseEntries); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs deleted file mode 100644 index 38d0a78..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using AnakinRaW.CommonUtilities.Collections; -using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Files.XML; - -namespace PG.StarWarsGame.Engine.Database; - -internal class XmlDatabase(IReadOnlyValueListDictionary parsedObjects, IServiceProvider serviceProvider) : IXmlDatabase - where T : XmlObject -{ - - private readonly IServiceProvider _serviceProvider = - serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - - private readonly IReadOnlyValueListDictionary _parsedObjects = parsedObjects ?? throw new ArgumentNullException(nameof(parsedObjects)); - - public ICollection Entries => _parsedObjects.Values; - - public ICollection EntryKeys => _parsedObjects.Keys; - - public ReadOnlyFrugalList GetEntries(Crc32 key) - { - return _parsedObjects.GetValues(key); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssert.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssert.cs new file mode 100644 index 0000000..5cc4c49 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssert.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public sealed class EngineAssert +{ + private static readonly string ThisNameSpace = typeof(EngineAssert).Namespace!; + private const string NullLiteral = "NULL"; + + public string Value { get; } + + public IReadOnlyCollection Context { get; } + + public string Message { get; } + + public string Method { get; } + + public int MethodOffset { get; } + + public string? TypeName { get; } + + public EngineAssertKind Kind { get; } + + private EngineAssert(EngineAssertKind kind, object? value, IEnumerable context, string? type, string method, int methodOffset, string message) + { + Kind = kind; + Value = value?.ToString() ?? NullLiteral; + Context = [..context]; + Message = message ?? throw new ArgumentNullException(nameof(message)); + TypeName = type ?? throw new ArgumentNullException(nameof(type)); + Method = method ?? throw new ArgumentNullException(nameof(method)); + MethodOffset = methodOffset; + } + + internal static EngineAssert FromNullOrEmpty(string? message = null) + { + return FromNullOrEmpty([], message); + } + + internal static EngineAssert FromNullOrEmpty(IEnumerable context, string? message = null) + { + return Create(EngineAssertKind.NullOrEmptyValue, null, context, message ?? "Expected value to be not null or empty"); + } + + internal static EngineAssert Create(EngineAssertKind kind, object? value, IEnumerable context, string message) + { + var frame = GetCausingFrame(new StackTrace()); + if (frame is null) + return new EngineAssert(kind, value, context, null, "UNKNOWN SOURCE", -1, message); + var offset = frame.GetNativeOffset(); + var method = frame.GetMethod(); + var methodInfo = GetMethodInfo(method); + return new EngineAssert(kind, value, context, methodInfo.type, methodInfo.method, offset, message); + } + + private static StackFrame? GetCausingFrame(StackTrace trace) + { + if (trace.FrameCount == 0) + return null; + for (var i = 0; i < trace.FrameCount; i++) + { + var frame = trace.GetFrame(i); + var method = frame.GetMethod(); + if (method.DeclaringType is null || method.DeclaringType.Namespace?.Equals(ThisNameSpace) == false) + return frame; + } + return null; + } + + private static (string? type, string method) GetMethodInfo(MethodBase method) + { + string? type = null; + if (method.DeclaringType is not null) + type = method.DeclaringType.FullName; + + var methodName = new StringBuilder(); + methodName.Append(method.Name); + methodName.Append('('); + methodName.Append(string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name))); + methodName.Append(')'); + return (type, methodName.ToString()); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssertKind.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssertKind.cs new file mode 100644 index 0000000..e3a2d69 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/EngineAssertKind.cs @@ -0,0 +1,10 @@ +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public enum EngineAssertKind +{ + NullOrEmptyValue, + ValueOutOfRange, + InvalidValue, + CorruptBinary, + FileNotFound, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs new file mode 100644 index 0000000..0030344 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporter.cs @@ -0,0 +1,16 @@ +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public abstract class GameEngineErrorReporter : IGameEngineErrorReporter +{ + public virtual void Report(XmlError error) + { + } + + public virtual void Report(InitializationError error) + { + } + + public virtual void Assert(EngineAssert assert) + { + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporterWrapper.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporterWrapper.cs new file mode 100644 index 0000000..a636aba --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/GameEngineErrorReporterWrapper.cs @@ -0,0 +1,50 @@ +using System; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.ErrorReporting; + +internal sealed class GameEngineErrorReporterWrapper : XmlErrorReporter, IGameEngineErrorReporter +{ + internal event EventHandler? InitializationError; + + private readonly IGameEngineErrorReporter? _errorReporter; + + public GameEngineErrorReporterWrapper(IGameEngineErrorReporter? errorReporter) + { + if (errorReporter is null) + return; + _errorReporter = errorReporter; + } + + public void Report(XmlError error) + { + _errorReporter?.Report(error); + } + + public void Report(InitializationError error) + { + InitializationError?.Invoke(this, error); + _errorReporter?.Report(error); + } + + public void Assert(EngineAssert assert) + { + _errorReporter?.Assert(assert); + } + + public override void Report(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) + { + if (_errorReporter is null) + return; + + Report(new XmlError + { + FileLocation = error.Location, + Parser = parser, + Message = error.Message, + ErrorKind = error.ErrorKind, + Element = error.Element + }); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/IGameEngineErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/IGameEngineErrorReporter.cs new file mode 100644 index 0000000..1294fd1 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/IGameEngineErrorReporter.cs @@ -0,0 +1,10 @@ +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public interface IGameEngineErrorReporter +{ + void Report(XmlError error); + + void Report(InitializationError error); + + void Assert(EngineAssert assert); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/InitializationError.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/InitializationError.cs new file mode 100644 index 0000000..c7f8e27 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/InitializationError.cs @@ -0,0 +1,8 @@ +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public sealed class InitializationError +{ + public required string GameManager { get; init; } + + public required string Message { get; init; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/XmlError.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/XmlError.cs new file mode 100644 index 0000000..83e6e9f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/ErrorReporting/XmlError.cs @@ -0,0 +1,19 @@ +using System.Xml.Linq; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.ErrorReporting; + +public sealed class XmlError +{ + public required XmlLocationInfo FileLocation { get; init; } + + public required IPetroglyphXmlParser Parser { get; init; } + + public XElement? Element { get; init; } + + public required XmlParseErrorKind ErrorKind { get; init; } + + public required string Message { get; init; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs index 72996da..d7eb4af 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/FocHardcodedConstants.cs @@ -5,7 +5,7 @@ namespace PG.StarWarsGame.Engine; public static class FocHardcodedConstants { /// - /// These models are hardcoded into StarWarsG.exe. + /// These models / particles are hardcoded into StarWarsG.exe. /// public static IList HardcodedModels { get; } = new List { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs new file mode 100644 index 0000000..1c36061 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstants.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; + +namespace PG.StarWarsGame.Engine.GameConstants; + +internal class GameConstants(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), IGameConstants +{ + protected override Task InitializeCoreAsync(CancellationToken token) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs new file mode 100644 index 0000000..e98ed52 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/GameConstantsXml.cs @@ -0,0 +1,3 @@ +namespace PG.StarWarsGame.Engine.GameConstants; + +public class GameConstantsXml; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs new file mode 100644 index 0000000..552e406 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameConstants/IGameConstants.cs @@ -0,0 +1,3 @@ +namespace PG.StarWarsGame.Engine.GameConstants; + +public interface IGameConstants; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngine.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngine.cs new file mode 100644 index 0000000..8188117 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameEngine.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.Audio.Sfx; +using PG.StarWarsGame.Engine.CommandBar; +using PG.StarWarsGame.Engine.GameConstants; +using PG.StarWarsGame.Engine.GameObjects; +using PG.StarWarsGame.Engine.GuiDialog; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Engine.Localization; +using PG.StarWarsGame.Engine.Rendering; +using PG.StarWarsGame.Engine.Rendering.Font; + +namespace PG.StarWarsGame.Engine; + +internal sealed class GameEngine : IStarWarsGameEngine +{ + public required GameEngineType EngineType { get; init; } + + public required IPGRender PGRender { get; init; } + + public required IFontManager FontManager { get; init; } + + public required ICommandBarGameManager CommandBar { get; init; } + + public required IGameRepository GameRepository { get; init; } + + public required IGameConstants GameConstants { get; init; } + + public required IGuiDialogManager GuiDialogManager { get; init; } + + public required IGameObjectTypeGameManager GameObjectTypeManager { get; init; } + + public required ISfxEventGameManager SfxGameManager { get; init; } + + public required IEnumerable InstalledLanguages { get; init; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs new file mode 100644 index 0000000..7fa4d02 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameManagerBase.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Threading; +using System.Threading.Tasks; +using AnakinRaW.CommonUtilities.Collections; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; + +namespace PG.StarWarsGame.Engine; + +internal abstract class GameManagerBase(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), IGameManager +{ + protected readonly ValueListDictionary NamedEntries = new(); + + public ICollection Entries => NamedEntries.Values; + + public ICollection EntryKeys => NamedEntries.Keys; + + public ReadOnlyFrugalList GetEntries(Crc32 key) + { + return NamedEntries.GetValues(key); + } +} + +internal abstract class GameManagerBase +{ + public event EventHandler? Initialized; + + private bool _initialized; + private protected readonly GameRepository GameRepository; + protected readonly IServiceProvider ServiceProvider; + protected readonly IFileSystem FileSystem; + protected readonly ILogger? Logger; + + protected readonly GameEngineErrorReporterWrapper ErrorReporter; + + public bool IsInitialized => _initialized; + + protected GameManagerBase(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + { + GameRepository = repository ?? throw new ArgumentNullException(nameof(repository)); + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + Logger = serviceProvider.GetService()?.CreateLogger(GetType()); + FileSystem = serviceProvider.GetRequiredService(); + ErrorReporter = errorReporter ?? throw new ArgumentNullException(nameof(errorReporter)); + } + + public async Task InitializeAsync(CancellationToken token) + { + ThrowIfAlreadyInitialized(); + token.ThrowIfCancellationRequested(); + try + { + await InitializeCoreAsync(token); + _initialized = true; + } + catch (Exception e) + { + Logger?.LogError(e, $"Initialization of {this} failed: {e.Message}"); + throw; + } + OnInitialized(); + } + + public override string ToString() + { + return GetType().Name; + } + + protected abstract Task InitializeCoreAsync(CancellationToken token); + + protected void ThrowIfAlreadyInitialized() + { + if (_initialized) + throw new InvalidOperationException("Game manager is already initialized."); + } + + protected void ThrowIfNotInitialized() + { + if (!_initialized) + throw new InvalidOperationException("Game manager not initialized."); + } + + private void OnInitialized() + { + Initialized?.Invoke(this, EventArgs.Empty); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs new file mode 100644 index 0000000..de7ed01 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObject.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.GameObjects; + +public sealed class GameObject : NamedXmlObject +{ + internal GameObject(string type, string name, Crc32 nameCrc, GameObjectType estimatedType, XmlLocationInfo location) + : base(name, nameCrc, location) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + EstimatedType = estimatedType; + LandTerrainModelMapping = new ReadOnlyDictionary(InternalLandTerrainModelMapping); + } + + public string Type { get; } + + public GameObjectType EstimatedType { get; } + + public string? GalacticModel { get; internal set; } + + public string? DestroyedGalacticModel { get; internal set; } + + public string? LandModel { get; internal set; } + + public string? SpaceModel { get; internal set; } + + public string? TacticalModel { get; internal set; } + + public string? GalacticFleetOverrideModel { get; internal set; } + + public string? GuiModel { get; internal set; } + + public string? ModelName { get; internal set; } + + public string? LandAnimOverrideModel { get; internal set; } + + public string? XxxSpaceModeModel { get; internal set; } + + public string? DamagedSmokeAssetModel { get; internal set; } + + public IReadOnlyDictionary LandTerrainModelMapping { get; } + + internal Dictionary InternalLandTerrainModelMapping { get; } = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets all model files (including particles) the game object references. + /// + public IEnumerable Models + { + get + { + var models = new HashSet(StringComparer.OrdinalIgnoreCase); + AddNotEmpty(models, GalacticModel); + AddNotEmpty(models, DestroyedGalacticModel); + AddNotEmpty(models, LandModel); + AddNotEmpty(models, SpaceModel); + AddNotEmpty(models, TacticalModel); + AddNotEmpty(models, GalacticFleetOverrideModel); + AddNotEmpty(models, GuiModel); + AddNotEmpty(models, ModelName); + AddNotEmpty(models, LandAnimOverrideModel, s => s.EndsWith(".alo", StringComparison.OrdinalIgnoreCase)); + AddNotEmpty(models, XxxSpaceModeModel); + AddNotEmpty(models, DamagedSmokeAssetModel); + foreach (var model in InternalLandTerrainModelMapping.Values) + models.Add(model); + + return models; + } + + } + + private static void AddNotEmpty(ISet set, string? value, Predicate? predicate = null) + { + if (value is null) + return; + if (predicate is null || predicate(value)) + set.Add(value); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs similarity index 95% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs index 116fa29..0b84517 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObjectType.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectType.cs @@ -1,4 +1,4 @@ -namespace PG.StarWarsGame.Engine.DataTypes; +namespace PG.StarWarsGame.Engine.GameObjects; public enum GameObjectType { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeGameManager.cs new file mode 100644 index 0000000..407d9c5 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/GameObjectTypeGameManager.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Engine.Xml.Parsers; + +namespace PG.StarWarsGame.Engine.GameObjects; + +internal class GameObjectTypeGameManager(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), IGameObjectTypeGameManager +{ + protected override async Task InitializeCoreAsync(CancellationToken token) + { + Logger?.LogInformation("Parsing GameObjects..."); + + var contentParser = new XmlContainerContentParser(ServiceProvider, ErrorReporter); + + await Task.Run(() => contentParser.ParseEntriesFromFileListXml( + "DATA\\XML\\GAMEOBJECTFILES.XML", + GameRepository, + ".\\DATA\\XML", + NamedEntries, + VerifyFilePathLength), token); + } + + private void VerifyFilePathLength(string filePath) + { + if (filePath.Length > PGConstants.MaxGameObjectDatabaseFileName) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"Game object file '{filePath}' is longer than {PGConstants.MaxGameObjectDatabaseFileName} characters." + }); + } + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs new file mode 100644 index 0000000..f31273f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GameObjects/IGameObjectTypeGameManager.cs @@ -0,0 +1,3 @@ +namespace PG.StarWarsGame.Engine.GameObjects; + +public interface IGameObjectTypeGameManager : IGameManager; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs new file mode 100644 index 0000000..f1ffb04 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/ComponentTextureEntry.cs @@ -0,0 +1,10 @@ +namespace PG.StarWarsGame.Engine.GuiDialog; + +public readonly struct ComponentTextureEntry(GuiComponentType componentType, string texture, bool isOverride) +{ + public string Texture { get; } = texture; + + public GuiComponentType ComponentType { get; } = componentType; + + public bool IsOverride { get; } = isOverride; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs new file mode 100644 index 0000000..548cfee --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiComponentType.cs @@ -0,0 +1,93 @@ +namespace PG.StarWarsGame.Engine.GuiDialog; + +public enum GuiComponentType +{ + ButtonLeft = 0x0, + ButtonMiddle = 0x1, + ButtonRight = 0x2, + ButtonLeftMouseOver = 0x3, + ButtonMiddleMouseOver = 0x4, + ButtonRightMouseOver = 0x5, + ButtonLeftPressed = 0x6, + ButtonMiddlePressed = 0x7, + ButtonRightPressed = 0x8, + ButtonLeftDisabled = 0x9, + ButtonMiddleDisabled = 0xA, + ButtonRightDisabled = 0xB, + CheckOff = 0xC, + CheckOn = 0xD, + DialLeft = 0xE, + DialMiddle = 0xF, + DialRight = 0x10, + DialPlus = 0x11, + DialPlusMouseOver = 0x12, + DialPlusPressed = 0x13, + DialMinus = 0x14, + DialMinusMouseOver = 0x15, + DialMinusPressed = 0x16, + DialTab = 0x17, + FrameBottom = 0x18, + FrameBottomLeft = 0x19, + FrameBottomRight = 0x1A, + FrameBackground = 0x1B, + FrameLeft = 0x1C, + FrameRight = 0x1D, + FrameTop = 0x1E, + FrameTopLeft = 0x1F, + FrameTopRight = 0x20, + FrameTopTransitionLeft = 0x21, + FrameTopTransitionRight = 0x22, + FrameBottomTransitionLeft = 0x23, + FrameBottomTransitionRight = 0x24, + FrameLeftTransitionTop = 0x25, + FrameLeftTransitionBottom = 0x26, + FrameRightTransitionTop = 0x27, + FrameRightTransitionBottom = 0x28, + RadioOff = 0x29, + RadioOn = 0x2A, + RadioDisabled = 0x2B, + RadioMouseOver = 0x2C, + ScrollDownButton = 0x2D, + ScrollDownButtonPressed = 0x2E, + ScrollDownButtonMouseOver = 0x2F, + ScrollMiddle = 0x30, + ScrollTab = 0x31, + ScrollUpButton = 0x32, + ScrollUpButtonPressed = 0x33, + ScrollUpButtonMouseOver = 0x34, + ScrollUpButtonDisabled = 0x35, + ScrollDownButtonDisabled = 0x36, + ScrollMiddleDisabled = 0x37, + ScrollTabDisabled = 0x38, + TrackbarScrollDownButton = 0x39, + TrackbarScrollDownButtonPressed = 0x3A, + TrackbarScrollDownButtonMouseOver = 0x3B, + TrackbarScrollMiddle = 0x3C, + TrackbarScrollTab = 0x3D, + TrackbarScrollUpButton = 0x3E, + TrackbarScrollUpButtonPressed = 0x3F, + TrackbarScrollUpButtonMouseOver = 0x40, + TrackbarScrollUpButtonDisabled = 0x41, + TrackbarScrollDownButtonDisabled = 0x42, + TrackbarScrollMiddleDisabled = 0x43, + TrackbarScrollTabDisabled = 0x44, + SmallFrameBottom = 0x45, + SmallFrameBottomLeft = 0x46, + SmallFrameBottomRight = 0x47, + SmallFrameMiddleLeft = 0x48, + SmallFrameMiddleRight = 0x49, + SmallFrameTop = 0x4A, + SmallFrameTopLeft = 0x4B, + SmallFrameTopRight = 0x4C, + SmallFrameBackground = 0x4D, + ComboboxPopdown = 0x4E, + ComboboxPopdownMouseOver = 0x4F, + ComboboxPopdownPressed = 0x50, + ComboboxTextBox = 0x51, + ComboboxLeftCap = 0x52, + ProgressLeft = 0x53, + ProgressMiddleOff = 0x54, + ProgressMiddleOn = 0x55, + ProgressRight = 0x56, + Scanlines = 0x57, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs new file mode 100644 index 0000000..82ce825 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.GuiDialog.Xml; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Files.MTD.Binary; +using PG.StarWarsGame.Files.MTD.Files; +using PG.StarWarsGame.Files.MTD.Services; + +namespace PG.StarWarsGame.Engine.GuiDialog; + +internal partial class GuiDialogGameManager(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : GameManagerBase(repository, errorReporter, serviceProvider), IGuiDialogManager +{ + private readonly IMtdFileService _mtdFileService = serviceProvider.GetRequiredService(); + private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService(); + + // Unlike other strings for this game, the component's (aka gadget) name is case-sensitive. + private readonly Dictionary> _perComponentTextures = new(StringComparer.Ordinal); + private readonly Dictionary _defaultTextures = new(); + private ReadOnlyDictionary _defaultTexturesRo = null!; + + private bool _megaTextureExists; + private string? _megaTextureFileName; + + + public IMtdFile? MtdFile + { + get + { + ThrowIfNotInitialized(); + return field; + } + private set + { + ThrowIfAlreadyInitialized(); + field = value; + } + } + + public GuiDialogsXml? GuiDialogsXml + { + get + { + ThrowIfNotInitialized(); + return field; + } + private set + { + ThrowIfAlreadyInitialized(); + field = value; + } + } + + public IReadOnlyCollection Components + { + get + { + ThrowIfNotInitialized(); + return _perComponentTextures.Keys; + } + } + + public IReadOnlyDictionary DefaultTextureEntries + { + get + { + ThrowIfNotInitialized(); + return _defaultTexturesRo; + } + } + + + public IReadOnlyDictionary GetTextureEntries(string component, out bool componentExist) + { + if (!_perComponentTextures.TryGetValue(component, out var textures)) + { + Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures."); + componentExist = false; + return DefaultTextureEntries; + } + + componentExist = true; + return new ReadOnlyDictionary(textures); + } + + public bool TryGetTextureEntry(string component, GuiComponentType key, out ComponentTextureEntry texture) + { + if (!_perComponentTextures.TryGetValue(component, out var textures)) + { + Logger?.LogDebug($"The component '{component}' has no overrides. Using default textures."); + textures = _defaultTextures; + } + + return textures.TryGetValue(key, out texture); + } + + public bool TextureExists( + in ComponentTextureEntry textureInfo, + out GuiTextureOrigin textureOrigin, + out bool isNone, + bool buttonMiddleInRepoMode = false) + { + if (textureInfo.Texture == "none") + { + textureOrigin = default; + isNone = true; + return false; + } + + isNone = false; + + // Apparently, Scanlines only use the repository and not the MTD. + if (textureInfo.ComponentType == GuiComponentType.Scanlines) + { + textureOrigin = GuiTextureOrigin.Repository; + return GameRepository.TextureRepository.FileExists(textureInfo.Texture); + } + + // The engine uses ButtonMiddle to switch to the special button mode. + // It searches first in the repo and then falls back to MTD + // (but only for this very type; the variants do not fallback to MTD). + if (textureInfo.ComponentType == GuiComponentType.ButtonMiddle) + { + if (GameRepository.TextureRepository.FileExists(textureInfo.Texture)) + { + textureOrigin = GuiTextureOrigin.Repository; + return true; + } + } + + // The engine does not fallback to MTD once it is in this special Button mode. + if (buttonMiddleInRepoMode && textureInfo.ComponentType is + GuiComponentType.ButtonMiddleDisabled or + GuiComponentType.ButtonMiddleMouseOver or + GuiComponentType.ButtonMiddlePressed) + { + textureOrigin = GuiTextureOrigin.Repository; + return GameRepository.TextureRepository.FileExists(textureInfo.Texture); + } + + if (textureInfo.Texture.Length <= 63 && MtdFile is not null && _megaTextureExists) + { + var crc32 = _hashingService.GetCrc32Upper(textureInfo.Texture.AsSpan(), MtdFileConstants.NameEncoding); + if (MtdFile.Content.Contains(crc32)) + { + textureOrigin = GuiTextureOrigin.MegaTexture; + return true; + } + } + + // The background image for frames include a fallback the repository. + if (textureInfo.ComponentType == GuiComponentType.FrameBackground) + { + textureOrigin = GuiTextureOrigin.Repository; + return GameRepository.TextureRepository.FileExists(textureInfo.Texture); + } + + textureOrigin = default; + return false; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs new file mode 100644 index 0000000..c3814ea --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiDialogGameManager_Initialization.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AnakinRaW.CommonUtilities.Collections; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.GuiDialog.Xml; +using PG.StarWarsGame.Engine.Xml.Parsers.File; +using PG.StarWarsGame.Engine.Xml.Tags; +using PG.StarWarsGame.Files.Binary; + +namespace PG.StarWarsGame.Engine.GuiDialog; + +partial class GuiDialogGameManager +{ + public const int MegaTextureMaxFilePathLength = 255; + + protected override Task InitializeCoreAsync(CancellationToken token) + { + return Task.Run(() => + { + var guiDialogParser = new GuiDialogParser(ServiceProvider, ErrorReporter); + + _defaultTexturesRo = new ReadOnlyDictionary(_defaultTextures); + + Logger?.LogInformation("Parsing GuiDialogs..."); + using var fileStream = GameRepository.TryOpenFile("DATA\\XML\\GUIDIALOGS.XML"); + + if (fileStream is null) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = "Unable to find GuiDialogs.xml" + }); + return; + } + + var guiDialogs = guiDialogParser.ParseFile(fileStream); + if (guiDialogs is null) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = "Unable to parse GuiDialogs.xml" + }); + return; + } + + GuiDialogsXml = guiDialogs; + + InitializeTextures(guiDialogs.TextureData); + + }, token); + } + + private void InitializeTextures(GuiDialogsXmlTextureData textureData) + { + InitializeMegaTextures(textureData); + + var textures = textureData.Textures; + + if (textures.Count == 0) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = "No Textures defined in GuiDialogs.xml" + }); + } + else + { + var defaultCandidate = textures.First(); + + // Regardless of its name, the game treats the first entry as default. + var defaultTextures = InitializeComponentTextures(defaultCandidate, true, out var invalidKeys); + foreach (var entry in defaultTextures) + _defaultTextures.Add(entry.Key, entry.Value); + + ReportInvalidComponent(in invalidKeys); + } + + foreach (var componentTextureData in textures.Skip(1)) + { + // The game only uses the *first* entry. + if (_perComponentTextures.ContainsKey(componentTextureData.Component)) + continue; + + _perComponentTextures.Add(componentTextureData.Component, InitializeComponentTextures(componentTextureData, false, out var invalidKeys)); + ReportInvalidComponent(in invalidKeys); + } + } + + private Dictionary InitializeComponentTextures(XmlComponentTextureData textureData, bool isDefaultComponent, out FrugalList invalidKeys) + { + invalidKeys = new FrugalList(); + + var result = new Dictionary(); + + if (!isDefaultComponent) + { + // This assumes that _defaultTextures is already filled + foreach (var key in _defaultTextures.Keys) + result.Add(key, _defaultTextures[key]); + } + + + foreach (var keyText in textureData.Textures.Keys) + { + if (!ComponentTextureKeyExtensions.TryConvertToKey(keyText.AsSpan(), out var key)) + { + invalidKeys.Add(keyText); + continue; + } + + var textureValue = textureData.Textures.GetLastValue(keyText); + result[key] = new ComponentTextureEntry(key, textureValue, !isDefaultComponent); + } + + return result; + } + + private void InitializeMegaTextures(GuiDialogsXmlTextureData guiDialogs) + { + if (guiDialogs.MegaTexture is null) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = "MtdFile is not defined in GuiDialogs.xml" + }); + } + else + { + var mtdPath = FileSystem.Path.Combine("DATA\\ART\\TEXTURES", $"{guiDialogs.MegaTexture}.mtd"); + + if (mtdPath.Length > MegaTextureMaxFilePathLength) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"Mtd file path is longer than {MegaTextureMaxFilePathLength}." + }); + } + + using var megaTexture = GameRepository.TryOpenFile(mtdPath); + try + { + MtdFile = megaTexture is null ? null : _mtdFileService.Load(megaTexture); + } + catch (BinaryCorruptedException e) + { + var message = $"Failed to load MTD file '{mtdPath}': {e.Message}"; + Logger?.LogError(e, message); + ErrorReporter.Assert(EngineAssert.Create(EngineAssertKind.CorruptBinary, mtdPath, [], message)); + } + } + + if (guiDialogs.CompressedMegaTexture is null) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = "CompressedMegaTexture is not defined in GuiDialogs.xml" + }); + } + + + // TODO: Support using the correct texture based on desired low-RAM flag + _megaTextureFileName = guiDialogs.MegaTexture; + var textureFileNameWithExtension = $"{guiDialogs.MegaTexture}.tga"; + _megaTextureExists = GameRepository.TextureRepository.FileExists(textureFileNameWithExtension); + + if (textureFileNameWithExtension.Length > MegaTextureMaxFilePathLength) + { + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"MegaTexture file path is longer than {MegaTextureMaxFilePathLength}." + }); + } + } + + private void ReportInvalidComponent(in FrugalList invalidKeys) + { + if (invalidKeys.Count == 0) + return; + + ErrorReporter.Report(new InitializationError + { + GameManager = ToString(), + Message = $"The following XML keys are not valid to describe a GUI component: {string.Join(",", invalidKeys)}" + }); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs new file mode 100644 index 0000000..751afea --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/GuiTextureOrigin.cs @@ -0,0 +1,7 @@ +namespace PG.StarWarsGame.Engine.GuiDialog; + +public enum GuiTextureOrigin +{ + MegaTexture, + Repository +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs new file mode 100644 index 0000000..1ac7632 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/IGuiDialogManager.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.GuiDialog.Xml; +using PG.StarWarsGame.Files.MTD.Files; + +namespace PG.StarWarsGame.Engine.GuiDialog; + +public interface IGuiDialogManager +{ + IMtdFile? MtdFile { get; } + + GuiDialogsXml? GuiDialogsXml { get; } + + IReadOnlyCollection Components { get; } + + IReadOnlyDictionary DefaultTextureEntries { get; } + + IReadOnlyDictionary GetTextureEntries(string component, out bool componentExist); + + bool TryGetTextureEntry(string component, GuiComponentType key, out ComponentTextureEntry texture); + + bool TextureExists( + in ComponentTextureEntry textureInfo, + out GuiTextureOrigin textureOrigin, + out bool isNone, + bool buttonMiddleInRepoMode = false); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs new file mode 100644 index 0000000..eda572e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/TypeBasedComponentTextureEntryComparer.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace PG.StarWarsGame.Engine.GuiDialog; + +public sealed class TypeBasedComponentTextureEntryComparer : IEqualityComparer +{ + public static readonly TypeBasedComponentTextureEntryComparer Instance = new(); + + private TypeBasedComponentTextureEntryComparer() + { + } + + public bool Equals(ComponentTextureEntry x, ComponentTextureEntry y) + { + return x.ComponentType.Equals(y.ComponentType); + } + + public int GetHashCode(ComponentTextureEntry obj) + { + return obj.ComponentType.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs new file mode 100644 index 0000000..72cc5a0 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXml.cs @@ -0,0 +1,9 @@ +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.GuiDialog.Xml; + +public class GuiDialogsXml(GuiDialogsXmlTextureData textureData, XmlLocationInfo location) : XmlObject(location) +{ + public GuiDialogsXmlTextureData TextureData { get; } = textureData; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs new file mode 100644 index 0000000..6496417 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/GuiDialogsXmlTextureData.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.GuiDialog.Xml; + +public class GuiDialogsXmlTextureData(IEnumerable textures, XmlLocationInfo location) : XmlObject(location) +{ + public string? MegaTexture { get; init; } + + public string? CompressedMegaTexture { get; init; } + + public IReadOnlyList Textures { get; } = textures.ToList(); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs new file mode 100644 index 0000000..2ed95ce --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/GuiDialog/Xml/XmlComponentTextureData.cs @@ -0,0 +1,14 @@ +using System; +using PG.Commons.Collections; +using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.GuiDialog.Xml; + +public class XmlComponentTextureData(string componentId, IReadOnlyValueListDictionary textures, XmlLocationInfo location) + : XmlObject(location) +{ + public string Component { get; } = componentId ?? throw new ArgumentNullException(componentId); + + public IReadOnlyValueListDictionary Textures { get; } = textures ?? throw new ArgumentNullException(nameof(textures)); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs similarity index 64% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs index c2edb8e..b9f60a7 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IGameManager.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using AnakinRaW.CommonUtilities.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; -namespace PG.StarWarsGame.Engine.Database; +namespace PG.StarWarsGame.Engine; -public interface IXmlDatabase where T : XmlObject +public interface IGameManager { ICollection Entries { get; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/FileFoundInfo.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/FileFoundInfo.cs new file mode 100644 index 0000000..1809a3b --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/FileFoundInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using PG.StarWarsGame.Files.MEG.Data.Entries; + +namespace PG.StarWarsGame.Engine.IO; + +internal readonly ref struct FileFoundInfo +{ + public bool FileFound => FilePath != ReadOnlySpan.Empty || InMeg; + + public bool PathTooLong { get; init; } + + public ReadOnlySpan FilePath { get; } + + public MegDataEntryReference? MegDataEntryReference { get; } + + [MemberNotNullWhen(true, nameof(MegDataEntryReference))] + public bool InMeg => MegDataEntryReference is not null; + + public FileFoundInfo(ReadOnlySpan filePath) + { + FilePath = filePath; + } + + public FileFoundInfo(MegDataEntryReference? megDataReference) + { + MegDataEntryReference = megDataReference; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepositoryFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/GameRepositoryFactory.cs similarity index 52% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepositoryFactory.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/GameRepositoryFactory.cs index fe64368..5aed364 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepositoryFactory.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/GameRepositoryFactory.cs @@ -1,15 +1,15 @@ using System; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; -namespace PG.StarWarsGame.Engine.Repositories; +namespace PG.StarWarsGame.Engine.IO; internal sealed class GameRepositoryFactory(IServiceProvider serviceProvider) : IGameRepositoryFactory { - public GameRepository Create(GameEngineType engineType, GameLocations gameLocations, IXmlParserErrorListener? xmlParserErrorListener) + public GameRepository Create(GameEngineType engineType, GameLocations gameLocations, GameEngineErrorReporterWrapper errorReporter) { if (engineType == GameEngineType.Eaw) throw new NotImplementedException("Empire at War is currently not supported."); - return new FocGameRepository(gameLocations, xmlParserErrorListener, serviceProvider); + return new FocGameRepository(gameLocations, errorReporter, serviceProvider); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepository.cs similarity index 70% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepository.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepository.cs index 62e6619..32fff7a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepository.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using PG.StarWarsGame.Engine.Language; +using PG.StarWarsGame.Engine.Localization; -namespace PG.StarWarsGame.Engine.Repositories; +namespace PG.StarWarsGame.Engine.IO; public interface IGameRepository : IRepository { @@ -16,9 +15,9 @@ public interface IGameRepository : IRepository IRepository TextureRepository { get; } - bool FileExists(string filePath, string[] extensions, bool megFileOnly = false); + IRepository ModelRepository { get; } - IEnumerable FindFiles(string searchPattern, bool megFileOnly = false); + bool FileExists(string filePath, string[] extensions, bool megFileOnly = false); bool IsLanguageInstalled(LanguageType languageType); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepositoryFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepositoryFactory.cs new file mode 100644 index 0000000..fa59118 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepositoryFactory.cs @@ -0,0 +1,9 @@ +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; + +namespace PG.StarWarsGame.Engine.IO; + +internal interface IGameRepositoryFactory +{ + GameRepository Create(GameEngineType engineType, GameLocations gameLocations, GameEngineErrorReporterWrapper errorReporter); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IRepository.cs new file mode 100644 index 0000000..4ca45db --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; + +namespace PG.StarWarsGame.Engine.IO; + +public interface IRepository +{ + Stream OpenFile(string filePath, bool megFileOnly = false); + Stream OpenFile(ReadOnlySpan filePath, bool megFileOnly = false); + + bool FileExists(string filePath, bool megFileOnly = false); + bool FileExists(ReadOnlySpan filePath, bool megFileOnly = false); + bool FileExists(ReadOnlySpan filePath, bool megFileOnly, out bool pathTooLong); + + Stream? TryOpenFile(string filePath, bool megFileOnly = false); + + Stream? TryOpenFile(ReadOnlySpan filePath, bool megFileOnly = false); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/MultiPassRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/MultiPassRepository.cs new file mode 100644 index 0000000..0fce4d3 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/MultiPassRepository.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Engine.Utilities; + +namespace PG.StarWarsGame.Engine.IO; + +internal abstract class MultiPassRepository(GameRepository baseRepository, IServiceProvider serviceProvider) : IRepository +{ + protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService(); + protected readonly GameRepository BaseRepository = baseRepository; + + public Stream OpenFile(string filePath, bool megFileOnly = false) + { + return OpenFile(filePath.AsSpan(), megFileOnly); + } + + public Stream OpenFile(ReadOnlySpan filePath, bool megFileOnly = false) + { + var fileStream = TryOpenFile(filePath, megFileOnly); + if (fileStream is null) + throw new FileNotFoundException($"Unable to find game file: {filePath.ToString()}"); + return fileStream; + } + + public bool FileExists(string filePath, bool megFileOnly = false) + { + return FileExists(filePath.AsSpan(), megFileOnly); + } + + public bool FileExists(ReadOnlySpan filePath, bool megFileOnly = false) + { + return FileExists(filePath, megFileOnly, out _); + } + + public bool FileExists(ReadOnlySpan filePath, bool megFileOnly, out bool pathTooLong) + { + var multiPassSb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var destinationSb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var result = MultiPassAction(filePath, ref multiPassSb, ref destinationSb, megFileOnly); + var fileFound = result.FileFound; + pathTooLong = result.PathTooLong; + multiPassSb.Dispose(); + destinationSb.Dispose(); + return fileFound; + } + + private protected abstract FileFoundInfo MultiPassAction( + ReadOnlySpan filePath, + ref ValueStringBuilder reusableStringBuilder, + ref ValueStringBuilder destination, + bool megFileOnly); + + public Stream? TryOpenFile(string filePath, bool megFileOnly = false) + { + return TryOpenFile(filePath.AsSpan(), megFileOnly); + } + + public Stream? TryOpenFile(ReadOnlySpan filePath, bool megFileOnly = false) + { + var multiPassSb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var destinationSb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var fileFound = MultiPassAction(filePath, ref multiPassSb, ref destinationSb, megFileOnly); + var result = BaseRepository.OpenFileCore(fileFound); + multiPassSb.Dispose(); + destinationSb.Dispose(); + return result; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs new file mode 100644 index 0000000..22f4949 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/EffectsRepository.cs @@ -0,0 +1,104 @@ +using System; +using PG.StarWarsGame.Engine.IO.Utilities; +using PG.StarWarsGame.Engine.Utilities; + +namespace PG.StarWarsGame.Engine.IO.Repositories; + +internal class EffectsRepository(GameRepository baseRepository, IServiceProvider serviceProvider) + : MultiPassRepository(baseRepository, serviceProvider) +{ + private static readonly string[] LookupPaths = + [ + "DATA\\ART\\SHADERS", + "DATA\\ART\\SHADERS\\TERRAIN", + // This path is not coded to the engine + "DATA\\ART\\SHADERS\\ENGINE", + ]; + + // The engine does not support ".fxh" as a shader lookup, but as there might be some pre-compiling going on, this should be OK. + private static readonly string[] ShaderExtensions = [".fx", ".fxo", ".fxh"]; + + private protected override FileFoundInfo MultiPassAction( + ReadOnlySpan filePath, + ref ValueStringBuilder reusableStringBuilder, + ref ValueStringBuilder destination, + bool megFileOnly) + { + var strippedName = StripFileName(filePath); + + if (strippedName.Length > PGConstants.MaxEffectFileName) + return default; + + foreach (var ext in ShaderExtensions) + { + var extSpan = ext.AsSpan(); + + + var fileFoundInfo = FindEffect( + strippedName, + extSpan, + ReadOnlySpan.Empty, + ref reusableStringBuilder, + ref destination); + + if (fileFoundInfo.FileFound) + return fileFoundInfo; + + + foreach (var directory in LookupPaths) + { + fileFoundInfo = FindEffect( + strippedName, + extSpan, + directory.AsSpan(), + ref reusableStringBuilder, + ref destination); + + if (fileFoundInfo.FileFound) + return fileFoundInfo; + } + } + + + return default; + } + + private FileFoundInfo FindEffect( + ReadOnlySpan strippedName, + ReadOnlySpan extension, + ReadOnlySpan directory, + ref ValueStringBuilder multiPassStringBuilder, + ref ValueStringBuilder filePathStringBuilder) + { + multiPassStringBuilder.Length = 0; + + if (directory != ReadOnlySpan.Empty) + FileSystem.Path.Join(directory, strippedName, ref multiPassStringBuilder); + else + multiPassStringBuilder.Append(strippedName); + + multiPassStringBuilder.Append(extension); + + if (multiPassStringBuilder.Length > PGConstants.MaxEffectFileName) + return default; + + return BaseRepository.FindFile(multiPassStringBuilder.AsSpan(), ref filePathStringBuilder); + } + + private static ReadOnlySpan StripFileName(ReadOnlySpan src) + { + var destination = src; + + for (var i = src.Length - 1; i >= 0; --i) + { + + if (src[i] == '.') + destination = src.Slice(0, i); + + if (src[i] == '/' || src[i] == '\\') + break; + } + + return destination; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/FocGameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/FocGameRepository.cs similarity index 56% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/FocGameRepository.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/FocGameRepository.cs index a36382c..a1b07fb 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/FocGameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/FocGameRepository.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.Utilities; using PG.StarWarsGame.Files.MEG.Files; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers; -namespace PG.StarWarsGame.Engine.Repositories; +namespace PG.StarWarsGame.Engine.IO.Repositories; // EaW file lookup works slightly different! internal class FocGameRepository : GameRepository { public override GameEngineType EngineType => GameEngineType.Foc; - public FocGameRepository(GameLocations gameLocations, IXmlParserErrorListener? listener, IServiceProvider serviceProvider) : base(gameLocations, listener, serviceProvider) + public FocGameRepository(GameLocations gameLocations, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : base(gameLocations, errorReporter, serviceProvider) { if (gameLocations == null) throw new ArgumentNullException(nameof(gameLocations)); @@ -52,49 +53,31 @@ public FocGameRepository(GameLocations gameLocations, IXmlParserErrorListener? l AddMegFiles(megsToConsider); } - - - protected override T? RepositoryFileLookup(string filePath, Func> pathAction, Func> megAction, bool megFileOnly, T? defaultValue = default) where T: default + + protected internal override FileFoundInfo FindFile(ReadOnlySpan filePath, ref ValueStringBuilder pathStringBuilder, bool megFileOnly = false) { if (!megFileOnly) { - foreach (var modPath in ModPaths) - { - var modFilePath = FileSystem.Path.Combine(modPath, filePath); + var fileFoundInfo = FileFromAltExists(filePath, ModPaths, ref pathStringBuilder); + if (fileFoundInfo.FileFound) + return fileFoundInfo; - var result = pathAction(modFilePath); - if (result.ShallReturn) - return result.Result; - } - - { - var normalFilePath = FileSystem.Path.Combine(GameDirectory, filePath); - var result = pathAction(normalFilePath); - if (result.ShallReturn) - return result.Result; - } - + fileFoundInfo = FindFileCore(filePath, ref pathStringBuilder); + if (fileFoundInfo.FileFound) + return fileFoundInfo; } + if (MasterMegArchive is not null) { - var result = megAction(filePath); - if (result.ShallReturn) - return result.Result; + var fileFoundInfo = GetFileInfoFromMasterMeg(filePath); + if (fileFoundInfo.InMeg) + return fileFoundInfo; } if (!megFileOnly) - { - foreach (var fallbackPath in FallbackPaths) - { - var fallbackFilePath = FileSystem.Path.Combine(fallbackPath, filePath); - - var result = pathAction(fallbackFilePath); - if (result.ShallReturn) - return result.Result; - } - } + return FileFromAltExists(filePath, FallbackPaths, ref pathStringBuilder); - return defaultValue; + return default; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs new file mode 100644 index 0000000..1304a89 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using AnakinRaW.CommonUtilities.FileSystem; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine.IO.Utilities; +using PG.StarWarsGame.Engine.Utilities; +using PG.StarWarsGame.Files.MEG.Binary; + +namespace PG.StarWarsGame.Engine.IO.Repositories; + +internal partial class GameRepository +{ + private static readonly string[] DataPathPrefixes = ["DATA/", "DATA\\", "./DATA/", ".\\DATA\\"]; + + public bool FileExists(string filePath, string[] extensions, bool megFileOnly = false) + { + foreach (var extension in extensions) + { + var newPath = FileSystem.Path.ChangeExtension(filePath, extension); + if (FileExists(newPath, megFileOnly)) + return true; + } + return false; + } + + public bool FileExists(string filePath, bool megFileOnly = false) + { + return FileExists(filePath.AsSpan(), megFileOnly); + } + + public bool FileExists(ReadOnlySpan filePath, bool megFileOnly = false) + { + return FileExists(filePath, megFileOnly, out _); + } + + public bool FileExists(ReadOnlySpan filePath, bool megFileOnly, out bool pathTooLong) + { + var sb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var fileFound = FindFile(filePath, ref sb, megFileOnly); + var fileExists = fileFound.FileFound; + pathTooLong = fileFound.PathTooLong; + sb.Dispose(); + return fileExists; + } + + public Stream OpenFile(string filePath, bool megFileOnly = false) + { + return OpenFile(filePath.AsSpan(), megFileOnly); + } + + + public Stream OpenFile(ReadOnlySpan filePath, bool megFileOnly = false) + { + var stream = TryOpenFile(filePath, megFileOnly); + if (stream is null) + throw new FileNotFoundException($"Unable to find game data: {filePath.ToString()}"); + return stream; + } + + public Stream? TryOpenFile(string filePath, bool megFileOnly = false) + { + return TryOpenFile(filePath.AsSpan(), megFileOnly); + } + + public Stream? TryOpenFile(ReadOnlySpan filePath, bool megFileOnly) + { + var sb = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + var fileFoundInfo = FindFile(filePath, ref sb, megFileOnly); + var fileStream = OpenFileCore(fileFoundInfo); + sb.Dispose(); + return fileStream; + } + + protected internal abstract FileFoundInfo FindFile(ReadOnlySpan filePath, + ref ValueStringBuilder pathStringBuilder, bool megFileOnly = false); + + protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan filePath) + { + Debug.Assert(MasterMegArchive is not null); + + if (filePath.Length > PGConstants.MaxMegEntryPathLength) + { + Logger.LogWarning($"Trying to open a MEG entry which is longer than 259 characters: '{filePath.ToString()}'"); + return default; + } + + Span fileNameSpan = stackalloc char[PGConstants.MaxMegEntryPathLength]; + + if (!_megPathNormalizer.TryNormalize(filePath, fileNameSpan, out var length)) + return default; + + var fileName = fileNameSpan.Slice(0, length); + + if (fileName.Length > PGConstants.MaxMegEntryPathLength) + { + Logger.LogWarning($"Trying to open a MEG entry which is longer than 259 characters after normalization: '{fileName.ToString()}'"); + return default; + } + + var crc = _crc32HashingService.GetCrc32(fileName, MegFileConstants.MegDataEntryPathEncoding); + + var entry = MasterMegArchive!.EntriesWithCrc(crc).FirstOrDefault(); + + return new FileFoundInfo(entry); + } + + protected FileFoundInfo FindFileCore(ReadOnlySpan filePath, ref ValueStringBuilder stringBuilder) + { + bool exists; + + stringBuilder.Length = 0; + + if (FileSystem.Path.IsPathFullyQualified(filePath)) + stringBuilder.Append(filePath); + else + FileSystem.Path.Join(GameDirectory.AsSpan(), filePath, ref stringBuilder); + + var actualFilePath = stringBuilder.AsSpan(); + + // We accept a *possible* difference here between platforms, + // unless it's proven the differences are too significant. + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + exists = FileSystem.File.Exists(actualFilePath.ToString()); + else + { + // We *could* also use the slightly faster GetFileAttributesA. + // However, CreateFileA and GetFileAttributesA are implemented complete independent. + // The game uses CreateFileA. + // Thus, we should stick to what the game uses in order to be as close to the engine as possible + // NB: It's also important that the string builder is zero-terminated, as otherwise CreateFileA might get invalid data. + var fileHandle = CreateFile( + in stringBuilder.GetPinnableReference(true), + FileAccess.Read, + FileShare.Read, + IntPtr.Zero, + FileMode.Open, + FileAttributes.Normal, IntPtr.Zero); + + exists = IsValidAndClose(fileHandle); + } + return !exists ? new FileFoundInfo() : new FileFoundInfo(actualFilePath); + } + + protected FileFoundInfo FileFromAltExists(ReadOnlySpan filePath, IList fallbackPaths, ref ValueStringBuilder pathStringBuilder) + { + if (fallbackPaths.Count == 0) + return default; + if (!PathStartsWithDataDirectory(filePath, out var prefixLength)) + return default; + + var pathWithNormalizedData = filePath.Slice(prefixLength); + + foreach (var fallbackPath in fallbackPaths) + { + pathStringBuilder.Length = 0; + + FileSystem.Path.Join(fallbackPath.AsSpan(), pathWithNormalizedData, ref pathStringBuilder); + var newPath = pathStringBuilder.AsSpan(); + + var fileFoundInfo = FindFileCore(newPath, ref pathStringBuilder); + if (fileFoundInfo.FileFound) + return fileFoundInfo; + } + + return default; + } + + private static bool PathStartsWithDataDirectory(ReadOnlySpan path, out int cutoffLength) + { + cutoffLength = 0; + if (path.Length < 5) + return false; + foreach (var prefix in DataPathPrefixes) + { + if (path.StartsWith(prefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + if (path[0] == '.') + cutoffLength = 2; + return true; + } + } + return false; + } + + internal Stream? OpenFileCore(FileFoundInfo fileFoundInfo) + { + if (!fileFoundInfo.FileFound) + return null; + + if (fileFoundInfo.InMeg) + return _megExtractor.GetFileData(fileFoundInfo.MegDataEntryReference.Location); + + return FileSystem.FileStream.New(fileFoundInfo.FilePath.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidAndClose(IntPtr handle) + { + var isValid = handle != IntPtr.Zero && handle != new IntPtr(-1); + if (isValid) + CloseHandle(handle); + return isValid; + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern IntPtr CreateFile( + in char lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess access, + [MarshalAs(UnmanagedType.U4)] FileShare share, + IntPtr securityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, + IntPtr templateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); +} diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs similarity index 61% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs index 48c99f0..42bfa54 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.cs @@ -1,30 +1,25 @@ using System; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; using System.Linq; -using System.Runtime.InteropServices; using AnakinRaW.CommonUtilities.FileSystem; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.Logging; using PG.Commons.Hashing; using PG.Commons.Services; -using PG.StarWarsGame.Engine.Language; -using PG.StarWarsGame.Engine.Utilities; -using PG.StarWarsGame.Engine.Xml; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.Localization; using PG.StarWarsGame.Files.MEG.Data.Archives; using PG.StarWarsGame.Files.MEG.Data.Entries; using PG.StarWarsGame.Files.MEG.Data.EntryLocations; using PG.StarWarsGame.Files.MEG.Files; using PG.StarWarsGame.Files.MEG.Services; using PG.StarWarsGame.Files.MEG.Services.Builder.Normalization; -using PG.StarWarsGame.Files.XML; -using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; -namespace PG.StarWarsGame.Engine.Repositories; +namespace PG.StarWarsGame.Engine.IO.Repositories; -internal abstract class GameRepository : ServiceBase, IGameRepository +internal abstract partial class GameRepository : ServiceBase, IGameRepository { private readonly IMegFileService _megFileService; private readonly IMegFileExtractor _megExtractor; @@ -32,7 +27,7 @@ internal abstract class GameRepository : ServiceBase, IGameRepository private readonly ICrc32HashingService _crc32HashingService; private readonly IVirtualMegArchiveBuilder _virtualMegBuilder; private readonly IGameLanguageManagerProvider _languageManagerProvider; - private readonly IXmlParserErrorListener? _xmlParserErrorListener; + private readonly GameEngineErrorReporterWrapper _errorReporter; protected readonly string GameDirectory; @@ -46,12 +41,13 @@ internal abstract class GameRepository : ServiceBase, IGameRepository public IRepository EffectsRepository { get; } public IRepository TextureRepository { get; } + public IRepository ModelRepository { get; } private readonly List _loadedMegFiles = new(); protected IVirtualMegArchive? MasterMegArchive { get; private set; } - protected GameRepository(GameLocations gameLocations, IXmlParserErrorListener? errorListener, IServiceProvider serviceProvider) : base(serviceProvider) + protected GameRepository(GameLocations gameLocations, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) : base(serviceProvider) { if (gameLocations == null) throw new ArgumentNullException(nameof(gameLocations)); @@ -60,9 +56,9 @@ protected GameRepository(GameLocations gameLocations, IXmlParserErrorListener? e _megFileService = serviceProvider.GetRequiredService(); _virtualMegBuilder = serviceProvider.GetRequiredService(); _crc32HashingService = serviceProvider.GetRequiredService(); - _megPathNormalizer = new PetroglyphDataEntryPathNormalizer(serviceProvider); + _megPathNormalizer = EmpireAtWarMegDataEntryPathNormalizer.Instance; _languageManagerProvider = serviceProvider.GetRequiredService(); - _xmlParserErrorListener = errorListener; + _errorReporter = errorReporter; foreach (var mod in gameLocations.ModPaths) { @@ -87,7 +83,7 @@ protected GameRepository(GameLocations gameLocations, IXmlParserErrorListener? e EffectsRepository = new EffectsRepository(this, serviceProvider); TextureRepository = new TextureRepository(this, serviceProvider); - + ModelRepository = new ModelRepository(this, serviceProvider); var path = ModPaths.Any() ? ModPaths.First() : GameDirectory; if (!FileSystem.Path.HasTrailingDirectorySeparator(path)) @@ -119,100 +115,16 @@ public void AddMegFile(string megFile) var megArchive = LoadMegArchive(megFile); if (megArchive is null) { - Logger.LogWarning($"Unable to find MEG file at '{megFile}'"); + if (IsSpeechMeg(megFile)) + Logger.LogDebug($"Unable to find Speech MEG file at '{megFile}'"); + else + Logger.LogWarning($"Unable to find MEG file at '{megFile}'"); return; } AddMegFiles([megArchive]); } - public bool FileExists(string filePath, bool megFileOnly = false) - { - return RepositoryFileLookup(filePath, fp => - { - if (FileSystem.File.Exists(fp)) - return new ActionResult(true, true); - return ActionResult.DoNotReturn; - }, - fp => - { - var entry = FindFileInMasterMeg(fp); - if (entry is not null) - return new ActionResult(true, true); - return ActionResult.DoNotReturn; - }, megFileOnly); - } - - public Stream? TryOpenFile(string filePath, bool megFileOnly = false) - { - return RepositoryFileLookup(filePath, fp => - { - if (FileSystem.File.Exists(fp)) - return new ActionResult(true, OpenFileRead(fp)); - return ActionResult.DoNotReturn; - }, - fp => - { - var entry = FindFileInMasterMeg(fp); - if (entry is not null) - return new ActionResult(true, _megExtractor.GetFileData(entry.Location)); - return ActionResult.DoNotReturn; - }, megFileOnly); - } - - - public Stream OpenFile(string filePath, bool megFileOnly = false) - { - var stream = TryOpenFile(filePath, megFileOnly); - if (stream is null) - throw new FileNotFoundException($"Unable to find game data: {filePath}"); - return stream; - } - - public bool FileExists(string filePath, string[] extensions, bool megFileOnly = false) - { - foreach (var extension in extensions) - { - var newPath = FileSystem.Path.ChangeExtension(filePath, extension); - if (FileExists(newPath, megFileOnly)) - return true; - } - return false; - } - - public IEnumerable FindFiles(string searchPattern, bool megFileOnly = false) - { - var files = new HashSet(); - - var matcher = new Matcher(); - matcher.AddInclude(searchPattern); - - RepositoryFileLookup(searchPattern, - pattern => - { - var path = pattern.AsSpan().TrimEnd(searchPattern.AsSpan()); - - var matcherResult = matcher.Execute(FileSystem, path.ToString()); - - foreach (var matchedFile in matcherResult.Files) - { - var normalizedFile = _megPathNormalizer.Normalize(matchedFile.Path); - files.Add(normalizedFile); - } - - return ActionResult.DoNotReturn; - }, - _ => - { - var foundFiles = MasterMegArchive!.FindAllEntries(searchPattern, true); - foreach (var x in foundFiles) - files.Add(x.FilePath); - - return ActionResult.DoNotReturn; - }, megFileOnly); - - return files; - } public bool IsLanguageInstalled(LanguageType language) { @@ -301,8 +213,6 @@ protected IList LoadMegArchivesFromXml(string lookupPath) { var megFilesXmlPath = FileSystem.Path.Combine(lookupPath, "Data\\MegaFiles.xml"); - var fileParserFactory = Services.GetRequiredService(); - using var xmlStream = TryOpenFile(megFilesXmlPath); if (xmlStream is null) @@ -311,9 +221,11 @@ protected IList LoadMegArchivesFromXml(string lookupPath) return Array.Empty(); } - var parser = fileParserFactory.GetFileParser(_xmlParserErrorListener); + var parser = new XmlFileListParser(Services ,_errorReporter); var megaFilesXml = parser.ParseFile(xmlStream); + if (megaFilesXml is null) + return []; var megs = new List(megaFilesXml.Files.Count); @@ -333,14 +245,21 @@ internal void Seal() _sealed = true; } - protected abstract T? RepositoryFileLookup(string filePath, Func> pathAction, - Func> megAction, bool megFileOnly, T? defaultValue = default); - protected IMegFile? LoadMegArchive(string megPath) { using var megFileStream = TryOpenFile(megPath); if (megFileStream is not FileSystemStream fileSystemStream) + { + if (IsSpeechMeg(megPath)) + Logger.LogDebug($"Unable to find Speech MEG file '{megPath}'"); + else + { + var message = $"Unable to find MEG file '{megPath}'"; + _errorReporter.Assert(EngineAssert.Create(EngineAssertKind.FileNotFound, megPath, [], message)); + Logger.LogWarning($"Unable to find MEG file '{megPath}'"); + } return null; + } var megFile = _megFileService.Load(fileSystemStream); @@ -350,43 +269,9 @@ internal void Seal() return megFile; } - protected MegDataEntryReference? FindFileInMasterMeg(string filePath) - { - Span fileNameBuffer = stackalloc char[PGConstants.MaxPathLength]; - - // TODO: Is the engine really "to-uppering" the input??? - var length = _megPathNormalizer.Normalize(filePath.AsSpan(), fileNameBuffer); - var fileName = fileNameBuffer.Slice(0, length); - var crc = _crc32HashingService.GetCrc32(fileName, PGConstants.PGCrc32Encoding); - - return MasterMegArchive?.FirstEntryWithCrc(crc); - } - - protected FileSystemStream OpenFileRead(string filePath) - { - if (!AllowOpenFile(filePath)) - throw new UnauthorizedAccessException("The data is not part of the Games!"); - return FileSystem.FileStream.New(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - } - - private bool AllowOpenFile(string filePath) + private bool IsSpeechMeg(string megFile) { - foreach (var modPath in ModPaths) - { - if (FileSystem.Path.IsChildOf(modPath, filePath)) - return true; - } - - if (FileSystem.Path.IsChildOf(GameDirectory, filePath)) - return true; - - foreach (var fallbackPath in FallbackPaths) - { - if (FileSystem.Path.IsChildOf(fallbackPath, filePath)) - return true; - } - - return false; + return FileSystem.Path.GetFileName(megFile.AsSpan()).EndsWith("Speech.meg".AsSpan(), StringComparison.OrdinalIgnoreCase); } private void ThrowIfSealed() @@ -394,19 +279,7 @@ private void ThrowIfSealed() if (_sealed) throw new InvalidOperationException("The object is sealed for modifications"); } - - protected readonly struct ActionResult(bool shallReturn, T? result) - { - public T? Result { get; } = result; - - public bool ShallReturn { get; } = shallReturn; - - public static ActionResult DoNotReturn => default; - } - - [StructLayout(LayoutKind.Explicit)] - private readonly struct EmptyStruct; - + private sealed class LanguageFiles { public LanguageType Language { get; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/ModelRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/ModelRepository.cs new file mode 100644 index 0000000..1b6cb6d --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/ModelRepository.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.CompilerServices; +using PG.StarWarsGame.Engine.IO.Utilities; +using PG.StarWarsGame.Engine.Utilities; +#if NETSTANDARD2_0 || NETFRAMEWORK +using AnakinRaW.CommonUtilities.FileSystem; +#endif + +namespace PG.StarWarsGame.Engine.IO.Repositories; + +internal class ModelRepository(GameRepository baseRepository, IServiceProvider serviceProvider) + : MultiPassRepository(baseRepository, serviceProvider) +{ + private protected override FileFoundInfo MultiPassAction( + ReadOnlySpan filePath, + ref ValueStringBuilder reusableStringBuilder, + ref ValueStringBuilder destination, + bool megFileOnly) + { + if (!IsValidSize(filePath)) + return default; + + var fileInfo = BaseRepository.FindFile(filePath, ref destination, megFileOnly); + if (fileInfo.FileFound) + return fileInfo; + + destination.Length = 0; + + var stripped = StripFileName(filePath); + + var path = FileSystem.Path.GetDirectoryName(filePath); + FileSystem.Path.Join(path, stripped, ref reusableStringBuilder); + reusableStringBuilder.Append(".ALO"); + + var alternatePath = reusableStringBuilder.AsSpan(); + return !IsValidSize(alternatePath) + ? default + : BaseRepository.FindFile(alternatePath, ref destination, megFileOnly); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidSize(ReadOnlySpan path) + { + return path.Length != 0 && path.Length < PGConstants.MaxModelFileName; + } + + private static ReadOnlySpan StripFileName(ReadOnlySpan src) + { + var tmp = src; + + for (var i = src.Length - 1; i >= 0; --i) + { + if (src[i] == '.') + tmp = src.Slice(0, i); + + if (src[i] == '/' || src[i] == '\\') + return tmp.Slice(i + 1); + } + + return tmp; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/TextureRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/TextureRepository.cs new file mode 100644 index 0000000..03ff446 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/TextureRepository.cs @@ -0,0 +1,71 @@ +using PG.StarWarsGame.Engine.Utilities; +using System; + +namespace PG.StarWarsGame.Engine.IO.Repositories; + +internal class TextureRepository(GameRepository baseRepository, IServiceProvider serviceProvider) : + MultiPassRepository(baseRepository, serviceProvider) +{ + private static readonly string DdsExtension = ".dds"; + private static readonly string TexturePath = "DATA\\ART\\TEXTURES\\"; + + private protected override FileFoundInfo MultiPassAction( + ReadOnlySpan filePath, + ref ValueStringBuilder reusableStringBuilder, + ref ValueStringBuilder destination, + bool megFileOnly) + { + if (filePath.Length > PGConstants.MaxTextureFileName) + return new FileFoundInfo { PathTooLong = true }; + + reusableStringBuilder.Append(filePath); + + var foundInfo = FindTexture(ref reusableStringBuilder, ref destination); + + if (foundInfo.FileFound) + return foundInfo; + + reusableStringBuilder.Length = 0; + reusableStringBuilder.Append(filePath); + + ChangeExtensionTo(ref reusableStringBuilder, DdsExtension.AsSpan()); + + return FindTexture(ref reusableStringBuilder, ref destination); + } + + private FileFoundInfo FindTexture(ref ValueStringBuilder multiPassStringBuilder, ref ValueStringBuilder pathStringBuilder) + { + var fileInfo = BaseRepository.FindFile(multiPassStringBuilder.AsSpan(), ref pathStringBuilder); + if (fileInfo.FileFound) + return fileInfo; + + // Only PG knows why they only search for backslash and not also forward slash, + // when in fact in other methods, they handle both. + var separatorIndex = multiPassStringBuilder.AsSpan().LastIndexOf('\\'); + if (separatorIndex != -1) + multiPassStringBuilder.Remove(0 , separatorIndex + 1); + + multiPassStringBuilder.Insert(0, TexturePath); + + if (multiPassStringBuilder.AsSpan().Length > PGConstants.MaxTextureFileName) + return new FileFoundInfo { PathTooLong = true }; + + return BaseRepository.FindFile(multiPassStringBuilder.AsSpan(), ref pathStringBuilder); + } + + private static void ChangeExtensionTo(ref ValueStringBuilder stringBuilder, ReadOnlySpan extension) + { + // We cannot use Path.ChangeExtension as the PG implementation supports some strange things + // like that a string "c:\\file.tga\\" ending with a directory separator. The PG result will be + // "c:\\file.dds" while Path.ChangeExtension would return "c:\\file.tga\\.dds" + + // Also, while there are many cases, where this method breaks (such as "c:/test.abc/path.dds"), + // it's the way how the engine works 🤷‍ + var firstPeriod = stringBuilder.AsSpan().IndexOf('.'); + if (firstPeriod == -1) + return; + + stringBuilder.Length = firstPeriod; + stringBuilder.Append(extension); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/DirectoryInfoGlobbingWrapper.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/DirectoryInfoGlobbingWrapper.cs similarity index 95% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/DirectoryInfoGlobbingWrapper.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/DirectoryInfoGlobbingWrapper.cs index 2f2114a..f012fdc 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/DirectoryInfoGlobbingWrapper.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/DirectoryInfoGlobbingWrapper.cs @@ -3,14 +3,14 @@ using System.IO; using System.IO.Abstractions; -namespace PG.StarWarsGame.Engine.Utilities; +namespace PG.StarWarsGame.Engine.IO.Utilities; // Taken from https://github.com/vipentti/Vipentti.IO.Abstractions.FileSystemGlobbing /// /// Wraps to be used with /// -public sealed class DirectoryInfoGlobbingWrapper : Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase +internal sealed class DirectoryInfoGlobbingWrapper : Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase { private readonly IFileSystem _fileSystem; private readonly IDirectoryInfo _directoryInfo; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/FileInfoGlobbingWrapper.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/FileInfoGlobbingWrapper.cs similarity index 92% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/FileInfoGlobbingWrapper.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/FileInfoGlobbingWrapper.cs index 744c377..fef3dd9 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/FileInfoGlobbingWrapper.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/FileInfoGlobbingWrapper.cs @@ -1,6 +1,6 @@ using System.IO.Abstractions; -namespace PG.StarWarsGame.Engine.Utilities; +namespace PG.StarWarsGame.Engine.IO.Utilities; // Taken from https://github.com/vipentti/Vipentti.IO.Abstractions.FileSystemGlobbing @@ -23,7 +23,7 @@ _fileInfo.Directory is null : new DirectoryInfoGlobbingWrapper(_fileSystem, _fileInfo.Directory); /// - /// Initialize a new instance + /// InitializeAsync a new instance /// /// The filesystem /// The file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/MatcherExtensions.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/MatcherExtensions.cs similarity index 97% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/MatcherExtensions.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/MatcherExtensions.cs index e2bde1e..48ddfa1 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/MatcherExtensions.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/MatcherExtensions.cs @@ -4,14 +4,14 @@ using System.Linq; using Microsoft.Extensions.FileSystemGlobbing; -namespace PG.StarWarsGame.Engine.Utilities; +namespace PG.StarWarsGame.Engine.IO.Utilities; // Taken from https://github.com/vipentti/Vipentti.IO.Abstractions.FileSystemGlobbing /// /// Provides extensions for to support /// -public static class MatcherExtensions +internal static class MatcherExtensions { /// /// Searches the directory specified for all files matching patterns added to this instance of diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/PathExtensions.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/PathExtensions.cs new file mode 100644 index 0000000..471b5a8 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Utilities/PathExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.IO.Abstractions; +using AnakinRaW.CommonUtilities.FileSystem; +using PG.StarWarsGame.Engine.Utilities; + +namespace PG.StarWarsGame.Engine.IO.Utilities; + +internal static class PathExtensions +{ + public static void Join(this IPath _, ReadOnlySpan path1, ReadOnlySpan path2, ref ValueStringBuilder stringBuilder) + { + if (path1.Length == 0 && path2.Length == 0) + return; + + if (path1.Length == 0 || path2.Length == 0) + { + ref var pathToUse = ref path1.Length == 0 ? ref path2 : ref path1; + stringBuilder.Append(pathToUse); + return; + } + + var needsSeparator = !(_.HasTrailingDirectorySeparator(path1) || _.HasLeadingDirectorySeparator(path2)); + + stringBuilder.Append(path1); + if (needsSeparator) + stringBuilder.Append(_.DirectorySeparatorChar); + + stringBuilder.Append(path2); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs new file mode 100644 index 0000000..314ce06 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IPetroglyphStarWarsGameEngineService.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using PG.StarWarsGame.Engine.ErrorReporting; + +namespace PG.StarWarsGame.Engine; + +public interface IPetroglyphStarWarsGameEngineService +{ + public Task InitializeAsync( + GameEngineType engineType, + GameLocations gameLocations, + IGameEngineErrorReporter? errorReporter = null, + IProgress? initProgress = null, + bool cancelOnInitializationError = false, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/IStarWarsGameEngine.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/IStarWarsGameEngine.cs new file mode 100644 index 0000000..bdb7215 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/IStarWarsGameEngine.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.Audio.Sfx; +using PG.StarWarsGame.Engine.CommandBar; +using PG.StarWarsGame.Engine.GameConstants; +using PG.StarWarsGame.Engine.GameObjects; +using PG.StarWarsGame.Engine.GuiDialog; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Engine.Localization; +using PG.StarWarsGame.Engine.Rendering; +using PG.StarWarsGame.Engine.Rendering.Font; + +namespace PG.StarWarsGame.Engine; + +public interface IStarWarsGameEngine +{ + GameEngineType EngineType { get; } + + IPGRender PGRender { get; } + + IFontManager FontManager { get; } + + IGameRepository GameRepository { get; } + + IGameConstants GameConstants { get; } + + IGuiDialogManager GuiDialogManager { get; } + + ICommandBarGameManager CommandBar { get; } + + IGameObjectTypeGameManager GameObjectTypeManager { get; } + + ISfxEventGameManager SfxGameManager { get; } + + IEnumerable InstalledLanguages { get; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/EawGameLanguageManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/EawGameLanguageManager.cs similarity index 92% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/EawGameLanguageManager.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/EawGameLanguageManager.cs index 2c720b4..ddf2be7 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/EawGameLanguageManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/EawGameLanguageManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; internal sealed class EawGameLanguageManager(IServiceProvider serviceProvider) : GameLanguageManager(serviceProvider) { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/FocGameLanguageManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/FocGameLanguageManager.cs similarity index 94% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/FocGameLanguageManager.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/FocGameLanguageManager.cs index 3636c40..363b1e7 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/FocGameLanguageManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/FocGameLanguageManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; internal sealed class FocGameLanguageManager(IServiceProvider serviceProvider) : GameLanguageManager(serviceProvider) { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManager.cs similarity index 85% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManager.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManager.cs index ec4d6b2..64b2222 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManager.cs @@ -5,8 +5,9 @@ using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using PG.Commons.Services; +using PG.StarWarsGame.Engine.Utilities; -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; internal abstract class GameLanguageManager(IServiceProvider serviceProvider) : ServiceBase(serviceProvider), IGameLanguageManager { @@ -113,9 +114,6 @@ private CultureInfo GetParentCultureRecursive(CultureInfo culture) public string LocalizeFileName(string fileName, LanguageType language, out bool localized) { - if (fileName.Length > PGConstants.MaxPathLength) - throw new ArgumentOutOfRangeException(nameof(fileName), "fileName is too long"); - // The game assumes that all localized audio files are referenced by using their english name. // Thus, PG takes this shortcut if (language == LanguageType.English) @@ -124,33 +122,37 @@ public string LocalizeFileName(string fileName, LanguageType language, out bool return fileName; } - - Span localizedName = stackalloc char[fileName.Length]; - var length = LocalizeFileName(fileName.AsSpan(), language, localizedName, out localized); + var stringBuilder = new ValueStringBuilder(stackalloc char[PGConstants.MaxMegEntryPathLength]); + LocalizeFileName(fileName.AsSpan(), language, ref stringBuilder, out localized); if (!localized) + { + stringBuilder.Dispose(); return fileName; + } - Debug.Assert(localizedName.Length == length); - return localizedName.ToString(); + Debug.Assert(stringBuilder.Length == fileName.Length); + return stringBuilder.ToString(); } - public int LocalizeFileName(ReadOnlySpan fileName, LanguageType language, Span destination, out bool localized) { - if (fileName.Length > PGConstants.MaxPathLength) - throw new ArgumentOutOfRangeException(nameof(fileName), "fileName is too long"); - - if (destination.Length < fileName.Length) - throw new ArgumentException("destination is too short", nameof(destination)); + var sb = new ValueStringBuilder(destination.Length); + LocalizeFileName(fileName, language, ref sb, out localized); + sb.TryCopyTo(destination, out var written); + sb.Dispose(); + return written; + } + public void LocalizeFileName(ReadOnlySpan fileName, LanguageType language, ref ValueStringBuilder stringBuilder, out bool localized) + { localized = true; // The game assumes that all localized audio files are referenced by using their english name. // Thus, PG takes this shortcut if (language == LanguageType.English) { - fileName.CopyTo(destination); - return fileName.Length; + stringBuilder.Append(fileName); + return; } var isWav = false; @@ -175,8 +177,8 @@ public int LocalizeFileName(ReadOnlySpan fileName, LanguageType language, { localized = false; Logger.LogWarning($"Unable to localize '{fileName.ToString()}'"); - fileName.CopyTo(destination); - return fileName.Length; + stringBuilder.Append(fileName); + return; } var withoutSuffix = fileName.Slice(0, engSuffixIndex); @@ -189,10 +191,8 @@ public int LocalizeFileName(ReadOnlySpan fileName, LanguageType language, else throw new InvalidOperationException(); - withoutSuffix.CopyTo(destination); - newLocalizedSuffix.CopyTo(destination.Slice(withoutSuffix.Length, newLocalizedSuffix.Length)); - - return fileName.Length; + stringBuilder.Append(withoutSuffix); + stringBuilder.Append(newLocalizedSuffix); } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManagerProvider.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManagerProvider.cs similarity index 93% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManagerProvider.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManagerProvider.cs index b6b00e8..6430c02 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/GameLanguageManagerProvider.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/GameLanguageManagerProvider.cs @@ -1,6 +1,6 @@ using System; -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; internal class GameLanguageManagerProvider(IServiceProvider serviceProvider) : IGameLanguageManagerProvider { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManager.cs similarity index 92% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManager.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManager.cs index b0e03da..de44657 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManager.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; public interface IGameLanguageManager { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManagerProvider.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManagerProvider.cs similarity index 70% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManagerProvider.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManagerProvider.cs index c47d0a4..fa1e3b8 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/IGameLanguageManagerProvider.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/IGameLanguageManagerProvider.cs @@ -1,4 +1,4 @@ -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; public interface IGameLanguageManagerProvider { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/SupportedLanguage.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/LanguageType.cs similarity index 80% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Language/SupportedLanguage.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/LanguageType.cs index 50e1aad..5e67d9e 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Language/SupportedLanguage.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Localization/LanguageType.cs @@ -1,4 +1,4 @@ -namespace PG.StarWarsGame.Engine.Language; +namespace PG.StarWarsGame.Engine.Localization; public enum LanguageType { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj index 705e571..6c05d99 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj @@ -1,6 +1,6 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net9.0 PG.StarWarsGame.Engine PG.StarWarsGame.Engine AlamoEngineTools.PG.StarWarsGame.Engine @@ -15,14 +15,12 @@ true snupkg true + preview - - - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -35,10 +33,17 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + - + + + + + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj.DotSettings b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj.DotSettings new file mode 100644 index 0000000..57b1fa3 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PG.StarWarsGame.Engine.csproj.DotSettings @@ -0,0 +1,7 @@ + + False + True + True + True + True + True \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PGConstants.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PGConstants.cs index 486aa40..b7a9218 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PGConstants.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PGConstants.cs @@ -4,7 +4,21 @@ namespace PG.StarWarsGame.Engine; public static class PGConstants { - public static readonly Encoding PGCrc32Encoding = Encoding.ASCII; + public static readonly Encoding DefaultPGEncoding = Encoding.ASCII; - public const int MaxPathLength = 260; + // Always reserve one character for the null-terminator + public const int MaxMegEntryPathLength = 259; + public const int MaxEffectFileName = 259; + public const int MaxTextureFileName = 259; + public const int MaxModelFileName = 259; + public const int MaxAnimationFileName = 255; + public const int MaxSFXEventDatabaseFileName = 259; + public const int MaxSFXEventName = 255; + public const int MaxGameObjectDatabaseFileName = 127; + public const int MaxCommandBarDatabaseFileName = 259; + public const int MaxCommandBarComponentName = 255; + + public const int MaxGuiDialogMegaTextureFileName = 255; + + public const string DefaultUnicodeFontName = "Arial"; } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs index ae4269b..0132d10 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Engine.Language; -using PG.StarWarsGame.Engine.Repositories; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Engine.Localization; using PG.StarWarsGame.Engine.Xml; namespace PG.StarWarsGame.Engine; @@ -10,10 +9,11 @@ public static class PetroglyphEngineServiceContribution { public static void ContributeServices(IServiceCollection serviceCollection) { + // Singletons serviceCollection.AddSingleton(sp => new GameRepositoryFactory(sp)); serviceCollection.AddSingleton(sp => new GameLanguageManagerProvider(sp)); serviceCollection.AddSingleton(sp => new PetroglyphXmlFileParserFactory(sp)); - serviceCollection.AddTransient(sp => new GameDatabaseService(sp)); + serviceCollection.AddSingleton(sp => new PetroglyphStarWarsGameEngineService(sp)); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs new file mode 100644 index 0000000..d2acfba --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphStarWarsGameEngineService.cs @@ -0,0 +1,126 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.StarWarsGame.Engine.Audio.Sfx; +using PG.StarWarsGame.Engine.CommandBar; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.GameObjects; +using PG.StarWarsGame.Engine.GuiDialog; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Engine.Rendering; +using PG.StarWarsGame.Engine.Rendering.Font; + +namespace PG.StarWarsGame.Engine; + +internal sealed class PetroglyphStarWarsGameEngineService(IServiceProvider serviceProvider) + : IPetroglyphStarWarsGameEngineService +{ + private readonly IServiceProvider _serviceProvider = + serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + private readonly ILogger? _logger = serviceProvider.GetService() + ?.CreateLogger(typeof(PetroglyphStarWarsGameEngineService)); + + public async Task InitializeAsync( + GameEngineType engineType, + GameLocations gameLocations, + IGameEngineErrorReporter? errorReporter = null, + IProgress? progress = null, + bool cancelOnInitializationError = false, + CancellationToken cancellationToken = default) + + { + var errorListenerWrapper = new GameEngineErrorReporterWrapper(errorReporter); + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + errorListenerWrapper.InitializationError += OnInitializationError; + + try + { + return await InitializeEngine(engineType, gameLocations, errorListenerWrapper, progress, cts.Token) + .ConfigureAwait(false); + } + finally + { + errorListenerWrapper.InitializationError -= OnInitializationError; + errorListenerWrapper.Dispose(); + cts.Dispose(); + } + + void OnInitializationError(object sender, InitializationError e) + { + if (cancelOnInitializationError) + cts.Cancel(); + } + } + + private async Task InitializeEngine( + GameEngineType engineType, + GameLocations gameLocations, + GameEngineErrorReporterWrapper errorReporter, + IProgress? progress, + CancellationToken token) + { + try + { + _logger?.LogInformation($"Initializing game engine for type '{engineType}'."); + + var repoFactory = _serviceProvider.GetRequiredService(); + var repository = repoFactory.Create(engineType, gameLocations, errorReporter); + + var pgRender = new PGRender(repository, errorReporter, serviceProvider); + + var gameConstants = new GameConstants.GameConstants(repository, errorReporter, serviceProvider); + progress?.Report("Initializing GameConstants"); + await gameConstants.InitializeAsync(token); + + // AudioConstants + + // MousePointer + + var fontManger = new FontManager(repository, errorReporter, serviceProvider); + progress?.Report("Initializing FontManager"); + await fontManger.InitializeAsync(token); + + var guiDialogs = new GuiDialogGameManager(repository, errorReporter, serviceProvider); + progress?.Report("Initializing GUIDialogManager"); + await guiDialogs.InitializeAsync(token); + + var sfxGameManager = new SfxEventGameManager(repository, errorReporter, serviceProvider); + progress?.Report("Initializing SFXManager"); + await sfxGameManager.InitializeAsync(token); + + var commandBarManager = new CommandBarGameManager(repository, pgRender, gameConstants, fontManger, errorReporter, serviceProvider); + progress?.Report("Initializing CommandBar"); + await commandBarManager.InitializeAsync(token); + + var gameObjetTypeManager = new GameObjectTypeGameManager(repository, errorReporter, serviceProvider); + progress?.Report("Initializing GameObjectTypeManager"); + await gameObjetTypeManager.InitializeAsync(token); + + token.ThrowIfCancellationRequested(); + + repository.Seal(); + + return new GameEngine + { + EngineType = engineType, + PGRender = pgRender, + FontManager = fontManger, + GameRepository = repository, + GameConstants = gameConstants, + GuiDialogManager = guiDialogs, + CommandBar = commandBarManager, + GameObjectTypeManager = gameObjetTypeManager, + SfxGameManager = sfxGameManager, + InstalledLanguages = sfxGameManager.InstalledLanguages + }; + } + finally + { + _logger?.LogDebug("Finished initializing game database."); + } + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs new file mode 100644 index 0000000..03916fe --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/AnimationCollection.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AnakinRaW.CommonUtilities; +using AnakinRaW.CommonUtilities.Collections; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.ALO.Files.Animations; + +namespace PG.StarWarsGame.Engine.Rendering.Animations; + +public sealed class AnimationCollection : DisposableObject, IEnumerable +{ + public static readonly AnimationCollection Empty = new(); + + private readonly ValueListDictionary _animations = new(); + private readonly ValueListDictionary _animationCrc = new(); + + public int Cout => _animations.Count; + + public Crc32 GetAnimationCrc(ModelAnimationType type, int subIndex) + { + if (subIndex < 0) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be negative."); + var checksumsForType = _animationCrc.GetValues(type); + if (subIndex >= checksumsForType.Count) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be larger than stored animations."); + return checksumsForType[subIndex]; + } + + public ReadOnlyFrugalList GetAnimations(ModelAnimationType type) + { + return _animations.GetValues(type); + } + + public bool TryGetAnimations(ModelAnimationType type, out ReadOnlyFrugalList animations) + { + return _animations.TryGetValues(type, out animations); + } + + public IAloAnimationFile GetAnimation(ModelAnimationType type, int subIndex) + { + if (subIndex < 0) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be negative."); + var animations = GetAnimations(type); + if (subIndex >= animations.Count) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be larger than stored animations."); + return animations[subIndex]; + } + + public bool TryGetAnimation(ModelAnimationType type, int subIndex, out IAloAnimationFile? animation) + { + animation = null; + if (subIndex < 0) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be negative."); + if (!TryGetAnimations(type, out var animations)) + return false; + if (subIndex >= animations.Count) + throw new ArgumentOutOfRangeException(nameof(subIndex), "subIndex cannot be larger than stored animations."); + animation = animations[subIndex]; + return true; + } + + internal void AddAnimation(ModelAnimationType type, IAloAnimationFile animation, Crc32 crc) + { + _animations.Add(type, animation); + _animationCrc.Add(type, crc); + } + + protected override void DisposeResources() + { + foreach (var animation in _animations.Values) + animation.Dispose(); + _animationCrc.Clear(); + _animations.Clear(); + } + + public IEnumerator GetEnumerator() + { + return _animations.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/EawModelAnimationTypes.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/EawModelAnimationTypes.cs new file mode 100644 index 0000000..f1a89c3 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/EawModelAnimationTypes.cs @@ -0,0 +1,109 @@ +namespace PG.StarWarsGame.Engine.Rendering.Animations; + +public static class EawModelAnimationTypes +{ + public static readonly ModelAnimationType Idle = new(GameEngineType.Eaw, 0x0); + public static readonly ModelAnimationType SpaceIdle = new(GameEngineType.Eaw, 0x1); + public static readonly ModelAnimationType Move = new(GameEngineType.Eaw, 0x2); + public static readonly ModelAnimationType TurnLeft = new(GameEngineType.Eaw, 0x3); + public static readonly ModelAnimationType TurnRight = new(GameEngineType.Eaw, 0x4); + public static readonly ModelAnimationType Attack = new(GameEngineType.Eaw, 0x5); + public static readonly ModelAnimationType AttackIdle = new(GameEngineType.Eaw, 0x6); + public static readonly ModelAnimationType Die = new(GameEngineType.Eaw, 0x7); + public static readonly ModelAnimationType Rotate = new(GameEngineType.Eaw, 0x8); + public static readonly ModelAnimationType SpecialA = new(GameEngineType.Eaw, 0x9); + public static readonly ModelAnimationType SpecialB = new(GameEngineType.Eaw, 0xa); + public static readonly ModelAnimationType SpecialC = new(GameEngineType.Eaw, 0xb); + public static readonly ModelAnimationType TransitionToLeftTurn = new(GameEngineType.Eaw, 0xc); + public static readonly ModelAnimationType TransitionFromLeftTurn = new(GameEngineType.Eaw, 0xd); + public static readonly ModelAnimationType TransitionToRightTurn = new(GameEngineType.Eaw, 0xe); + public static readonly ModelAnimationType TransitionFromRightTurn = new(GameEngineType.Eaw, 0xf); + public static readonly ModelAnimationType TransitionToMove = new(GameEngineType.Eaw, 0x10); + public static readonly ModelAnimationType TransitionFromMoveFrame0 = new(GameEngineType.Eaw, 0x11); + public static readonly ModelAnimationType TransitionFromMoveFrame40 = new(GameEngineType.Eaw, 0x12); + public static readonly ModelAnimationType TransitionFromMoveFrame80 = new(GameEngineType.Eaw, 0x13); + public static readonly ModelAnimationType TransitionFromMoveFrame120 = new(GameEngineType.Eaw, 0x14); + public static readonly ModelAnimationType TurnLeftHalf = new(GameEngineType.Eaw, 0x15); + public static readonly ModelAnimationType TurnLeftQuarter = new(GameEngineType.Eaw, 0x16); + public static readonly ModelAnimationType TurnRightHalf = new(GameEngineType.Eaw, 0x17); + public static readonly ModelAnimationType TurnRightQuarter = new(GameEngineType.Eaw, 0x18); + public static readonly ModelAnimationType Deploy = new(GameEngineType.Eaw, 0x19); + public static readonly ModelAnimationType Undeploy = new(GameEngineType.Eaw, 0x1a); + public static readonly ModelAnimationType Cinematic = new(GameEngineType.Eaw, 0x1b); + public static readonly ModelAnimationType BlockBlaster = new(GameEngineType.Eaw, 0x1c); + public static readonly ModelAnimationType RedirectBlaster = new(GameEngineType.Eaw, 0x1d); + public static readonly ModelAnimationType IdleBlockBlaster = new(GameEngineType.Eaw, 0x1e); + public static readonly ModelAnimationType ForceWhirlwindAttack = new(GameEngineType.Eaw, 0x1f); + public static readonly ModelAnimationType ForceWhirlwindDie = new(GameEngineType.Eaw, 0x20); + public static readonly ModelAnimationType ForceTelekinesisAttack = new(GameEngineType.Eaw, 0x21); + public static readonly ModelAnimationType ForceTelekinesisHold = new(GameEngineType.Eaw, 0x22); + public static readonly ModelAnimationType ForceTelekinesisRelease = new(GameEngineType.Eaw, 0x23); + public static readonly ModelAnimationType ForceTelekinesisDie = new(GameEngineType.Eaw, 0x24); + public static readonly ModelAnimationType EarthquakeAttack = new(GameEngineType.Eaw, 0x25); + public static readonly ModelAnimationType EarthquakeHold = new(GameEngineType.Eaw, 0x26); + public static readonly ModelAnimationType EarthquakeRelease = new(GameEngineType.Eaw, 0x27); + public static readonly ModelAnimationType ForceLightningAttack = new(GameEngineType.Eaw, 0x28); + public static readonly ModelAnimationType ForceLightningDie = new(GameEngineType.Eaw, 0x29); + public static readonly ModelAnimationType ForceRun = new(GameEngineType.Eaw, 0x2a); + public static readonly ModelAnimationType TransportLanding = new(GameEngineType.Eaw, 0x2b); + public static readonly ModelAnimationType TransportLeaving = new(GameEngineType.Eaw, 0x2c); + public static readonly ModelAnimationType FlameAttack = new(GameEngineType.Eaw, 0x2d); + public static readonly ModelAnimationType Demolition = new(GameEngineType.Eaw, 0x2e); + public static readonly ModelAnimationType BombToss = new(GameEngineType.Eaw, 0x2f); + public static readonly ModelAnimationType Jump = new(GameEngineType.Eaw, 0x30); + public static readonly ModelAnimationType FlyIdle = new(GameEngineType.Eaw, 0x31); + public static readonly ModelAnimationType FlyLand = new(GameEngineType.Eaw, 0x32); + public static readonly ModelAnimationType LandIdle = new(GameEngineType.Eaw, 0x33); + public static readonly ModelAnimationType Land = new(GameEngineType.Eaw, 0x34); + public static readonly ModelAnimationType HcWin = new(GameEngineType.Eaw, 0x35); + public static readonly ModelAnimationType HcLose = new(GameEngineType.Eaw, 0x36); + public static readonly ModelAnimationType HcDraw = new(GameEngineType.Eaw, 0x37); + public static readonly ModelAnimationType ShieldOn = new(GameEngineType.Eaw, 0x38); + public static readonly ModelAnimationType ShieldOff = new(GameEngineType.Eaw, 0x39); + public static readonly ModelAnimationType CableAttackDie = new(GameEngineType.Eaw, 0x3a); + public static readonly ModelAnimationType DeployedCableAttackDie = new(GameEngineType.Eaw, 0x3b); + public static readonly ModelAnimationType DeployedDie = new(GameEngineType.Eaw, 0x3c); + public static readonly ModelAnimationType RunAroundOnFire = new(GameEngineType.Eaw, 0x3d); + public static readonly ModelAnimationType FireDie = new(GameEngineType.Eaw, 0x3e); + public static readonly ModelAnimationType PoundAttack = new(GameEngineType.Eaw, 0x3f); + public static readonly ModelAnimationType EatAttack = new(GameEngineType.Eaw, 0x40); + public static readonly ModelAnimationType EatDie = new(GameEngineType.Eaw, 0x41); + public static readonly ModelAnimationType MoveWalk = new(GameEngineType.Eaw, 0x42); + public static readonly ModelAnimationType MoveCrouch = new(GameEngineType.Eaw, 0x43); + public static readonly ModelAnimationType StructureOpen = new(GameEngineType.Eaw, 0x44); + public static readonly ModelAnimationType StructureHold = new(GameEngineType.Eaw, 0x45); + public static readonly ModelAnimationType StructureClose = new(GameEngineType.Eaw, 0x46); + public static readonly ModelAnimationType IdleCrouch = new(GameEngineType.Eaw, 0x47); + public static readonly ModelAnimationType TurnLeftCrouch = new(GameEngineType.Eaw, 0x48); + public static readonly ModelAnimationType TurnRightCrouch = new(GameEngineType.Eaw, 0x49); + public static readonly ModelAnimationType Build = new(GameEngineType.Eaw, 0x4a); + public static readonly ModelAnimationType TransitionOwnership = new(GameEngineType.Eaw, 0x4b); + public static readonly ModelAnimationType SelfDestruct = new(GameEngineType.Eaw, 0x4c); + public static readonly ModelAnimationType Attention = new(GameEngineType.Eaw, 0x4d); + public static readonly ModelAnimationType Celebrate = new(GameEngineType.Eaw, 0x4e); + public static readonly ModelAnimationType FlinchLeft = new(GameEngineType.Eaw, 0x4f); + public static readonly ModelAnimationType FlinchRight = new(GameEngineType.Eaw, 0x50); + public static readonly ModelAnimationType FlinchFront = new(GameEngineType.Eaw, 0x51); + public static readonly ModelAnimationType FlinchBack = new(GameEngineType.Eaw, 0x52); + public static readonly ModelAnimationType AttackFlinchLeft = new(GameEngineType.Eaw, 0x53); + public static readonly ModelAnimationType AttackFlinchRight = new(GameEngineType.Eaw, 0x54); + public static readonly ModelAnimationType AttackFlinchFront = new(GameEngineType.Eaw, 0x55); + public static readonly ModelAnimationType AttackFlinchBack = new(GameEngineType.Eaw, 0x56); + public static readonly ModelAnimationType Talk = new(GameEngineType.Eaw, 0x57); + public static readonly ModelAnimationType TalkGesture = new(GameEngineType.Eaw, 0x58); + public static readonly ModelAnimationType TalkQuestion = new(GameEngineType.Eaw, 0x59); + public static readonly ModelAnimationType Hacking = new(GameEngineType.Eaw, 0x5a); + public static readonly ModelAnimationType Repairing = new(GameEngineType.Eaw, 0x5b); + public static readonly ModelAnimationType Choke = new(GameEngineType.Eaw, 0x5c); + public static readonly ModelAnimationType ChokeDie = new(GameEngineType.Eaw, 0x5d); + public static readonly ModelAnimationType DropTroopers = new(GameEngineType.Eaw, 0x5e); + public static readonly ModelAnimationType RopeSlide = new(GameEngineType.Eaw, 0x5f); + public static readonly ModelAnimationType RopeLand = new(GameEngineType.Eaw, 0x60); + public static readonly ModelAnimationType RopeDrop = new(GameEngineType.Eaw, 0x61); + public static readonly ModelAnimationType RopeLift = new(GameEngineType.Eaw, 0x62); + public static readonly ModelAnimationType Alarm = new(GameEngineType.Eaw, 0x63); + public static readonly ModelAnimationType Warning = new(GameEngineType.Eaw, 0x64); + public static readonly ModelAnimationType Crushed = new(GameEngineType.Eaw, 0x65); + public static readonly ModelAnimationType PowerDown = new(GameEngineType.Eaw, 0x66); + public static readonly ModelAnimationType PowerUp = new(GameEngineType.Eaw, 0x67); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/FocModelAnimationTypes.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/FocModelAnimationTypes.cs new file mode 100644 index 0000000..743491a --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/FocModelAnimationTypes.cs @@ -0,0 +1,124 @@ +namespace PG.StarWarsGame.Engine.Rendering.Animations; + +public static class FocModelAnimationTypes +{ + public static readonly ModelAnimationType Idle = new(GameEngineType.Foc, 0x0); + public static readonly ModelAnimationType SpaceIdle = new(GameEngineType.Foc, 0x1); + public static readonly ModelAnimationType Move = new(GameEngineType.Foc, 0x2); + public static readonly ModelAnimationType TurnLeft = new(GameEngineType.Foc, 0x3); + public static readonly ModelAnimationType TurnRight = new(GameEngineType.Foc, 0x4); + public static readonly ModelAnimationType Attack = new(GameEngineType.Foc, 0x5); + public static readonly ModelAnimationType AttackIdle = new(GameEngineType.Foc, 0x6); + public static readonly ModelAnimationType Die = new(GameEngineType.Foc, 0x7); + public static readonly ModelAnimationType Rotate = new(GameEngineType.Foc, 0x8); + public static readonly ModelAnimationType SpecialA = new(GameEngineType.Foc, 0x9); + public static readonly ModelAnimationType SpecialB = new(GameEngineType.Foc, 0xa); + public static readonly ModelAnimationType SpecialC = new(GameEngineType.Foc, 0xb); + public static readonly ModelAnimationType TransitionToLeftTurn = new(GameEngineType.Foc, 0xc); + public static readonly ModelAnimationType TransitionFromLeftTurn = new(GameEngineType.Foc, 0xd); + public static readonly ModelAnimationType TransitionToRightTurn = new(GameEngineType.Foc, 0xe); + public static readonly ModelAnimationType TransitionFromRightTurn = new(GameEngineType.Foc, 0xf); + public static readonly ModelAnimationType TransitionToMove = new(GameEngineType.Foc, 0x10); + public static readonly ModelAnimationType TransitionFromMoveFrame0 = new(GameEngineType.Foc, 0x11); + public static readonly ModelAnimationType TransitionFromMoveFrame40 = new(GameEngineType.Foc, 0x12); + public static readonly ModelAnimationType TransitionFromMoveFrame80 = new(GameEngineType.Foc, 0x13); + public static readonly ModelAnimationType TransitionFromMoveFrame120 = new(GameEngineType.Foc, 0x14); + public static readonly ModelAnimationType TurnLeftHalf = new(GameEngineType.Foc, 0x15); + public static readonly ModelAnimationType TurnLeftQuarter = new(GameEngineType.Foc, 0x16); + public static readonly ModelAnimationType TurnRightHalf = new(GameEngineType.Foc, 0x17); + public static readonly ModelAnimationType TurnRightQuarter = new(GameEngineType.Foc, 0x18); + public static readonly ModelAnimationType Deploy = new(GameEngineType.Foc, 0x19); + public static readonly ModelAnimationType Undeploy = new(GameEngineType.Foc, 0x1a); + public static readonly ModelAnimationType Cinematic = new(GameEngineType.Foc, 0x1b); + public static readonly ModelAnimationType BlockBlaster = new(GameEngineType.Foc, 0x1c); + public static readonly ModelAnimationType RedirectBlaster = new(GameEngineType.Foc, 0x1d); + public static readonly ModelAnimationType IdleBlockBlaster = new(GameEngineType.Foc, 0x1e); + public static readonly ModelAnimationType ForceWhirlwindAttack = new(GameEngineType.Foc, 0x1f); + public static readonly ModelAnimationType ForceWhirlwindDie = new(GameEngineType.Foc, 0x20); + public static readonly ModelAnimationType ForceTelekinesisAttack = new(GameEngineType.Foc, 0x21); + public static readonly ModelAnimationType ForceTelekinesisHold = new(GameEngineType.Foc, 0x22); + public static readonly ModelAnimationType ForceTelekinesisRelease = new(GameEngineType.Foc, 0x23); + public static readonly ModelAnimationType ForceTelekinesisDie = new(GameEngineType.Foc, 0x24); + public static readonly ModelAnimationType EarthquakeAttack = new(GameEngineType.Foc, 0x25); + public static readonly ModelAnimationType EarthquakeHold = new(GameEngineType.Foc, 0x26); + public static readonly ModelAnimationType EarthquakeRelease = new(GameEngineType.Foc, 0x27); + public static readonly ModelAnimationType ForceLightningAttack = new(GameEngineType.Foc, 0x28); + public static readonly ModelAnimationType ForceLightningDie = new(GameEngineType.Foc, 0x29); + public static readonly ModelAnimationType ForceRun = new(GameEngineType.Foc, 0x2a); + public static readonly ModelAnimationType TransportLanding = new(GameEngineType.Foc, 0x2b); + public static readonly ModelAnimationType TransportLeaving = new(GameEngineType.Foc, 0x2c); + public static readonly ModelAnimationType FlameAttack = new(GameEngineType.Foc, 0x2d); + public static readonly ModelAnimationType Demolition = new(GameEngineType.Foc, 0x2e); + public static readonly ModelAnimationType BombToss = new(GameEngineType.Foc, 0x2f); + public static readonly ModelAnimationType Jump = new(GameEngineType.Foc, 0x30); + public static readonly ModelAnimationType FlyIdle = new(GameEngineType.Foc, 0x31); + public static readonly ModelAnimationType FlyLand = new(GameEngineType.Foc, 0x32); + public static readonly ModelAnimationType LandIdle = new(GameEngineType.Foc, 0x33); + public static readonly ModelAnimationType Land = new(GameEngineType.Foc, 0x34); + public static readonly ModelAnimationType HcWin = new(GameEngineType.Foc, 0x35); + public static readonly ModelAnimationType HcLose = new(GameEngineType.Foc, 0x36); + public static readonly ModelAnimationType HcDraw = new(GameEngineType.Foc, 0x37); + public static readonly ModelAnimationType ShieldOn = new(GameEngineType.Foc, 0x38); + public static readonly ModelAnimationType ShieldOff = new(GameEngineType.Foc, 0x39); + public static readonly ModelAnimationType CableAttackDie = new(GameEngineType.Foc, 0x3a); + public static readonly ModelAnimationType DeployedCableAttackDie = new(GameEngineType.Foc, 0x3b); + public static readonly ModelAnimationType DeployedDie = new(GameEngineType.Foc, 0x3c); + public static readonly ModelAnimationType RunAroundOnFire = new(GameEngineType.Foc, 0x3d); + public static readonly ModelAnimationType FireDie = new(GameEngineType.Foc, 0x3e); + public static readonly ModelAnimationType PoundAttack = new(GameEngineType.Foc, 0x3f); + public static readonly ModelAnimationType EatAttack = new(GameEngineType.Foc, 0x40); + public static readonly ModelAnimationType EatDie = new(GameEngineType.Foc, 0x41); + public static readonly ModelAnimationType MoveWalk = new(GameEngineType.Foc, 0x42); + public static readonly ModelAnimationType MoveCrouch = new(GameEngineType.Foc, 0x43); + public static readonly ModelAnimationType StructureOpen = new(GameEngineType.Foc, 0x44); + public static readonly ModelAnimationType StructureHold = new(GameEngineType.Foc, 0x45); + public static readonly ModelAnimationType StructureClose = new(GameEngineType.Foc, 0x46); + public static readonly ModelAnimationType IdleCrouch = new(GameEngineType.Foc, 0x47); + public static readonly ModelAnimationType TurnLeftCrouch = new(GameEngineType.Foc, 0x48); + public static readonly ModelAnimationType TurnRightCrouch = new(GameEngineType.Foc, 0x49); + public static readonly ModelAnimationType Build = new(GameEngineType.Foc, 0x4a); + public static readonly ModelAnimationType TransitionOwnership = new(GameEngineType.Foc, 0x4b); + public static readonly ModelAnimationType SelfDestruct = new(GameEngineType.Foc, 0x4c); + public static readonly ModelAnimationType Attention = new(GameEngineType.Foc, 0x4d); + public static readonly ModelAnimationType Celebrate = new(GameEngineType.Foc, 0x4e); + public static readonly ModelAnimationType FlinchLeft = new(GameEngineType.Foc, 0x4f); + public static readonly ModelAnimationType FlinchRight = new(GameEngineType.Foc, 0x50); + public static readonly ModelAnimationType FlinchFront = new(GameEngineType.Foc, 0x51); + public static readonly ModelAnimationType FlinchBack = new(GameEngineType.Foc, 0x52); + public static readonly ModelAnimationType AttackFlinchLeft = new(GameEngineType.Foc, 0x53); + public static readonly ModelAnimationType AttackFlinchRight = new(GameEngineType.Foc, 0x54); + public static readonly ModelAnimationType AttackFlinchFront = new(GameEngineType.Foc, 0x55); + public static readonly ModelAnimationType AttackFlinchBack = new(GameEngineType.Foc, 0x56); + public static readonly ModelAnimationType Talk = new(GameEngineType.Foc, 0x57); + public static readonly ModelAnimationType TalkGesture = new(GameEngineType.Foc, 0x58); + public static readonly ModelAnimationType TalkQuestion = new(GameEngineType.Foc, 0x59); + public static readonly ModelAnimationType Hacking = new(GameEngineType.Foc, 0x5a); + public static readonly ModelAnimationType Repairing = new(GameEngineType.Foc, 0x5b); + public static readonly ModelAnimationType Choke = new(GameEngineType.Foc, 0x5c); + public static readonly ModelAnimationType ChokeDie = new(GameEngineType.Foc, 0x5d); + public static readonly ModelAnimationType DropTroopers = new(GameEngineType.Foc, 0x5e); + public static readonly ModelAnimationType RopeSlide = new(GameEngineType.Foc, 0x5f); + public static readonly ModelAnimationType RopeLand = new(GameEngineType.Foc, 0x60); + public static readonly ModelAnimationType RopeDrop = new(GameEngineType.Foc, 0x61); + public static readonly ModelAnimationType RopeLift = new(GameEngineType.Foc, 0x62); + public static readonly ModelAnimationType Alarm = new(GameEngineType.Foc, 0x63); + public static readonly ModelAnimationType Warning = new(GameEngineType.Foc, 0x64); + public static readonly ModelAnimationType Crushed = new(GameEngineType.Foc, 0x65); + public static readonly ModelAnimationType PowerDown = new(GameEngineType.Foc, 0x66); + public static readonly ModelAnimationType PowerUp = new(GameEngineType.Foc, 0x67); + public static readonly ModelAnimationType SpinMove = new(GameEngineType.Foc, 0x68); + public static readonly ModelAnimationType ForceRevealBegin = new(GameEngineType.Foc, 0x69); + public static readonly ModelAnimationType ForceRevealLoop = new(GameEngineType.Foc, 0x6a); + public static readonly ModelAnimationType ForceRevealEnd = new(GameEngineType.Foc, 0x6b); + public static readonly ModelAnimationType SaberThrow = new(GameEngineType.Foc, 0x6c); + public static readonly ModelAnimationType SaberControl = new(GameEngineType.Foc, 0x6d); + public static readonly ModelAnimationType SaberCatch = new(GameEngineType.Foc, 0x6e); + public static readonly ModelAnimationType SaberSpin = new(GameEngineType.Foc, 0x6f); + public static readonly ModelAnimationType ContaminateAttack = new(GameEngineType.Foc, 0x70); + public static readonly ModelAnimationType ContaminateLoop = new(GameEngineType.Foc, 0x71); + public static readonly ModelAnimationType ContaminateRelease = new(GameEngineType.Foc, 0x72); + public static readonly ModelAnimationType DeployedWalk = new(GameEngineType.Foc, 0x73); + public static readonly ModelAnimationType PadBuild = new(GameEngineType.Foc, 0x74); + public static readonly ModelAnimationType PadSell = new(GameEngineType.Foc, 0x75); + public static readonly ModelAnimationType Heal = new(GameEngineType.Foc, 0x76); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/ModelAnimationType.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/ModelAnimationType.cs new file mode 100644 index 0000000..867ba08 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/ModelAnimationType.cs @@ -0,0 +1,47 @@ +using System; + +namespace PG.StarWarsGame.Engine.Rendering.Animations; + +public readonly struct ModelAnimationType : IEquatable +{ + public GameEngineType TargetEngine { get; } + + public int Value { get; } + + internal ModelAnimationType(GameEngineType engine, int value) + { + TargetEngine = engine; + Value = value; + } + + public override string ToString() + { + var nameLookup = SupportedModelAnimationTypes.GetAnimationTypesForEngine(TargetEngine); + return $"'{nameLookup[this]}' ({Value})"; + } + + public bool Equals(ModelAnimationType other) + { + return TargetEngine == other.TargetEngine && Value == other.Value; + } + + public override bool Equals(object? obj) + { + return obj is ModelAnimationType other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine((int)TargetEngine, Value); + } + + public static bool operator ==(ModelAnimationType left, ModelAnimationType right) + { + return left.Equals(right); + } + + public static bool operator !=(ModelAnimationType left, ModelAnimationType right) + { + return !(left == right); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/SupportedModelAnimationTypes.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/SupportedModelAnimationTypes.cs new file mode 100644 index 0000000..2a7aa3f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Animations/SupportedModelAnimationTypes.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace PG.StarWarsGame.Engine.Rendering.Animations; + +public static class SupportedModelAnimationTypes +{ + public static IReadOnlyDictionary GetAnimationTypesForEngine(GameEngineType engineType) + { + return engineType switch + { + GameEngineType.Eaw => EawSupportedAnimations, + GameEngineType.Foc => FocSupportedAnimations, + _ => throw new NotSupportedException() + }; + } + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private static readonly Dictionary FocSupportedAnimations = new() + { + { FocModelAnimationTypes.Idle, "IDLE"}, + { FocModelAnimationTypes.SpaceIdle, "SPACE_IDLE"}, + { FocModelAnimationTypes.Move, "MOVE"}, + { FocModelAnimationTypes.TurnLeft, "TURNL"}, + { FocModelAnimationTypes.TurnRight, "TURNR"}, + { FocModelAnimationTypes.Attack, "ATTACK"}, + { FocModelAnimationTypes.AttackIdle, "ATTACKIDLE"}, + { FocModelAnimationTypes.Die, "DIE"}, + { FocModelAnimationTypes.Rotate, "ROTATE"}, + { FocModelAnimationTypes.SpecialA, "SPECIAL_A"}, + { FocModelAnimationTypes.SpecialB, "SPECIAL_B"}, + { FocModelAnimationTypes.SpecialC, "SPECIAL_C"}, + { FocModelAnimationTypes.TransitionToLeftTurn, "TURNL_BEGIN"}, + { FocModelAnimationTypes.TransitionFromLeftTurn, "TURNL_END"}, + { FocModelAnimationTypes.TransitionToRightTurn, "TURNR_BEGIN"}, + { FocModelAnimationTypes.TransitionFromRightTurn, "TURNR_END"}, + { FocModelAnimationTypes.TransitionToMove, "MOVESTART"}, + { FocModelAnimationTypes.TransitionFromMoveFrame0, "MOVE_ENDONE"}, + { FocModelAnimationTypes.TransitionFromMoveFrame40, "MOVE_ENDTWO"}, + { FocModelAnimationTypes.TransitionFromMoveFrame80, "MOVE_ENDTHREE"}, + { FocModelAnimationTypes.TransitionFromMoveFrame120, "MOVE_ENDFOUR"}, + { FocModelAnimationTypes.TurnLeftHalf, "TURNL_HALF"}, + { FocModelAnimationTypes.TurnLeftQuarter, "TURNL_QUARTER"}, + { FocModelAnimationTypes.TurnRightHalf, "TURNR_HALF"}, + { FocModelAnimationTypes.TurnRightQuarter, "TURNR_QUARTER"}, + { FocModelAnimationTypes.Deploy, "DEPLOY"}, + { FocModelAnimationTypes.Undeploy, "UNDEPLOY"}, + { FocModelAnimationTypes.Cinematic, "CINEMATIC"}, + { FocModelAnimationTypes.BlockBlaster, "BLOCK_BLASTER"}, + { FocModelAnimationTypes.RedirectBlaster, "REDIRECT_BLASTER"}, + { FocModelAnimationTypes.IdleBlockBlaster, "IDLE_BLOCKBLASTER"}, + { FocModelAnimationTypes.ForceWhirlwindAttack, "FW_ATTACK"}, + { FocModelAnimationTypes.ForceWhirlwindDie, "FW_DIE"}, + { FocModelAnimationTypes.ForceTelekinesisAttack, "FTK_ATTACK"}, + { FocModelAnimationTypes.ForceTelekinesisHold, "FTK_HOLD"}, + { FocModelAnimationTypes.ForceTelekinesisRelease, "FTK_RELEASE"}, + { FocModelAnimationTypes.ForceTelekinesisDie, "FTK_DIE"}, + { FocModelAnimationTypes.EarthquakeAttack, "FB_ATTACK"}, + { FocModelAnimationTypes.EarthquakeHold, "FB_HOLD"}, + { FocModelAnimationTypes.EarthquakeRelease, "FB_RELEASE"}, + { FocModelAnimationTypes.ForceLightningAttack, "FL_ATTACK"}, + { FocModelAnimationTypes.ForceLightningDie, "FL_DIE"}, + { FocModelAnimationTypes.ForceRun, "FORCE_RUN"}, + { FocModelAnimationTypes.TransportLanding, "LAND"}, + { FocModelAnimationTypes.TransportLeaving, "TAKEOFF"}, + { FocModelAnimationTypes.FlameAttack, "FLAME_ATTACK"}, + { FocModelAnimationTypes.Demolition, "DEMOLITION"}, + { FocModelAnimationTypes.BombToss, "BOMBTOSS"}, + { FocModelAnimationTypes.Jump, "JUMP"}, + { FocModelAnimationTypes.FlyIdle, "FLYIDLE"}, + { FocModelAnimationTypes.FlyLand, "FLYLAND"}, + { FocModelAnimationTypes.LandIdle, "FLYLANDIDLE"}, + { FocModelAnimationTypes.Land, "FLYLANDDROP"}, + { FocModelAnimationTypes.HcWin, "HC_WIN"}, + { FocModelAnimationTypes.HcLose, "HC_LOSE"}, + { FocModelAnimationTypes.HcDraw, "HC_DRAW"}, + { FocModelAnimationTypes.ShieldOn, "SHIELD_ON"}, + { FocModelAnimationTypes.ShieldOff, "SHIELD_OFF"}, + { FocModelAnimationTypes.CableAttackDie, "CA_DIE"}, + { FocModelAnimationTypes.DeployedCableAttackDie, "DEPLOYED_CA_DIE"}, + { FocModelAnimationTypes.DeployedDie, "DEPLOYED_DIE"}, + { FocModelAnimationTypes.RunAroundOnFire, "FIRE_MOVE"}, + { FocModelAnimationTypes.FireDie, "FIRE_DIE"}, + { FocModelAnimationTypes.PoundAttack, "POUND_ATTACK"}, + { FocModelAnimationTypes.EatAttack, "EAT_ATTACK"}, + { FocModelAnimationTypes.EatDie, "EATEN_DIE"}, + { FocModelAnimationTypes.MoveWalk, "WALKMOVE"}, + { FocModelAnimationTypes.MoveCrouch, "CROUCHMOVE"}, + { FocModelAnimationTypes.StructureOpen, "OPEN"}, + { FocModelAnimationTypes.StructureHold, "HOLD"}, + { FocModelAnimationTypes.StructureClose, "CLOSE"}, + { FocModelAnimationTypes.IdleCrouch, "CROUCHIDLE"}, + { FocModelAnimationTypes.TurnLeftCrouch, "CROUCHTURNL"}, + { FocModelAnimationTypes.TurnRightCrouch, "CROUCHTURNR"}, + { FocModelAnimationTypes.Build, "BUILD"}, + { FocModelAnimationTypes.TransitionOwnership, "TRANS"}, + { FocModelAnimationTypes.SelfDestruct, "SELF_DESTRUCT"}, + { FocModelAnimationTypes.Attention, "ATTENTION"}, + { FocModelAnimationTypes.Celebrate, "CELEBRATE"}, + { FocModelAnimationTypes.FlinchLeft, "FLINCHL"}, + { FocModelAnimationTypes.FlinchRight, "FLINCHR"}, + { FocModelAnimationTypes.FlinchFront, "FLINCHF"}, + { FocModelAnimationTypes.FlinchBack, "FLINCHB"}, + { FocModelAnimationTypes.AttackFlinchLeft, "ATTACKFLINCHL"}, + { FocModelAnimationTypes.AttackFlinchRight, "ATTACKFLINCHR"}, + { FocModelAnimationTypes.AttackFlinchFront, "ATTACKFLINCHF"}, + { FocModelAnimationTypes.AttackFlinchBack, "ATTACKFLINCHB"}, + { FocModelAnimationTypes.Talk, "TALK"}, + { FocModelAnimationTypes.TalkGesture, "TALKGESTURE"}, + { FocModelAnimationTypes.TalkQuestion, "TALKQUESTION"}, + { FocModelAnimationTypes.Hacking, "HACKING"}, + { FocModelAnimationTypes.Repairing, "REPAIRING"}, + { FocModelAnimationTypes.Choke, "CHOKE"}, + { FocModelAnimationTypes.ChokeDie, "CHOKEDEATH"}, + { FocModelAnimationTypes.DropTroopers, "TROOPDROP"}, + { FocModelAnimationTypes.RopeSlide, "ROPESLIDE"}, + { FocModelAnimationTypes.RopeLand, "ROPELAND"}, + { FocModelAnimationTypes.RopeDrop, "ROPE_DROP"}, + { FocModelAnimationTypes.RopeLift, "ROPE_LIFT"}, + { FocModelAnimationTypes.Alarm, "ALARM"}, + { FocModelAnimationTypes.Warning, "WARNING"}, + { FocModelAnimationTypes.Crushed, "CRUSHED"}, + { FocModelAnimationTypes.PowerDown, "POWERDOWN"}, + { FocModelAnimationTypes.PowerUp, "POWERUP"}, + { FocModelAnimationTypes.SpinMove, "SPINMOVE"}, + { FocModelAnimationTypes.ForceRevealBegin, "FORCE_REVEAL_BEGIN"}, + { FocModelAnimationTypes.ForceRevealLoop, "FORCE_REVEAL_LOOP"}, + { FocModelAnimationTypes.ForceRevealEnd, "FORCE_REVEAL_END"}, + { FocModelAnimationTypes.SaberThrow, "SWORD_THROW"}, + { FocModelAnimationTypes.SaberControl, "SWORD_CONTROL"}, + { FocModelAnimationTypes.SaberCatch, "SWORD_CATCH"}, + { FocModelAnimationTypes.SaberSpin, "SWORDSPIN"}, + { FocModelAnimationTypes.ContaminateAttack, "CONTAMINATE_ATTACK"}, + { FocModelAnimationTypes.ContaminateLoop, "CONTAMINATE_LOOP"}, + { FocModelAnimationTypes.ContaminateRelease, "CONTAMINATE_RELEASE"}, + { FocModelAnimationTypes.DeployedWalk, "WALK"}, + { FocModelAnimationTypes.PadBuild, "PAD_BUILD"}, + { FocModelAnimationTypes.PadSell, "PAD_SELL"}, + { FocModelAnimationTypes.Heal, "HEAL"}, + }; + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private static readonly Dictionary EawSupportedAnimations = new() + { + { EawModelAnimationTypes.Idle, "IDLE"}, + { EawModelAnimationTypes.SpaceIdle, "SPACE_IDLE"}, + { EawModelAnimationTypes.Move, "MOVE"}, + { EawModelAnimationTypes.TurnLeft, "TURNL"}, + { EawModelAnimationTypes.TurnRight, "TURNR"}, + { EawModelAnimationTypes.Attack, "ATTACK"}, + { EawModelAnimationTypes.AttackIdle, "ATTACKIDLE"}, + { EawModelAnimationTypes.Die, "DIE"}, + { EawModelAnimationTypes.Rotate, "ROTATE"}, + { EawModelAnimationTypes.SpecialA, "SPECIAL_A"}, + { EawModelAnimationTypes.SpecialB, "SPECIAL_B"}, + { EawModelAnimationTypes.SpecialC, "SPECIAL_C"}, + { EawModelAnimationTypes.TransitionToLeftTurn, "TURNL_BEGIN"}, + { EawModelAnimationTypes.TransitionFromLeftTurn, "TURNL_END"}, + { EawModelAnimationTypes.TransitionToRightTurn, "TURNR_BEGIN"}, + { EawModelAnimationTypes.TransitionFromRightTurn, "TURNR_END"}, + { EawModelAnimationTypes.TransitionToMove, "MOVESTART"}, + { EawModelAnimationTypes.TransitionFromMoveFrame0, "MOVE_ENDONE"}, + { EawModelAnimationTypes.TransitionFromMoveFrame40, "MOVE_ENDTWO"}, + { EawModelAnimationTypes.TransitionFromMoveFrame80, "MOVE_ENDTHREE"}, + { EawModelAnimationTypes.TransitionFromMoveFrame120, "MOVE_ENDFOUR"}, + { EawModelAnimationTypes.TurnLeftHalf, "TURNL_HALF"}, + { EawModelAnimationTypes.TurnLeftQuarter, "TURNL_QUARTER"}, + { EawModelAnimationTypes.TurnRightHalf, "TURNR_HALF"}, + { EawModelAnimationTypes.TurnRightQuarter, "TURNR_QUARTER"}, + { EawModelAnimationTypes.Deploy, "DEPLOY"}, + { EawModelAnimationTypes.Undeploy, "UNDEPLOY"}, + { EawModelAnimationTypes.Cinematic, "CINEMATIC"}, + { EawModelAnimationTypes.BlockBlaster, "BLOCK_BLASTER"}, + { EawModelAnimationTypes.RedirectBlaster, "REDIRECT_BLASTER"}, + { EawModelAnimationTypes.IdleBlockBlaster, "IDLE_BLOCKBLASTER"}, + { EawModelAnimationTypes.ForceWhirlwindAttack, "FW_ATTACK"}, + { EawModelAnimationTypes.ForceWhirlwindDie, "FW_DIE"}, + { EawModelAnimationTypes.ForceTelekinesisAttack, "FTK_ATTACK"}, + { EawModelAnimationTypes.ForceTelekinesisHold, "FTK_HOLD"}, + { EawModelAnimationTypes.ForceTelekinesisRelease, "FTK_RELEASE"}, + { EawModelAnimationTypes.ForceTelekinesisDie, "FTK_DIE"}, + { EawModelAnimationTypes.EarthquakeAttack, "FB_ATTACK"}, + { EawModelAnimationTypes.EarthquakeHold, "FB_HOLD"}, + { EawModelAnimationTypes.EarthquakeRelease, "FB_RELEASE"}, + { EawModelAnimationTypes.ForceLightningAttack, "FL_ATTACK"}, + { EawModelAnimationTypes.ForceLightningDie, "FL_DIE"}, + { EawModelAnimationTypes.ForceRun, "FORCE_RUN"}, + { EawModelAnimationTypes.TransportLanding, "LAND"}, + { EawModelAnimationTypes.TransportLeaving, "TAKEOFF"}, + { EawModelAnimationTypes.FlameAttack, "FLAME_ATTACK"}, + { EawModelAnimationTypes.Demolition, "DEMOLITION"}, + { EawModelAnimationTypes.BombToss, "BOMBTOSS"}, + { EawModelAnimationTypes.Jump, "JUMP"}, + { EawModelAnimationTypes.FlyIdle, "FLYIDLE"}, + { EawModelAnimationTypes.FlyLand, "FLYLAND"}, + { EawModelAnimationTypes.LandIdle, "FLYLANDIDLE"}, + { EawModelAnimationTypes.Land, "FLYLANDDROP"}, + { EawModelAnimationTypes.HcWin, "HC_WIN"}, + { EawModelAnimationTypes.HcLose, "HC_LOSE"}, + { EawModelAnimationTypes.HcDraw, "HC_DRAW"}, + { EawModelAnimationTypes.ShieldOn, "SHIELD_ON"}, + { EawModelAnimationTypes.ShieldOff, "SHIELD_OFF"}, + { EawModelAnimationTypes.CableAttackDie, "CA_DIE"}, + { EawModelAnimationTypes.DeployedCableAttackDie, "DEPLOYED_CA_DIE"}, + { EawModelAnimationTypes.DeployedDie, "DEPLOYED_DIE"}, + { EawModelAnimationTypes.RunAroundOnFire, "FIRE_MOVE"}, + { EawModelAnimationTypes.FireDie, "FIRE_DIE"}, + { EawModelAnimationTypes.PoundAttack, "POUND_ATTACK"}, + { EawModelAnimationTypes.EatAttack, "EAT_ATTACK"}, + { EawModelAnimationTypes.EatDie, "EATEN_DIE"}, + { EawModelAnimationTypes.MoveWalk, "WALKMOVE"}, + { EawModelAnimationTypes.MoveCrouch, "CROUCHMOVE"}, + { EawModelAnimationTypes.StructureOpen, "OPEN"}, + { EawModelAnimationTypes.StructureHold, "HOLD"}, + { EawModelAnimationTypes.StructureClose, "CLOSE"}, + { EawModelAnimationTypes.IdleCrouch, "CROUCHIDLE"}, + { EawModelAnimationTypes.TurnLeftCrouch, "CROUCHTURNL"}, + { EawModelAnimationTypes.TurnRightCrouch, "CROUCHTURNR"}, + { EawModelAnimationTypes.Build, "BUILD"}, + { EawModelAnimationTypes.TransitionOwnership, "TRANS"}, + { EawModelAnimationTypes.SelfDestruct, "SELF_DESTRUCT"}, + { EawModelAnimationTypes.Attention, "ATTENTION"}, + { EawModelAnimationTypes.Celebrate, "CELEBRATE"}, + { EawModelAnimationTypes.FlinchLeft, "FLINCHL"}, + { EawModelAnimationTypes.FlinchRight, "FLINCHR"}, + { EawModelAnimationTypes.FlinchFront, "FLINCHF"}, + { EawModelAnimationTypes.FlinchBack, "FLINCHB"}, + { EawModelAnimationTypes.AttackFlinchLeft, "ATTACKFLINCHL"}, + { EawModelAnimationTypes.AttackFlinchRight, "ATTACKFLINCHR"}, + { EawModelAnimationTypes.AttackFlinchFront, "ATTACKFLINCHF"}, + { EawModelAnimationTypes.AttackFlinchBack, "ATTACKFLINCHB"}, + { EawModelAnimationTypes.Talk, "TALK"}, + { EawModelAnimationTypes.TalkGesture, "TALKGESTURE"}, + { EawModelAnimationTypes.TalkQuestion, "TALKQUESTION"}, + { EawModelAnimationTypes.Hacking, "HACKING"}, + { EawModelAnimationTypes.Repairing, "REPAIRING"}, + { EawModelAnimationTypes.Choke, "CHOKE"}, + { EawModelAnimationTypes.ChokeDie, "CHOKEDEATH"}, + { EawModelAnimationTypes.DropTroopers, "TROOPDROP"}, + { EawModelAnimationTypes.RopeSlide, "ROPESLIDE"}, + { EawModelAnimationTypes.RopeLand, "ROPELAND"}, + { EawModelAnimationTypes.RopeDrop, "ROPE_DROP"}, + { EawModelAnimationTypes.RopeLift, "ROPE_LIFT"}, + { EawModelAnimationTypes.Alarm, "ALARM"}, + { EawModelAnimationTypes.Warning, "WARNING"}, + { EawModelAnimationTypes.Crushed, "CRUSHED"}, + { EawModelAnimationTypes.PowerDown, "POWERDOWN"}, + { EawModelAnimationTypes.PowerUp, "POWERUP"} + }; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontData.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontData.cs new file mode 100644 index 0000000..fdd3322 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontData.cs @@ -0,0 +1,16 @@ +namespace PG.StarWarsGame.Engine.Rendering.Font; + +public readonly struct FontData +{ + public int FontSize { get; init; } + + public string FontName { get; init; } + + public bool Bold { get; init; } + + public bool Italic { get; init; } + + public bool StaticSize { get; init; } + + public float StretchFactor { get; init; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontManager.cs new file mode 100644 index 0000000..00ca4f2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/FontManager.cs @@ -0,0 +1,71 @@ +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO.Repositories; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using PG.StarWarsGame.Engine.Localization; + +namespace PG.StarWarsGame.Engine.Rendering.Font; + +internal class FontManager : GameManagerBase, IFontManager +{ + private readonly IOSFontManager _fontManager; + + private ISet _fontNames = null!; + + public FontManager(GameRepository repository, GameEngineErrorReporterWrapper errorReporter, IServiceProvider serviceProvider) + : base(repository, errorReporter, serviceProvider) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _fontManager = new WindowsFontManager(); + else + _fontManager = new NetFontManager(); + } + + public IReadOnlyCollection FontNames => [.._fontNames]; + + public FontData? CreateFont(string fontName, int size, bool bold, bool italic, bool staticSize, float stretchFactor) + { + ThrowIfNotInitialized(); + + if (size <= 0) + { + ErrorReporter.Assert(EngineAssert.Create(EngineAssertKind.ValueOutOfRange, size, [fontName], + $"Font size '{size}' for '{fontName}' must be greater than 0.")); + } + + if (!_fontNames.Contains(fontName)) + fontName = PGConstants.DefaultUnicodeFontName; + + if (!_fontNames.Contains(fontName)) + { + ErrorReporter.Assert(EngineAssert.FromNullOrEmpty([fontName], $"Unable to find font '{fontName}'")); + return null; + } + + return new FontData(); + } + + public void NormalizeFontData(LanguageType language, ref string fontName, ref int fontSize) + { + if (language >= LanguageType.Japanese) + fontName = PGConstants.DefaultUnicodeFontName; + if (language == LanguageType.Russian && fontSize < 7) + fontSize = 7; + } + + protected override Task InitializeCoreAsync(CancellationToken token) + { + var fontNames = new HashSet(_fontManager.GetFontFamilies(), StringComparer.OrdinalIgnoreCase) + { + "EmpireAtWar-Bold", + "EmpireAtWar-Light", + "EmpireAtWar-Medium", + "EmpireAtWar-Stencil" + }; + _fontNames = fontNames; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IFontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IFontManager.cs new file mode 100644 index 0000000..e5802aa --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IFontManager.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Engine.Localization; + +namespace PG.StarWarsGame.Engine.Rendering.Font; + +public interface IFontManager +{ + public IReadOnlyCollection FontNames { get; } + + FontData? CreateFont(string fontName, int size, bool bold, bool italic, bool stat, float stretchFactor); + + public void NormalizeFontData(LanguageType language, ref string fontName, ref int fontSize); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IOSFontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IOSFontManager.cs new file mode 100644 index 0000000..c930cd2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/IOSFontManager.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace PG.StarWarsGame.Engine.Rendering.Font; + +internal interface IOSFontManager +{ + IEnumerable GetFontFamilies(); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/NetFontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/NetFontManager.cs new file mode 100644 index 0000000..2515dc5 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/NetFontManager.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace PG.StarWarsGame.Engine.Rendering.Font; + +internal class NetFontManager : IOSFontManager +{ + public IEnumerable GetFontFamilies() + { + // TODO: At the moment we do not have a platform-independent way to get font names. + yield return "Arial"; + yield return "Arial Medium"; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs new file mode 100644 index 0000000..f11374e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/Font/WindowsFontManager.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using Vanara.PInvoke; + +namespace PG.StarWarsGame.Engine.Rendering.Font; + +[SupportedOSPlatform("windows")] +internal class WindowsFontManager : IOSFontManager +{ + public IEnumerable GetFontFamilies() + { + var hdc = Gdi32.CreateCompatibleDC(); + + var fonts = new HashSet(StringComparer.OrdinalIgnoreCase); + try + { + foreach (var windowsFontData in GetFonts(hdc)) + { + var fontName = windowsFontData.lpelfe.elfEnumLogfontEx.elfFullName; + fonts.Add(fontName); + } + } + finally + { + Gdi32.DeleteDC(hdc); + } + return fonts; + } + + static IEnumerable<(Gdi32.ENUMLOGFONTEXDV lpelfe, Gdi32.ENUMTEXTMETRIC lpntme, Gdi32.FontType FontType)> GetFonts(Gdi32.SafeHDC hdc) + { + return Gdi32.EnumFontFamiliesEx(hdc, CharacterSet.DEFAULT_CHARSET); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/IPGRender.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/IPGRender.cs new file mode 100644 index 0000000..3eab2ad --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/IPGRender.cs @@ -0,0 +1,32 @@ +using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.Binary; +using System; +using PG.StarWarsGame.Engine.Rendering.Animations; + +namespace PG.StarWarsGame.Engine.Rendering; + +public interface IPGRender +{ + IAloFile? Load3DAsset( + string path, + bool metadataOnly = true, + bool throwsException = false); + + IAloFile? Load3DAsset( + ReadOnlySpan path, + bool metadataOnly = true, + bool throwsException = false); + + ModelClass? LoadModelAndAnimations( + ReadOnlySpan path, + string? animOverrideName, + bool metadataOnly = true, + bool throwsException = false); + + public AnimationCollection LoadAnimations( + ReadOnlySpan fileName, + ReadOnlySpan dirPath, + bool metadataOnly = true, + Action? corruptedAnimationHandler = null); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/ModelClass.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/ModelClass.cs new file mode 100644 index 0000000..18940ac --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/ModelClass.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using AnakinRaW.CommonUtilities; +using PG.StarWarsGame.Engine.Rendering.Animations; +using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Files; + +namespace PG.StarWarsGame.Engine.Rendering; + +public sealed class ModelClass( + IAloFile aloFile, + AnimationCollection animations) + : DisposableObject +{ + public IAloDataContent RenderableContent { get; } = aloFile.Content; + + public IAloFile File { get; } = + aloFile ?? throw new ArgumentNullException(nameof(aloFile)); + + public AlamoModel? Model { get; } = aloFile.Content as AlamoModel; + + [MemberNotNullWhen(true, nameof(Model))] + public bool IsModel => Model is not null; + + public AnimationCollection Animations { get; } = animations ?? throw new ArgumentNullException(nameof(animations)); + + public ModelClass(IAloFile aloFile) : this(aloFile, AnimationCollection.Empty) + { + } + + + public int IndexOfBone(string boneName) + { + if (!IsModel) + return -1; + var bones = Model.Bones; + for (var i = 0; i < bones.Count; i++) + { + if (bones[i].Equals(boneName, StringComparison.OrdinalIgnoreCase)) + return i; + } + + return -1; + } + + protected override void DisposeResources() + { + File.Dispose(); + Animations.Dispose(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PGRender.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PGRender.cs new file mode 100644 index 0000000..e87d9ea --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PGRender.cs @@ -0,0 +1,191 @@ +using AnakinRaW.CommonUtilities.FileSystem; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.ErrorReporting; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Engine.IO.Repositories; +using PG.StarWarsGame.Engine.Utilities; +using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.ALO.Files.Animations; +using PG.StarWarsGame.Files.ALO.Services; +using PG.StarWarsGame.Files.Binary; +using System; +using System.IO.Abstractions; +using PG.StarWarsGame.Engine.Rendering.Animations; + +namespace PG.StarWarsGame.Engine.Rendering; + +internal class PGRender( + GameRepository gameRepository, + GameEngineErrorReporterWrapper errorReporter, + IServiceProvider serviceProvider) : IPGRender +{ + private readonly IAloFileService _aloFileService = serviceProvider.GetRequiredService(); + private readonly IRepository _modelRepository = gameRepository.ModelRepository; + private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); + private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService(); + private readonly ILogger? _logger = serviceProvider.GetService()?.CreateLogger(typeof(PGRender)); + + public IAloFile? Load3DAsset( + string path, + bool metadataOnly = true, + bool throwsException = false) + { + return Load3DAsset(path.AsSpan(), metadataOnly, throwsException); + } + + public IAloFile? Load3DAsset( + ReadOnlySpan path, + bool metadataOnly = true, + bool throwsException = false) + { + if (path.IsEmpty) + errorReporter.Assert(EngineAssert.FromNullOrEmpty([], "Model path is null or empty.")); + + using var aloStream = _modelRepository.TryOpenFile(path); + if (aloStream is null) + return null; + + var loadOptions = metadataOnly ? AloLoadOptions.MetadataOnly : AloLoadOptions.Full; + + try + { + return _aloFileService.Load(aloStream, loadOptions); + } + catch (BinaryCorruptedException e) + { + if (throwsException) + throw; + + var pathString = path.ToString(); + var errorMessage = $"Unable to load 3D asset '{pathString}': {e.Message}"; + _logger?.LogWarning(e, errorMessage); + errorReporter.Assert(EngineAssert.Create(EngineAssertKind.CorruptBinary, pathString, [], errorMessage)); + return null; + } + } + + public ModelClass? LoadModelAndAnimations( + ReadOnlySpan path, + string? animOverrideName, + bool metadataOnly = true, + bool throwsException = false) + { + var aloFile = Load3DAsset(path, metadataOnly, throwsException); + + if (aloFile is null) + return null; + + if (!aloFile.FileInformation.IsModel) + return new ModelClass(aloFile); + + var dirPath = _fileSystem.Path.GetDirectoryName(path); + var fileName = _fileSystem.Path.GetFileNameWithoutExtension(path); + + if (!string.IsNullOrEmpty(animOverrideName)) + fileName = _fileSystem.Path.GetFileNameWithoutExtension(animOverrideName.AsSpan()); + + var animations = LoadAnimations(fileName, dirPath, metadataOnly, throwsException ? AnimationCorruptedHandler : null); + + return new ModelClass(aloFile, animations); + } + + private void AnimationCorruptedHandler(BinaryCorruptedException e) + { + throw e; + } + + public AnimationCollection LoadAnimations( + ReadOnlySpan fileName, + ReadOnlySpan dirPath, + bool metadataOnly = true, + Action? corruptedAnimationHandler = null) + { + fileName = _fileSystem.Path.GetFileNameWithoutExtension(fileName); + + var animations = new AnimationCollection(); + + Span stringBuffer = stackalloc char[256]; + + foreach (var animationData in SupportedModelAnimationTypes.GetAnimationTypesForEngine(gameRepository.EngineType)) + { + var subIndex = 0; + var loadingNumberedAnimations = true; + + var throwsOnLoad = corruptedAnimationHandler is not null; + + while (loadingNumberedAnimations) + { + var stringBuilder = new ValueStringBuilder(stringBuffer); + + CreateAnimationFilePath(ref stringBuilder, fileName, animationData.Value, subIndex); + var animationFilenameWithoutExtension = + _fileSystem.Path.GetFileNameWithoutExtension(stringBuilder.AsSpan()); + InsertPath(ref stringBuilder, dirPath); + + if (stringBuilder.Length > PGConstants.MaxAnimationFileName) + { + var animFile = stringBuilder.AsSpan().ToString(); + errorReporter.Assert( + EngineAssert.Create(EngineAssertKind.ValueOutOfRange, animFile, [], + $"Cannot get animation file '{animFile}' , because animation file path is too long.")); + continue; + } + + try + { + var animationAsset = Load3DAsset(stringBuilder.AsSpan(), metadataOnly, throwsOnLoad); + if (animationAsset is IAloAnimationFile animationFile) + { + loadingNumberedAnimations = true; + var crc = _hashingService.GetCrc32(animationFilenameWithoutExtension, + PGConstants.DefaultPGEncoding); + animations.AddAnimation(animationData.Key, animationFile, crc); + } + else + { + loadingNumberedAnimations = false; + } + } + catch (BinaryCorruptedException e) + { + corruptedAnimationHandler?.Invoke(e); + loadingNumberedAnimations = false; + } + finally + { + stringBuilder.Dispose(); + subIndex++; + } + } + } + return animations; + } + + private void InsertPath(ref ValueStringBuilder stringBuilder, ReadOnlySpan directory) + { + if (!_fileSystem.Path.HasTrailingDirectorySeparator(directory)) + stringBuilder.Insert(0, '\\', 1); + stringBuilder.Insert(0, directory); + } + + private static void CreateAnimationFilePath( + ref ValueStringBuilder stringBuilder, + ReadOnlySpan fileName, + string animationTypeName, + int subIndex) + { + stringBuilder.Append(fileName); + stringBuilder.Append('_'); + stringBuilder.Append(animationTypeName); + stringBuilder.Append('_'); +#if NETSTANDARD2_0 || NETFRAMEWORK + stringBuilder.Append(subIndex.ToString("D2")); +#else + subIndex.TryFormat(stringBuilder.AppendSpan(2), out var n, "D2"); +#endif + stringBuilder.Append(".ALA"); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PrimRenderMode.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PrimRenderMode.cs new file mode 100644 index 0000000..f58e5bb --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/PrimRenderMode.cs @@ -0,0 +1,20 @@ +namespace PG.StarWarsGame.Engine.Rendering; + +public enum PrimRenderMode +{ + PrimOpaque = 0x0, + PrimAdditive = 0x1, + PrimAlpha = 0x2, + PrimModulate = 0x3, + PrimDepthspriteAdditive = 0x4, + PrimDepthspriteAlpha = 0x5, + PrimDepthspriteModulate = 0x6, + PrimDiffuseAlpha = 0x7, + PrimStencilDarken = 0x8, + PrimStencilDarkenBlur = 0x9, + PrimHeat = 0xa, + PrimParticleBumpAlpha = 0xb, + PrimDecalBumpAlpha = 0xc, + PrimAlphaScanlines = 0xd, + PrimCount = 0xe +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/RgbaColor.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/RgbaColor.cs new file mode 100644 index 0000000..be1aaa9 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Rendering/RgbaColor.cs @@ -0,0 +1,67 @@ +using PG.Commons.Numerics; +using System; + +namespace PG.StarWarsGame.Engine.Rendering; + +public readonly struct RgbaColor(float r, float g, float b, float a) : IEquatable +{ + private readonly float _r = r; + private readonly float _g = g; + private readonly float _b = b; + private readonly float _a = a; + + public uint R => unchecked((uint)(_r * 255.0f)); + public uint G => unchecked((uint)(_g * 255.0f)); + public uint B => unchecked((uint)(_b * 255.0f)); + public uint A => unchecked((uint)(_a * 255.0f)); + + public float Rf => _r; + public float Gf => _g; + public float Bf => _b; + public float Af => _a; + + public RgbaColor() : this(1.0f, 1.0f, 1.0f, 1.0f) + { + } + + public RgbaColor(Vector4Int rgbaVector) : this( + unchecked((byte)rgbaVector.First), + unchecked ((byte)rgbaVector.Second), + unchecked ((byte)rgbaVector.Third), + unchecked ((byte)rgbaVector.Fourth)) + { + } + + public RgbaColor(uint r, uint g, uint b, uint a) : this(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f) + { + } + + public override string ToString() + { + return $"{nameof(RgbaColor)} [R={R}, G={G}, B={B}, A={A}]"; + } + + public RgbaColor Lerp(RgbaColor a, RgbaColor b, float t) + { + var red = a.Rf + (b.Rf - a.Rf) * t; + var green = a.Gf + (b.Gf - a.Gf) * t; + var blue = a.Bf + (b.Bf - a.Bf) * t; + var alpha = a.Af + (b.Af - a.Af) * t; + return new RgbaColor(red, green, blue, alpha); + } + + public bool Equals(RgbaColor other) + { + return _r.Equals(other._r) && _g.Equals(other._g) && _b.Equals(other._b) && _a.Equals(other._a); + } + + public override bool Equals(object? obj) + { + return obj is RgbaColor other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_r, _g, _b, _a); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/EffectsRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/EffectsRepository.cs deleted file mode 100644 index 4421c88..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/EffectsRepository.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace PG.StarWarsGame.Engine.Repositories; - -public class EffectsRepository(IGameRepository baseRepository, IServiceProvider serviceProvider) : MultiPassRepository(baseRepository, serviceProvider) -{ - private static readonly string[] LookupPaths = - [ - "Data\\Art\\Shaders", - "Data\\Art\\Shaders\\Terrain", - "Data\\Art\\Shaders\\Engine", - ]; - - // The engine does not support ".fxh" as a shader lookup, but as there might be som pre-compiling going on, this should be OK. - private static readonly string[] ShaderExtensions = [".fx", ".fxo", ".fxh"]; - - [return:MaybeNull] - protected override T MultiPassAction(string inputPath, Func fileAction) - { - var currExt = FileSystem.Path.GetExtension(inputPath); - if (!ShaderExtensions.Contains(currExt, StringComparer.OrdinalIgnoreCase)) - throw new ArgumentException("Invalid data extension for shader. Must be .fx, .fxh or .fxo", nameof(inputPath)); - - foreach (var directory in LookupPaths) - { - var lookupPath = FileSystem.Path.Combine(directory, inputPath); - - foreach (var ext in ShaderExtensions) - { - lookupPath = FileSystem.Path.ChangeExtension(lookupPath, ext); - - var actionResult = fileAction(lookupPath); - if (actionResult.success) - return actionResult.result; - } - } - - return default; - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepositoryFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepositoryFactory.cs deleted file mode 100644 index e7f25e2..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IGameRepositoryFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Engine.Repositories; - -internal interface IGameRepositoryFactory -{ - GameRepository Create(GameEngineType engineType, GameLocations gameLocations, IXmlParserErrorListener? xmlParserErrorListener); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IRepository.cs deleted file mode 100644 index fe5764f..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/IRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.IO; - -namespace PG.StarWarsGame.Engine.Repositories; - -public interface IRepository -{ - Stream OpenFile(string filePath, bool megFileOnly = false); - - bool FileExists(string filePath, bool megFileOnly = false); - - Stream? TryOpenFile(string filePath, bool megFileOnly = false); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/MultiPassRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/MultiPassRepository.cs deleted file mode 100644 index c4238bb..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/MultiPassRepository.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; -using System.IO.Abstractions; -using Microsoft.Extensions.DependencyInjection; - -namespace PG.StarWarsGame.Engine.Repositories; - -public abstract class MultiPassRepository(IGameRepository baseRepository, IServiceProvider serviceProvider) : IRepository -{ - protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService(); - - public Stream OpenFile(string filePath, bool megFileOnly = false) - { - var fileStream = TryOpenFile(filePath, megFileOnly); - if (fileStream is null) - throw new FileNotFoundException($"Unable to find game file: {filePath}"); - return fileStream; - } - - public bool FileExists(string filePath, bool megFileOnly = false) - { - return MultiPassAction(filePath, actualPath => baseRepository.FileExists(actualPath, megFileOnly)); - } - - public Stream? TryOpenFile(string filePath, bool megFileOnly = false) - { - return MultiPassAction(filePath, path => - { - var stream = baseRepository.TryOpenFile(path, megFileOnly); - if (stream is null) - return (false, null); - return (true, stream); - }); - } - - protected abstract T? MultiPassAction(string inputPath, Func fileAction); - - protected bool MultiPassAction(string inputPath, Predicate fileAction) - { - return MultiPassAction(inputPath, path => - { - var result = fileAction(path); - return (result, result); - }); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/TextureRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/TextureRepository.cs deleted file mode 100644 index e5253e0..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/TextureRepository.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; - -namespace PG.StarWarsGame.Engine.Repositories; - -public class TextureRepository(IGameRepository baseRepository, IServiceProvider serviceProvider) : MultiPassRepository(baseRepository, serviceProvider) -{ - protected override T? MultiPassAction(string inputPath, Func fileAction) where T : default - { - if (FindTexture(inputPath, fileAction, out var result)) - return result; - - var ddsFilePath = ChangeExtensionTo(inputPath, ".dds"); - - if (FindTexture(ddsFilePath, fileAction, out result)) - return result; - - return default; - } - - private bool FindTexture(string inputPath, Func fileAction, out T? result) - { - result = default; - - var actionResult = fileAction(inputPath); - if (actionResult.success) - { - result = actionResult.result; - return true; - } - - var newInput = inputPath; - - // Only PG knows why they only search for backslash and not also forward slash, - // when in fact in other methods, they handle both. - var separatorIndex = inputPath.LastIndexOf('\\'); - if (separatorIndex != -1 && separatorIndex + 1 < inputPath.Length) - newInput = inputPath.Substring(separatorIndex + 1); - - var pathWithFolders = FileSystem.Path.Combine("DATA\\ART\\TEXTURES", newInput); - actionResult = fileAction(pathWithFolders); - - if (actionResult.success) - { - result = actionResult.result; - return true; - } - - - return false; - } - - - private static string ChangeExtensionTo(string input, string extension) - { - // We cannot use Path.ChangeExtension as the PG implementation supports some strange things - // like that a string "c:\\file.tga\\" ending with a directory separator. The PG result will be - // "c:\\file.dds" while Path.ChangeExtension would return "c:\\file.tga\\.dds" - - // Also, while there are many cases, where method breaks (such as "c:/test.abc/path.dds"), - // it's the way how the engine works... - var firstPart = input.Split('.')[0]; - return firstPart + extension; - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/ValueStringBuilder.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/ValueStringBuilder.cs index e210518..0c6b95c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/ValueStringBuilder.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Utilities/ValueStringBuilder.cs @@ -1,25 +1,358 @@ using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace PG.StarWarsGame.Engine.Utilities; -// From https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs -internal ref struct ValueStringBuilder(Span initialBuffer) +internal ref struct ValueStringBuilder { - private readonly Span _chars = initialBuffer; - private int _pos = 0; + private char[]? _arrayToReturnToPool; + private Span _chars; + private int _pos; + + public ValueStringBuilder(Span initialBuffer) + { + _arrayToReturnToPool = null; + _chars = initialBuffer; + _pos = 0; + } + + public ValueStringBuilder(int initialCapacity) + { + _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); + _chars = _arrayToReturnToPool; + _pos = 0; + } + + public int Length + { + get => _pos; + set + { + Debug.Assert(value >= 0); + Debug.Assert(value <= _chars.Length); + _pos = value; + } + } + + public int Capacity => _chars.Length; + + public void EnsureCapacity(int capacity) + { + // This is not expected to be called this with negative capacity + Debug.Assert(capacity >= 0); + + // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. + if ((uint)capacity > (uint)_chars.Length) + Grow(capacity - _pos); + } + + /// + /// Get a pinnable reference to the builder. + /// Does not ensure there is a null char after + /// This overload is pattern matched in the C# 7.3+ compiler so you can omit + /// the explicit method call, and write eg "fixed (char* c = builder)" + /// + public ref char GetPinnableReference() + { + return ref MemoryMarshal.GetReference(_chars); + } + + /// + /// Get a pinnable reference to the builder. + /// + /// Ensures that the builder has a null char after + public ref char GetPinnableReference(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; + } + return ref MemoryMarshal.GetReference(_chars); + } + + public ref char this[int index] + { + get + { + Debug.Assert(index < _pos); + return ref _chars[index]; + } + } public override string ToString() { - return _chars.Slice(0, _pos).ToString(); + var s = _chars.Slice(0, _pos).ToString(); + Dispose(); + return s; + } + + /// Returns the underlying storage of the builder. + public Span RawChars => _chars; + + /// + /// Returns a span around the contents of the builder. + /// + /// Ensures that the builder has a null char after + public ReadOnlySpan AsSpan(bool terminate) + { + if (terminate) + { + EnsureCapacity(Length + 1); + _chars[Length] = '\0'; + } + return _chars.Slice(0, _pos); + } + + public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); + public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); + public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); + + public bool TryCopyTo(Span destination, out int charsWritten) + { + if (_chars.Slice(0, _pos).TryCopyTo(destination)) + { + charsWritten = _pos; + Dispose(); + return true; + } + else + { + charsWritten = 0; + Dispose(); + return false; + } + } + + public void Insert(int index, char value, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + var remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + _chars.Slice(index, count).Fill(value); + _pos += count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(int index, scoped ReadOnlySpan value) + { + if (value.IsEmpty) + return; + + var count = value.Length; + + if (_pos > _chars.Length - count) + Grow(count); + + var remaining = _pos - index; + _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); + value.CopyTo(_chars.Slice(index)); + _pos += count; + } + + public void Insert(int index, string? s) + { + if (s == null) + return; + Insert(index, s.AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + var pos = _pos; + var chars = _chars; + if ((uint)pos < (uint)chars.Length) + { + chars[pos] = c; + _pos = pos + 1; + } + else + { + GrowAndAppend(c); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(string? s) + { + if (s == null) + { + return; + } + + var pos = _pos; + if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. + { + _chars[pos] = s[0]; + _pos = pos + 1; + } + else + { + AppendSlow(s); + } + } + + private void AppendSlow(string s) + { + var pos = _pos; + if (pos > _chars.Length - s.Length) + { + Grow(s.Length); + } + + s +#if !NETCOREAPP + .AsSpan() +#endif + .CopyTo(_chars.Slice(pos)); + _pos += s.Length; + } + + public void Append(char c, int count) + { + if (_pos > _chars.Length - count) + { + Grow(count); + } + + var dst = _chars.Slice(_pos, count); + for (var i = 0; i < dst.Length; i++) + { + dst[i] = c; + } + _pos += count; + } + + public unsafe void Append(char* value, int length) + { + var pos = _pos; + if (pos > _chars.Length - length) + { + Grow(length); + } + + var dst = _chars.Slice(_pos, length); + for (var i = 0; i < dst.Length; i++) + { + dst[i] = *value++; + } + _pos += length; } public void Append(scoped ReadOnlySpan value) { var pos = _pos; if (pos > _chars.Length - value.Length) - throw new InvalidOperationException("Value string builder is too small."); + { + Grow(value.Length); + } value.CopyTo(_chars.Slice(_pos)); _pos += value.Length; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendSpan(int length) + { + var origPos = _pos; + if (origPos > _chars.Length - length) + { + Grow(length); + } + + _pos = origPos + length; + return _chars.Slice(origPos, length); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void GrowAndAppend(char c) + { + Grow(1); + Append(c); + } + + /// + /// Resize the internal buffer either by doubling current buffer size or + /// by adding to + /// whichever is greater. + /// + /// + /// Number of chars requested beyond current position. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void Grow(int additionalCapacityBeyondPos) + { + Debug.Assert(additionalCapacityBeyondPos > 0); + Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); + + const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength + + // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try + // to double the size if possible, bounding the doubling to not go beyond the max array length. + var newCapacity = (int)Math.Max( + (uint)(_pos + additionalCapacityBeyondPos), + Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); + + // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. + // This could also go negative if the actual required length wraps around. + var poolArray = ArrayPool.Shared.Rent(newCapacity); + + _chars.Slice(0, _pos).CopyTo(poolArray); + + var toReturn = _arrayToReturnToPool; + _chars = _arrayToReturnToPool = poolArray; + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } + + /// + /// Removes a range of characters from this builder. + /// + /// The inclusive index from where the string gets removed. + /// The length of the slice to remove. + /// + /// This method will not affect the internal size of the string. + /// + public void Remove(int startIndex, int length) + { + if (length == 0) + return; + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), "The given length can't be negative."); + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex), "The given start index can't be negative."); + if (length > Length - startIndex) + throw new ArgumentOutOfRangeException(nameof(length), $"The given Span ({startIndex}..{length})length is outside the the represented string."); + if (Length == length && startIndex == 0) + { + Length = 0; + return; + } + var currentLength = Length; + var remaining = _chars.Slice(startIndex + length, currentLength - length); + var toOverwrite = _chars.Slice(startIndex); + remaining.CopyTo(toOverwrite); + _pos = currentLength - length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + var toReturn = _arrayToReturnToPool; + this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again + if (toReturn != null) + { + ArrayPool.Shared.Return(toReturn); + } + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/IPetroglyphXmlFileParserFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/IPetroglyphXmlFileParserFactory.cs index 36cb12c..4532365 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/IPetroglyphXmlFileParserFactory.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/IPetroglyphXmlFileParserFactory.cs @@ -5,5 +5,5 @@ namespace PG.StarWarsGame.Engine.Xml; public interface IPetroglyphXmlFileParserFactory { - IPetroglyphXmlFileParser GetFileParser(IXmlParserErrorListener? listener = null); + IPetroglyphXmlFileContainerParser CreateFileParser(IXmlParserErrorReporter? errorReporter) where T : notnull; } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/NamedXmlObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/NamedXmlObject.cs new file mode 100644 index 0000000..7cf9c4e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/NamedXmlObject.cs @@ -0,0 +1,13 @@ +using System; +using PG.Commons.Data; +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.Xml; + +public abstract class NamedXmlObject(string name, Crc32 nameCrc, XmlLocationInfo location) : XmlObject(location), IHasCrc32 +{ + public Crc32 Crc32 { get; } = nameCrc; + + public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs new file mode 100644 index 0000000..1e20a8b --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/CommandBarComponentParser.cs @@ -0,0 +1,365 @@ +using System; +using System.Collections.ObjectModel; +using System.Xml.Linq; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.Xml.Tags; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; + +public sealed class CommandBarComponentParser( + IReadOnlyValueListDictionary parsedElements, + IServiceProvider serviceProvider, + IXmlParserErrorReporter? errorReporter = null) + : XmlObjectParser(parsedElements, serviceProvider, errorReporter) +{ + public override CommandBarComponentData Parse(XElement element, out Crc32 crc32) + { + var name = GetXmlObjectName(element, out crc32, true); + var component = new CommandBarComponentData(name, crc32, XmlLocationInfo.FromElement(element)); + Parse(component, element, default); + ValidateValues(component, element); + component.CoerceValues(); + return component; + } + + protected override bool ParseTag(XElement tag, CommandBarComponentData componentData) + { + switch (tag.Name.LocalName) + { + case CommandBarComponentTags.SelectedTextureName: + componentData.SelectedTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.BlankTextureName: + componentData.BlankTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.IconAlternateTextureName: + componentData.IconAlternateTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.MouseOverTextureName: + componentData.MouseOverTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.BarTextureName: + componentData.BarTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.BarOverlayName: + componentData.BarOverlayNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.AlternateFontName: + componentData.AlternateFontNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.TooltipText: + componentData.TooltipTexts = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.LowerEffectTextureName: + componentData.LowerEffectTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.UpperEffectTextureName: + componentData.UpperEffectTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.OverlayTextureName: + componentData.OverlayTextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case CommandBarComponentTags.Overlay2TextureName: + componentData.Overlay2TextureNames = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + + case CommandBarComponentTags.IconTextureName: + componentData.IconTextureName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DisabledTextureName: + componentData.DisabledTextureName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.FlashTextureName: + componentData.FlashTextureName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BuildTextureName: + componentData.BuildTextureName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ModelName: + componentData.ModelName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BoneName: + componentData.BoneName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.CursorTextureName: + componentData.CursorTextureName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.FontName: + componentData.FontName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ClickSfx: + componentData.ClickSfx = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.MouseOverSfx: + componentData.MouseOverSfx = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.RightClickSfx: + componentData.RightClickSfx = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Type: + componentData.Type = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Group: + componentData.Group = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.AssociatedText: + componentData.AssociatedText = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.DragAndDrop: + componentData.DragAndDrop = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DragSelect: + componentData.DragSelect = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Receptor: + componentData.Receptor = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Toggle: + componentData.Toggle = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Tab: + componentData.Tab = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Hidden: + componentData.Hidden = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ClearColor: + componentData.ClearColor = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Disabled: + componentData.Disabled = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.SwapTexture: + componentData.SwapTexture = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DrawAdditive: + componentData.DrawAdditive = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Editable: + componentData.Editable = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextOutline: + componentData.TextOutline = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Stackable: + componentData.Stackable = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ModelOffsetX: + componentData.ModelOffsetX = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ModelOffsetY: + componentData.ModelOffsetY = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ScaleModelX: + componentData.ScaleModelX = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ScaleModelY: + componentData.ScaleModelY = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Collideable: + componentData.Collideable = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextEmboss: + componentData.TextEmboss = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ShouldGhost: + componentData.ShouldGhost = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.GhostBaseOnly: + componentData.GhostBaseOnly = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.CrossFade: + componentData.CrossFade = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.LeftJustified: + componentData.LeftJustified = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.RightJustified: + componentData.RightJustified = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.NoShell: + componentData.NoShell = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.SnapDrag: + componentData.SnapDrag = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.SnapLocation: + componentData.SnapLocation = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.OffsetRender: + componentData.OffsetRender = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BlinkFade: + componentData.BlinkFade = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.NoHiddenCollision: + componentData.NoHiddenCollision = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ManualOffset: + componentData.ManualOffset = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.SelectedAlpha: + componentData.SelectedAlpha = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.PixelAlign: + componentData.PixelAlign = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.CanDragStack: + componentData.CanDragStack = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.CanAnimate: + componentData.CanAnimate = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.LoopAnim: + componentData.LoopAnim = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.SmoothBar: + componentData.SmoothBar = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.OutlinedBar: + componentData.OutlinedBar = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DragBack: + componentData.DragBack = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.LowerEffectAdditive: + componentData.LowerEffectAdditive = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.UpperEffectAdditive: + componentData.UpperEffectAdditive = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ClickShift: + componentData.ClickShift = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TutorialScene: + componentData.TutorialScene = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DialogScene: + componentData.DialogScene = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ShouldRenderAtDragPos: + componentData.ShouldRenderAtDragPos = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DisableDarken: + componentData.DisableDarken = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.AnimateBack: + componentData.AnimateBack = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.AnimateUpperEffect: + componentData.AnimateUpperEffect = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.Size: + componentData.Size = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextOffset: + componentData.TextOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextOffset2: + componentData.TextOffset2 = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Offset: + componentData.Offset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DefaultOffset: + componentData.DefaultOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DefaultOffsetWidescreen: + componentData.DefaultOffsetWidescreen = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.IconOffset: + componentData.IconOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.MouseOverOffset: + componentData.MouseOverOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.DisabledOffset: + componentData.DisabledOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BuildDialOffset: + componentData.BuildDialOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BuildDial2Offset: + componentData.BuildDial2Offset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.LowerEffectOffset: + componentData.LowerEffectOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.UpperEffectOffset: + componentData.UpperEffectOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.OverlayOffset: + componentData.OverlayOffset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.Overlay2Offset: + componentData.Overlay2Offset = PetroglyphXmlVector2FParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.MaxTextLength: + componentData.MaxTextLength = PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.FontPointSize: + componentData.FontPointSize = (int)PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.Scale: + componentData.Scale = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BlinkRate: + componentData.BlinkRate = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.MaxTextWidth: + componentData.MaxTextWidth = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.BlinkDuration: + componentData.BlinkDuration = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.ScaleDuration: + componentData.ScaleDuration = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.AnimFps: + componentData.AnimFps = PetroglyphXmlFloatParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.BaseLayer: + componentData.BaseLayer = (int)PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.MaxBarLevel: + componentData.MaxBarLevel = (int)PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; + + case CommandBarComponentTags.Color: + componentData.Color = PetroglyphXmlRgbaColorParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextColor: + componentData.TextColor = PetroglyphXmlRgbaColorParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.TextColor2: + componentData.TextColor2 = PetroglyphXmlRgbaColorParser.Instance.Parse(tag); + return true; + case CommandBarComponentTags.MaxBarColor: + componentData.MaxBarColor = PetroglyphXmlRgbaColorParser.Instance.Parse(tag); + return true; + + default: return true; + } + } + + private void ValidateValues(CommandBarComponentData xmlData, XElement element) + { + if (xmlData.Name.Length > PGConstants.MaxCommandBarComponentName) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.TooLongData, + $"CommandbarComponent name '{xmlData.Name}' is too long.")); + } + } + + public override CommandBarComponentData Parse(XElement element) => throw new NotSupportedException(); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs index 212bab1..64de5ec 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs @@ -1,23 +1,16 @@ using System; using System.Xml.Linq; -using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Engine.GameConstants; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; -internal class GameConstantsParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) : - PetroglyphXmlFileParser(serviceProvider, listener) +internal class GameConstantsParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : + PetroglyphXmlFileParser(serviceProvider, errorReporter) { - public override GameConstants Parse(XElement element) + protected override GameConstantsXml Parse(XElement element, string fileName) { - return new GameConstants(); - } - - protected override void Parse(XElement element, IValueListDictionary parsedElements) - { - throw new NotSupportedException(); + return new GameConstantsXml(); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs index 831cc55..4f445a7 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs @@ -1,54 +1,98 @@ using System; using System.Xml.Linq; +using PG.Commons.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.GameObjects; using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; +public static class GameObjectXmlTags +{ + public const string LandTerrainModelMapping = "Land_Terrain_Model_Mapping"; + public const string GalacticModelName = "Galactic_Model_Name"; + public const string DestroyedGalacticModelName = "Destroyed_Galactic_Model_Name"; + public const string LandModelName = "Land_Model_Name"; + public const string SpaceModelName = "Space_Model_Name"; + public const string ModelName = "Model_Name"; + public const string TacticalModelName = "Tactical_Model_Name"; + public const string GalacticFleetOverrideModelName = "Galactic_Fleet_Override_Model_Name"; + public const string GuiModelName = "GUI_Model_Name"; + public const string LandModelAnimOverrideName = "Land_Model_Anim_Override_Name"; + public const string XxxSpaceModelName = "xxxSpace_Model_Name"; + public const string DamagedSmokeAssetName = "Damaged_Smoke_Asset_Name"; +} + public sealed class GameObjectParser( IReadOnlyValueListDictionary parsedElements, IServiceProvider serviceProvider, - IXmlParserErrorListener? listener = null) - : XmlObjectParser(parsedElements, serviceProvider, listener) -{ - protected override IPetroglyphXmlElementParser? GetParser(string tag) - { - switch (tag) - { - case "Land_Terrain_Model_Mapping": - return PrimitiveParserProvider.CommaSeparatedStringKeyValueListParser; - case "Galactic_Model_Name": - case "Destroyed_Galactic_Model_Name": - case "Land_Model_Name": - case "Space_Model_Name": - case "Model_Name": - case "Tactical_Model_Name": - case "Galactic_Fleet_Override_Model_Name": - case "GUI_Model_Name": - case "GUI_Model": - case "Land_Model_Anim_Override_Name": - case "xxxSpace_Model_Name": - case "Damaged_Smoke_Asset_Name": - return PrimitiveParserProvider.StringParser; - default: - return null; - } - } - - public override GameObject Parse(XElement element, out Crc32 nameCrc) + IXmlParserErrorReporter? errorReporter = null) + : XmlObjectParser(parsedElements, serviceProvider, errorReporter) +{ + public override GameObject Parse(XElement element, out Crc32 crc32) { - var properties = ParseXmlElement(element); - var name = GetNameAttributeValue(element); - nameCrc = HashingService.GetCrc32Upper(name.AsSpan(), PGConstants.PGCrc32Encoding); + var name = GetXmlObjectName(element, out crc32, true); var type = GetTagName(element); var objectType = EstimateType(type); - var gameObject = new GameObject(type, name, nameCrc, objectType, properties, XmlLocationInfo.FromElement(element)); + var gameObject = new GameObject(type, name, crc32, objectType, XmlLocationInfo.FromElement(element)); + + Parse(gameObject, element, default); + return gameObject; } + protected override bool ParseTag(XElement tag, GameObject xmlObject) + { + switch (tag.Name.LocalName) + { + case GameObjectXmlTags.LandTerrainModelMapping: + var mappingValue = CommaSeparatedStringKeyValueListParser.Instance.Parse(tag); + var dict = xmlObject.InternalLandTerrainModelMapping; + foreach (var keyValuePair in mappingValue) + { + if (!dict.ContainsKey(keyValuePair.key)) + dict.Add(keyValuePair.key, keyValuePair.value); + } + return true; + case GameObjectXmlTags.GalacticModelName: + xmlObject.GalacticModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.DestroyedGalacticModelName: + xmlObject.DestroyedGalacticModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.LandModelName: + xmlObject.LandModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.SpaceModelName: + xmlObject.SpaceModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.ModelName: + xmlObject.ModelName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.TacticalModelName: + xmlObject.TacticalModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.GalacticFleetOverrideModelName: + xmlObject.GalacticFleetOverrideModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.GuiModelName: + xmlObject.GuiModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.LandModelAnimOverrideName: + xmlObject.LandAnimOverrideModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.XxxSpaceModelName: + xmlObject.XxxSpaceModeModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case GameObjectXmlTags.DamagedSmokeAssetName: + xmlObject.DamagedSmokeAssetModel = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + default: return true; // TODO: Once parsing is complete, switch to false. + } + } + private static GameObjectType EstimateType(string tagName) { if (tagName.StartsWith("Props_")) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs index ffb7e18..4e3a5cb 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.ObjectModel; using System.Xml.Linq; +using PG.Commons.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.Audio.Sfx; using PG.StarWarsGame.Engine.Xml.Tags; using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; @@ -12,112 +14,191 @@ namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; public sealed class SfxEventParser( IReadOnlyValueListDictionary parsedElements, IServiceProvider serviceProvider, - IXmlParserErrorListener? listener = null) - : XmlObjectParser(parsedElements, serviceProvider, listener) -{ - protected override IPetroglyphXmlElementParser? GetParser(string tag) + IXmlParserErrorReporter? errorReporter = null) + : XmlObjectParser(parsedElements, serviceProvider, errorReporter) +{ + public override SfxEvent Parse(XElement element, out Crc32 crc32) { - switch (tag) + var name = GetXmlObjectName(element, out crc32, true); + var sfxEvent = new SfxEvent(name, crc32, XmlLocationInfo.FromElement(element)); + Parse(sfxEvent, element, default); + ValidateValues(sfxEvent, element); + sfxEvent.CoerceValues(); + return sfxEvent; + } + + private void ValidateValues(SfxEvent sfxEvent, XElement element) + { + if (sfxEvent.Name.Length > PGConstants.MaxSFXEventName) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.TooLongData, + $"SFXEvent name '{sfxEvent.Name}' is too long.")); + } + + if (sfxEvent is { Is2D: true, Is3D: true }) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"SFXEvent '{sfxEvent.Name}' is defined as 2D and 3D.")); + } + + if (sfxEvent.MinVolume > sfxEvent.MaxVolume) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"{SfxEventXmlTags.MinVolume} should not be higher than {SfxEventXmlTags.MaxVolume} for SFXEvent '{sfxEvent.Name}'")); + } + + if (sfxEvent.MinPitch > sfxEvent.MaxPitch) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"{SfxEventXmlTags.MinPitch} should not be higher than {SfxEventXmlTags.MaxPitch} for SFXEvent '{sfxEvent.Name}'")); + } + + if (sfxEvent.MinPan2D > sfxEvent.MaxPan2D) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"{SfxEventXmlTags.MinPan2D} should not be higher than {SfxEventXmlTags.MaxPan2D} for SFXEvent '{sfxEvent.Name}'")); + } + + if (sfxEvent.MinPredelay > sfxEvent.MaxPredelay) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"{SfxEventXmlTags.MinPredelay} should not be higher than {SfxEventXmlTags.MaxPredelay} for SFXEvent '{sfxEvent.Name}'")); + } + + if (sfxEvent.MinPostdelay > sfxEvent.MaxPostdelay) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"{SfxEventXmlTags.MinPostdelay} should not be higher than {SfxEventXmlTags.MaxPostdelay} for SFXEvent '{sfxEvent.Name}'")); + } + } + + protected override bool ParseTag(XElement tag, SfxEvent sfxEvent) + { + switch (tag.Name.LocalName) { - case SfxEventXmlTags.UsePreset: case SfxEventXmlTags.OverlapTest: + sfxEvent.OverlapTestName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.ChainedSfxEvent: - return PrimitiveParserProvider.StringParser; + sfxEvent.ChainedSfxEventName = PetroglyphXmlStringParser.Instance.Parse(tag); + return true; + case SfxEventXmlTags.UsePreset: + { + var presetName = PetroglyphXmlStringParser.Instance.Parse(tag); + var presetNameCrc = HashingService.GetCrc32Upper(presetName.AsSpan(), PGConstants.DefaultPGEncoding); + if (presetNameCrc != default && ParsedElements.TryGetFirstValue(presetNameCrc, out var preset)) + sfxEvent.ApplyPreset(preset); + else + { + OnParseError(new XmlParseErrorEventArgs(tag, + XmlParseErrorKind.MissingReference, $"Cannot to find preset '{presetName}' for SFXEvent '{sfxEvent.Name}'")); + } + return true; + } + case SfxEventXmlTags.IsPreset: + sfxEvent.IsPreset = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.Is3D: + sfxEvent.Is3D = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.Is2D: + sfxEvent.Is2D = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.IsGui: + sfxEvent.IsGui = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.IsHudVo: + sfxEvent.IsHudVo = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.IsUnitResponseVo: + sfxEvent.IsUnitResponseVo = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.IsAmbientVo: + sfxEvent.IsAmbientVo = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.Localize: + sfxEvent.IsLocalized = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.PlaySequentially: + sfxEvent.PlaySequentially = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.KillsPreviousObjectSFX: - return PrimitiveParserProvider.BooleanParser; + sfxEvent.KillsPreviousObjectsSfx = PetroglyphXmlBooleanParser.Instance.Parse(tag); + return true; + case SfxEventXmlTags.Samples: + sfxEvent.Samples = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; case SfxEventXmlTags.PreSamples: + sfxEvent.PreSamples = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; case SfxEventXmlTags.PostSamples: + sfxEvent.PostSamples = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; case SfxEventXmlTags.TextID: - return PrimitiveParserProvider.LooseStringListParser; + sfxEvent.LocalizedTextIDs = new ReadOnlyCollection(PetroglyphXmlLooseStringListParser.Instance.Parse(tag)); + return true; + case SfxEventXmlTags.Priority: + sfxEvent.Priority = (byte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, SfxEvent.MinPriorityValue, SfxEvent.MaxPriorityValue); + return true; case SfxEventXmlTags.MinPitch: + sfxEvent.MinPitch = (byte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, SfxEvent.MinPitchValue, SfxEvent.MaxPitchValue); + return true; case SfxEventXmlTags.MaxPitch: + sfxEvent.MaxPitch = (byte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, SfxEvent.MinPitchValue, SfxEvent.MaxPitchValue); + return true; case SfxEventXmlTags.MinPan2D: + sfxEvent.MinPan2D = (byte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, byte.MinValue, SfxEvent.MaxPan2dValue); + return true; case SfxEventXmlTags.MaxPan2D: + sfxEvent.MaxPan2D = (byte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, byte.MinValue, SfxEvent.MaxPan2dValue); + return true; case SfxEventXmlTags.PlayCount: + sfxEvent.PlayCount = (sbyte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, SfxEvent.InfinitivePlayCount, sbyte.MaxValue); + return true; case SfxEventXmlTags.MaxInstances: - return PrimitiveParserProvider.IntParser; + sfxEvent.MaxInstances = (sbyte)PetroglyphXmlIntegerParser.Instance.ParseWithRange(tag, SfxEvent.MinMaxInstances, sbyte.MaxValue); + return true; + case SfxEventXmlTags.Probability: + sfxEvent.Probability = PetroglyphXmlMax100ByteParser.Instance.ParseWithRange(tag, byte.MinValue, SfxEvent.MaxProbability); + return true; case SfxEventXmlTags.MinVolume: + sfxEvent.MinVolume = PetroglyphXmlMax100ByteParser.Instance.ParseWithRange(tag, byte.MinValue, SfxEvent.MaxVolumeValue); + return true; case SfxEventXmlTags.MaxVolume: - return PrimitiveParserProvider.Max100ByteParser; + sfxEvent.MaxVolume = PetroglyphXmlMax100ByteParser.Instance.ParseWithRange(tag, byte.MinValue, SfxEvent.MaxVolumeValue); + return true; + case SfxEventXmlTags.MinPredelay: + sfxEvent.MinPredelay = PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.MaxPredelay: + sfxEvent.MaxPredelay = PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.MinPostdelay: + sfxEvent.MinPostdelay = PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; case SfxEventXmlTags.MaxPostdelay: - return PrimitiveParserProvider.UIntParser; + sfxEvent.MaxPostdelay = PetroglyphXmlUnsignedIntegerParser.Instance.Parse(tag); + return true; + case SfxEventXmlTags.LoopFadeInSeconds: + sfxEvent.LoopFadeInSeconds = PetroglyphXmlFloatParser.Instance.ParseAtLeast(tag, SfxEvent.MinLoopSeconds); + return true; case SfxEventXmlTags.LoopFadeOutSeconds: + sfxEvent.LoopFadeOutSeconds = PetroglyphXmlFloatParser.Instance.ParseAtLeast(tag, SfxEvent.MinLoopSeconds); + return true; case SfxEventXmlTags.VolumeSaturationDistance: - return PrimitiveParserProvider.FloatParser; - default: - return null; + // I think it was planned at some time to support -1.0 and >= 0.0, since you don't get a warning when -1.0 is coded + // but the Engine coerces anything < 0.0 to 0.0. + sfxEvent.VolumeSaturationDistance = PetroglyphXmlFloatParser.Instance.ParseAtLeast(tag, SfxEvent.MinVolumeSaturation); + return true; + default: return false; } } - public override SfxEvent Parse(XElement element, out Crc32 nameCrc) - { - var name = GetNameAttributeValue(element); - nameCrc = HashingService.GetCrc32Upper(name.AsSpan(), PGConstants.PGCrc32Encoding); - - var properties = ParseXmlElement(element); - - return new SfxEvent(name, nameCrc, properties, XmlLocationInfo.FromElement(element)); - } - - protected override bool OnParsed(XElement element, string tag, object value, ValueListDictionary properties, string? outerElementName) - { - if (tag == SfxEventXmlTags.UsePreset) - { - var presetName = value as string; - var presetNameCrc = HashingService.GetCrc32Upper(presetName.AsSpan(), PGConstants.PGCrc32Encoding); - if (presetNameCrc != default && ParsedElements.TryGetFirstValue(presetNameCrc, out var preset)) - CopySfxPreset(properties, preset); - else - { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.MissingReference, - $"Cannot to find preset '{presetName}' for SFXEvent '{outerElementName ?? "NONE"}'")); - } - } - return true; - } - - private static void CopySfxPreset(ValueListDictionary currentXmlProperties, SfxEvent preset) - { - /* - * The engine also copies the Use_Preset *of* the preset, (which almost most cases is null) - * As this would cause that the SfxEvent using the preset, would not have a reference to its original preset, we do not copy the preset - * Example: - * - * - * Preset Yes - * 90 - * - * - * Engine Behavior: SFXEvent instance(Name: A, Use_Preset: null, Min_Volume: 90) - * PG.StarWarsGame.Engine Behavior: SFXEvent instance(Name: A, Use_Preset: Preset, Min_Volume: 90) - */ - - foreach (var keyValuePair in preset.XmlProperties) - { - if (keyValuePair.Key is SfxEventXmlTags.UsePreset or SfxEventXmlTags.IsPreset) - continue; - currentXmlProperties.Add(keyValuePair.Key, keyValuePair.Value); - } - - currentXmlProperties.Add(SfxEventXmlTags.PresetXRef, preset); - } - public override SfxEvent Parse(XElement element) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs new file mode 100644 index 0000000..a32ff09 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/CommandBarComponentFileParser.cs @@ -0,0 +1,31 @@ +using System; +using System.Xml.Linq; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.Xml.Parsers.Data; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.File; + +internal class CommandBarComponentFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) +{ + protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) + { + var parser = new CommandBarComponentParser(parsedElements, ServiceProvider, ErrorReporter); + + if (!element.HasElements) + { + OnParseError(XmlParseErrorEventArgs.FromEmptyRoot(element)); + return; + } + + foreach (var xElement in element.Elements()) + { + var commandBarComponent = parser.Parse(xElement, out var nameCrc); + parsedElements.Add(nameCrc, commandBarComponent); + } + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs similarity index 54% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs index 4eaedc7..d2abfed 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileParser.cs @@ -1,22 +1,20 @@ using System; using System.Xml.Linq; +using PG.Commons.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.GameObjects; using PG.StarWarsGame.Engine.Xml.Parsers.Data; -using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; namespace PG.StarWarsGame.Engine.Xml.Parsers.File; -internal class GameObjectFileFileParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) - : PetroglyphXmlFileParser(serviceProvider, listener) +internal class GameObjectFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) { - private readonly IXmlParserErrorListener? _listener = listener; - - protected override void Parse(XElement element, IValueListDictionary parsedElements) + protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) { - var parser = new GameObjectParser(parsedElements, ServiceProvider, _listener); + var parser = new GameObjectParser(parsedElements, ServiceProvider, ErrorReporter); foreach (var xElement in element.Elements()) { @@ -24,9 +22,4 @@ protected override void Parse(XElement element, IValueListDictionary(serviceProvider, errorReporter) +{ + protected override GuiDialogsXml Parse(XElement element, string fileName) + { + var textures = ParseTextures(element.Element("Textures"), fileName); + return new GuiDialogsXml(textures, XmlLocationInfo.FromElement(element)); + } + + private GuiDialogsXmlTextureData ParseTextures(XElement? element, string fileName) + { + if (element is null) + { + OnParseError(new XmlParseErrorEventArgs(new XmlLocationInfo(fileName, null), XmlParseErrorKind.MissingNode, + "Expected node is missing.")); + return new GuiDialogsXmlTextureData([], new XmlLocationInfo(fileName, null)); + } + + var textures = new List(); + + GetAttributeValue(element, "File", out var megaTexture); + GetAttributeValue(element, "Compressed_File", out var compressedMegaTexture); + + foreach (var texture in element.Elements()) + textures.Add(ParseTexture(texture)); + + if (textures.Count == 0) + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.MissingNode, + "Textures must contain at least one child node.")); + + return new GuiDialogsXmlTextureData(textures, XmlLocationInfo.FromElement(element)) + { + MegaTexture = megaTexture, + CompressedMegaTexture = compressedMegaTexture + }; + } + + private XmlComponentTextureData ParseTexture(XElement texture) + { + var componentId = GetTagName(texture); + var textures = new ValueListDictionary(); + + foreach (var entry in texture.Elements()) + textures.Add(entry.Name.ToString(), PetroglyphXmlStringParser.Instance.Parse(entry)); + + return new XmlComponentTextureData(componentId, textures, XmlLocationInfo.FromElement(texture)); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs index 7edc637..f40dd1a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs @@ -1,53 +1,32 @@ using System; using System.Xml.Linq; -using Microsoft.Extensions.Logging; +using PG.Commons.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.Audio.Sfx; using PG.StarWarsGame.Engine.Xml.Parsers.Data; -using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; namespace PG.StarWarsGame.Engine.Xml.Parsers.File; -internal class SfxEventFileParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) - : PetroglyphXmlFileParser(serviceProvider, listener) +internal class SfxEventFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlFileContainerParser(serviceProvider, errorReporter) { - private readonly IXmlParserErrorListener? _listener = listener; - - protected override void Parse(XElement element, IValueListDictionary parsedElements) + protected override void Parse(XElement element, IValueListDictionary parsedElements, string fileName) { - var parser = new SfxEventParser(parsedElements, ServiceProvider, _listener); + var parser = new SfxEventParser(parsedElements, ServiceProvider, ErrorReporter); if (!element.HasElements) { - OnParseError(XmlParseErrorEventArgs.FromEmptyRoot(XmlLocationInfo.FromElement(element).XmlFile, element)); + OnParseError(XmlParseErrorEventArgs.FromEmptyRoot(element)); return; } foreach (var xElement in element.Elements()) { var sfxEvent = parser.Parse(xElement, out var nameCrc); - if (nameCrc == default) - { - var location = XmlLocationInfo.FromElement(xElement); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, xElement, XmlParseErrorKind.MissingAttribute, - $"SFXEvent has no name at location '{location}'")); - } - parsedElements.Add(nameCrc, sfxEvent); } } - - protected override void OnParseError(XmlParseErrorEventArgs e) - { - Logger?.LogWarning($"Error while parsing {e.File}: {e.Message}"); - base.OnParseError(e); - } - - public override SfxEvent Parse(XElement element) - { - throw new NotSupportedException(); - } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs new file mode 100644 index 0000000..5d5f98a --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerContentParser.cs @@ -0,0 +1,120 @@ +using System; +using System.Linq; +using System.Xml; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.Commons.Services; +using PG.StarWarsGame.Engine.IO; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Data; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers; + +internal sealed class XmlContainerContentParser : ServiceBase, IPetroglyphXmlParser +{ + public event EventHandler? XmlParseError; + + private readonly IXmlParserErrorReporter? _reporter; + private readonly IPetroglyphXmlFileParserFactory _fileParserFactory; + + public XmlContainerContentParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? reporter) : base(serviceProvider) + { + _reporter = reporter; + _fileParserFactory = serviceProvider.GetRequiredService(); + Name = GetType().FullName!; + } + + public string Name { get; } + + public void ParseEntriesFromFileListXml( + string xmlFile, + IGameRepository gameRepository, + string lookupPath, + ValueListDictionary entries, + Action? onFileParseAction = null) where T : notnull + { + Logger.LogDebug($"Parsing container data '{xmlFile}'"); + + using var containerStream = gameRepository.TryOpenFile(xmlFile); + if (containerStream == null) + { + _reporter?.Report(this, XmlParseErrorEventArgs.FromMissingFile(xmlFile)); + Logger.LogWarning($"Could not find XML file '{xmlFile}'"); + + var args = new XmlContainerParserErrorEventArgs(xmlFile, null, true) + { + // No reason to continue + Continue = false + }; + XmlParseError?.Invoke(this, args); + return; + } + + XmlFileListContainer? container; + + try + { + var containerParser = new XmlFileListParser(Services, _reporter); + container = containerParser.ParseFile(containerStream); + if (container is null) + throw new XmlException($"Unable to parse XML container file '{xmlFile}'."); + } + catch (XmlException e) + { + var args = new XmlContainerParserErrorEventArgs(xmlFile, e, true) + { + // No reason to continue + Continue = false + }; + XmlParseError?.Invoke(this, args); + return; + } + + + var xmlFiles = container.Files.Select(x => FileSystem.Path.Combine(lookupPath, x)).ToList(); + + var parser = _fileParserFactory.CreateFileParser(_reporter); + + foreach (var file in xmlFiles) + { + if (onFileParseAction is not null) + onFileParseAction(file); + + using var fileStream = gameRepository.TryOpenFile(file); + + if (fileStream is null) + { + _reporter?.Report(parser, XmlParseErrorEventArgs.FromMissingFile(file)); + Logger.LogWarning($"Could not find XML file '{file}'"); + + var args = new XmlContainerParserErrorEventArgs(file); + XmlParseError?.Invoke(this, args); + + if (args.Continue) + continue; + return; + } + + Logger.LogDebug($"Parsing File '{file}'"); + + try + { + parser.ParseFile(fileStream, entries); + } + catch (XmlException e) + { + _reporter?.Report(parser, new XmlParseErrorEventArgs(new XmlLocationInfo(file, 0), XmlParseErrorKind.Unknown, e.Message)); + + var args = new XmlContainerParserErrorEventArgs(file, e); + XmlParseError?.Invoke(this, args); + + if (!args.Continue) + return; + } + } + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerParserErrorEventArgs.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerParserErrorEventArgs.cs new file mode 100644 index 0000000..87ccce1 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlContainerParserErrorEventArgs.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using System.Xml; + +namespace PG.StarWarsGame.Engine.Xml.Parsers; + +internal class XmlContainerParserErrorEventArgs(string file, XmlException? exception = null, bool isXmlFileList = false) +{ + public bool Continue + { + get; + // Once this is set to false, there is no way back. + set => field &= value; + } = true; + + public bool ErrorInXmlFileList { get; } = isXmlFileList; + + public string File { get; } = file; + + [MemberNotNullWhen(true, nameof(Exception))] + public bool HasException => Exception is not null; + + public bool IsFileNotFound => !HasException; + + public XmlException? Exception { get; } = exception; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs index 5c3142c..fead02d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/XmlObjectParser.cs @@ -1,52 +1,79 @@ using System; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Collections; using PG.Commons.Hashing; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; namespace PG.StarWarsGame.Engine.Xml.Parsers; -public abstract class XmlObjectParser(IReadOnlyValueListDictionary parsedElements, IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) - : PetroglyphXmlElementParser(serviceProvider, listener) where T : XmlObject -{ - protected IReadOnlyValueListDictionary ParsedElements { get; } = parsedElements ?? throw new ArgumentNullException(nameof(parsedElements)); +public abstract class XmlObjectParser( + IReadOnlyValueListDictionary parsedElements, + IServiceProvider serviceProvider, + IXmlParserErrorReporter? errorReporter = null) + : XmlObjectParser(parsedElements, serviceProvider, errorReporter) where TObject : XmlObject +{ + protected void Parse(TObject xmlObject, XElement element) + { + Parse(xmlObject, element, EmptyParseState.Instance); + } - protected ICrc32HashingService HashingService { get; } = serviceProvider.GetRequiredService(); + protected sealed override bool ParseTag(XElement tag, TObject xmlObject, in EmptyParseState parseState) + { + return ParseTag(tag, xmlObject); + } - public abstract T Parse(XElement element, out Crc32 nameCrc); + protected abstract bool ParseTag(XElement tag, TObject xmlObject); +} - protected abstract IPetroglyphXmlElementParser? GetParser(string tag); +public readonly struct EmptyParseState +{ + public static readonly EmptyParseState Instance = new(); +} - protected ValueListDictionary ParseXmlElement(XElement element, string? name = null) - { - var xmlProperties = new ValueListDictionary(); - foreach (var elm in element.Elements()) - { - var tagName = elm.Name.LocalName; - var parser = GetParser(tagName); +public abstract class XmlObjectParser( + IReadOnlyValueListDictionary parsedElements, + IServiceProvider serviceProvider, + IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlElementParser(errorReporter) where TObject : XmlObject +{ + protected IReadOnlyValueListDictionary ParsedElements { get; } = + parsedElements ?? throw new ArgumentNullException(nameof(parsedElements)); + + protected ICrc32HashingService HashingService { get; } = serviceProvider.GetRequiredService(); - if (parser is null) + public abstract TObject Parse(XElement element, out Crc32 crc32); + + protected void Parse(TObject xmlObject, XElement element, in TParseState state) + { + foreach (var tag in element.Elements()) + { + if (!ParseTag(tag, xmlObject, state)) { - // TODO - //var nameOrPosition = name ?? XmlLocationInfo.FromElement(element).ToString(); - //Logger?.LogWarning($"Unable to find parser for tag '{tagName}' in element '{nameOrPosition}'"); - continue; + OnParseError(new XmlParseErrorEventArgs(tag, XmlParseErrorKind.UnknownNode, + $"The node '{tag.Name}' is not supported.")); + break; } - - var value = parser.Parse(elm); - - if (OnParsed(elm, tagName, value, xmlProperties, name)) - xmlProperties.Add(tagName, value); } - return xmlProperties; } - protected virtual bool OnParsed(XElement element, string tag, object value, ValueListDictionary properties, string? outerElementName) + protected abstract bool ParseTag(XElement tag, TObject xmlObject, in TParseState parseState); + + protected string GetXmlObjectName(XElement element, out Crc32 crc32, bool uppercaseName) { - return true; + GetNameAttributeValue(element, out var name); + crc32 = uppercaseName + ? HashingService.GetCrc32Upper(name.AsSpan(), PGConstants.DefaultPGEncoding) + : HashingService.GetCrc32(name.AsSpan(), PGConstants.DefaultPGEncoding); + + if (crc32 == default) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Name for XmlObject cannot be empty.")); + } + + return name; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs index 7a283e5..550f459 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs @@ -1,35 +1,27 @@ using System; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Xml.Parsers.Data; +using PG.StarWarsGame.Engine.Audio.Sfx; +using PG.StarWarsGame.Engine.CommandBar.Xml; +using PG.StarWarsGame.Engine.GameObjects; using PG.StarWarsGame.Engine.Xml.Parsers.File; -using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.ErrorHandling; using PG.StarWarsGame.Files.XML.Parsers; -using PG.StarWarsGame.Files.XML.Parsers.Primitives; namespace PG.StarWarsGame.Engine.Xml; internal sealed class PetroglyphXmlFileParserFactory(IServiceProvider serviceProvider) : IPetroglyphXmlFileParserFactory { - public IPetroglyphXmlFileParser GetFileParser(IXmlParserErrorListener? listener = null) + public IPetroglyphXmlFileContainerParser CreateFileParser(IXmlParserErrorReporter? errorReporter) where T : notnull { - return (IPetroglyphXmlFileParser)GetFileParser(typeof(T), listener); - } - - private IPetroglyphXmlFileParser GetFileParser(Type type, IXmlParserErrorListener? listener) - { - if (type == typeof(XmlFileContainer)) - return new XmlFileContainerParser(serviceProvider, listener); + if (typeof(T) == typeof(SfxEvent)) + return (IPetroglyphXmlFileContainerParser) new SfxEventFileParser(serviceProvider, errorReporter); - if (type == typeof(GameConstants)) - return new GameConstantsParser(serviceProvider, listener); + if (typeof(T) == typeof(CommandBarComponentData)) + return (IPetroglyphXmlFileContainerParser)new CommandBarComponentFileParser(serviceProvider, errorReporter); - if (type == typeof(GameObject)) - return new GameObjectFileFileParser(serviceProvider, listener); + if (typeof(T) == typeof(GameObject)) + return (IPetroglyphXmlFileContainerParser)new GameObjectFileParser(serviceProvider, errorReporter); - if (type == typeof(SfxEvent)) - return new SfxEventFileParser(serviceProvider, listener); - throw new ParserNotFoundException(type); + throw new NotImplementedException($"Unable to get parser for type {typeof(T)}"); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/CommandBarComponentTags.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/CommandBarComponentTags.cs new file mode 100644 index 0000000..bc5498b --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/CommandBarComponentTags.cs @@ -0,0 +1,108 @@ +namespace PG.StarWarsGame.Engine.Xml.Tags; + +public static class CommandBarComponentTags +{ + public const string SelectedTextureName = "Selected_Texture_Name"; + public const string BlankTextureName = "Blank_Texture_Name"; + public const string IconTextureName = "Icon_Texture_Name"; + public const string IconAlternateTextureName = "Icon_Alternate_Texture_Name"; + public const string MouseOverTextureName = "Mouse_Over_Texture_Name"; + public const string DisabledTextureName = "Disabled_Texture_Name"; + public const string FlashTextureName = "Flash_Texture_Name"; + public const string BarTextureName = "Bar_Texture_Name"; + public const string BarOverlayName = "Bar_Overlay_Name"; + public const string BuildTextureName = "Build_Texture_Name"; + public const string ModelName = "Model_Name"; + public const string BoneName = "Bone_Name"; + public const string CursorTextureName = "Cursor_Texture_Name"; + public const string FontName = "Font_Name"; + public const string AlternateFontName = "Alternate_Font_Name"; + public const string TooltipText = "Tooltip_Text"; + public const string ClickSfx = "Click_SFX"; + public const string MouseOverSfx = "Mouse_Over_SFX"; + public const string LowerEffectTextureName = "Lower_Effect_Texture_Name"; + public const string UpperEffectTextureName = "Upper_Effect_Texture_Name"; + public const string OverlayTextureName = "Overlay_Texture_Name"; + public const string Overlay2TextureName = "Overlay2_Texture_Name"; + public const string RightClickSfx = "Right_Click_SFX"; + public const string Type = "Type"; + public const string Group = "Group"; + public const string DragAndDrop = "Drag_And_Drop"; + public const string DragSelect = "Drag_Select"; + public const string Receptor = "Receptor"; + public const string Toggle = "Toggle"; + public const string Tab = "Tab"; + public const string AssociatedText = "Associated_Text"; + public const string Hidden = "Hidden"; + public const string Scale = "Scale"; + public const string Color = "Color"; + public const string TextColor = "Text_Color"; + public const string TextColor2 = "Text_Color2"; + public const string Size = "Size"; + public const string ClearColor = "Clear_Color"; + public const string Disabled = "Disabled"; + public const string SwapTexture = "Swap_Texture"; + public const string BaseLayer = "Base_Layer"; + public const string DrawAdditive = "Draw_Additive"; + public const string TextOffset = "Text_Offset"; + public const string TextOffset2 = "Text_Offset2"; + public const string Offset = "Offset"; + public const string DefaultOffset = "Default_Offset"; + public const string DefaultOffsetWidescreen = "Default_Offset_Widescreen"; + public const string IconOffset = "Icon_Offset"; + public const string MouseOverOffset = "Mouse_Over_Offset"; + public const string DisabledOffset = "Disabled_Offset"; + public const string BuildDialOffset = "Build_Dial_Offset"; + public const string BuildDial2Offset = "Build_Dial2_Offset"; + public const string LowerEffectOffset = "Lower_Effect_Offset"; + public const string UpperEffectOffset = "Upper_Effect_Offset"; + public const string OverlayOffset = "Overlay_Offset"; + public const string Overlay2Offset = "Overlay2_Offset"; + public const string Editable = "Editable"; + public const string MaxTextLength = "Max_Text_Length"; + public const string BlinkRate = "Blink_Rate"; + public const string FontPointSize = "Font_Point_Size"; + public const string TextOutline = "Text_Outline"; + public const string MaxTextWidth = "Max_Text_Width"; + public const string Stackable = "Stackable"; + public const string ModelOffsetX = "Model_Offset_X"; + public const string ModelOffsetY = "Model_Offset_Y"; + public const string ScaleModelX = "Scale_Model_X"; + public const string ScaleModelY = "Scale_Model_Y"; + public const string Collideable = "Collideable"; + public const string TextEmboss = "Text_Emboss"; + public const string ShouldGhost = "Should_Ghost"; + public const string GhostBaseOnly = "Ghost_Base_Only"; + public const string MaxBarLevel = "Max_Bar_Level"; + public const string MaxBarColor = "Max_Bar_Color"; + public const string CrossFade = "Cross_Fade"; + public const string LeftJustified = "Left_Justified"; + public const string RightJustified = "Right_Justified"; + public const string NoShell = "No_Shell"; + public const string SnapDrag = "Snap_Drag"; + public const string SnapLocation = "Snap_Location"; + public const string BlinkDuration = "Blink_Duration"; + public const string ScaleDuration = "Scale_Duration"; + public const string OffsetRender = "Offset_Render"; + public const string BlinkFade = "Blink_Fade"; + public const string NoHiddenCollision = "No_Hidden_Collision"; + public const string ManualOffset = "Manual_Offset"; + public const string SelectedAlpha = "Selected_Alpha"; + public const string PixelAlign = "Pixel_Align"; + public const string CanDragStack = "Can_Drag_Stack"; + public const string CanAnimate = "Can_Animate"; + public const string AnimFps = "Anim_FPS"; + public const string LoopAnim = "Loop_Anim"; + public const string SmoothBar = "Smooth_Bar"; + public const string OutlinedBar = "Outlined_Bar"; + public const string DragBack = "Drag_Back"; + public const string LowerEffectAdditive = "Lower_Effect_Additive"; + public const string UpperEffectAdditive = "Upper_Effect_Additive"; + public const string ClickShift = "Click_Shift"; + public const string TutorialScene = "Tutorial_Scene"; + public const string DialogScene = "Dialog_Scene"; + public const string ShouldRenderAtDragPos = "Should_Render_At_Drag_Pos"; + public const string DisableDarken = "Disable_Darken"; + public const string AnimateBack = "Animate_Back"; + public const string AnimateUpperEffect = "Animate_Upper_Effect"; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/ComponentTextureKeyExtensions.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/ComponentTextureKeyExtensions.cs new file mode 100644 index 0000000..c072c90 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Tags/ComponentTextureKeyExtensions.cs @@ -0,0 +1,114 @@ +using System; +using PG.StarWarsGame.Engine.GuiDialog; + +namespace PG.StarWarsGame.Engine.Xml.Tags; + +internal static class ComponentTextureKeyExtensions +{ + public static bool TryConvertToKey(ReadOnlySpan keyValue, out GuiComponentType key) + { + key = keyValue switch + { + "Button_Left" => GuiComponentType.ButtonLeft, + "Button_Middle" => GuiComponentType.ButtonMiddle, + "Button_Right" => GuiComponentType.ButtonRight, + "Button_Left_Mouse_Over" => GuiComponentType.ButtonLeftMouseOver, + "Button_Middle_Mouse_Over" => GuiComponentType.ButtonMiddleMouseOver, + "Button_Right_Mouse_Over" => GuiComponentType.ButtonRightMouseOver, + "Button_Left_Pressed" => GuiComponentType.ButtonLeftPressed, + "Button_Middle_Pressed" => GuiComponentType.ButtonMiddlePressed, + "Button_Right_Pressed" => GuiComponentType.ButtonRightPressed, + "Button_Left_Disabled" => GuiComponentType.ButtonLeftDisabled, + "Button_Middle_Disabled" => GuiComponentType.ButtonMiddleDisabled, + "Button_Right_Disabled" => GuiComponentType.ButtonRightDisabled, + + "Check_Off" => GuiComponentType.CheckOff, + "Check_On" => GuiComponentType.CheckOn, + + "Dial_Left" => GuiComponentType.DialLeft, + "Dial_Middle" => GuiComponentType.DialMiddle, + "Dial_Right" => GuiComponentType.DialRight, + "Dial_Plus" => GuiComponentType.DialPlus, + "Dial_Plus_Mouse_Over" => GuiComponentType.DialPlusMouseOver, + "Dial_Plus_Pressed" => GuiComponentType.DialPlusPressed, + "Dial_Minus" => GuiComponentType.DialMinus, + "Dial_Minus_Mouse_Over" => GuiComponentType.DialMinusMouseOver, + "Dial_Minus_Pressed" => GuiComponentType.DialMinusPressed, + "Dial_Tab" => GuiComponentType.DialTab, + + "Frame_Bottom" => GuiComponentType.FrameBottom, + "Frame_Bottom_Left" => GuiComponentType.FrameBottomLeft, + "Frame_Bottom_Right" => GuiComponentType.FrameBottomRight, + "Frame_Background" => GuiComponentType.FrameBackground, + "Frame_Left" => GuiComponentType.FrameLeft, + "Frame_Right" => GuiComponentType.FrameRight, + "Frame_Top" => GuiComponentType.FrameTop, + "Frame_Top_Left" => GuiComponentType.FrameTopLeft, + "Frame_Top_Right" => GuiComponentType.FrameTopRight, + "Frame_Top_Transition_Left" => GuiComponentType.FrameTopTransitionLeft, + "Frame_Top_Transition_Right" => GuiComponentType.FrameTopTransitionRight, + "Frame_Bottom_Transition_Left" => GuiComponentType.FrameBottomTransitionLeft, + "Frame_Bottom_Transition_Right" => GuiComponentType.FrameBottomTransitionRight, + "Frame_Left_Transition_Top" => GuiComponentType.FrameLeftTransitionTop, + "Frame_Left_Transition_Bottom" => GuiComponentType.FrameLeftTransitionBottom, + "Frame_Right_Transition_Top" => GuiComponentType.FrameRightTransitionTop, + "Frame_Right_Transition_Bottom" => GuiComponentType.FrameRightTransitionBottom, + + "Radio_Off" => GuiComponentType.RadioOff, + "Radio_On" => GuiComponentType.RadioOn, + "Radio_Disabled" => GuiComponentType.RadioDisabled, + "Radio_Mouse_Over" => GuiComponentType.RadioMouseOver, + + "Scroll_Down_Button" => GuiComponentType.ScrollDownButton, + "Scroll_Down_Button_Pressed" => GuiComponentType.ScrollDownButtonPressed, + "Scroll_Down_Button_Mouse_Over" => GuiComponentType.ScrollDownButtonMouseOver, + "Scroll_Down_Button_Disabled" => GuiComponentType.ScrollDownButtonDisabled, + "Scroll_Middle" => GuiComponentType.ScrollMiddle, + "Scroll_Middle_Disabled" => GuiComponentType.ScrollMiddleDisabled, + "Scroll_Tab" => GuiComponentType.ScrollTab, + "Scroll_Tab_Disabled" => GuiComponentType.ScrollTabDisabled, + "Scroll_Up_Button" => GuiComponentType.ScrollUpButton, + "Scroll_Up_Button_Pressed" => GuiComponentType.ScrollUpButtonPressed, + "Scroll_Up_Button_Mouse_Over" => GuiComponentType.ScrollUpButtonMouseOver, + "Scroll_Up_Button_Disabled" => GuiComponentType.ScrollUpButtonDisabled, + + "Trackbar_Scroll_Down_Button" => GuiComponentType.TrackbarScrollDownButton, + "Trackbar_Scroll_Down_Button_Pressed" => GuiComponentType.TrackbarScrollDownButtonPressed, + "Trackbar_Scroll_Down_Button_Mouse_Over" => GuiComponentType.TrackbarScrollDownButtonMouseOver, + "Trackbar_Scroll_Down_Button_Disabled" => GuiComponentType.TrackbarScrollDownButtonDisabled, + "Trackbar_Scroll_Middle" => GuiComponentType.TrackbarScrollMiddle, + "Trackbar_Scroll_Middle_Disabled" => GuiComponentType.TrackbarScrollMiddleDisabled, + "Trackbar_Scroll_Tab" => GuiComponentType.TrackbarScrollTab, + "Trackbar_Scroll_Tab_Disabled" => GuiComponentType.TrackbarScrollTabDisabled, + "Trackbar_Scroll_Up_Button" => GuiComponentType.TrackbarScrollUpButton, + "Trackbar_Scroll_Up_Button_Pressed" => GuiComponentType.TrackbarScrollUpButtonPressed, + "Trackbar_Scroll_Up_Button_Mouse_Over" => GuiComponentType.TrackbarScrollUpButtonMouseOver, + "Trackbar_Scroll_Up_Button_Disabled" => GuiComponentType.TrackbarScrollUpButtonDisabled, + + "Small_Frame_Bottom" => GuiComponentType.SmallFrameBottom, + "Small_Frame_Bottom_Left" => GuiComponentType.SmallFrameBottomLeft, + "Small_Frame_Bottom_Right" => GuiComponentType.SmallFrameBottomRight, + "Small_Frame_Left" => GuiComponentType.SmallFrameMiddleLeft, + "Small_Frame_Right" => GuiComponentType.SmallFrameMiddleRight, + "Small_Frame_Top" => GuiComponentType.SmallFrameTop, + "Small_Frame_Top_Left" => GuiComponentType.SmallFrameTopLeft, + "Small_Frame_Top_Right" => GuiComponentType.SmallFrameTopRight, + "Small_Frame_Background" => GuiComponentType.SmallFrameBackground, + + "Combo_Box_Popdown_Button" => GuiComponentType.ComboboxPopdown, + "Combo_Box_Popdown_Button_Pressed" => GuiComponentType.ComboboxPopdownPressed, + "Combo_Box_Popdown_Button_Mouse_Over" => GuiComponentType.ComboboxPopdownMouseOver, + "Combo_Box_Text_Box" => GuiComponentType.ComboboxTextBox, + "Combo_Box_Left_Cap" => GuiComponentType.ComboboxLeftCap, + + "Progress_Bar_Left" => GuiComponentType.ProgressLeft, + "Progress_Bar_Middle_Off" => GuiComponentType.ProgressMiddleOff, + "Progress_Bar_Middle_On" => GuiComponentType.ProgressMiddleOn, + "Progress_Bar_Right" => GuiComponentType.ProgressRight, + + "Scanlines" => GuiComponentType.Scanlines, + _ => (GuiComponentType)int.MaxValue + }; + return (int)key != int.MaxValue; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/XmlObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/XmlObject.cs new file mode 100644 index 0000000..71840fe --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/XmlObject.cs @@ -0,0 +1,12 @@ +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.Xml; + +public abstract class XmlObject(XmlLocationInfo location) +{ + public XmlLocationInfo Location { get; } = location; + + internal virtual void CoerceValues() + { + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/AloServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/AloServiceContribution.cs index 2b5b880..98b608b 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/AloServiceContribution.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/AloServiceContribution.cs @@ -7,7 +7,7 @@ namespace PG.StarWarsGame.Files.ALO; public static class AloServiceContribution { - public static void ContributeServices(IServiceCollection serviceCollection) + public static void SupportALO(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(sp => new AloFileService(sp)); serviceCollection.AddSingleton(sp => new AloFileReaderFactory(sp)); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/AloFileReaderFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/AloFileReaderFactory.cs index 48a5607..f6324f8 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/AloFileReaderFactory.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/AloFileReaderFactory.cs @@ -1,6 +1,9 @@ using System; using System.IO; using PG.StarWarsGame.Files.ALO.Binary.Reader; +using PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; +using PG.StarWarsGame.Files.ALO.Binary.Reader.Models; +using PG.StarWarsGame.Files.ALO.Binary.Reader.Particles; using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Files; using PG.StarWarsGame.Files.ALO.Services; @@ -11,10 +14,20 @@ internal class AloFileReaderFactory(IServiceProvider serviceProvider) : IAloFile { public IAloFileReader GetReader(AloContentInfo contentInfo, Stream dataStream, AloLoadOptions loadOptions) { - if (contentInfo.Type == AloType.Model) - return new ModelFileReader(loadOptions, dataStream); - if (contentInfo.Version == AloVersion.V1) - return new ParticleReaderV1(loadOptions, dataStream); - return new ParticleReaderV2(loadOptions, dataStream); + switch (contentInfo.Type) + { + case AloType.Model: + return new ModelFileReader(loadOptions, dataStream); + case AloType.Animation when contentInfo.Version == AloVersion.V1: + return new AnimationReaderV1(loadOptions, dataStream); + case AloType.Animation: + return new AnimationReaderV2(loadOptions, dataStream); + case AloType.Particle when contentInfo.Version == AloVersion.V1: + return new ParticleReaderV1(loadOptions, dataStream); + case AloType.Particle: + return new ParticleReaderV2(loadOptions, dataStream); + default: + throw new NotSupportedException($"ALO content type {contentInfo.Type} is not supported."); + } } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs index 3471ccd..6fce5c0 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/AloContentInfoIdentifier.cs @@ -1,6 +1,6 @@ using System.IO; -using PG.Commons.Binary; using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; using PG.StarWarsGame.Files.ChunkFiles.Binary.Reader; @@ -26,11 +26,36 @@ public AloContentInfo GetContentInfo(Stream stream) return new AloContentInfo(AloType.Particle, AloVersion.V1); case ChunkType.ParticleUaW: return new AloContentInfo(AloType.Particle, AloVersion.V2); + case ChunkType.Animation: + return FromAnimation(chunkReader); default: throw new BinaryCorruptedException("Unable to get ALO content information."); } } + private static AloContentInfo FromAnimation(ChunkReader chunkReader) + { + var chunk = chunkReader.TryReadChunk(); + while (chunk.HasValue) + { + switch ((ChunkType)chunk.Value.Type) + { + case ChunkType.AnimationInformation: + return chunk.Value.Size switch + { + 36 => new AloContentInfo(AloType.Animation, AloVersion.V2), + 18 => new AloContentInfo(AloType.Animation, AloVersion.V1), + _ => throw new BinaryCorruptedException("Invalid ALA animation.") + }; + default: + chunkReader.Skip(chunk.Value.Size); + break; + } + chunk = chunkReader.TryReadChunk(); + } + throw new BinaryCorruptedException("Invalid ALA animation."); + } + private static AloContentInfo FromConnection(ChunkReader chunkReader) { var chunk = chunkReader.TryReadChunk(); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/IAloContentInfoIdentifier.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/IAloContentInfoIdentifier.cs index 5853410..591a54c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/IAloContentInfoIdentifier.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Identifier/IAloContentInfoIdentifier.cs @@ -3,7 +3,7 @@ namespace PG.StarWarsGame.Files.ALO.Binary.Identifier; -interface IAloContentInfoIdentifier +internal interface IAloContentInfoIdentifier { AloContentInfo GetContentInfo(Stream stream); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationChunkTypes.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationChunkTypes.cs new file mode 100644 index 0000000..4382ac2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationChunkTypes.cs @@ -0,0 +1,19 @@ +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; + +public enum AnimationChunkTypes +{ + NumFrames = 0x01, + Fps = 0x02, + NumBones = 0x03, + BoneName = 0x04, + BoneIndex = 0x05, + RotationBlockSize = 0x0b, + TranslationBlockSize = 0x0c, + ScaleBlockSize = 0x0d, + AnimationFile = 0x1000, + AnimationInfo = 0x1001, + BoneData = 0x1002, + BoneDataInfo = 0x1003, + BoneVisibility = 0x1007, + BoneStepKeyTrack = 0x1008, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationInformationData.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationInformationData.cs new file mode 100644 index 0000000..529e41b --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationInformationData.cs @@ -0,0 +1,11 @@ +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; + +internal ref struct AnimationInformationData +{ + public float FPS; + public uint NumberFrames; + public uint NumberBones; + public uint RotationBlockSize; + public uint TranslationBlockSize; + public uint ScaleBlockSize; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs new file mode 100644 index 0000000..632caf3 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderBase.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Services; +using PG.StarWarsGame.Files.Binary; +using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; + +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; + +internal abstract class AnimationReaderBase(AloLoadOptions loadOptions, Stream stream) + : AloFileReader(loadOptions, stream) +{ + public sealed override AlamoAnimation Read() + { + var bones = new List(); + AnimationInformationData info = default; + + var rootChunk = ChunkReader.ReadChunk(); + + if (rootChunk is not { Type: (int)AnimationChunkTypes.AnimationFile }) + throw new BinaryCorruptedException("Unable to read animation file."); + + var actualSize = 0; + + do + { + var chunk = ChunkReader.ReadChunk(ref actualSize); + ReadAnimation(chunk, ref info, bones); + actualSize += chunk.Size; + + } while (actualSize < rootChunk.Size); + + if (actualSize != rootChunk.Size) + throw new BinaryCorruptedException(); + + if (info.NumberBones != bones.Count) + throw new BinaryCorruptedException("The number of bones does not match the number of bone data."); + + return new AlamoAnimation + { + FPS = info.FPS, + NumberBones = info.NumberBones, + NumberFrames = info.NumberFrames, + BoneData = bones, + }; + } + + protected virtual void ReadAnimation( + ChunkMetadata chunk, + ref AnimationInformationData animationInformation, + List bones) + { + switch (chunk.Type) + { + case (int)AnimationChunkTypes.AnimationInfo: + animationInformation = ReadAnimationInfo(chunk.Size); + break; + case (int)AnimationChunkTypes.BoneData: + ReadBonesData(chunk.Size, bones); + break; + default: + ChunkReader.Skip(chunk.Size); + break; + } + } + + protected virtual void ReadBoneDataCore(ChunkMetadata chunk, List bones) + { + switch (chunk.Type) + { + case (int)AnimationChunkTypes.BoneDataInfo: + ReadBoneInfo(chunk.Size, bones); + break; + default: + ChunkReader.Skip(chunk.Size); + break; + } + } + + private void ReadBonesData(int chunkSize, List bones) + { + var actualSize = 0; + + do + { + var chunk = ChunkReader.ReadChunk(ref actualSize); + ReadBoneDataCore(chunk, bones); + actualSize += chunk.Size; + + } while (actualSize < chunkSize); + + if (actualSize != chunkSize) + throw new BinaryCorruptedException("Unable to read particle"); + } + + private void ReadBoneInfo(int chunkSize, List bones) + { + var actualSize = 0; + + string name = null!; + uint index = 0; + + do + { + var chunk = ChunkReader.ReadMiniChunk(ref actualSize); + + switch (chunk.Type) + { + case (int)AnimationChunkTypes.BoneName: + name = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true, ref actualSize); + break; + case (int)AnimationChunkTypes.BoneIndex: + index = ChunkReader.ReadDword(ref actualSize); + break; + default: + ChunkReader.Skip(chunk.Size, ref actualSize); + break; + } + + } while (actualSize < chunkSize); + + if (actualSize != chunkSize) + throw new BinaryCorruptedException("Unable to read particle"); + + bones.Add(new AnimationBoneData(index, name)); + } + + private AnimationInformationData ReadAnimationInfo(int chunkSize) + { + var actualSize = 0; + + var info = new AnimationInformationData(); + do + { + var chunk = ChunkReader.ReadMiniChunk(ref actualSize); + + switch (chunk.Type) + { + case (int)AnimationChunkTypes.NumFrames: + info.NumberFrames = ChunkReader.ReadDword(ref actualSize); + break; + case (int)AnimationChunkTypes.Fps: + info.FPS = ChunkReader.ReadFloat(ref actualSize); + break; + case (int)AnimationChunkTypes.NumBones: + info.NumberBones = ChunkReader.ReadDword(ref actualSize); + break; + case (int)AnimationChunkTypes.TranslationBlockSize: + info.TranslationBlockSize = ChunkReader.ReadDword(ref actualSize); + break; + case (int)AnimationChunkTypes.RotationBlockSize: + info.RotationBlockSize = ChunkReader.ReadDword(ref actualSize); + break; + case (int)AnimationChunkTypes.ScaleBlockSize: + info.ScaleBlockSize = ChunkReader.ReadDword(ref actualSize); + break; + default: + ChunkReader.Skip(chunk.Size, ref actualSize); + break; + } + + } while (actualSize < chunkSize); + + if (actualSize != chunkSize) + throw new BinaryCorruptedException("Unable to read animation"); + + return info; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV1.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV1.cs new file mode 100644 index 0000000..838fc86 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV1.cs @@ -0,0 +1,6 @@ +using PG.StarWarsGame.Files.ALO.Services; +using System.IO; + +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; + +internal class AnimationReaderV1(AloLoadOptions loadOptions, Stream stream) : AnimationReaderBase(loadOptions, stream); \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV2.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV2.cs new file mode 100644 index 0000000..e6d1eb4 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Animations/AnimationReaderV2.cs @@ -0,0 +1,6 @@ +using System.IO; +using PG.StarWarsGame.Files.ALO.Services; + +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Animations; + +internal class AnimationReaderV2(AloLoadOptions loadOptions, Stream stream) : AnimationReaderBase(loadOptions, stream); \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelChunkTypes.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelChunkTypes.cs new file mode 100644 index 0000000..c15818e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelChunkTypes.cs @@ -0,0 +1,19 @@ +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Models; + +public enum ModelChunkTypes +{ + Name = 0x0, + Id = 0x1, + Skeleton = 0x200, + BoneCount = 0x201, + Bone = 0x202, + BoneName = 0x203, + Mesh = 0x400, + Connections = 0x600, + ProxyConnection = 0x603, + Particle = 0x900, + Animation = 0x1000, + SubMeshMaterialInformation = 0x00010100, + ShaderFileName = 0x00010101, + ShaderTexture = 0x00010105, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ModelFileReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs similarity index 89% rename from src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ModelFileReader.cs rename to src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs index d43116b..13da425 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ModelFileReader.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Models/ModelFileReader.cs @@ -3,12 +3,11 @@ using System.Diagnostics; using System.IO; using System.Text; -using PG.Commons.Binary; using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Services; -using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; +using PG.StarWarsGame.Files.Binary; -namespace PG.StarWarsGame.Files.ALO.Binary.Reader; +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Models; internal class ModelFileReader(AloLoadOptions loadOptions, Stream stream) : AloFileReader(loadOptions, stream) { @@ -25,13 +24,13 @@ public override AlamoModel Read() { switch (chunk.Value.Type) { - case (int)ChunkType.Skeleton: + case (int)ModelChunkTypes.Skeleton: ReadSkeleton(chunk.Value.Size, bones); break; - case (int)ChunkType.Mesh: + case (int)ModelChunkTypes.Mesh: ReadMesh(chunk.Value.Size, textures, shaders); break; - case (int)ChunkType.Connections: + case (int)ModelChunkTypes.Connections: ReadConnections(chunk.Value.Size, proxies); break; default: @@ -61,7 +60,7 @@ private void ReadConnections(int size, HashSet proxies) { var chunk = ChunkReader.ReadChunk(ref actualSize); - if (chunk.Type == (int)ChunkType.ProxyConnection) + if (chunk.Type == (int)ModelChunkTypes.ProxyConnection) { ReadProxy(chunk.Size, out var proxy, ref actualSize); proxies.Add(proxy); @@ -110,7 +109,7 @@ private void ReadMesh(int size, ISet textures, ISet shaders) { var chunk = ChunkReader.ReadChunk(ref actualSize); - if (chunk.Type == (int)ChunkType.SubMeshMaterialInformation) + if (chunk.Type == (int)ModelChunkTypes.SubMeshMaterialInformation) ReadSubMeshMaterialInformation(chunk.Size, textures, shaders, ref actualSize); else ChunkReader.Skip(chunk.Size, ref actualSize); @@ -131,13 +130,13 @@ private void ReadSubMeshMaterialInformation(int size, ISet textures, ISe switch (chunk.Type) { - case (int)ChunkType.ShaderFileName: + case (int)ModelChunkTypes.ShaderFileName: { var shader = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true, ref actualSize); shaders.Add(shader); break; } - case (int)ChunkType.ShaderTexture: + case (int)ModelChunkTypes.ShaderTexture: ReadShaderTexture(chunk.Size, textures, ref actualSize); break; default: @@ -192,7 +191,7 @@ private void ReadSkeleton(int size, IList bones) var boneCountChunk = ChunkReader.ReadChunk(ref actualSize); - Debug.Assert(boneCountChunk is { Size: 128, Type: (int)ChunkType.BoneCount }); + Debug.Assert(boneCountChunk is { Size: 128, Type: (int)ModelChunkTypes.BoneCount }); var boneCount = ChunkReader.ReadDword(ref actualSize); @@ -202,7 +201,7 @@ private void ReadSkeleton(int size, IList bones) { var bone = ChunkReader.ReadChunk(ref actualSize); - Debug.Assert(bone is { Type: (int)ChunkType.Bone, IsContainer: true }); + Debug.Assert(bone is { Type: (int)ModelChunkTypes.Bone, IsContainer: true }); var boneReadSize = 0; @@ -210,7 +209,7 @@ private void ReadSkeleton(int size, IList bones) { var innerBoneChunk = ChunkReader.ReadChunk(ref boneReadSize); - if (innerBoneChunk.Type == (int)ChunkType.BoneName) + if (innerBoneChunk.Type == (int)ModelChunkTypes.BoneName) { var nameSize = innerBoneChunk.Size; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleChunkType.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleChunkType.cs new file mode 100644 index 0000000..19c2f64 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleChunkType.cs @@ -0,0 +1,18 @@ +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Particles; + +public enum ParticleChunkType +{ + Name = 0x0, + Id = 0x1, + Properties = 0x2, + ColorTextureName = 0x3, + BumpTextureName = 0x45, + Bone = 0x202, + BoneName = 0x203, + Mesh = 0x400, + Connections = 0x600, + Emitter = 0x700, + Emitters = 0x800, + Particle = 0x900, + Animation = 0x1000, +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV1.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs similarity index 85% rename from src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV1.cs rename to src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs index 1f487f7..ed6de4b 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV1.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV1.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Text; -using PG.Commons.Binary; using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Services; -using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; +using PG.StarWarsGame.Files.Binary; -namespace PG.StarWarsGame.Files.ALO.Binary.Reader; +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Particles; internal class ParticleReaderV1(AloLoadOptions loadOptions, Stream stream) : AloFileReader(loadOptions, stream) { @@ -19,7 +18,7 @@ public override AlamoParticle Read() var rootChunk = ChunkReader.ReadChunk(); - if (rootChunk.Type != (int)ChunkType.Particle) + if (rootChunk.Type != (int)ParticleChunkType.Particle) throw new NotSupportedException("This reader only support V1 particles."); var actualSize = 0; @@ -30,10 +29,10 @@ public override AlamoParticle Read() switch (chunk.Type) { - case (int)ChunkType.Name: + case (int)ParticleChunkType.Name: ReadName(chunk.Size, out name); break; - case (int)ChunkType.Emitters: + case (int)ParticleChunkType.Emitters: ReadEmitters(chunk.Size, textures); break; default: @@ -68,7 +67,7 @@ private void ReadEmitters(int size, HashSet textures) { var chunk = ChunkReader.ReadChunk(ref actualSize); - if (chunk.Type != (int)ChunkType.Emitter) + if (chunk.Type != (int)ParticleChunkType.Emitter) throw new BinaryCorruptedException("Unable to read particle"); ReadEmitter(chunk.Size, textures); @@ -90,17 +89,17 @@ private void ReadEmitter(int chunkSize, HashSet textures) { var chunk = ChunkReader.ReadChunk(ref actualSize); - if (chunk.Type == (int)ChunkType.Properties) + if (chunk.Type == (int)ParticleChunkType.Properties) { var shader = ChunkReader.ReadDword(); ChunkReader.Skip(chunk.Size - sizeof(uint)); } - else if (chunk.Type == (int)ChunkType.ColorTextureName) + else if (chunk.Type == (int)ParticleChunkType.ColorTextureName) { var texture = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true); textures.Add(texture); } - else if (chunk.Type == (int)ChunkType.BumpTextureName) + else if (chunk.Type == (int)ParticleChunkType.BumpTextureName) { var bump = ChunkReader.ReadString(chunk.Size, Encoding.ASCII, true); textures.Add(bump); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV2.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV2.cs similarity index 84% rename from src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV2.cs rename to src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV2.cs index fc79903..014f377 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/ParticleReaderV2.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Binary/Reader/Particles/ParticleReaderV2.cs @@ -3,7 +3,7 @@ using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Services; -namespace PG.StarWarsGame.Files.ALO.Binary.Reader; +namespace PG.StarWarsGame.Files.ALO.Binary.Reader.Particles; internal class ParticleReaderV2(AloLoadOptions loadOptions, Stream stream) : AloFileReader(loadOptions, stream) { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoAnimation.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoAnimation.cs new file mode 100644 index 0000000..80938f4 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoAnimation.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace PG.StarWarsGame.Files.ALO.Data; + +public sealed class AlamoAnimation : IAloDataContent +{ + public float FPS { get; init; } + + public uint NumberFrames { get; init; } + + public uint NumberBones { get; init; } + + public IReadOnlyCollection BoneData { get; init; } + + public void Dispose() + { + } +} + +public readonly struct AnimationBoneData(uint index, string name) +{ + public uint BoneIndex { get; } = index; + + public string Name { get; } = name; +} diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoModel.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoModel.cs index 63a7089..86f7bf9 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoModel.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoModel.cs @@ -2,7 +2,7 @@ namespace PG.StarWarsGame.Files.ALO.Data; -public class AlamoModel : IAloDataContent +public sealed class AlamoModel : IAloDataContent { public IList Bones { get; init; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoParticle.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoParticle.cs index b2d94cb..e810f3a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoParticle.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Data/AlamoParticle.cs @@ -2,7 +2,7 @@ namespace PG.StarWarsGame.Files.ALO.Data; -public class AlamoParticle : IAloDataContent +public sealed class AlamoParticle : IAloDataContent { public required string Name { get; init; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloFileInformation.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloFileInformation.cs index 43df83c..cb70248 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloFileInformation.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloFileInformation.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using PG.Commons.Files; namespace PG.StarWarsGame.Files.ALO.Files; @@ -13,7 +12,9 @@ public sealed record AloFileInformation : PetroglyphMegPackableFileInformation public bool IsModel => ContentInfo.Type == AloType.Model; - public bool IsParticle => !IsModel; + public bool IsParticle => ContentInfo.Type == AloType.Particle; + + public bool IsAnimation => ContentInfo.Type == AloType.Animation; /// /// Initializes a new instance of the class. diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloType.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloType.cs index 2a152c3..cc32355 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloType.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/AloType.cs @@ -3,5 +3,6 @@ public enum AloType { Model, - Particle + Particle, + Animation } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/AloAnimationFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/AloAnimationFile.cs new file mode 100644 index 0000000..0ad88db --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/AloAnimationFile.cs @@ -0,0 +1,10 @@ +using System; +using PG.StarWarsGame.Files.ALO.Data; + +namespace PG.StarWarsGame.Files.ALO.Files.Animations; + +public sealed class AloAnimationFile( + AlamoAnimation animation, + AloFileInformation fileInformation, + IServiceProvider serviceProvider) + : PetroglyphFileHolder(animation, fileInformation, serviceProvider), IAloAnimationFile; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/IAloAnimationFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/IAloAnimationFile.cs new file mode 100644 index 0000000..f6bcaed --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Animations/IAloAnimationFile.cs @@ -0,0 +1,5 @@ +using PG.StarWarsGame.Files.ALO.Data; + +namespace PG.StarWarsGame.Files.ALO.Files.Animations; + +public interface IAloAnimationFile : IAloFile; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/IAloFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/IAloFile.cs index 4812535..db28fe0 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/IAloFile.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/IAloFile.cs @@ -1,5 +1,4 @@ -using PG.Commons.Files; -using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ChunkFiles.Files; namespace PG.StarWarsGame.Files.ALO.Files; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Models/AloModelFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Models/AloModelFile.cs index 07a8723..48408d1 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Models/AloModelFile.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Models/AloModelFile.cs @@ -1,5 +1,4 @@ using System; -using PG.Commons.Files; using PG.StarWarsGame.Files.ALO.Data; namespace PG.StarWarsGame.Files.ALO.Files.Models; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Particles/AloParticleFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Particles/AloParticleFile.cs index 19008ab..2178e2d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Particles/AloParticleFile.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Files/Particles/AloParticleFile.cs @@ -1,5 +1,4 @@ using System; -using PG.Commons.Files; using PG.StarWarsGame.Files.ALO.Data; namespace PG.StarWarsGame.Files.ALO.Files.Particles; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj index 490459c..4fd3dd4 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/PG.StarWarsGame.Files.ALO.csproj @@ -28,4 +28,7 @@ + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloFileService.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloFileService.cs index d21191b..4eabac4 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloFileService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloFileService.cs @@ -1,14 +1,13 @@ using System; using System.IO; -using System.IO.Abstractions; using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Files; using PG.Commons.Services; using PG.Commons.Utilities; using PG.StarWarsGame.Files.ALO.Binary; using PG.StarWarsGame.Files.ALO.Binary.Identifier; using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.ALO.Files.Animations; using PG.StarWarsGame.Files.ALO.Files.Models; using PG.StarWarsGame.Files.ALO.Files.Particles; @@ -19,7 +18,7 @@ public class AloFileService(IServiceProvider serviceProvider) : ServiceBase(serv private readonly IAloFileReaderFactory _readerFactory = serviceProvider.GetRequiredService(); private readonly IAloContentInfoIdentifier _aloContentIdentifier = serviceProvider.GetRequiredService(); - public IAloFile Load(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full) + public IAloFile Load(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full) { return Load(stream, null, loadOptions); } @@ -34,7 +33,12 @@ public IAloParticleFile LoadParticle(Stream stream, AloLoadOptions loadOptions = return (IAloParticleFile)Load(stream, AloType.Particle, loadOptions); } - public IAloFile Load(Stream stream, AloType? supportedType = null, + public IAloAnimationFile LoadAnimation(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full) + { + return (IAloAnimationFile)Load(stream, AloType.Animation, loadOptions); + } + + public IAloFile Load(Stream stream, AloType? supportedType = null, AloLoadOptions loadOptions = AloLoadOptions.Full) { if (stream == null) @@ -56,7 +60,7 @@ public IAloFile Load(Stre var alo = reader.Read(); - var filePath = GetFilePath(stream, out var isInMeg); + var filePath = stream.GetFilePath(out var isInMeg); var fileInfo = new AloFileInformation(filePath, isInMeg, contentInfo); if (alo is AlamoModel model) @@ -65,25 +69,12 @@ public IAloFile Load(Stre if (alo is AlamoParticle particle) return new AloParticleFile(particle, fileInfo, Services); - throw new InvalidOperationException(); - } - - private static string GetFilePath(Stream stream, out bool isInMeg) - { - isInMeg = false; - if (stream is FileStream fileStream) - return fileStream.Name; - if (stream is FileSystemStream fileSystemStream) - return fileSystemStream.Name; - if (stream is IMegFileDataStream megFileDataStream) - { - isInMeg = true; - return megFileDataStream.EntryPath; - } + if (alo is AlamoAnimation animation) + return new AloAnimationFile(animation, fileInfo, Services); throw new InvalidOperationException(); } - + public AloContentInfo GetAloContentInfo(Stream stream) { return _aloContentIdentifier.GetContentInfo(stream); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloLoadOptions.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloLoadOptions.cs index 84fed92..20672e5 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloLoadOptions.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/AloLoadOptions.cs @@ -20,4 +20,8 @@ public enum AloLoadOptions /// If the file is a model, this option gets the list of bones from the model. /// Bones = 2, + /// + /// Extracts only metadata from the model/particle/animation. + /// + MetadataOnly = Assets | Bones } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/IAloFileService.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/IAloFileService.cs index ad0c099..4632466 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/IAloFileService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ALO/Services/IAloFileService.cs @@ -1,19 +1,21 @@ -using System.IO; -using PG.Commons.Files; -using PG.StarWarsGame.Files.ALO.Data; +using PG.StarWarsGame.Files.ALO.Data; using PG.StarWarsGame.Files.ALO.Files; +using PG.StarWarsGame.Files.ALO.Files.Animations; using PG.StarWarsGame.Files.ALO.Files.Models; using PG.StarWarsGame.Files.ALO.Files.Particles; +using System.IO; namespace PG.StarWarsGame.Files.ALO.Services; public interface IAloFileService { - IAloFile Load(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full); + IAloFile Load(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full); IAloModelFile LoadModel(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full); IAloParticleFile LoadParticle(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full); + IAloAnimationFile LoadAnimation(Stream stream, AloLoadOptions loadOptions = AloLoadOptions.Full); + AloContentInfo GetAloContentInfo(Stream stream); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkType.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkType.cs index 9b34a66..70af55d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkType.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Metadata/ChunkType.cs @@ -25,6 +25,8 @@ public enum ChunkType Emitters = 0x800, Particle = 0x900, Animation = 0x1000, + AnimationInformation = 0x1001, + AnimationBoneData = 0x1002, Light = 0x1300, ParticleUaW = 0x1500, SubMeshData = 0x00010000, diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs index 782be52..01d085a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkFileReaderBase.cs @@ -15,9 +15,9 @@ IChunkData IChunkFileReader.Read() return Read(); } - protected override void DisposeManagedResources() + protected override void DisposeResources() { - base.DisposeManagedResources(); + base.DisposeResources(); ChunkReader.Dispose(); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs index 60105e5..742924d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Binary/Reader/ChunkReader.cs @@ -2,28 +2,20 @@ using System.IO; using System.Text; using AnakinRaW.CommonUtilities; -using PG.Commons.Utilities; +using PG.StarWarsGame.Files.Binary; using PG.StarWarsGame.Files.ChunkFiles.Binary.Metadata; namespace PG.StarWarsGame.Files.ChunkFiles.Binary.Reader; public class ChunkReader : DisposableObject { - private readonly BinaryReader _binaryReader; + private readonly PetroglyphBinaryReader _binaryReader; public ChunkReader(Stream stream, bool leaveOpen = false) { if (stream == null) throw new ArgumentNullException(nameof(stream)); - - // Using default encoding here is OK as we don't read strings using the .NET methods. - _binaryReader = new BinaryReader(stream, Encoding.Default, leaveOpen); - } - - protected override void DisposeManagedResources() - { - base.DisposeManagedResources(); - _binaryReader.Dispose(); + _binaryReader = new PetroglyphBinaryReader(stream, leaveOpen); } public ChunkMetadata ReadChunk() @@ -61,6 +53,13 @@ public uint ReadDword(ref int readSize) return value; } + public float ReadFloat(ref int readSize) + { + var value = _binaryReader.ReadSingle(); + readSize += sizeof(float); + return value; + } + public uint ReadDword() { return _binaryReader.ReadUInt32(); @@ -79,17 +78,29 @@ public void Skip(int bytesToSkip) public string ReadString(int size, Encoding encoding, bool zeroTerminated, ref int readSize) { - var value = _binaryReader.ReadString(size, encoding, zeroTerminated); + var value = ReadString(encoding, size, zeroTerminated); readSize += size; return value; } public string ReadString(int size, Encoding encoding, bool zeroTerminated) { - var value = _binaryReader.ReadString(size, encoding, zeroTerminated); + var value = ReadString(encoding, size, zeroTerminated); return value; } + private string ReadString(Encoding encoding, int size, bool zeroTerminated) + { + try + { + return _binaryReader.ReadString(encoding, size, zeroTerminated); + } + catch (Exception e) + { + throw new BinaryCorruptedException($"Unable to read string: {e.Message}", e); + } + } + public ChunkMetadata? TryReadChunk() { var _ = 0; @@ -100,16 +111,15 @@ public string ReadString(int size, Encoding encoding, bool zeroTerminated) { if (_binaryReader.BaseStream.Position == _binaryReader.BaseStream.Length) return null; - try - { - var chunk = ReadChunk(); - size += 8; - return chunk; - } - catch (EndOfStreamException e) - { - Console.WriteLine(e); - throw; - } + + var chunk = ReadChunk(); + size += 8; + return chunk; + } + + protected override void DisposeResources() + { + base.DisposeResources(); + _binaryReader.Dispose(); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Files/IChunkFile.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Files/IChunkFile.cs index 619e0ba..8b03a3b 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Files/IChunkFile.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/Files/IChunkFile.cs @@ -1,5 +1,4 @@ -using PG.Commons.Files; -using PG.StarWarsGame.Files.ChunkFiles.Data; +using PG.StarWarsGame.Files.ChunkFiles.Data; namespace PG.StarWarsGame.Files.ChunkFiles.Files; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj index d909270..d6a7939 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.ChunkFiles/PG.StarWarsGame.Files.ChunkFiles.csproj @@ -16,6 +16,9 @@ snupkg - + + + + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Data/XmlFileListContainer.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Data/XmlFileListContainer.cs new file mode 100644 index 0000000..31a16cc --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Data/XmlFileListContainer.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace PG.StarWarsGame.Files.XML.Data; + +public class XmlFileListContainer(IList files) +{ + public IList Files { get; } = files; +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlErrorParserProvider.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlErrorParserProvider.cs deleted file mode 100644 index 3ed446e..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlErrorParserProvider.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PG.StarWarsGame.Files.XML.ErrorHandling; - -public interface IPrimitiveXmlErrorParserProvider : IXmlParserErrorProvider; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlParserErrorListener.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlParserErrorListener.cs deleted file mode 100644 index 5157546..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IPrimitiveXmlParserErrorListener.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PG.StarWarsGame.Files.XML.ErrorHandling; - -internal interface IPrimitiveXmlParserErrorListener : IXmlParserErrorListener, IPrimitiveXmlErrorParserProvider; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorListener.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorListener.cs deleted file mode 100644 index 59b395d..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorListener.cs +++ /dev/null @@ -1,8 +0,0 @@ -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Files.XML.ErrorHandling; - -public interface IXmlParserErrorListener -{ - public void OnXmlParseError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorReporter.cs new file mode 100644 index 0000000..6bb14c1 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/IXmlParserErrorReporter.cs @@ -0,0 +1,8 @@ +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Files.XML.ErrorHandling; + +public interface IXmlParserErrorReporter +{ + void Report(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlErrorReporter.cs new file mode 100644 index 0000000..39de0e6 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlErrorReporter.cs @@ -0,0 +1,22 @@ +using System; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Files.XML.ErrorHandling; + +internal sealed class PrimitiveXmlErrorReporter : IXmlParserErrorReporter, IXmlParserErrorProvider +{ + public event XmlErrorEventHandler? XmlParseError; + + private static readonly Lazy LazyInstance = new(() => new PrimitiveXmlErrorReporter()); + + public static PrimitiveXmlErrorReporter Instance => LazyInstance.Value; + + private PrimitiveXmlErrorReporter() + { + } + + public void Report(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) + { + XmlParseError?.Invoke(parser, error); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlParserErrorBroker.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlParserErrorBroker.cs deleted file mode 100644 index c66ba6b..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/PrimitiveXmlParserErrorBroker.cs +++ /dev/null @@ -1,13 +0,0 @@ -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Files.XML.ErrorHandling; - -internal class PrimitiveXmlParserErrorBroker : IPrimitiveXmlParserErrorListener -{ - public event XmlErrorEventHandler? XmlParseError; - - public void OnXmlParseError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) - { - XmlParseError?.Invoke(parser, error); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlErrorReporter.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlErrorReporter.cs new file mode 100644 index 0000000..8234729 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlErrorReporter.cs @@ -0,0 +1,29 @@ +using AnakinRaW.CommonUtilities; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Files.XML.ErrorHandling; + +public class XmlErrorReporter : DisposableObject, IXmlParserErrorReporter, IXmlParserErrorProvider +{ + public event XmlErrorEventHandler? XmlParseError; + + public XmlErrorReporter() + { + PrimitiveXmlErrorReporter.Instance.XmlParseError += OnPrimitiveError; + } + + public virtual void Report(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) + { + XmlParseError?.Invoke(parser, error); + } + + protected override void DisposeResources() + { + PrimitiveXmlErrorReporter.Instance.XmlParseError -= OnPrimitiveError; + } + + private void OnPrimitiveError(IPetroglyphXmlParser parser, XmlParseErrorEventArgs error) + { + Report(parser, error); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorEventArgs.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorEventArgs.cs index 1ea7d70..afaa2ce 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorEventArgs.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorEventArgs.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Files.XML.ErrorHandling; public class XmlParseErrorEventArgs : EventArgs { - public string File { get; } + public XmlLocationInfo Location { get; } public XElement? Element { get; } @@ -14,24 +14,29 @@ public class XmlParseErrorEventArgs : EventArgs public string Message { get; } - public XmlParseErrorEventArgs(string file, XElement? element, XmlParseErrorKind errorKind, string message) + public XmlParseErrorEventArgs(XElement element, XmlParseErrorKind errorKind, string message) { - ThrowHelper.ThrowIfNullOrEmpty(file); - File = file; - Element = element; + Element = element ?? throw new ArgumentNullException(nameof(element)); + Location = XmlLocationInfo.FromElement(element); ErrorKind = errorKind; Message = message; } + public XmlParseErrorEventArgs(XmlLocationInfo location, XmlParseErrorKind errorKind, string message) + { + Location = location; + Message = message; + ErrorKind = errorKind; + } + public static XmlParseErrorEventArgs FromMissingFile(string file) { ThrowHelper.ThrowIfNullOrEmpty(file); - return new XmlParseErrorEventArgs(file, null, XmlParseErrorKind.MissingFile, $"XML file '{file}' not found."); + return new XmlParseErrorEventArgs(new XmlLocationInfo(file, null), XmlParseErrorKind.MissingFile, "XML file not found."); } - public static XmlParseErrorEventArgs FromEmptyRoot(string file, XElement element) + public static XmlParseErrorEventArgs FromEmptyRoot(XElement element) { - ThrowHelper.ThrowIfNullOrEmpty(file); - return new XmlParseErrorEventArgs(file, element, XmlParseErrorKind.EmptyRoot, $"XML file '{file}' has an empty root node."); + return new XmlParseErrorEventArgs(element, XmlParseErrorKind.EmptyRoot, "XML file has an empty root node."); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorKind.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorKind.cs index ea329d9..aa3f8d2 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorKind.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ErrorHandling/XmlParseErrorKind.cs @@ -20,11 +20,11 @@ public enum XmlParseErrorKind /// InvalidValue = 3, /// - /// A tag's value is has an invalid syntax. + /// A tag's value has an invalid syntax. /// MalformedValue = 4, /// - /// The value is too long + /// The tag or value is too long. /// TooLongData = 5, /// @@ -39,4 +39,16 @@ public enum XmlParseErrorKind /// The XML file does not start with the XML header. /// DataBeforeHeader = 8, + /// + /// The XML file is missing an expected node. + /// + MissingNode = 9, + /// + /// The XML element contains an unsupported tag. + /// + UnknownNode = 10, + /// + /// The XML tag name is null or empty. + /// + EmptyNodeName } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj index 32d7562..066e32d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj @@ -14,14 +14,16 @@ true snupkg + true + all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj.DotSettings b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj.DotSettings index 2dedfd6..62d0b15 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj.DotSettings +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/PG.StarWarsGame.Files.XML.csproj.DotSettings @@ -1,6 +1,8 @@  False True + True True True + True True \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlElementParser.cs new file mode 100644 index 0000000..14a8aa1 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlElementParser.cs @@ -0,0 +1,8 @@ +using System.Xml.Linq; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public interface IPetroglyphXmlElementParser : IPetroglyphXmlParser where T : notnull +{ + T Parse(XElement element); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs new file mode 100644 index 0000000..f42a9a9 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileContainerParser.cs @@ -0,0 +1,10 @@ +using System.IO; +using PG.Commons.Collections; +using PG.Commons.Hashing; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public interface IPetroglyphXmlFileContainerParser : IPetroglyphXmlParser where T : notnull +{ + void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileParser.cs new file mode 100644 index 0000000..5f9c4b4 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlFileParser.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public interface IPetroglyphXmlFileParser : IPetroglyphXmlParser where T : notnull +{ + T? ParseFile(Stream xmlStream); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlParser.cs new file mode 100644 index 0000000..fdf5fd5 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/IPetroglyphXmlParser.cs @@ -0,0 +1,6 @@ +namespace PG.StarWarsGame.Files.XML.Parsers; + +public interface IPetroglyphXmlParser +{ + string Name { get; } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlFileParserBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlFileParserBase.cs new file mode 100644 index 0000000..6ffba48 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlFileParserBase.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.IO.Abstractions; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Utilities; +using PG.StarWarsGame.Files.XML.ErrorHandling; +#if NETSTANDARD2_0 +using AnakinRaW.CommonUtilities.FileSystem; +#endif + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public abstract class PetroglyphXmlFileParserBase(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter) + : PetroglyphXmlParserBase(errorReporter) +{ + protected readonly IServiceProvider ServiceProvider = + serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + protected readonly IFileSystem FileSystem = serviceProvider.GetRequiredService(); + + protected virtual bool LoadLineInfo => true; + + protected XElement? GetRootElement(Stream xmlStream, out string fileName) + { + fileName = GetStrippedFileName(xmlStream.GetFilePath()); + + if (string.IsNullOrEmpty(fileName)) + throw new InvalidOperationException("Unable to parse XML from unnamed stream. Either parse from a file or MEG stream."); + + SkipLeadingWhiteSpace(fileName, xmlStream); + + var xmlReader = XmlReader.Create(xmlStream, new XmlReaderSettings + { + IgnoreWhitespace = true, + IgnoreComments = true, + IgnoreProcessingInstructions = true + }, fileName); + + var options = LoadOptions.SetBaseUri; + if (LoadLineInfo) + options |= LoadOptions.SetLineInfo; + + var doc = XDocument.Load(xmlReader, options); + return doc.Root; + } + + private string GetStrippedFileName(string filePath) + { + if (!FileSystem.Path.IsPathFullyQualified(filePath)) + return filePath; + + var pathPartIndex = filePath.LastIndexOf("DATA\\XML\\", StringComparison.OrdinalIgnoreCase); + + if (pathPartIndex == -1) + return filePath; + + return filePath.Substring(pathPartIndex); + } + + + private void SkipLeadingWhiteSpace(string fileName, Stream stream) + { + using var r = new StreamReader(stream, Encoding.ASCII, false, 10, true); + var count = 0; + + while (true) + { + var c = (char)r.Read(); + if (!char.IsWhiteSpace(c)) + break; + count++; + } + + if (count != 0) + { + OnParseError(new XmlParseErrorEventArgs(new XmlLocationInfo(fileName, 0), + XmlParseErrorKind.DataBeforeHeader, $"XML header is not the first entry of the XML file."));} + + stream.Position = count; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlParserBase.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlParserBase.cs new file mode 100644 index 0000000..7395eb6 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Base/PetroglyphXmlParserBase.cs @@ -0,0 +1,61 @@ +using System.Linq; +using PG.StarWarsGame.Files.XML.ErrorHandling; +using System.Xml.Linq; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public abstract class PetroglyphXmlParserBase : IPetroglyphXmlParser +{ + protected readonly IXmlParserErrorReporter? ErrorReporter; + + public string Name { get; } + + public override string ToString() + { + return Name; + } + + protected PetroglyphXmlParserBase(IXmlParserErrorReporter? errorReporter) + { + Name = GetType().FullName!; + ErrorReporter = errorReporter; + } + + protected string GetTagName(XElement element) + { + return element.Name.LocalName; + } + + protected string GetNameAttributeValue(XElement element) + { + var nameAttribute = element.Attributes() + .FirstOrDefault(a => a.Name.LocalName == "Name"); + return nameAttribute is null ? string.Empty : nameAttribute.Value; + } + + protected bool GetNameAttributeValue(XElement element, out string value) + { + return GetAttributeValue(element, "Name", out value!, string.Empty); + } + + protected bool GetAttributeValue(XElement element, string attribute, out string? value, string? defaultValue = null) + { + var nameAttribute = element.Attributes() + .FirstOrDefault(a => a.Name.LocalName == attribute); + + if (nameAttribute is null) + { + value = defaultValue; + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.MissingAttribute, $"Missing attribute '{attribute}'")); + return false; + } + + value = nameAttribute.Value; + return true; + } + + protected virtual void OnParseError(XmlParseErrorEventArgs error) + { + ErrorReporter?.Report(this, error); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs deleted file mode 100644 index e699b80..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace PG.StarWarsGame.Files.XML.Parsers; - -public interface IPetroglyphXmlElementParser : IPetroglyphXmlParser; - -public interface IPetroglyphXmlElementParser : IPetroglyphXmlElementParser, IPetroglyphXmlParser; \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs deleted file mode 100644 index 2c81137..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; -using PG.Commons.Hashing; - -namespace PG.StarWarsGame.Files.XML.Parsers; - -public interface IPetroglyphXmlFileParser : IPetroglyphXmlParser -{ - public object? ParseFile(Stream stream); -} - -public interface IPetroglyphXmlFileParser : IPetroglyphXmlParser, IPetroglyphXmlFileParser -{ - public new T ParseFile(Stream stream); - - public void ParseFile(Stream stream, IValueListDictionary parsedEntries); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlParser.cs deleted file mode 100644 index 220fa05..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlParser.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Xml.Linq; - -namespace PG.StarWarsGame.Files.XML.Parsers; - -public interface IPetroglyphXmlParser -{ - object Parse(XElement element); -} - -public interface IPetroglyphXmlParser : IPetroglyphXmlParser -{ - new T Parse(XElement element); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs index 53ce6cb..5fa2c6d 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs @@ -1,22 +1,10 @@ -using System; -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; namespace PG.StarWarsGame.Files.XML.Parsers; -public abstract class PetroglyphXmlElementParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) - : PetroglyphXmlParser(serviceProvider, listener) +public abstract class PetroglyphXmlElementParser(IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlParserBase(errorReporter), IPetroglyphXmlElementParser where T : notnull { - protected string GetTagName(XElement element) - { - return element.Name.LocalName; - } - - protected string GetNameAttributeValue(XElement element) - { - var nameAttribute = element.Attributes() - .FirstOrDefault(a => a.Name.LocalName == "Name"); - return nameAttribute is null ? string.Empty : nameAttribute.Value; - } + public abstract T Parse(XElement element); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs new file mode 100644 index 0000000..4e371e2 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileContainerParser.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Xml.Linq; +using PG.Commons.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.XML.ErrorHandling; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public abstract class PetroglyphXmlFileContainerParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? listener = null) + : PetroglyphXmlFileParserBase(serviceProvider, listener), IPetroglyphXmlFileContainerParser where T : notnull +{ + public void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries) + { + var root = GetRootElement(xmlStream, out var fileName); + if (root is not null) + Parse(root, parsedEntries, fileName); + } + + protected abstract void Parse(XElement element, IValueListDictionary parsedElements, string fileName); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs index f9b64f9..986cfef 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs @@ -1,101 +1,24 @@ using System; using System.IO; -using System.IO.Abstractions; -using System.Text; -using System.Xml; using System.Xml.Linq; -using AnakinRaW.CommonUtilities.FileSystem; -using Microsoft.Extensions.DependencyInjection; -using PG.Commons.Hashing; -using PG.Commons.Utilities; using PG.StarWarsGame.Files.XML.ErrorHandling; namespace PG.StarWarsGame.Files.XML.Parsers; -public abstract class PetroglyphXmlFileParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) : - PetroglyphXmlParser(serviceProvider, listener), IPetroglyphXmlFileParser +public abstract class PetroglyphXmlFileParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) + : PetroglyphXmlFileParserBase(serviceProvider, errorReporter), IPetroglyphXmlFileParser where T : notnull { - private readonly IXmlParserErrorListener? _listener = listener; - private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); - - protected virtual bool LoadLineInfo => true; - - public T ParseFile(Stream xmlStream) - { - var root = GetRootElement(xmlStream); - return root is null ? default! : Parse(root); - } - - public void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries) + public T? ParseFile(Stream xmlStream) { - var root = GetRootElement(xmlStream); - if (root is not null) - Parse(root, parsedEntries); - } - - protected abstract void Parse(XElement element, IValueListDictionary parsedElements); - - private XElement? GetRootElement(Stream xmlStream) - { - var fileName = GetStrippedFileName(xmlStream.GetFilePath()); - - if (string.IsNullOrEmpty(fileName)) - throw new InvalidOperationException("Unable to parse XML from unnamed stream. Either parse from a file or MEG stream."); - - SkipLeadingWhiteSpace(fileName, xmlStream); - - var xmlReader = XmlReader.Create(xmlStream, new XmlReaderSettings + var root = GetRootElement(xmlStream, out var fileName); + if (root is null) { - IgnoreWhitespace = true, - IgnoreComments = true, - IgnoreProcessingInstructions = true - }, fileName); - - var options = LoadOptions.SetBaseUri; - if (LoadLineInfo) - options |= LoadOptions.SetLineInfo; - - var doc = XDocument.Load(xmlReader, options); - return doc.Root; - } - - private string GetStrippedFileName(string filePath) - { - if (!_fileSystem.Path.IsPathFullyQualified(filePath)) - return filePath; - - var pathPartIndex = filePath.LastIndexOf("DATA\\XML\\", StringComparison.OrdinalIgnoreCase); - - if (pathPartIndex == -1) - return filePath; - - return filePath.Substring(pathPartIndex); - } - - - private void SkipLeadingWhiteSpace(string fileName, Stream stream) - { - using var r = new StreamReader(stream, Encoding.ASCII, false, 10, true); - var count = 0; - - while (true) - { - var c = (char)r.Read(); - if (!char.IsWhiteSpace(c)) - break; - count++; + var location = new XmlLocationInfo(fileName, 0); + OnParseError(new XmlParseErrorEventArgs(location, XmlParseErrorKind.EmptyRoot, + "Unable to get root node from XML file.")); } - - if (count != 0) - _listener?.OnXmlParseError(this, new XmlParseErrorEventArgs(fileName, null, - XmlParseErrorKind.DataBeforeHeader, $"XML header is not the first entry of the file '{fileName}'")); - - stream.Position = count; + return root is null ? default : Parse(root, fileName); } - - object? IPetroglyphXmlFileParser.ParseFile(Stream stream) - { - return ParseFile(stream); - } + protected abstract T Parse(XElement element, string fileName); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlParser.cs deleted file mode 100644 index 21b4b43..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlParser.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Xml.Linq; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers.Primitives; - -namespace PG.StarWarsGame.Files.XML.Parsers; - -public abstract class PetroglyphXmlParser : IPetroglyphXmlParser -{ - private readonly IXmlParserErrorListener? _errorListener; - - protected IServiceProvider ServiceProvider { get; } - - protected ILogger? Logger { get; } - - protected IPrimitiveParserProvider PrimitiveParserProvider { get; } - - protected PetroglyphXmlParser(IServiceProvider serviceProvider, IXmlParserErrorListener? errorListener = null) - { - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - Logger = serviceProvider.GetService()?.CreateLogger(GetType()); - PrimitiveParserProvider = serviceProvider.GetRequiredService(); - _errorListener = errorListener; - } - - public abstract T Parse(XElement element); - - protected virtual void OnParseError(XmlParseErrorEventArgs e) - { - _errorListener?.OnXmlParseError(this, e); - } - - object IPetroglyphXmlParser.Parse(XElement element) - { - return Parse(element); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlPrimitiveElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlPrimitiveElementParser.cs deleted file mode 100644 index cd97dd8..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlPrimitiveElementParser.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Files.XML.Parsers; - -public abstract class PetroglyphXmlPrimitiveElementParser : PetroglyphXmlParser, IPetroglyphXmlElementParser -{ - private protected PetroglyphXmlPrimitiveElementParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : - base(serviceProvider, listener) - { - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs index a0418db..1d2a5b3 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs @@ -1,21 +1,23 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml.Linq; -using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; // Used e.g, by // Format: Key, Value, Key, Value // There might be arbitrary spaces, tabs and newlines // TODO: This class is not yet implemented, compliant to the engine -public sealed class CommaSeparatedStringKeyValueListParser : PetroglyphXmlPrimitiveElementParser> +public sealed class CommaSeparatedStringKeyValueListParser : PetroglyphPrimitiveXmlParser> { - internal CommaSeparatedStringKeyValueListParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + public static readonly CommaSeparatedStringKeyValueListParser Instance = new(); + + private CommaSeparatedStringKeyValueListParser() { } - public override IList<(string key, string value)> Parse(XElement element) + private protected override IList<(string key, string value)> DefaultValue => []; + + protected internal override IList<(string key, string value)> ParseCore(string trimmedValue, XElement element) { var values = element.Value.Split(','); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/IPrimitiveParserProvider.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/IPrimitiveParserProvider.cs deleted file mode 100644 index 3be1a0f..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/IPrimitiveParserProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; - -public interface IPrimitiveParserProvider -{ - PetroglyphXmlStringParser StringParser { get; } - - PetroglyphXmlUnsignedIntegerParser UIntParser { get; } - - PetroglyphXmlLooseStringListParser LooseStringListParser { get; } - - PetroglyphXmlIntegerParser IntParser { get; } - - PetroglyphXmlFloatParser FloatParser { get; } - - PetroglyphXmlByteParser ByteParser { get; } - - PetroglyphXmlMax100ByteParser Max100ByteParser { get; } - - PetroglyphXmlBooleanParser BooleanParser { get; } - - CommaSeparatedStringKeyValueListParser CommaSeparatedStringKeyValueListParser { get; } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphPrimitiveXmlParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphPrimitiveXmlParser.cs new file mode 100644 index 0000000..0fd6fc1 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphPrimitiveXmlParser.cs @@ -0,0 +1,30 @@ +using System.Xml.Linq; +using PG.StarWarsGame.Files.XML.ErrorHandling; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public abstract class PetroglyphPrimitiveXmlParser : PetroglyphXmlElementParser where T : notnull +{ + private protected abstract T DefaultValue { get; } + + private protected PetroglyphPrimitiveXmlParser() : base(PrimitiveXmlErrorReporter.Instance) + { + } + + public sealed override T Parse(XElement element) + { + var tagName = element.Name.LocalName; + if (string.IsNullOrEmpty(tagName)) + { + ErrorReporter?.Report(this, new XmlParseErrorEventArgs(element, XmlParseErrorKind.EmptyNodeName, "A tag name cannot be null or empty.")); + return DefaultValue; + } + if (tagName.Length >= 256) + ErrorReporter?.Report(this, new XmlParseErrorEventArgs(element, XmlParseErrorKind.TooLongData, "A tag name cannot be null or empty.")); + + var value = element.Value.Trim(); + return value.Length == 0 ? DefaultValue : ParseCore(value, element); + } + + protected internal abstract T ParseCore(string trimmedValue, XElement element); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlBooleanParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlBooleanParser.cs index f0124a8..8672d1b 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlBooleanParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlBooleanParser.cs @@ -1,31 +1,24 @@ -using System; -using System.Xml.Linq; -using PG.StarWarsGame.Files.XML.ErrorHandling; +using System.Xml.Linq; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlBooleanParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlBooleanParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlBooleanParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) - { - } + public static readonly PetroglyphXmlBooleanParser Instance = new(); - public override bool Parse(XElement element) + private PetroglyphXmlBooleanParser() { - var valueSpan = element.Value.AsSpan(); - var trimmed = valueSpan.Trim(); + } - if (trimmed.Length == 0) - return false; + private protected override bool DefaultValue => false; + protected internal override bool ParseCore(string trimmedValue, XElement element) + { // Yes! The engine only checks if the values is exact 1 or starts with Tt or Yy // At least it's efficient, I guess... - if (trimmed.Length == 1 && trimmed[0] == '1') - return true; - - if (trimmed[0] is 'y' or 'Y' or 't' or 'T') + if (trimmedValue.Length == 1 && trimmedValue[0] == '1') return true; - return false; + return trimmedValue[0] is 'y' or 'Y' or 't' or 'T'; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlByteParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlByteParser.cs index 68d8ec7..aa7ba4b 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlByteParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlByteParser.cs @@ -1,34 +1,29 @@ -using System; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; +using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlByteParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlByteParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlByteParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + public static readonly PetroglyphXmlByteParser Instance = new(); + + private PetroglyphXmlByteParser() { } - public override byte Parse(XElement element) + private protected override byte DefaultValue => 0; + + protected internal override byte ParseCore(string trimmedValue, XElement element) { - var intValue = PrimitiveParserProvider.IntParser.Parse(element); + var intValue = PetroglyphXmlIntegerParser.Instance.ParseCore(trimmedValue, element); var asByte = (byte)intValue; if (intValue != asByte) { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.InvalidValue, - $"Expected a byte value (0 - 255) but got value '{intValue}' at {location}")); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected a byte value (0 - 255) but got value '{intValue}'.")); } return asByte; } - - protected override void OnParseError(XmlParseErrorEventArgs e) - { - Logger?.LogWarning(e.Message); - base.OnParseError(e); - } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlFloatParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlFloatParser.cs index 6691061..d3b4e85 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlFloatParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlFloatParser.cs @@ -1,33 +1,43 @@ using System; using System.Globalization; using System.Xml.Linq; -using Microsoft.Extensions.Logging; using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlFloatParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlFloatParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlFloatParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + public static readonly PetroglyphXmlFloatParser Instance = new(); + + private protected override float DefaultValue => 0.0f; + + private PetroglyphXmlFloatParser() { } - public override float Parse(XElement element) + public float ParseAtLeast(XElement element, float minValue) { - // The engine always loads FP numbers a long double and then converts that result to float - if (!double.TryParse(element.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue)) + var value = Parse(element); + var corrected = Math.Max(value, minValue); + if (corrected != value) { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.MalformedValue, - $"Expected double but got value '{element.Value}' at {location}")); - return 0.0f; + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected float to be at least {minValue} but got value '{value}'.")); } - return (float)doubleValue; + + return corrected; } - protected override void OnParseError(XmlParseErrorEventArgs e) + protected internal override float ParseCore(string trimmedValue, XElement element) { - Logger?.LogWarning(e.Message); - base.OnParseError(e); + // The engine always loads FP numbers a long double and then converts that result to float + if (!double.TryParse(trimmedValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue)) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.MalformedValue, + $"Expected double but got value '{trimmedValue}'.")); + return 0.0f; + } + + return (float)doubleValue; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlIntegerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlIntegerParser.cs index 02400b8..fd0c0ec 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlIntegerParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlIntegerParser.cs @@ -1,36 +1,43 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Xml.Linq; +using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Utilities; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlIntegerParser : PetroglyphXmlPrimitiveElementParser -{ - internal PetroglyphXmlIntegerParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) +public sealed class PetroglyphXmlIntegerParser : PetroglyphPrimitiveXmlParser +{ + public static readonly PetroglyphXmlIntegerParser Instance = new(); + + private protected override int DefaultValue => 0; + + private PetroglyphXmlIntegerParser() { } - public override int Parse(XElement element) + protected internal override int ParseCore(string trimmedValue, XElement element) { // The engines uses the C++ function std::atoi which is a little more loose. // For example the value '123d' get parsed to 123, // whereas in C# int.TryParse returns (false, 0) - - if (!int.TryParse(element.Value, out var i)) + if (!int.TryParse(trimmedValue, out var i)) { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.MalformedValue, - $"Expected integer but got '{element.Value}' at {location}")); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.MalformedValue, + $"Expected integer but got '{trimmedValue}'.")); return 0; } return i; } - protected override void OnParseError(XmlParseErrorEventArgs e) + public int ParseWithRange(XElement element, int minValue, int maxValue) { - Logger?.LogWarning(e.Message); - base.OnParseError(e); + var value = Parse(element); + var clamped = PGMath.Clamp(value, minValue, maxValue); + if (value != clamped) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected integer between {minValue} and {maxValue} but got value '{value}'.")); + } + return clamped; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlLooseStringListParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlLooseStringListParser.cs index 82fbb38..d06f660 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlLooseStringListParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlLooseStringListParser.cs @@ -1,12 +1,11 @@ -using Microsoft.Extensions.Logging; -using System; +using System; using System.Collections.Generic; using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlLooseStringListParser : PetroglyphXmlPrimitiveElementParser> +public sealed class PetroglyphXmlLooseStringListParser : PetroglyphPrimitiveXmlParser> { // These are the characters the engine uses as a generic list separator private static readonly char[] Separators = @@ -17,35 +16,25 @@ public sealed class PetroglyphXmlLooseStringListParser : PetroglyphXmlPrimitiveE '\n', '\r' ]; - - internal PetroglyphXmlLooseStringListParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + + public static readonly PetroglyphXmlLooseStringListParser Instance = new(); + + private protected override IList DefaultValue => []; + + private PetroglyphXmlLooseStringListParser() { } - public override IList Parse(XElement element) + protected internal override IList ParseCore(string trimmedValue, XElement element) { - var trimmedValued = element.Value.Trim(); - - if (trimmedValued.Length == 0) - return Array.Empty(); - - if (trimmedValued.Length > 0x2000) + if (trimmedValue.Length > 0x2000) { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.TooLongData, - $"Input value is too long '{trimmedValued.Length}' at {XmlLocationInfo.FromElement(element)}")); - - return Array.Empty(); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.TooLongData, + $"Input value is too long '{trimmedValue.Length}' at {XmlLocationInfo.FromElement(element)}")); + return DefaultValue; } - var entries = trimmedValued.Split(Separators, StringSplitOptions.RemoveEmptyEntries); - + var entries = trimmedValue.Split(Separators, StringSplitOptions.RemoveEmptyEntries); return entries; } - - protected override void OnParseError(XmlParseErrorEventArgs e) - { - Logger?.LogWarning(e.Message); - base.OnParseError(e); - } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlMax100ByteParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlMax100ByteParser.cs index af72d8b..ede64a3 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlMax100ByteParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlMax100ByteParser.cs @@ -1,20 +1,22 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Xml.Linq; +using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; +using PG.StarWarsGame.Files.XML.Utilities; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlMax100ByteParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlMax100ByteParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlMax100ByteParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) - { + public static readonly PetroglyphXmlMax100ByteParser Instance = new(); + + private protected override byte DefaultValue => 0; + private PetroglyphXmlMax100ByteParser() + { } - public override byte Parse(XElement element) + protected internal override byte ParseCore(string trimmedValue, XElement element) { - var intValue = PrimitiveParserProvider.IntParser.Parse(element); + var intValue = PetroglyphXmlIntegerParser.Instance.ParseCore(trimmedValue, element); if (intValue > 100) intValue = 100; @@ -22,26 +24,39 @@ public override byte Parse(XElement element) var asByte = (byte)intValue; if (intValue != asByte) { - var location = XmlLocationInfo.FromElement(element); - - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.InvalidValue, - $"Expected a byte value (0 - 255) but got value '{intValue}' at {location}")); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected a byte value (0 - 255) but got value '{intValue}'.")); } // Add additional check, cause the PG implementation is broken, but we need to stay "bug-compatible". if (asByte > 100) { - var location = XmlLocationInfo.FromElement(element); - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.InvalidValue, - $"Expected a byte value (0 - 100) but got value '{asByte}' at {location}")); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected a byte value (0 - 100) but got value '{asByte}'.")); } return asByte; } - protected override void OnParseError(XmlParseErrorEventArgs e) + public byte ParseWithRange(XElement element, byte minValue, byte maxValue) { - Logger?.LogWarning(e.Message); - base.OnParseError(e); + if (maxValue > 100) + { + OnParseError(new XmlParseErrorEventArgs( + element, XmlParseErrorKind.InvalidValue, + $"The provided maxValue '{maxValue}' is above 100.")); + } + + // TODO: Do we need to coerce maxValue??? + + var value = Parse(element); + + var clamped = PGMath.Clamp(value, minValue, maxValue); + if (value != clamped) + { + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected byte between {minValue} and {maxValue} but got value '{value}'.")); + } + return clamped; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlRgbaColorParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlRgbaColorParser.cs new file mode 100644 index 0000000..1ecf10a --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlRgbaColorParser.cs @@ -0,0 +1,34 @@ +using System; +using System.Xml.Linq; +using PG.Commons.Numerics; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public sealed class PetroglyphXmlRgbaColorParser : PetroglyphPrimitiveXmlParser +{ + public static readonly PetroglyphXmlRgbaColorParser Instance = new(); + + private protected override Vector4Int DefaultValue => default; + + private PetroglyphXmlRgbaColorParser() + { + } + + protected internal override Vector4Int ParseCore(string trimmedValue, XElement element) + { + var values = PetroglyphXmlLooseStringListParser.Instance.ParseCore(trimmedValue, element); + + if (values.Count == 0) + return DefaultValue; + + Span intValues = stackalloc int[4] { 0, 0, 0, 0 }; + for (var i = 0; i < intValues.Length; i++) + { + if (values.Count <= i) + break; + intValues[i] = PetroglyphXmlIntegerParser.Instance.ParseCore(values[i], element); + } + + return new Vector4Int(intValues); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs index bc8ca42..dcca294 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs @@ -1,17 +1,19 @@ -using System; -using System.Xml.Linq; -using PG.StarWarsGame.Files.XML.ErrorHandling; +using System.Xml.Linq; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlStringParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlStringParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlStringParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + public static readonly PetroglyphXmlStringParser Instance = new(); + + private PetroglyphXmlStringParser() { } - public override string Parse(XElement element) + private protected override string DefaultValue => string.Empty; + + protected internal override string ParseCore(string trimmedValue, XElement element) { - return element.Value.Trim(); + return trimmedValue; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlUnsignedIntegerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlUnsignedIntegerParser.cs index 2206f12..7061cca 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlUnsignedIntegerParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlUnsignedIntegerParser.cs @@ -1,35 +1,29 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Xml.Linq; +using System.Xml.Linq; using PG.StarWarsGame.Files.XML.ErrorHandling; -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; +namespace PG.StarWarsGame.Files.XML.Parsers; -public sealed class PetroglyphXmlUnsignedIntegerParser : PetroglyphXmlPrimitiveElementParser +public sealed class PetroglyphXmlUnsignedIntegerParser : PetroglyphPrimitiveXmlParser { - internal PetroglyphXmlUnsignedIntegerParser(IServiceProvider serviceProvider, IPrimitiveXmlParserErrorListener listener) : base(serviceProvider, listener) + public static readonly PetroglyphXmlUnsignedIntegerParser Instance = new(); + + private protected override uint DefaultValue => 0; + + private PetroglyphXmlUnsignedIntegerParser() { } - public override uint Parse(XElement element) + protected internal override uint ParseCore(string trimmedValue, XElement element) { - var intValue = PrimitiveParserProvider.IntParser.Parse(element); + var intValue = PetroglyphXmlIntegerParser.Instance.ParseCore(trimmedValue, element); var asUint = (uint)intValue; if (intValue != asUint) { - var location = XmlLocationInfo.FromElement(element); - - OnParseError(new XmlParseErrorEventArgs(location.XmlFile, element, XmlParseErrorKind.InvalidValue, - $"Expected unsigned integer but got '{intValue}' at {location}")); + OnParseError(new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, + $"Expected unsigned integer but got '{intValue}'.")); } return asUint; } - - protected override void OnParseError(XmlParseErrorEventArgs e) - { - Logger?.LogWarning(e.Message); - base.OnParseError(e); - } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlVector2FParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlVector2FParser.cs new file mode 100644 index 0000000..94900b7 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlVector2FParser.cs @@ -0,0 +1,38 @@ +using System.Numerics; +using System.Xml.Linq; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public sealed class PetroglyphXmlVector2FParser : PetroglyphPrimitiveXmlParser +{ + public static readonly PetroglyphXmlVector2FParser Instance = new(); + + private static readonly PetroglyphXmlFloatParser FloatParser = PetroglyphXmlFloatParser.Instance; + + private static readonly PetroglyphXmlLooseStringListParser LooseStringListParser = PetroglyphXmlLooseStringListParser.Instance; + + private PetroglyphXmlVector2FParser() + { + } + + private protected override Vector2 DefaultValue => default; + + protected internal override Vector2 ParseCore(string trimmedValue, XElement element) + { + var listOfValues = LooseStringListParser.Parse(element); + + if (listOfValues.Count == 0) + return default; + + if (listOfValues.Count == 1) + { + var value = FloatParser.ParseCore(listOfValues[0], element); + return new Vector2(value, 0.0f); + } + + var value1 = FloatParser.ParseCore(listOfValues[0], element); + var value2 = FloatParser.ParseCore(listOfValues[1], element); + + return new Vector2(value1, value2); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PrimitiveParserProvider.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PrimitiveParserProvider.cs deleted file mode 100644 index 747c83a..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PrimitiveParserProvider.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; - -internal class PrimitiveParserProvider(IServiceProvider serviceProvider) : IPrimitiveParserProvider -{ - private readonly IPrimitiveXmlParserErrorListener _primitiveParserErrorListener = serviceProvider.GetRequiredService(); - - private PetroglyphXmlStringParser _stringParser = null!; - private PetroglyphXmlUnsignedIntegerParser _uintParser = null!; - private PetroglyphXmlLooseStringListParser _looseStringListParser = null!; - private PetroglyphXmlIntegerParser _intParser = null!; - private PetroglyphXmlFloatParser _floatParser = null!; - private PetroglyphXmlByteParser _byteParser = null!; - private PetroglyphXmlMax100ByteParser _max100ByteParser = null!; - private PetroglyphXmlBooleanParser _booleanParser = null!; - private CommaSeparatedStringKeyValueListParser _commaSeparatedStringKeyValueListParser = null!; - - public PetroglyphXmlStringParser StringParser => - LazyInitializer.EnsureInitialized(ref _stringParser, () => new PetroglyphXmlStringParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlUnsignedIntegerParser UIntParser => - LazyInitializer.EnsureInitialized(ref _uintParser, () => new PetroglyphXmlUnsignedIntegerParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlLooseStringListParser LooseStringListParser => - LazyInitializer.EnsureInitialized(ref _looseStringListParser, () => new PetroglyphXmlLooseStringListParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlIntegerParser IntParser => - LazyInitializer.EnsureInitialized(ref _intParser, () => new PetroglyphXmlIntegerParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlFloatParser FloatParser => - LazyInitializer.EnsureInitialized(ref _floatParser, () => new PetroglyphXmlFloatParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlByteParser ByteParser => - LazyInitializer.EnsureInitialized(ref _byteParser, () => new PetroglyphXmlByteParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlMax100ByteParser Max100ByteParser => - LazyInitializer.EnsureInitialized(ref _max100ByteParser, () => new PetroglyphXmlMax100ByteParser(serviceProvider, _primitiveParserErrorListener)); - - public PetroglyphXmlBooleanParser BooleanParser => - LazyInitializer.EnsureInitialized(ref _booleanParser, () => new PetroglyphXmlBooleanParser(serviceProvider, _primitiveParserErrorListener)); - - public CommaSeparatedStringKeyValueListParser CommaSeparatedStringKeyValueListParser => - LazyInitializer.EnsureInitialized(ref _commaSeparatedStringKeyValueListParser, () => new CommaSeparatedStringKeyValueListParser(serviceProvider, _primitiveParserErrorListener)); -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs deleted file mode 100644 index 20aeace..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using PG.Commons.Hashing; -using PG.StarWarsGame.Files.XML.ErrorHandling; - -namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; - -public class XmlFileContainerParser(IServiceProvider serviceProvider, IXmlParserErrorListener? listener = null) : - PetroglyphXmlFileParser(serviceProvider, listener) -{ - protected override bool LoadLineInfo => false; - - protected override void Parse(XElement element, IValueListDictionary parsedElements) - { - throw new NotSupportedException(); - } - - public override XmlFileContainer Parse(XElement element) - { - var files = new List(); - foreach (var child in element.Elements()) - { - if (child.Name == "File") - { - var file = PrimitiveParserProvider.StringParser.Parse(child); - files.Add(file); - } - } - return new XmlFileContainer(files); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/XmlFileListParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/XmlFileListParser.cs new file mode 100644 index 0000000..380ea3f --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/XmlFileListParser.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using PG.StarWarsGame.Files.XML.Data; +using PG.StarWarsGame.Files.XML.ErrorHandling; + +namespace PG.StarWarsGame.Files.XML.Parsers; + +public sealed class XmlFileListParser(IServiceProvider serviceProvider, IXmlParserErrorReporter? errorReporter = null) : + PetroglyphXmlFileParser(serviceProvider, errorReporter) +{ + protected override bool LoadLineInfo => false; + + protected override XmlFileListContainer Parse(XElement element, string fileName) + { + var files = new List(); + foreach (var child in element.Elements()) + { + var tagName = GetTagName(child); + if (tagName == "File") + { + var file = PetroglyphXmlStringParser.Instance.Parse(child); + if (file.Length == 0) + { + ErrorReporter?.Report(this, + new XmlParseErrorEventArgs(element, XmlParseErrorKind.InvalidValue, "Empty value in tag.")); + } + files.Add(file); + } + else + { + ErrorReporter?.Report(this, new XmlParseErrorEventArgs(child, XmlParseErrorKind.UnknownNode, + $"Tag '<{tagName}>' is not supported. Only '' is supported.")); + } + } + return new XmlFileListContainer(files); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Utilities/PGMath.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Utilities/PGMath.cs new file mode 100644 index 0000000..bc40317 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Utilities/PGMath.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.CompilerServices; + +namespace PG.StarWarsGame.Files.XML.Utilities; + +internal static class PGMath +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Clamp(int value, int min, int max) + { + if (min > max) + throw new ArgumentException("min cannot be larger than max."); + if (value < min) + return min; + return value > max ? max : value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte Clamp(byte value, byte min, byte max) + { + if (min > max) + throw new ArgumentException("min cannot be larger than max."); + if (value < min) + return min; + return value > max ? max : value; + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs deleted file mode 100644 index 73f8aab..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using AnakinRaW.CommonUtilities.Collections; - -namespace PG.StarWarsGame.Files.XML; - - -public interface IReadOnlyValueListDictionary : IEnumerable> where TKey : notnull -{ - ICollection Values { get; } - - ICollection Keys { get; } - - int Count { get; } - - TKey this[int index] { get; } - - TValue GetValueAtIndex(int index); - - bool ContainsKey(TKey key); - - ReadOnlyFrugalList GetValues(TKey key); - - TValue GetLastValue(TKey key); - - TValue GetFirstValue(TKey key); - - bool TryGetFirstValue(TKey key, [NotNullWhen(true)] out TValue value); - - bool TryGetLastValue(TKey key, [NotNullWhen(true)] out TValue value); - - bool TryGetValues(TKey key, out ReadOnlyFrugalList values); -} - -public interface IValueListDictionary : IReadOnlyValueListDictionary where TKey : notnull -{ - bool Add(TKey key, TValue value); -} - -// NOT THREAD-SAFE! -public class ValueListDictionary : IValueListDictionary where TKey : notnull -{ - private readonly List _insertionTrackingList = new(); - private readonly Dictionary _singleValueDictionary = new (); - private readonly Dictionary> _multiValueDictionary = new(); - - private readonly EqualityComparer _equalityComparer = EqualityComparer.Default; - - public int Count => _insertionTrackingList.Count; - - public TKey this[int index] => _insertionTrackingList[index]; - - public ICollection Keys => _singleValueDictionary.Keys.Concat(_multiValueDictionary.Keys).ToList(); - - public ICollection Values => this.Select(x => x.Value).ToList(); - - public TValue GetValueAtIndex(int index) - { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index)); - - var key = this[index]; - if (_singleValueDictionary.TryGetValue(key, out var value)) - return value; - - if (index == 0) - return _multiValueDictionary[key].First(); - - if (index == Count - 1) - return _multiValueDictionary[key].Last(); - - var keyCount = 0; - foreach (var k in _insertionTrackingList.Take(index + 1)) - { - if (_equalityComparer.Equals(key, k)) - keyCount++; - } - - return _multiValueDictionary[key][keyCount - 1]; - } - - public bool ContainsKey(TKey key) - { - return _singleValueDictionary.ContainsKey(key) || _multiValueDictionary.ContainsKey(key); - } - - public bool Add(TKey key, TValue value) - { - if (key is null) - throw new ArgumentNullException(nameof(key)); - - _insertionTrackingList.Add(key); - - if (!_singleValueDictionary.ContainsKey(key)) - { - if (!_multiValueDictionary.TryGetValue(key, out var list)) - { - _singleValueDictionary.Add(key, value); - return false; - } - - list.Add(value); - return true; - } - - Debug.Assert(_multiValueDictionary.ContainsKey(key) == false); - - var firstValue = _singleValueDictionary[key]; - _singleValueDictionary.Remove(key); - - _multiValueDictionary.Add(key, [ - firstValue, - value - ]); - - return true; - } - - public TValue GetLastValue(TKey key) - { - if (_singleValueDictionary.TryGetValue(key, out var value)) - return value; - - if (_multiValueDictionary.TryGetValue(key, out var valueList)) - return valueList.Last(); - - throw new KeyNotFoundException($"The key '{key}' was not found."); - } - - public TValue GetFirstValue(TKey key) - { - if (_singleValueDictionary.TryGetValue(key, out var value)) - return value; - - if (_multiValueDictionary.TryGetValue(key, out var valueList)) - return valueList.First(); - - throw new KeyNotFoundException($"The key '{key}' was not found."); - } - - public ReadOnlyFrugalList GetValues(TKey key) - { - if (TryGetValues(key, out var values)) - return values; - - throw new KeyNotFoundException($"The key '{key}' was not found."); - - } - - public bool TryGetFirstValue(TKey key, [NotNullWhen(true)] out TValue value) - { - if (_singleValueDictionary.TryGetValue(key, out value!)) - return true; - - if (_multiValueDictionary.TryGetValue(key, out var valueList)) - { - value = valueList.First()!; - return true; - } - - return false; - } - - public bool TryGetLastValue(TKey key, [NotNullWhen(true)] out TValue value) - { - if (_singleValueDictionary.TryGetValue(key, out value!)) - return true; - - if (_multiValueDictionary.TryGetValue(key, out var valueList)) - { - value = valueList.Last()!; - return true; - } - - return false; - } - - public bool TryGetValues(TKey key, out ReadOnlyFrugalList values) - { - if (_singleValueDictionary.TryGetValue(key, out var value)) - { - values = new ReadOnlyFrugalList(value); - return true; - } - - if (_multiValueDictionary.TryGetValue(key, out var valueList)) - { - values = new ReadOnlyFrugalList(valueList); - return true; - } - - values = ReadOnlyFrugalList.Empty; - return false; - } - - public IEnumerator> GetEnumerator() - { - return new Enumerator(this); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public struct Enumerator : IEnumerator> - { - private Dictionary.Enumerator _singleEnumerator; - private Dictionary>.Enumerator _multiEnumerator; - private List.Enumerator _currentListEnumerator = default; - private bool _isMultiEnumeratorActive = false; - - internal Enumerator(ValueListDictionary valueListDictionary) - { - _singleEnumerator = valueListDictionary._singleValueDictionary.GetEnumerator(); - _multiEnumerator = valueListDictionary._multiValueDictionary.GetEnumerator(); - } - - public KeyValuePair Current => - _isMultiEnumeratorActive - ? new KeyValuePair(_multiEnumerator.Current.Key, _currentListEnumerator.Current) - : _singleEnumerator.Current; - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - if (_singleEnumerator.MoveNext()) - return true; - - if (_isMultiEnumeratorActive) - { - if (_currentListEnumerator.MoveNext()) - return true; - _isMultiEnumeratorActive = false; - } - - if (_multiEnumerator.MoveNext()) - { - _currentListEnumerator = _multiEnumerator.Current.Value.GetEnumerator(); - _isMultiEnumeratorActive = true; - return _currentListEnumerator.MoveNext(); - } - - return false; - } - - public void Reset() - { - throw new NotSupportedException(); - } - - public void Dispose() - { - _singleEnumerator.Dispose(); - _multiEnumerator.Dispose(); - } - } -} - -public static class ValueListDictionaryExtensions -{ - public static IEnumerable AggregateValues( - this IReadOnlyValueListDictionary valueListDictionary, - ISet keys, Predicate filter, - AggregateStrategy aggregateStrategy) - where TKey : notnull - where T : TValue - { - foreach (var key in keys) - { - if (!valueListDictionary.ContainsKey(key)) - continue; - if (aggregateStrategy == AggregateStrategy.MultipleValuesPerKey) - { - foreach (var value in valueListDictionary.GetValues(key)) - { - if (value is not null) - { - var typedValue = (T)value; - if (filter(typedValue)) - yield return typedValue; - } - - } - } - else - { - var value = aggregateStrategy == AggregateStrategy.FirstValuePerKey - ? valueListDictionary.GetFirstValue(key) - : valueListDictionary.GetLastValue(key); - if (value is not null) - { - var typedValue = (T)value; - if (filter(typedValue)) - yield return typedValue; - } - } - } - } - - public enum AggregateStrategy - { - FirstValuePerKey, - LastValuePerKey, - MultipleValuesPerKey, - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlFileContainer.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlFileContainer.cs deleted file mode 100644 index d42d8ad..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlFileContainer.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace PG.StarWarsGame.Files.XML; - -public class XmlFileContainer(IList files) -{ - public IList Files { get; } = files; -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlLocationInfo.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlLocationInfo.cs index e92cc63..6a2cb89 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlLocationInfo.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlLocationInfo.cs @@ -24,6 +24,6 @@ public override string ToString() { if (string.IsNullOrEmpty(XmlFile)) return "(n/a)"; - return Line is null ? XmlFile : $"{XmlFile} at line: {Line}"; + return Line is null ? $"'{XmlFile}'" : $"'{XmlFile}':#{Line}"; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlServiceContribution.cs index 6fb18d4..9e7c853 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlServiceContribution.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/XmlServiceContribution.cs @@ -2,17 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Files.XML.ErrorHandling; -using PG.StarWarsGame.Files.XML.Parsers.Primitives; namespace PG.StarWarsGame.Files.XML; public static class XmlServiceContribution { - public static void ContributeServices(IServiceCollection serviceCollection) + public static void SupportXML(this IServiceCollection serviceCollection) { - serviceCollection.AddSingleton(_ => new PrimitiveXmlParserErrorBroker()); - serviceCollection.AddSingleton(sp => sp.GetRequiredService()); - serviceCollection.AddSingleton(sp => new PrimitiveParserProvider(sp)); } } \ No newline at end of file