mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
Compare commits
7
Commits
0e637d772e
...
4e9c3df5eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e9c3df5eb | ||
|
|
c100a1e655 | ||
|
|
26cc0cc723 | ||
|
|
f697f94bd5 | ||
|
|
30fa18f0fd | ||
|
|
c45af8e156 | ||
|
|
f95a6e3495 |
No files matched your search
+2
-2
@@ -4,13 +4,13 @@
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"setup": "bun install && bun run build:protos && bun run build:proxyclient && bun run build:catalogitemaccesssor",
|
||||
"setup": "bun install && bun run build:protos && bun run build:proxyclient && bun run build:catalogitemaccessor",
|
||||
"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:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor",
|
||||
"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"
|
||||
|
||||
+1
-1
Submodule packages/closed updated: ea13d6f193...c2175a1520.
@@ -45,7 +45,7 @@
|
||||
"build:bot": "bun src/buildBot.ts",
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
|
||||
"build:proxyclient": "bun src/buildProxyClient.ts",
|
||||
"build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts",
|
||||
"build:catalogitemaccessor": "bun src/buildCatalogItemAccessor.ts",
|
||||
"build:protos": "bun src/protos.ts",
|
||||
"patchnotes": "bun src/patchNotes.ts"
|
||||
},
|
||||
|
||||
@@ -1,62 +1,50 @@
|
||||
import { readdir, readFile, writeFile } from "fs/promises";
|
||||
import { lowerFirst } from "lodash-es";
|
||||
|
||||
const SOURCES = "../../packages/closed/protos/blitz";
|
||||
const SOURCES = "../../packages/closed/protos";
|
||||
const TARGET = "../../packages/closed/src/unreal/catalogItemAccessor.ts";
|
||||
|
||||
const componentPattern = /^message (\w+)Component {/gm;
|
||||
const componentPattern = /^export interface (\w+Component) {/gm;
|
||||
const componentSuffixPattern = /Component$/;
|
||||
const tsSuffixPattern = /\.ts$/;
|
||||
const blitzStaticPrefixPattern = /^BlitzStatic/;
|
||||
|
||||
const files = await readdir(SOURCES).then((files) =>
|
||||
files.filter(
|
||||
(file) =>
|
||||
file.startsWith("blitz_static_") && file.endsWith("_component.proto"),
|
||||
),
|
||||
);
|
||||
let imports = "";
|
||||
let methods = "";
|
||||
|
||||
const imports: { name: string; file: string }[] = [];
|
||||
const files = await readdir(SOURCES);
|
||||
|
||||
for (const file of files) {
|
||||
const content = await readFile(`${SOURCES}/${file}`).then((buffer) =>
|
||||
buffer.toString(),
|
||||
);
|
||||
const components = content.matchAll(componentPattern);
|
||||
if (!file.endsWith("_component.ts")) continue;
|
||||
|
||||
for (const match of components) {
|
||||
const name = match[1];
|
||||
const duplicate = imports.find((_import) => _import.name === name);
|
||||
const buffer = await readFile(`${SOURCES}/${file}`);
|
||||
const content = buffer.toString();
|
||||
const matches = content.matchAll(componentPattern);
|
||||
|
||||
if (duplicate) {
|
||||
console.warn(
|
||||
`Duplicate component ${name} across ${duplicate.file}.proto and ${file}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for (const match of matches) {
|
||||
const interfaceName = match[1];
|
||||
const componentName = interfaceName.split("_").at(-1)!;
|
||||
const fieldName = lowerFirst(
|
||||
componentName.replace(blitzStaticPrefixPattern, ""),
|
||||
);
|
||||
const methodName = componentName.replace(componentSuffixPattern, "");
|
||||
const importName = file.replace(tsSuffixPattern, "");
|
||||
|
||||
const importFile = file.replace(".proto", "");
|
||||
imports += `import { ${interfaceName} } from "@protos/${importName}";\n`;
|
||||
|
||||
imports.push({ name, file: importFile });
|
||||
methods += ` ${methodName}(name = "${fieldName}") {\n`;
|
||||
methods += ` return this.component(name, ${interfaceName});`;
|
||||
methods += "\n }\n";
|
||||
}
|
||||
}
|
||||
|
||||
let content = "";
|
||||
let body = "";
|
||||
|
||||
for (const _import of imports) {
|
||||
content += `import { ${_import.name}Component } from "@protos/${_import.file}";\n`;
|
||||
}
|
||||
|
||||
content +=
|
||||
body += imports;
|
||||
body +=
|
||||
'import { BaseCatalogItemAccessor } from "./baseCatalogItemAccessor";\n\n';
|
||||
content +=
|
||||
"export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n";
|
||||
body += `export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n`;
|
||||
body += methods;
|
||||
body += `}\n`;
|
||||
|
||||
for (const _import of imports) {
|
||||
const strippedName = _import.name.replace("BlitzStatic", "");
|
||||
const defaultName = strippedName[0].toLowerCase() + strippedName.slice(1);
|
||||
|
||||
content += ` ${strippedName}(name = "${defaultName}Component") {\n`;
|
||||
content += ` return this.component(name, ${_import.name}Component);\n`;
|
||||
content += " }\n";
|
||||
}
|
||||
|
||||
content += "}\n";
|
||||
|
||||
await writeFile(TARGET, content);
|
||||
await writeFile(TARGET, body);
|
||||
@@ -7,6 +7,7 @@ import type { Avatars } from "@protos/avatars";
|
||||
import type { Background } from "@protos/background";
|
||||
import type { Backgrounds } from "@protos/backgrounds";
|
||||
import type { TankUpgradePricePresetComponent } from "@protos/blitz_static_tank_upgrade_price_preset_component";
|
||||
import type { Equipment } from "@protos/equipment";
|
||||
import type { PopularTanks } from "@protos/popular_tanks";
|
||||
import type { Tank } from "@protos/tank";
|
||||
import type { TankList } from "@protos/tank_list";
|
||||
@@ -25,6 +26,7 @@ export abstract class AbstractAPI {
|
||||
): Promise<TankUpgradePricePresetComponent>;
|
||||
|
||||
abstract tiers(): Promise<Tiers>;
|
||||
abstract equipment(): Promise<Equipment>;
|
||||
|
||||
abstract strings(locale: string): Promise<Strings>;
|
||||
abstract gameStrings(locale: string): Promise<Record<string, string>>;
|
||||
|
||||
@@ -2,13 +2,16 @@ import { fetchPB } from "@blitzkit/core";
|
||||
import type { Strings } from "@blitzkit/i18n";
|
||||
import { Avatars } from "@protos/avatars";
|
||||
import { TankUpgradePricePresetComponent } from "@protos/blitz_static_tank_upgrade_price_preset_component";
|
||||
import { Equipment } from "@protos/equipment";
|
||||
import { PopularTanks } from "@protos/popular_tanks";
|
||||
import { TankList } from "@protos/tank_list";
|
||||
import { Tanks } from "@protos/tanks";
|
||||
import { Tiers } from "@protos/tiers";
|
||||
import { AbstractAPI, Cache } from "./abstract";
|
||||
|
||||
const rejected = Promise.reject("Not implemented on client");
|
||||
function rejected() {
|
||||
return Promise.reject("Not implemented on client");
|
||||
}
|
||||
|
||||
export class ClientAPI extends AbstractAPI {
|
||||
get metadata(): never {
|
||||
@@ -49,6 +52,11 @@ export class ClientAPI extends AbstractAPI {
|
||||
return fetchPB("/api/tiers.pb", Tiers);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
equipment() {
|
||||
return fetchPB("/api/equipment.pb", Equipment);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async strings(locale: string) {
|
||||
const response = await fetch(`/api/strings/${locale}.json`);
|
||||
@@ -59,7 +67,7 @@ export class ClientAPI extends AbstractAPI {
|
||||
|
||||
@UnimplementedOnClient
|
||||
gameStrings() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@Cache()
|
||||
@@ -72,12 +80,12 @@ export class ClientAPI extends AbstractAPI {
|
||||
|
||||
@UnimplementedOnClient
|
||||
gameStringGroups() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
avatar() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
avatars() {
|
||||
@@ -86,17 +94,22 @@ export class ClientAPI extends AbstractAPI {
|
||||
|
||||
@UnimplementedOnClient
|
||||
avatarList() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
background() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
backgrounds() {
|
||||
return rejected;
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
currencyIcon() {
|
||||
return rejected();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import locales from "@blitzkit/i18n/locales.json";
|
||||
import type { Avatar, DeepPartial } from "@protos/avatar";
|
||||
import type { Background } from "@protos/background";
|
||||
import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component";
|
||||
import { Equipment } from "@protos/equipment";
|
||||
import type { PopularTanks } from "@protos/popular_tanks";
|
||||
import type { Tank } from "@protos/tank";
|
||||
import type { TankListEntry } from "@protos/tank_list";
|
||||
@@ -226,6 +227,25 @@ export class ServerAPI extends AbstractAPI {
|
||||
return tiers;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async equipment() {
|
||||
const equipment = Equipment.create();
|
||||
|
||||
for (const item of this.metadata.group("EquipmentPresetEntity")) {
|
||||
equipment.presets[item.id] = item.EquipmentPreset();
|
||||
}
|
||||
|
||||
for (const item of this.metadata.group("EquipmentPricePresetEntity")) {
|
||||
equipment.prices[item.id] = item.EquipmentPricePreset();
|
||||
}
|
||||
|
||||
for (const item of this.metadata.group("EquipmentEntity")) {
|
||||
equipment.equipments[item.id] = item.BlitzStaticEquipment();
|
||||
}
|
||||
|
||||
return equipment;
|
||||
}
|
||||
|
||||
@Cache(true)
|
||||
async strings(locale: string) {
|
||||
const localized = (await globbedStrings[
|
||||
@@ -366,6 +386,6 @@ export class ServerAPI extends AbstractAPI {
|
||||
metroResources.remote_storage!.remote_urls,
|
||||
);
|
||||
|
||||
return `${remoteStorage.url}${remoteStorage.relative_path}${stuffUI.icon!.value}`;
|
||||
return `${remoteStorage.url}/${remoteStorage.relative_path}${stuffUI.icon!.value}`;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
svg,
|
||||
img {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
|
||||
aspect-ratio: 1 / 1;
|
||||
|
||||
font-size: var(--font-size-regular);
|
||||
font-size: var(--font-size-major);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: var(--space-3);
|
||||
|
||||
.entry {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
|
||||
@@ -9,6 +9,49 @@
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.equipment {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
|
||||
.slots {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
|
||||
.slot {
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: var(--space-3);
|
||||
|
||||
font-size: var(--space-5);
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lines {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -17,37 +60,41 @@
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
gap: var(--space-3);
|
||||
|
||||
.stage {
|
||||
position: relative;
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
|
||||
padding: var(--space-3);
|
||||
.stage {
|
||||
position: relative;
|
||||
|
||||
font-size: 2em;
|
||||
padding: var(--space-3);
|
||||
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-size: 2em;
|
||||
|
||||
padding: var(--space-3) 0;
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
transform: translate(var(--space-1), calc(-1 * var(--space-1)));
|
||||
}
|
||||
transform: translate(var(--space-1), var(--space-1));
|
||||
}
|
||||
|
||||
.tier {
|
||||
position: absolute;
|
||||
left: var(--space-2);
|
||||
top: var(--space-2);
|
||||
}
|
||||
.tier {
|
||||
position: absolute;
|
||||
left: var(--space-2);
|
||||
top: var(--space-2);
|
||||
}
|
||||
|
||||
.price {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: var(--space-2);
|
||||
transform: translateX(-50%);
|
||||
.price {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: var(--space-2);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { romanize } from "@blitzkit/core";
|
||||
import type { BlitzStaticEquipmentPresetComponent_EquipmentSlot } from "@protos/blitz_static_equipment_preset_component";
|
||||
import { Grade } from "@protos/blitz_static_standard_grades_enum";
|
||||
import type { StandardPrice } from "@protos/blitz_static_standard_price";
|
||||
import type { UpgradeLine } from "@protos/blitz_static_tank_upgrade_line";
|
||||
import type { StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons";
|
||||
import { chunk } from "lodash-es";
|
||||
import { useMemo } from "react";
|
||||
import { equipmentColumns } from "../../config/equipment";
|
||||
import { catImages } from "../../config/images";
|
||||
import {
|
||||
alternativeLines,
|
||||
isAlternativeLine,
|
||||
originalLineName,
|
||||
} from "../../config/alternativeLines";
|
||||
} from "../../config/modules";
|
||||
import { useEquipment } from "../../hooks/useEquipment";
|
||||
import { useProtagonist } from "../../hooks/useProtagonist";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { useUpgradePreset } from "../../hooks/useUpgradePreset";
|
||||
@@ -34,23 +39,121 @@ export function TankopediaLoadout() {
|
||||
function Equipment() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
// const equipment = useEquipment(
|
||||
// tank.tank!.equipment_preset_catalog_id,
|
||||
// tank.tank!.equipment_price_preset_catalog_id,
|
||||
// );
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
|
||||
console.log(equipment);
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<div className={styles.header}>
|
||||
<Heading size="4">{strings.tanks.loadout.equipment}</Heading>
|
||||
</div>
|
||||
preset: {tank.tank!.equipment_preset_catalog_id}
|
||||
<br />
|
||||
price: {tank.tank!.equipment_price_preset_catalog_id}
|
||||
|
||||
<div className={styles.equipment}>
|
||||
{chunk(equipment.preset.slots, equipmentColumns).map((chunk, index) => (
|
||||
<EquipmentRow key={index} chunk={chunk} rowIndex={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EquipmentRowProps {
|
||||
chunk: BlitzStaticEquipmentPresetComponent_EquipmentSlot[];
|
||||
rowIndex: number;
|
||||
}
|
||||
|
||||
function EquipmentRow({ chunk, rowIndex }: EquipmentRowProps) {
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
{chunk.map((slot, index) => (
|
||||
<EquipmentSlot
|
||||
key={index}
|
||||
slot={slot}
|
||||
rowIndex={rowIndex}
|
||||
columnIndex={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EquipmentSlotProps {
|
||||
slot: BlitzStaticEquipmentPresetComponent_EquipmentSlot;
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonist();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
||||
|
||||
return (
|
||||
<div className={styles.slots}>
|
||||
<div className={styles.options}>
|
||||
{slot.options_catalog_i_ds.map((id, index) => (
|
||||
<EquipmentOption
|
||||
key={id}
|
||||
optionIndex={index}
|
||||
rowIndex={rowIndex}
|
||||
columnIndex={columnIndex}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Price price={price!} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EquipmentOptionProps {
|
||||
optionIndex: number;
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
function EquipmentOption({
|
||||
optionIndex,
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
}: EquipmentOptionProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
|
||||
const tank = useProtagonist();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const isSelected = Tankopedia.use(
|
||||
(state) =>
|
||||
equipmentIndex in state.protagonist.equipment &&
|
||||
state.protagonist.equipment[equipmentIndex] === optionIndex,
|
||||
);
|
||||
const name =
|
||||
equipment.preset.slots[equipmentIndex].options_catalog_i_ds[optionIndex];
|
||||
const equipmentData = equipment.equipments[name];
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={isSelected ? undefined : "gray"}
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
data-selected={isSelected}
|
||||
radius="1"
|
||||
className={styles.slot}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
if (isSelected) {
|
||||
delete draft.protagonist.equipment[equipmentIndex];
|
||||
} else {
|
||||
draft.protagonist.equipment[equipmentIndex] = optionIndex;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={catImages.squareTransparent} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function Modules() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
@@ -203,87 +306,91 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={isSelected ? undefined : "gray"}
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
radius="1"
|
||||
className={styles.stage}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
const tankData = tank.tank!;
|
||||
<div className={styles.wrapper}>
|
||||
<Button
|
||||
className={styles.stage}
|
||||
color={isSelected ? undefined : "gray"}
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
radius="1"
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
const tankData = tank.tank!;
|
||||
|
||||
if (isAlternativeLine(lineName)) {
|
||||
const originalLine = originalLineName(lineName)!;
|
||||
if (isAlternativeLine(lineName)) {
|
||||
const originalLine = originalLineName(lineName)!;
|
||||
|
||||
draft.protagonist.alternates[originalLine] = true;
|
||||
draft.protagonist.upgrades[lineName] = index;
|
||||
} else {
|
||||
draft.protagonist.alternates[lineName] = false;
|
||||
draft.protagonist.upgrades[lineName] = index;
|
||||
}
|
||||
draft.protagonist.alternates[originalLine] = true;
|
||||
draft.protagonist.upgrades[lineName] = index;
|
||||
} else {
|
||||
draft.protagonist.alternates[lineName] = false;
|
||||
draft.protagonist.upgrades[lineName] = index;
|
||||
}
|
||||
|
||||
for (const required of stage.required_upgrades) {
|
||||
for (const line of tankData.upgrade_lines) {
|
||||
let i = 0;
|
||||
for (const required of stage.required_upgrades) {
|
||||
for (const line of tankData.upgrade_lines) {
|
||||
let i = 0;
|
||||
|
||||
for (const candidateStage of line.stages) {
|
||||
if (candidateStage.tech_name === required) {
|
||||
draft.protagonist.upgrades[line.name] = Math.max(
|
||||
draft.protagonist.upgrades[line.name],
|
||||
i,
|
||||
);
|
||||
for (const candidateStage of line.stages) {
|
||||
if (candidateStage.tech_name === required) {
|
||||
draft.protagonist.upgrades[line.name] = Math.max(
|
||||
draft.protagonist.upgrades[line.name],
|
||||
i,
|
||||
);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const line of tankData.upgrade_lines) {
|
||||
if (draft.protagonist.alternates[line.name]) {
|
||||
continue;
|
||||
for (const line of tankData.upgrade_lines) {
|
||||
if (draft.protagonist.alternates[line.name]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let i = draft.protagonist.upgrades[line.name];
|
||||
|
||||
while (i > 0) {
|
||||
const currentStage = line.stages[i];
|
||||
|
||||
const valid = currentStage.required_upgrades.every(
|
||||
(required) => {
|
||||
for (const otherLine of tankData.upgrade_lines) {
|
||||
const idx = draft.protagonist.upgrades[otherLine.name];
|
||||
|
||||
if (otherLine.stages[idx]?.tech_name === required) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (valid) break;
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
draft.protagonist.upgrades[line.name] = i;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.icon}
|
||||
src={`/api/tanks/modules/${stage.stage_type}.webp`}
|
||||
/>
|
||||
{/* {stage.display_name} */}
|
||||
|
||||
let i = draft.protagonist.upgrades[line.name];
|
||||
|
||||
while (i > 0) {
|
||||
const currentStage = line.stages[i];
|
||||
|
||||
const valid = currentStage.required_upgrades.every((required) => {
|
||||
for (const otherLine of tankData.upgrade_lines) {
|
||||
const idx = draft.protagonist.upgrades[otherLine.name];
|
||||
|
||||
if (otherLine.stages[idx]?.tech_name === required) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (valid) break;
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
draft.protagonist.upgrades[line.name] = i;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.icon}
|
||||
src={`/api/tanks/modules/${stage.stage_type}.webp`}
|
||||
/>
|
||||
{/* {stage.display_name} */}
|
||||
|
||||
<Text weight="light" lowContrast size="minor" className={styles.tier}>
|
||||
{isAlternativeLine(lineName)
|
||||
? strings.tanks.loadout.alternative
|
||||
: romanize(stage.number)}
|
||||
</Text>
|
||||
<Text weight="light" lowContrast size="minor" className={styles.tier}>
|
||||
{isAlternativeLine(lineName)
|
||||
? strings.tanks.loadout.alternative
|
||||
: romanize(stage.number)}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
{price && <Price className={styles.price} price={price} />}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const equipmentColumns = 3;
|
||||
@@ -0,0 +1,4 @@
|
||||
export const catImages = {
|
||||
squareTransparent:
|
||||
"https://icatcare.org/img/asset/aW1hZ2VzL2N1dG91dHMvYWRvYmVzdG9ja18zMTYzODM5NjkucG5n/adobestock_316383969.png?p=default&s=86bb641e805a56548c51f3385510026b",
|
||||
};
|
||||
File renamed without changes.
@@ -1,17 +1,11 @@
|
||||
import type { TankCatalogComponent } from "@protos/blitz_static_tank_component";
|
||||
import { api } from "../api/dynamic";
|
||||
import { useAwait } from "./useAwait";
|
||||
|
||||
export function useEquipment(presetId: string, priceId: string) {
|
||||
const [, presetName] = presetId.split(".");
|
||||
const [, priceName] = priceId.split(".");
|
||||
const preset = useAwait(
|
||||
() => api.equipmentPreset(presetId),
|
||||
`equipment-preset-${presetName}`,
|
||||
);
|
||||
const price = useAwait(
|
||||
() => api.equipmentPricePreset(priceId),
|
||||
`equipment-price-${priceName}`,
|
||||
);
|
||||
export function useEquipment(tank: TankCatalogComponent) {
|
||||
const equipment = useAwait(() => api.equipment(), "equipment");
|
||||
const preset = equipment.presets[tank.equipment_preset_catalog_id];
|
||||
const price = equipment.prices[tank.equipment_price_preset_catalog_id];
|
||||
|
||||
return { preset, price };
|
||||
return { preset, price, equipments: equipment.equipments };
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { api } from "../../../../api/dynamic";
|
||||
import { TankopediaLoadout } from "../../../../components/TankopediaLoadout";
|
||||
import { useAwait } from "../../../../hooks/useAwait";
|
||||
import { useCharacteristicRenderer } from "../../../../hooks/useCharacteristicRenderer";
|
||||
import { useGameStrings } from "../../../../hooks/useGameStrings";
|
||||
import { useEquipment } from "../../../../hooks/useEquipment";
|
||||
import { LocaleProvider } from "../../../../hooks/useLocale";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
import { characteristicsOrder } from "../../../../tankopedia/characteristicsOrder";
|
||||
@@ -27,7 +27,6 @@ export function Page({ id, locale }: PageProps) {
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const tankEntityGameStrings = useGameStrings("TankEntity");
|
||||
const renderCharacteristic = useCharacteristicRenderer();
|
||||
|
||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
||||
@@ -35,9 +34,15 @@ function Content() {
|
||||
() => api.tank(protagonist.id),
|
||||
`tank-${protagonist.id}`,
|
||||
);
|
||||
const protagonistEquipment = useEquipment(protagonistTank.tank!);
|
||||
|
||||
const { characteristics } = useMemo(
|
||||
() => computeCharacteristics(protagonistTank, protagonist),
|
||||
() =>
|
||||
computeCharacteristics(
|
||||
protagonistTank,
|
||||
protagonistEquipment,
|
||||
protagonist,
|
||||
),
|
||||
[protagonist],
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Equipment } from "@protos/equipment";
|
||||
import { api } from "../../api/dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const equipment = await api.equipment();
|
||||
return new Response(Equipment.encode(equipment).finish());
|
||||
}
|
||||
@@ -14,10 +14,7 @@ import {
|
||||
TankAttributeChange_Modifier,
|
||||
VisualChanges,
|
||||
} from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import {
|
||||
isAlternativeLine,
|
||||
originalLineName,
|
||||
} from "../config/alternativeLines";
|
||||
import { isAlternativeLine, originalLineName } from "../config/modules";
|
||||
|
||||
function patch(stage0: StageParameters, stage1: StageParameters) {
|
||||
if (stage1.number !== ++stage0.number) {
|
||||
@@ -244,6 +241,7 @@ export function aggregateParameters(
|
||||
tank: TankCatalogComponent,
|
||||
upgrades: Record<string, number>,
|
||||
alternates: Record<string, boolean>,
|
||||
equipmentParameters: StageParameters[],
|
||||
) {
|
||||
const stage0 = StageParameters.create({});
|
||||
|
||||
@@ -273,5 +271,10 @@ export function aggregateParameters(
|
||||
}
|
||||
}
|
||||
|
||||
for (const equipmentParameter of equipmentParameters) {
|
||||
stage0.number = -1;
|
||||
patch(stage0, equipmentParameter);
|
||||
}
|
||||
|
||||
return stage0;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import {
|
||||
ShellUpgradeSingleChange_AttributeName,
|
||||
TankAttributeChange_AttributeName,
|
||||
} from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import type { Tank } from "@protos/tank";
|
||||
import { useEquipment } from "../hooks/useEquipment";
|
||||
import { aggregateParameters } from "./aggregateParameters";
|
||||
import {
|
||||
characteristics,
|
||||
@@ -16,11 +18,27 @@ export type ComputedCharacteristics = Record<
|
||||
CharacteristicOutput
|
||||
>;
|
||||
|
||||
export function computeCharacteristics(tank: Tank, state: TankState) {
|
||||
export function computeCharacteristics(
|
||||
tank: Tank,
|
||||
equipment: ReturnType<typeof useEquipment>,
|
||||
state: TankState,
|
||||
) {
|
||||
const equipmentParameters: StageParameters[] = [];
|
||||
|
||||
for (const key in state.equipment) {
|
||||
const index = Number(key);
|
||||
const choice = state.equipment[index];
|
||||
const name = equipment.preset.slots[index].options_catalog_i_ds[choice];
|
||||
const data = equipment.equipments[name];
|
||||
|
||||
equipmentParameters.push(data.upgrade_level!);
|
||||
}
|
||||
|
||||
const parameters = aggregateParameters(
|
||||
tank.tank!,
|
||||
state.upgrades,
|
||||
state.alternates,
|
||||
equipmentParameters,
|
||||
);
|
||||
|
||||
const computed: Partial<ComputedCharacteristics> = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Tank } from "@protos/tank";
|
||||
import { alternativeLines } from "../config/alternativeLines";
|
||||
import { alternativeLines } from "../config/modules";
|
||||
import { TerrainHardness, type TankState } from "./tankState";
|
||||
|
||||
export function createTankState({ tank, id }: Tank) {
|
||||
@@ -22,6 +22,8 @@ export function createTankState({ tank, id }: Tank) {
|
||||
return {
|
||||
id,
|
||||
|
||||
equipment: {},
|
||||
|
||||
upgrades,
|
||||
alternates,
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ export enum TerrainHardness {
|
||||
export interface TankState {
|
||||
id: string;
|
||||
|
||||
equipment: Record<number, number>;
|
||||
|
||||
upgrades: Record<string, number>;
|
||||
alternates: Record<string, boolean>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user