Compare commits

...
5 Commits
Author SHA1 Message Date
tresabhi 402d054b30 stuff icon 2026-05-18 17:08:12 -05:00
tresabhi 61d6d42742 hide modules for no-up 2026-05-18 16:57:21 -05:00
tresabhi 03e06b050e separate game interface import better 2026-05-18 16:45:36 -05:00
tresabhi cca46957bc apply correct media prefixes 2026-05-18 16:32:46 -05:00
tresabhi 3c92ab3a19 force latest node 2026-05-18 16:25:10 -05:00
12 changed files with 98 additions and 29 deletions

No files matched your search

+5
View File
@@ -43,6 +43,11 @@ jobs:
- name: Set up Bun
uses: oven-sh/setup-bun@v2
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: latest
- name: Set up Dotnet
uses: actions/setup-dotnet@v5
with:
+1 -9
View File
@@ -5,15 +5,7 @@
"private": true,
"scripts": {
"setup": "chmod a+x scripts/*.sh && ./scripts/setup.sh",
"dev": "bun run dev:website",
"dev:website": "cd packages/website-ue && bun run dev",
"build:game": "cd packages/game && bun run build",
"build:website": "cd packages/website-ue && bun run build",
"build:proxyclient": "cd packages/scripts && bun run build:proxyclient",
"build:catalogitemaccessor": "cd packages/scripts && bun run build:catalogitemaccessor",
"build:protos": "cd packages/scripts && bun run build:protos",
"lint": "tsc",
"lint:website": "cd packages/website-ue && bun run lint"
"dev": "./scripts/dev.sh"
},
"type": "module",
"workspaces": [
+3
View File
@@ -17,6 +17,8 @@ import type { Tiers } from "@protos/tiers";
export abstract class AbstractAPI {
abstract metadata: MetadataAccessor;
abstract mediaPrefix(path: string): string;
abstract tankList(): Promise<TankList>;
abstract tank(id: string): Promise<Tank>;
abstract tanks(): Promise<Tanks>;
@@ -44,6 +46,7 @@ export abstract class AbstractAPI {
abstract backgrounds(): Promise<Backgrounds>;
abstract currencyIcon(id: string): Promise<string>;
abstract stuffIcon(id: string): Promise<string>;
private assertLocale(locale: string) {
const supported = locales.supported.find(
+10
View File
@@ -18,6 +18,11 @@ export class ClientAPI extends AbstractAPI {
throw new Error("MetadataAccessor is inaccessible on the client");
}
@UnimplementedOnClient
mediaPrefix(): never {
throw rejected();
}
@Cache()
tankList() {
return fetchPB("/api/tanks/list.pb", TankList);
@@ -111,6 +116,11 @@ export class ClientAPI extends AbstractAPI {
currencyIcon() {
return rejected();
}
@UnimplementedOnClient
stuffIcon() {
return rejected();
}
}
function UnimplementedOnClient(
+19 -7
View File
@@ -36,6 +36,16 @@ export class ServerAPI extends AbstractAPI {
super();
}
mediaPrefix(path: string) {
const config = this.resolveClientConfig();
const metroResources = config.MetroResources();
const remoteStorage = this.resolveRemoteUrl(
metroResources.remote_storage!.remote_urls,
);
return `${remoteStorage.url}/${remoteStorage.relative_path}${path}`;
}
resolveRemoteUrl(remoteUrls: string[]) {
let remoteStorage: RemoteStorageComponent | undefined = undefined;
@@ -378,14 +388,16 @@ export class ServerAPI extends AbstractAPI {
@Cache()
async currencyIcon(id: string) {
const item = this.metadata.item(`CurrencyEntity.${id}`);
const item = this.metadata.item(id);
const stuffUI = item.StuffUI("UIComponent");
const config = this.resolveClientConfig();
const metroResources = config.MetroResources();
const remoteStorage = this.resolveRemoteUrl(
metroResources.remote_storage!.remote_urls,
);
return this.mediaPrefix(stuffUI.icon!.value);
}
return `${remoteStorage.url}/${remoteStorage.relative_path}${stuffUI.icon!.value}`;
@Cache()
async stuffIcon(id: string) {
const item = this.metadata.item(id);
console.log(id);
const stuffUI = item.StuffUI("UIComponent");
return this.mediaPrefix(stuffUI.icon!.value);
}
}
@@ -26,23 +26,44 @@ interface PriceEntryProps {
}
function PriceEntry({ entry }: PriceEntryProps) {
const locale = useLocale();
if (entry.premium_time_price !== undefined) {
console.log(entry);
if (
entry.premium_time_price !== undefined ||
entry.stuff_price !== undefined ||
entry.currency_price === undefined
) {
throw new Error("Unsupported price type");
}
const [, iconName] = entry.currency_price.currency_catalog_id.split(".");
return (
<>
{entry.currency_price && (
<PriceInternal
icon={`/api/currencies/${entry.currency_price.currency_catalog_id}.webp`}
value={entry.currency_price.amount}
/>
)}
{entry.stuff_price && (
<PriceInternal
icon={`/api/stuff/${entry.stuff_price.stuff_catalog_id}.webp`}
value={entry.stuff_price.amount}
/>
)}
</>
);
}
interface PriceInternalProps {
icon: string;
value: number;
}
function PriceInternal({ icon, value }: PriceInternalProps) {
const locale = useLocale();
return (
<Text weight="light" size="minor" lowContrast className={styles.entry}>
<div className={styles.wrapper}>
<img className={styles.icon} src={`/api/currencies/${iconName}.webp`} />
{formatCompact(locale, entry.currency_price.amount)}
<img className={styles.icon} src={icon} />
{formatCompact(locale, value)}
</div>
</Text>
);
@@ -28,9 +28,15 @@ import { Text } from "../Text";
import styles from "./index.module.css";
export function TankopediaLoadout() {
const tank = useProtagonist();
const showModules = tank.tank!.upgrade_lines.some(
(line) => line.stages.length > 1 || line.name in alternativeLines,
);
return (
<Section>
<Modules />
{showModules && <Modules />}
<Equipment />
</Section>
);
@@ -199,6 +205,7 @@ function Modules() {
</div>
<div className={styles.lines}>
{/* TODO: hide lines with 1 module except for alts */}
{tank.tank!.upgrade_lines.map((line) => (
<Line
key={line.name}
+2 -1
View File
@@ -1,10 +1,11 @@
import { assertSecret } from "@blitzkit/core";
import { GameInterface } from "@blitzkit/game";
import type { GameInterface } from "@blitzkit/game";
import { clientUnmounted } from "./unmounted";
let _game: GameInterface | null = null;
if (!clientUnmounted) {
const { GameInterface } = await import("@blitzkit/game");
_game = new GameInterface(assertSecret(import.meta.env.PUBLIC_WOTB_CLIENT));
}
@@ -12,5 +12,7 @@ export { getStaticPaths } from "./[id].json";
*/
export async function GET({ params }: APIContext<never, { id: string }>) {
const avatar = await api.avatar(params.id);
return await imageProxy(avatar.profile_avatar!.avatar);
return await imageProxy(
api.mediaPrefix(avatar.profile_avatar!.avatar!.value),
);
}
@@ -5,7 +5,7 @@ import { getStaticPaths as _getStaticPaths } from "../_index";
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
const group = api.metadata.group("CurrencyEntity");
return group.map(({ name }) => ({ params: { id: name } }));
return group.map(({ id }) => ({ params: { id } }));
});
export async function GET({ params }: { params: { id: string } }) {
@@ -0,0 +1,14 @@
import { api } from "../../../api/dynamic";
import { imageProxy } from "../../../api/imageProxy";
import { mixStaticPaths } from "../../../astro/mixStaticPaths";
import { getStaticPaths as _getStaticPaths } from "../_index";
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
const group = api.metadata.group("ProxyEntity");
return group.map(({ id }) => ({ params: { id } }));
});
export async function GET({ params }: { params: { id: string } }) {
const icon = await api.stuffIcon(params.id);
return await imageProxy(icon);
}
Executable
+2
View File
@@ -0,0 +1,2 @@
cd packages/website-ue
bun run dev