diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 7d440a366..f771dfc6e 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - chunk: [pages, api] + chunk: [pages, api, media] steps: - name: Checkout diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index e5c51bf73..ffad4228b 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -22,7 +22,7 @@ public partial class GameInterface public HashSet files; public string[] tanksDirectoryNations; - public GameInterface(string directory, string map) + public GameInterface(string directory, string map, string temp) { provider = new(directory, map); @@ -39,8 +39,8 @@ public partial class GameInterface .Distinct(), ]; - string oodlePath = "temp/oodle.so"; - OodleHelper.DownloadOodleDll(ref oodlePath); + string oodlePath = $"{temp}/oodle.so"; + OodleHelper.DownloadOodleDll(ref oodlePath!); OodleHelper.Initialize(oodlePath); } @@ -157,27 +157,9 @@ public partial class GameInterface return []; } - var path = $"{candidate}.{Path.GetFileNameWithoutExtension(candidate)}"; + var pda = provider.PDA(candidate); + var icon = pda.Get("Icon"); - if (provider.TryLoadPackageObject(path, out var pda)) - { - var icon = pda.Get("Icon"); - var uTexture = icon.Load(); - var cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile); - - if (cTexture is null) - { - Console.WriteLine($"Could not extract icon for {path}"); - return []; - } - - var bitmap = cTexture.ToSkBitmap(); - var data = bitmap.Encode(SKEncodedImageFormat.Webp, 80); - - return data.ToArray(); - } - - Console.WriteLine($"Could not load {path} as pda"); - return []; + return provider.Image(icon); } } diff --git a/packages/game/Models/BlitzFileProvider.cs b/packages/game/Models/BlitzFileProvider.cs index 4463be523..bb91e988f 100644 --- a/packages/game/Models/BlitzFileProvider.cs +++ b/packages/game/Models/BlitzFileProvider.cs @@ -1,10 +1,15 @@ using System.Runtime.InteropServices; +using CUE4Parse_Conversion.Textures; using CUE4Parse.Compression; using CUE4Parse.FileProvider; using CUE4Parse.MappingsProvider; +using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Exceptions; +using CUE4Parse.UE4.Objects.Engine; +using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Versions; using CUE4Parse.Utils; +using SkiaSharp; namespace BlitzKit.Game.Models; @@ -23,4 +28,32 @@ public class BlitzFileProvider : DefaultFileProvider Initialize(); Mount(); } + + public UPrimaryDataAsset PDA(string path) + { + var name = Path.GetFileNameWithoutExtension(path); + var combined = $"{path}.{name}"; + return LoadPackageObject(combined); + } + + public byte[] Image(FSoftObjectPath path) + { + var uTexture = path.Load(); + return Image(uTexture); + } + + private readonly SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Webp; + private readonly int imageQuality = 80; + + public byte[] Image(UTexture2D uTexture) + { + var cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile); + + ArgumentNullException.ThrowIfNull(cTexture); + + var bitmap = cTexture.ToSkBitmap(); + var data = bitmap.Encode(imageFormat, imageQuality); + + return data.ToArray(); + } } diff --git a/packages/website-ue/src/components/TankopediaLoadout/index.tsx b/packages/website-ue/src/components/TankopediaLoadout/index.tsx index 019a275d3..eda8c98c0 100644 --- a/packages/website-ue/src/components/TankopediaLoadout/index.tsx +++ b/packages/website-ue/src/components/TankopediaLoadout/index.tsx @@ -12,7 +12,6 @@ import { import { chunk } from "lodash-es"; import { useMemo } from "react"; import { equipmentColumns } from "../../config/equipment"; -import { catImages } from "../../config/images"; import { alternativeLines, isAlternativeLine, @@ -148,6 +147,10 @@ function EquipmentOption({ columnIndex, }: EquipmentOptionProps) { const equipmentIndex = rowIndex * equipmentColumns + columnIndex; + const tank = useProtagonist(); + const equipment = useEquipment(tank.tank!); + const slot = equipment.preset.slots[equipmentIndex]; + const id = slot.options_catalog_i_ds[optionIndex]; const isSelected = Tankopedia.use( (state) => @@ -172,7 +175,7 @@ function EquipmentOption({ }); }} > - + ); } diff --git a/packages/website-ue/src/game/game.ts b/packages/website-ue/src/game/game.ts index 23206d27a..440e9435a 100644 --- a/packages/website-ue/src/game/game.ts +++ b/packages/website-ue/src/game/game.ts @@ -6,7 +6,11 @@ let _game: GameInterface | null = null; if (!clientUnmounted) { const { GameInterface } = await import("@blitzkit/game"); - _game = new GameInterface(assertSecret(import.meta.env.PUBLIC_WOTB_CLIENT)); + _game = new GameInterface( + assertSecret(import.meta.env.PUBLIC_WOTB_CLIENT), + assertSecret(import.meta.env.WOTB_USMAP), + "../../temp", + ); } export const game = _game; diff --git a/packages/website-ue/src/pages/[media]/_index.ts b/packages/website-ue/src/pages/[media]/_index.ts new file mode 100644 index 000000000..8877263c1 --- /dev/null +++ b/packages/website-ue/src/pages/[media]/_index.ts @@ -0,0 +1,7 @@ +import type { GetStaticPaths } from "astro"; +import { isChunkEnabled } from "../../astro/isChunkEnabled"; + +export const getStaticPaths = (() => { + if (!isChunkEnabled("media")) return []; + return [{ params: { media: "media" } }]; +}) satisfies GetStaticPaths; diff --git a/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts b/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts new file mode 100644 index 000000000..258b2e7f8 --- /dev/null +++ b/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts @@ -0,0 +1,40 @@ +import type { APIContext } from "astro"; +import { api } from "../../../api/dynamic"; +import { notFoundResponse } from "../../../api/responses"; +import { mixStaticPaths } from "../../../astro/mixStaticPaths"; +import { game } from "../../../game/game"; +import { getStaticPaths as _getStaticPaths } from "../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { + return api.metadata + .group("EquipmentEntity") + .map(({ id }) => ({ params: { id } })); +}); + +const HACK_nameMap: Record = { + SystemsFineTuning: "SystemFineTuning", +}; + +export function GET({ params }: APIContext) { + const item = api.metadata.item(params.id); + const assetDiscovery = item.BlitzStaticAssetsDiscovery(); + const names = assetDiscovery.required_tags!.tags.map( + (tag) => tag.value.split(".").at(-1)!, + ); + + for (const name of names) { + if (name in HACK_nameMap) { + names.push(HACK_nameMap[name]); + } + } + + const icon = game!.equipmentIcon(names); + + if (icon.length === 0) { + return notFoundResponse; + } + + const array = new Uint8Array(icon); + + return new Response(array); +}