diff --git a/cspell.json b/cspell.json index a71222e13..16b4ea7f5 100644 --- a/cspell.json +++ b/cspell.json @@ -2,6 +2,8 @@ "words": [ "gltf", "amogos", - "Tankopedia" + "Tankopedia", + "uasset", + "oodle" ] } \ No newline at end of file diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index ffad4228b..1eeb378ac 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -11,6 +11,10 @@ using CUE4Parse.UE4.Objects.UObject; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.JavaScript.NodeApi; using SkiaSharp; +using CUE4Parse.UE4.AssetRegistry.Objects; +using CUE4Parse.UE4.Readers; +using CUE4Parse.UE4.AssetRegistry.Readers; +using CUE4Parse.UE4.AssetRegistry; namespace BlitzKit.Game.JSExport; @@ -24,7 +28,7 @@ public partial class GameInterface public GameInterface(string directory, string map, string temp) { - provider = new(directory, map); + provider = new(directory, map, temp); files = [.. provider.Files.Keys]; @@ -38,10 +42,6 @@ public partial class GameInterface .Where(nation => !nation.EndsWith(".uasset") && nation != "TankStub") .Distinct(), ]; - - string oodlePath = $"{temp}/oodle.so"; - OodleHelper.DownloadOodleDll(ref oodlePath!); - OodleHelper.Initialize(oodlePath); } public HashSet Files => files; @@ -123,6 +123,13 @@ public partial class GameInterface return []; } + public byte[] ConsumableIcon(string tag) + { + var pda = provider.Discovered(tag); + var icon = pda.Get("Icon"); + return provider.Image(icon); + } + private static readonly string equipmentPDAPrefix = "Blitz/Content/TanksStuff/Equipment/PDA_Equipment"; private static readonly string equipmentPDASuffix = ".uasset"; diff --git a/packages/game/Models/BlitzFileProvider.cs b/packages/game/Models/BlitzFileProvider.cs index bb91e988f..89f4bb777 100644 --- a/packages/game/Models/BlitzFileProvider.cs +++ b/packages/game/Models/BlitzFileProvider.cs @@ -10,12 +10,21 @@ using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Versions; using CUE4Parse.Utils; using SkiaSharp; +using CUE4Parse.UE4.Readers; +using CUE4Parse.UE4.AssetRegistry; +using System.Text.RegularExpressions; +using CUE4Parse.UE4.Assets.Exports; namespace BlitzKit.Game.Models; -public class BlitzFileProvider : DefaultFileProvider +public partial class BlitzFileProvider : DefaultFileProvider { - public BlitzFileProvider(string directory, string map) + [GeneratedRegex(@"\(GameplayTags=\(\(TagName=""(.+)""\)\)\)")] + private static partial Regex GameplayTagsRegex(); + + private readonly Dictionary discovered = []; + + public BlitzFileProvider(string directory, string map, string temp) : base( directory: directory, searchOption: SearchOption.AllDirectories, @@ -27,6 +36,38 @@ public class BlitzFileProvider : DefaultFileProvider Initialize(); Mount(); + + string oodlePath = $"{temp}/oodle.so"; + OodleHelper.DownloadOodleDll(ref oodlePath!); + OodleHelper.Initialize(oodlePath); + + if (TryGetGameFile("Blitz/AssetRegistry.bin", out var file)) + { + var bytes = file.Read(); + var archive = new FByteArchive(file.Path, bytes, Versions); + var state = new FAssetRegistryState(archive); + + foreach (var asset in state.PreallocatedAssetDataBuffers) + { + if (asset.TagsAndValues.TryGetValue("DiscoveryTags", out var tags)) + { + var match = GameplayTagsRegex().Match(tags); + + if (!match.Success) + { + continue; + } + + var tag = match.Groups[1].Value; + discovered[tag] = asset.ObjectPath; + } + } + } + } + + public T Discovered(string path) where T : UObject + { + return LoadPackageObject(discovered[path]); } public UPrimaryDataAsset PDA(string path) diff --git a/packages/website-ue/src/game/assertDiscoveryTag.ts b/packages/website-ue/src/game/assertDiscoveryTag.ts new file mode 100644 index 000000000..a6012adaf --- /dev/null +++ b/packages/website-ue/src/game/assertDiscoveryTag.ts @@ -0,0 +1,13 @@ +import type { BlitzStaticAssetsDiscoveryComponent } from "@protos/blitz_static_assets_discovery_component"; + +export function assertDiscoveryTag(discovery: BlitzStaticAssetsDiscoveryComponent) { + if ( + discovery.blocked_tags !== undefined || + discovery.required_tags === undefined || + discovery.required_tags.tags.length !== 1 + ) { + throw new TypeError("Unexpected discovery tag format"); + } + + return discovery.required_tags.tags[0].value; +} diff --git a/packages/website-ue/src/pages/[media]/consumables/[id].webp.ts b/packages/website-ue/src/pages/[media]/consumables/[id].webp.ts new file mode 100644 index 000000000..5e9961f77 --- /dev/null +++ b/packages/website-ue/src/pages/[media]/consumables/[id].webp.ts @@ -0,0 +1,23 @@ +import type { APIContext } from "astro"; +import { api } from "../../../api/dynamic"; +import { mixStaticPaths } from "../../../astro/mixStaticPaths"; +import { game } from "../../../game/game"; +import { getStaticPaths as _getStaticPaths } from "../_index"; +import { assertDiscoveryTag } from "../../../game/assertDiscoveryTag"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { + return api.metadata + .group("ConsumableEntity") + .map(({ id }) => ({ params: { id } })); +}); + + +export function GET({ params }: APIContext) { + const item = api.metadata.item(params.id); + const assetsDiscovery = item.BlitzStaticAssetsDiscovery(); + const tag = assertDiscoveryTag(assetsDiscovery); + const icon = game!.consumableIcon(tag); + const array = new Uint8Array(icon); + + return new Response(array); +} diff --git a/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts b/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts index 258b2e7f8..740d3c9df 100644 --- a/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts +++ b/packages/website-ue/src/pages/[media]/equipment/[id].webp.ts @@ -4,6 +4,7 @@ import { notFoundResponse } from "../../../api/responses"; import { mixStaticPaths } from "../../../astro/mixStaticPaths"; import { game } from "../../../game/game"; import { getStaticPaths as _getStaticPaths } from "../_index"; +import { assetsDiscovery } from "../../../game/assetsDiscovery"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { return api.metadata @@ -17,17 +18,7 @@ const HACK_nameMap: Record = { 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 names = assetsDiscovery(item, HACK_nameMap); const icon = game!.equipmentIcon(names); if (icon.length === 0) {