mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 13:09:51 +01:00
Compare commits
8
Commits
960279bf00
...
e18d6f58f9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e18d6f58f9 | ||
|
|
488451d71f | ||
|
|
558a02b6ae | ||
|
|
d8f6a55ee4 | ||
|
|
763ecf22b0 | ||
|
|
2beb084b67 | ||
|
|
f7ca622906 | ||
|
|
88643d366d |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { MapDefinitions } from "@blitzkit/core";
|
||||
import { MapDefinitions } from "@blitzkit/protos";
|
||||
import { Cache } from "./0_base";
|
||||
import { ServerBlitzKitAPI3 } from "./3_models";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { emptyAllStats } from '..';
|
||||
import { BlitzStats } from './compositeStats/constants';
|
||||
import { emptyAllStats } from "../types";
|
||||
import { BlitzStats } from "./compositeStats/constants";
|
||||
|
||||
export function sumAllStats(allStatsCollection: BlitzStats[]) {
|
||||
return allStatsCollection.reduce((accumulated, current) => {
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
import { emptyAllStats } from '../blitz';
|
||||
import type { BlitzStats } from '../statistics';
|
||||
import type { BlitzStats } from "../statistics";
|
||||
|
||||
export const emptyAllStats: BlitzStats = {
|
||||
battles: 0,
|
||||
capture_points: 0,
|
||||
damage_dealt: 0,
|
||||
damage_received: 0,
|
||||
dropped_capture_points: 0,
|
||||
frags: 0,
|
||||
frags8p: 0,
|
||||
hits: 0,
|
||||
losses: 0,
|
||||
max_frags: 0,
|
||||
max_xp: 0,
|
||||
shots: 0,
|
||||
spotted: 0,
|
||||
survived_battles: 0,
|
||||
win_and_survived: 0,
|
||||
wins: 0,
|
||||
xp: 0,
|
||||
};
|
||||
|
||||
export interface IndividualTankStats {
|
||||
all: BlitzStats;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./literals";
|
||||
export * from "./strings";
|
||||
export * from "./unwrapper";
|
||||
export * from "./wrapper";
|
||||
@@ -2,7 +2,11 @@ import locales from "@blitzkit/i18n/locales.json";
|
||||
import { I18nString } from "@blitzkit/protos";
|
||||
|
||||
export function unwrapper(locale: string) {
|
||||
return function (i18nString: I18nString) {
|
||||
return function (i18nString?: I18nString) {
|
||||
if (i18nString === undefined) {
|
||||
throw new Error("i18nString is undefined");
|
||||
}
|
||||
|
||||
if (locale in i18nString.locales) {
|
||||
return i18nString.locales[locale];
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import locales from "../locales.json";
|
||||
import { type BlitzKitStrings, STRINGS } from "./strings";
|
||||
|
||||
export function wrapper(slicer: (strings: BlitzKitStrings) => string) {
|
||||
const defaultString = slicer(STRINGS[locales.default] as BlitzKitStrings);
|
||||
|
||||
const object = { [locales.default]: defaultString };
|
||||
|
||||
for (const { locale } of locales.supported) {
|
||||
const string = slicer(STRINGS[locale] as BlitzKitStrings);
|
||||
if (string !== defaultString) object[locale] = string;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
"hp": "{value}од",
|
||||
"x": "x{value}",
|
||||
"percentage": "{value}%",
|
||||
"deg_s": "{value}град/c",
|
||||
"deg_s": "{value}°/c",
|
||||
"plus": "+{value}"
|
||||
},
|
||||
"gun_types": {
|
||||
@@ -37,8 +37,8 @@
|
||||
"d_1": "Вчора"
|
||||
},
|
||||
"branches": {
|
||||
"beta": "Beta",
|
||||
"development": "Development",
|
||||
"beta": "Бета",
|
||||
"development": "Розробка",
|
||||
"opentest": "Відкритий тест",
|
||||
"preview": "Preview",
|
||||
"production": "Production"
|
||||
@@ -340,8 +340,8 @@
|
||||
},
|
||||
"mixer": {
|
||||
"name": "Mixer",
|
||||
"button": "Mix and match",
|
||||
"description": "Mix tank modules",
|
||||
"button": "Змішувати та поєднувати",
|
||||
"description": "Змішані танкові модулі",
|
||||
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
|
||||
"screenshot": "Знімок екрану",
|
||||
"randomize": "Рандомізувати"
|
||||
@@ -501,11 +501,11 @@
|
||||
"tech_tree": {
|
||||
"title": "Звичайний",
|
||||
"battle": "1 бій",
|
||||
"battles": "{count} battles",
|
||||
"battles": "{count} битви",
|
||||
"route": "Route {index} of {count}"
|
||||
},
|
||||
"game_modes": {
|
||||
"title": "Game mode abilities"
|
||||
"title": "Здібності ігрового режиму"
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
@@ -626,7 +626,7 @@
|
||||
"camouflageShootingStill": "Нерухомо",
|
||||
"camouflageShootingMoving": "У русі",
|
||||
"camouflageCaughtOnFire": "Caught on fire",
|
||||
"width": "Width",
|
||||
"width": "Ширина",
|
||||
"height": "Height",
|
||||
"length": "Length",
|
||||
"volume": "Volume",
|
||||
@@ -740,7 +740,7 @@
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"name": "Gallery",
|
||||
"name": "Галерея",
|
||||
"button": "Browse all",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "Усі World of Tanks Blitz аватари гравців",
|
||||
@@ -843,7 +843,7 @@
|
||||
"breakdown": {
|
||||
"name": "Breakdown",
|
||||
"options": {
|
||||
"width": "Width",
|
||||
"width": "Ширина",
|
||||
"height": "Height",
|
||||
"showTotal": "Show total",
|
||||
"listGap": "List gap",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
syntax = "proto2";
|
||||
syntax = "proto3";
|
||||
|
||||
package blitzkit;
|
||||
|
||||
@@ -10,8 +10,8 @@ message EquipmentDefinitions {
|
||||
}
|
||||
|
||||
message Equipment {
|
||||
required I18nString name = 1;
|
||||
required I18nString description = 2;
|
||||
I18nString name = 1;
|
||||
I18nString description = 2;
|
||||
}
|
||||
|
||||
message EquipmentPreset {
|
||||
@@ -19,6 +19,5 @@ message EquipmentPreset {
|
||||
}
|
||||
|
||||
message EquipmentSlot {
|
||||
required uint32 left = 1;
|
||||
required uint32 right = 2;
|
||||
repeated uint32 options = 1;
|
||||
}
|
||||
+11
-10
@@ -17,9 +17,9 @@ import {
|
||||
Vector2,
|
||||
} from "three";
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import { hasEquipment } from "../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../core/blitzkit/jsxTree";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { DuelModule, getDuel } from "../../../../hooks/useDuel";
|
||||
import { hasEquipment } from "../../../../hooks/useEquipment";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
||||
import { transitionEvent } from "../../../Lighting";
|
||||
@@ -82,9 +82,8 @@ export function PrimaryArmorSceneComponent({
|
||||
|
||||
useEffect(() => {
|
||||
function handleShellChange() {
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
Tankopedia.state.customShell ?? getDuel("antagonist", DuelModule.Shell);
|
||||
|
||||
material.uniforms.caliber.value = shell.caliber;
|
||||
material.uniforms.ricochet.value = degToRad(
|
||||
@@ -117,15 +116,16 @@ export function PrimaryArmorSceneComponent({
|
||||
material.wireframe = wireframe;
|
||||
}
|
||||
function handleProtagonistEquipmentChange(noInvalidate = false) {
|
||||
const equipment = Duel.state.protagonist.equipmentMatrix;
|
||||
const equipment = Tankopedia.state.protagonist.equipment;
|
||||
const protagonist = getDuel("protagonist", DuelModule.Tank);
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
protagonist.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
const equalizer =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.protagonist.tank.equalizer?.armor
|
||||
(Tankopedia.state.equalize
|
||||
? protagonist.equalizer?.armor
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.thickness.value =
|
||||
@@ -134,10 +134,11 @@ export function PrimaryArmorSceneComponent({
|
||||
if (!noInvalidate) invalidate();
|
||||
}
|
||||
function handleAntagonistEquipmentChange(noInvalidate = false) {
|
||||
const equipment = Duel.state.antagonist.equipmentMatrix;
|
||||
const equipment = Tankopedia.state.antagonist.equipment;
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
tankopediaEphemeral.customShell ??
|
||||
getDuel("antagonist", DuelModule.Shell);
|
||||
const penetration = shell.penetration!.near;
|
||||
const hasCalibratedShells = hasEquipment(
|
||||
103,
|
||||
|
||||
@@ -74,7 +74,7 @@ export function StaticArmorSceneComponent({
|
||||
...props
|
||||
}: StaticArmorSceneComponentProps) {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const hasEnhancedArmor = useEquipment(110);
|
||||
const hasEnhancedArmor = useEquipment("protagonist", 110);
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isEqual } from "lodash-es";
|
||||
import { useMemo } from "react";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { TankFilters } from "../../stores/tankFilters";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
import { TankSort } from "../../stores/tankopediaSort";
|
||||
@@ -29,7 +29,8 @@ export function RecentlyViewedTanks() {
|
||||
}),
|
||||
[filters],
|
||||
);
|
||||
const { strings } = useLocale();
|
||||
|
||||
const strings = useStrings();
|
||||
const by = TankSort.use((state) => state.by);
|
||||
|
||||
if (recentlyViewed.length === 0 || by !== "meta.none" || hasFilters) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { alias, TankType, type TankDefinition } from "@blitzkit/core";
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { TankType, type TankDefinition } from "@blitzkit/protos";
|
||||
import { uniq } from "lodash-es";
|
||||
import { type ReactNode } from "react";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { useUnwrapper } from "../../hooks/useUnwrapper";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
||||
import { classIcons } from "../ClassIcon";
|
||||
import { ClassIcon } from "../ClassIcon";
|
||||
import { Flex } from "../Flex";
|
||||
import { LinkI18nWrapper } from "../LinkI18nWrapper";
|
||||
import { TankCardSkeleton } from "../TankCardSkeleton";
|
||||
@@ -28,7 +30,8 @@ export const TankCard = (props: MaybeSkeletonComponentProps<TankCardProps>) => {
|
||||
return <TankCardSkeleton />;
|
||||
}
|
||||
|
||||
const { unwrap, locale } = useLocale();
|
||||
const unwrap = useUnwrapper();
|
||||
const locale = useLocale();
|
||||
|
||||
const provideLink = !props.noLink && props.onTankSelect === undefined;
|
||||
const name = unwrap(props.tank.name!);
|
||||
@@ -41,8 +44,6 @@ export const TankCard = (props: MaybeSkeletonComponentProps<TankCardProps>) => {
|
||||
: "gray";
|
||||
const lowContrast = props.tank.type !== TankType.TANK_TYPE_RESEARCHABLE;
|
||||
|
||||
const Icon = classIcons[props.tank.class];
|
||||
|
||||
const content = (
|
||||
<Flex
|
||||
className={styles.wrapper}
|
||||
@@ -75,7 +76,7 @@ export const TankCard = (props: MaybeSkeletonComponentProps<TankCardProps>) => {
|
||||
</Flex>
|
||||
|
||||
<Text className={styles.name} color={color} lowContrast={lowContrast}>
|
||||
<Icon className={styles.icon} />
|
||||
<ClassIcon class={props.tank.class} className={styles.icon} />
|
||||
<span className={styles.text}>{name}</span>
|
||||
</Text>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CaretDownIcon } from "@radix-ui/react-icons";
|
||||
import { useLocale } from "../../../hooks/useLocale";
|
||||
import { useStrings } from "../../../hooks/useStrings";
|
||||
import type {
|
||||
TankopediaSortBy,
|
||||
TankopediaSortDirection,
|
||||
@@ -17,7 +17,7 @@ interface ItemProps {
|
||||
}
|
||||
|
||||
function Item({ by }: ItemProps) {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
const _by = TankSort.use((state) => state.by);
|
||||
|
||||
return (
|
||||
@@ -36,7 +36,7 @@ function Item({ by }: ItemProps) {
|
||||
|
||||
export function Sort() {
|
||||
const direction = TankSort.use((state) => state.direction);
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { TankDefinition } from "@blitzkit/core";
|
||||
import type { TankDefinition } from "@blitzkit/protos";
|
||||
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
type KeyboardEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
type KeyboardEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { TankFilters } from "../../stores/tankFilters";
|
||||
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
||||
import { Flex } from "../Flex";
|
||||
@@ -23,7 +23,7 @@ export function TankSearchBar({
|
||||
skeleton,
|
||||
onSelect,
|
||||
}: SearchBarProps) {
|
||||
const { strings } = useLocale();
|
||||
const strings = useStrings();
|
||||
|
||||
const search = TankFilters.use((state) => state.search);
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const { strings } = useLocale();
|
||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||
const hasImprovedVentilation = useEquipment(102);
|
||||
const hasImprovedVentilation = useEquipment("protagonist", 102);
|
||||
const provisionCrewBonus =
|
||||
provisions.reduce(
|
||||
(total, provision) =>
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
},
|
||||
);
|
||||
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||
const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124);
|
||||
|
||||
useEffect(() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ export function Consumables() {
|
||||
const cooldownBooster = Duel.use(
|
||||
(state) => state.protagonist.cooldownBooster,
|
||||
);
|
||||
const hasConsumableDeliverySystem = useEquipment(118);
|
||||
const hasHighEndConsumables = useEquipment(101);
|
||||
const hasConsumableDeliverySystem = useEquipment("protagonist", 118);
|
||||
const hasHighEndConsumables = useEquipment("protagonist", 101);
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ export function QuickInputs() {
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const initialTurretPitch =
|
||||
tankModelDefinition.initial_turret_rotation?.pitch ?? 0;
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||
const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124);
|
||||
|
||||
function displayPitch(pitch: number) {
|
||||
return radToDeg(pitch) - initialTurretPitch;
|
||||
|
||||
@@ -68,9 +68,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
|
||||
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||
const hasCalibratedShells = useEquipment(103, true);
|
||||
const hasCalibratedShells = useEquipment("protagonist", 103, true);
|
||||
const [tab, setTab] = useState("search");
|
||||
const hasEnhancedArmor = useEquipment(110);
|
||||
const hasEnhancedArmor = useEquipment("protagonist", 110);
|
||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||
const { strings, unwrap } = useLocale();
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import type { Consumable } from "@blitzkit/core";
|
||||
import { TrashIcon } from "@radix-ui/react-icons";
|
||||
import type { Consumable, EquipmentSlot } from "@blitzkit/protos";
|
||||
import { ClockIcon, ResetIcon, TrashIcon } from "@radix-ui/react-icons";
|
||||
import { chunk } from "lodash-es";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { equipmentColumns } from "../../config/equipment";
|
||||
import { useConsumables } from "../../hooks/useConsumables";
|
||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||
import { useEquipmentPreset } from "../../hooks/useEquipmentPreset";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
||||
import { useUnwrapper } from "../../hooks/useUnwrapper";
|
||||
import { Tankopedia } from "../../stores/tankopedia";
|
||||
import { hasUpgrades } from "../../tankopedia/hasUpgrades";
|
||||
import { vehicleStatusKeys } from "../../tankopedia/tankState";
|
||||
import type { ComputedCharacteristics } from "../../types/characteristics";
|
||||
import { Button } from "../Button";
|
||||
import { Heading } from "../Heading";
|
||||
import { IconButton } from "../IconButton";
|
||||
import { Text } from "../Text";
|
||||
import { Tooltip } from "../Tooltip";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
const equipment = await api.equipment();
|
||||
|
||||
interface TankopediaLoadoutProps {
|
||||
characteristics: ComputedCharacteristics;
|
||||
}
|
||||
|
||||
export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) {
|
||||
const tank = useProtagonistTank();
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const showModules = hasUpgrades(tank);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModules && <Modules />}
|
||||
{/* {showModules && <Modules />} */}
|
||||
<Equipment />
|
||||
<Consumables characteristics={characteristics} />
|
||||
<State />
|
||||
@@ -120,8 +128,8 @@ interface ConsumablesProps {
|
||||
function Consumables({ characteristics }: ConsumablesProps) {
|
||||
const strings = useStrings();
|
||||
const consumables = useConsumables();
|
||||
const tank = useProtagonistTank();
|
||||
const gun = useProtagonistGun();
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const gun = useDuel("protagonist", DuelModule.Gun);
|
||||
const isCompatible = useTankCompatibility(tank, gun);
|
||||
|
||||
return (
|
||||
@@ -148,7 +156,7 @@ function Consumables({ characteristics }: ConsumablesProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Consumable key={id} id={Number(id)} consumable={consumable} />
|
||||
<ConsumableEntry key={id} id={Number(id)} consumable={consumable} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -161,12 +169,12 @@ interface ConsumableProps {
|
||||
consumable: Consumable;
|
||||
}
|
||||
|
||||
function Consumable({ id, consumable }: ConsumableProps) {
|
||||
function ConsumableEntry({ id, consumable }: ConsumableProps) {
|
||||
const isSelected = Tankopedia.use((state) =>
|
||||
state.protagonist.consumables.includes(id),
|
||||
);
|
||||
const tank = useProtagonistTank();
|
||||
const unwrap = useUnwrap();
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const unwrap = useUnwrapper();
|
||||
|
||||
return (
|
||||
<div className={styles["consumable-wrapper"]}>
|
||||
@@ -207,15 +215,15 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Price price={price} />
|
||||
{/* <Price price={price} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Equipment() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const equipment = useEquipmentPreset(tank.equipment_preset);
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
@@ -237,7 +245,7 @@ function Equipment() {
|
||||
</div>
|
||||
|
||||
<div className={styles.equipment}>
|
||||
{chunk(equipment.preset.slots, equipmentColumns).map((chunk, index) => (
|
||||
{chunk(equipment.slots, equipmentColumns).map((chunk, index) => (
|
||||
<EquipmentRow key={index} chunk={chunk} rowIndex={index} />
|
||||
))}
|
||||
</div>
|
||||
@@ -246,7 +254,7 @@ function Equipment() {
|
||||
}
|
||||
|
||||
interface EquipmentRowProps {
|
||||
chunk: BlitzStaticEquipmentPresetComponent_EquipmentSlot[];
|
||||
chunk: EquipmentSlot[];
|
||||
rowIndex: number;
|
||||
}
|
||||
|
||||
@@ -254,7 +262,7 @@ function EquipmentRow({ chunk, rowIndex }: EquipmentRowProps) {
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
{chunk.map((slot, index) => (
|
||||
<EquipmentSlot
|
||||
<EquipmentSlotEntry
|
||||
key={index}
|
||||
slot={slot}
|
||||
rowIndex={rowIndex}
|
||||
@@ -266,21 +274,25 @@ function EquipmentRow({ chunk, rowIndex }: EquipmentRowProps) {
|
||||
}
|
||||
|
||||
interface EquipmentSlotProps {
|
||||
slot: BlitzStaticEquipmentPresetComponent_EquipmentSlot;
|
||||
slot: EquipmentSlot;
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||
function EquipmentSlotEntry({
|
||||
slot,
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
}: EquipmentSlotProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const equipment = useEquipmentPreset(tank.equipment_preset);
|
||||
// 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) => (
|
||||
{slot.options.map((id, index) => (
|
||||
<EquipmentOption
|
||||
key={id}
|
||||
optionIndex={index}
|
||||
@@ -290,7 +302,7 @@ function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Price price={price!} />
|
||||
{/* <Price price={price!} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -307,11 +319,14 @@ function EquipmentOption({
|
||||
columnIndex,
|
||||
}: EquipmentOptionProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const slot = equipment.preset.slots[equipmentIndex];
|
||||
const id = slot.options_catalog_i_ds[optionIndex];
|
||||
const gameStrings = useGameStrings("EquipmentEntity");
|
||||
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const equipmentPreset = useEquipmentPreset(tank.equipment_preset);
|
||||
const unwrap = useUnwrapper();
|
||||
|
||||
const slot = equipmentPreset.slots[equipmentIndex];
|
||||
const id = slot.options[optionIndex];
|
||||
const specificEquipment = equipment.equipments[id];
|
||||
|
||||
const isSelected = Tankopedia.use(
|
||||
(state) =>
|
||||
@@ -320,13 +335,13 @@ function EquipmentOption({
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip tooltip={gameStrings[equipment.equipments[id].equipment_name]}>
|
||||
<Tooltip tooltip={unwrap(specificEquipment.name)}>
|
||||
<Button
|
||||
color={isSelected ? undefined : "gray"}
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
data-selected={isSelected}
|
||||
radius="1"
|
||||
parentArray
|
||||
listChild
|
||||
className={styles.slot}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
@@ -344,292 +359,292 @@ function EquipmentOption({
|
||||
);
|
||||
}
|
||||
|
||||
function Modules() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonistTank();
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const isStock = useMemo(() => {
|
||||
return tank.tank!.upgrade_lines.every((line) => {
|
||||
if (isAlternativeLine(line.name)) return true;
|
||||
return upgrades[line.name] === 0;
|
||||
});
|
||||
}, [upgrades]);
|
||||
const isUpgraded = useMemo(() => {
|
||||
return tank.tank!.upgrade_lines.every((line) => {
|
||||
if (isAlternativeLine(line.name)) return true;
|
||||
// function Modules() {
|
||||
// const strings = useStrings();
|
||||
// const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
// const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
// const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
// const isStock = useMemo(() => {
|
||||
// return tank.tank!.upgrade_lines.every((line) => {
|
||||
// if (isAlternativeLine(line.name)) return true;
|
||||
// return upgrades[line.name] === 0;
|
||||
// });
|
||||
// }, [upgrades]);
|
||||
// const isUpgraded = useMemo(() => {
|
||||
// return tank.tank!.upgrade_lines.every((line) => {
|
||||
// if (isAlternativeLine(line.name)) return true;
|
||||
|
||||
return (
|
||||
upgrades[line.name] === line.stages.length - 1 ||
|
||||
(line.name in alternates && alternates[line.name])
|
||||
);
|
||||
});
|
||||
}, [upgrades]);
|
||||
// return (
|
||||
// upgrades[line.name] === line.stages.length - 1 ||
|
||||
// (line.name in alternates && alternates[line.name])
|
||||
// );
|
||||
// });
|
||||
// }, [upgrades]);
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<div className={styles.header}>
|
||||
<Heading size="4">{strings.tanks.loadout.modules}</Heading>
|
||||
// return (
|
||||
// <div className={styles.section}>
|
||||
// <div className={styles.header}>
|
||||
// <Heading size="4">{strings.tanks.loadout.modules}</Heading>
|
||||
|
||||
<div className={styles["upgrade-buttons"]}>
|
||||
<IconButton
|
||||
variant={isStock ? "solid" : "soft"}
|
||||
size="minor"
|
||||
array
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
for (const line of tank.tank!.upgrade_lines) {
|
||||
if (isAlternativeLine(line.name)) continue;
|
||||
// <div className={styles["upgrade-buttons"]}>
|
||||
// <IconButton
|
||||
// variant={isStock ? "solid" : "soft"}
|
||||
// size="minor"
|
||||
// array
|
||||
// onClick={() => {
|
||||
// Tankopedia.mutate((draft) => {
|
||||
// for (const line of tank.tank!.upgrade_lines) {
|
||||
// if (isAlternativeLine(line.name)) continue;
|
||||
|
||||
draft.protagonist.upgrades[line.name] = 0;
|
||||
draft.protagonist.alternates[line.name] = false;
|
||||
}
|
||||
});
|
||||
}}
|
||||
color="gray"
|
||||
>
|
||||
<DoubleArrowLeftIcon />
|
||||
</IconButton>
|
||||
// draft.protagonist.upgrades[line.name] = 0;
|
||||
// draft.protagonist.alternates[line.name] = false;
|
||||
// }
|
||||
// });
|
||||
// }}
|
||||
// color="gray"
|
||||
// >
|
||||
// <DoubleArrowLeftIcon />
|
||||
// </IconButton>
|
||||
|
||||
<IconButton
|
||||
variant={isUpgraded ? "solid" : "soft"}
|
||||
size="minor"
|
||||
array
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
for (const line of tank.tank!.upgrade_lines) {
|
||||
draft.protagonist.upgrades[line.name] =
|
||||
line.stages.length - 1;
|
||||
// <IconButton
|
||||
// variant={isUpgraded ? "solid" : "soft"}
|
||||
// size="minor"
|
||||
// array
|
||||
// onClick={() => {
|
||||
// Tankopedia.mutate((draft) => {
|
||||
// for (const line of tank.tank!.upgrade_lines) {
|
||||
// draft.protagonist.upgrades[line.name] =
|
||||
// line.stages.length - 1;
|
||||
|
||||
if (isAlternativeLine(line.name)) {
|
||||
const original = originalLineName(line.name)!;
|
||||
draft.protagonist.alternates[original] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
color="gray"
|
||||
>
|
||||
<DoubleArrowRightIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
// if (isAlternativeLine(line.name)) {
|
||||
// const original = originalLineName(line.name)!;
|
||||
// draft.protagonist.alternates[original] = true;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }}
|
||||
// color="gray"
|
||||
// >
|
||||
// <DoubleArrowRightIcon />
|
||||
// </IconButton>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
<div className={styles.lines}>
|
||||
{/* TODO: hide lines with 1 module except for alts */}
|
||||
{tank.tank!.upgrade_lines.map((line) => (
|
||||
<Line
|
||||
key={line.name}
|
||||
name={line.name}
|
||||
lines={tank.tank!.upgrade_lines}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// <div className={styles.lines}>
|
||||
// {/* TODO: hide lines with 1 module except for alts */}
|
||||
// {tank.tank!.upgrade_lines.map((line) => (
|
||||
// <Line
|
||||
// key={line.name}
|
||||
// name={line.name}
|
||||
// lines={tank.tank!.upgrade_lines}
|
||||
// />
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
interface LineProps {
|
||||
name: string;
|
||||
lines: UpgradeLine[];
|
||||
}
|
||||
// interface LineProps {
|
||||
// name: string;
|
||||
// lines: UpgradeLine[];
|
||||
// }
|
||||
|
||||
function Line({ name, lines }: LineProps) {
|
||||
const combinedLines = useMemo(() => {
|
||||
const names: string[] = [name];
|
||||
// function Line({ name, lines }: LineProps) {
|
||||
// const combinedLines = useMemo(() => {
|
||||
// const names: string[] = [name];
|
||||
|
||||
for (const originalLine in alternativeLines) {
|
||||
const alternativeLine = alternativeLines[originalLine];
|
||||
// for (const originalLine in alternativeLines) {
|
||||
// const alternativeLine = alternativeLines[originalLine];
|
||||
|
||||
if (
|
||||
originalLine === name &&
|
||||
lines.some((line) => line.name === alternativeLine)
|
||||
) {
|
||||
names.push(alternativeLine);
|
||||
}
|
||||
}
|
||||
// if (
|
||||
// originalLine === name &&
|
||||
// lines.some((line) => line.name === alternativeLine)
|
||||
// ) {
|
||||
// names.push(alternativeLine);
|
||||
// }
|
||||
// }
|
||||
|
||||
return names;
|
||||
}, [name]);
|
||||
// return names;
|
||||
// }, [name]);
|
||||
|
||||
return (
|
||||
<div className={styles.line}>
|
||||
{!isAlternativeLine(name) &&
|
||||
combinedLines.map((name) => (
|
||||
<div className={styles["chunks-wrapper"]}>
|
||||
<LineInner key={name} name={name} lines={lines} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div className={styles.line}>
|
||||
// {!isAlternativeLine(name) &&
|
||||
// combinedLines.map((name) => (
|
||||
// <div className={styles["chunks-wrapper"]}>
|
||||
// <LineInner key={name} name={name} lines={lines} />
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
function LineInner({ name, lines }: LineProps) {
|
||||
const line = lines.find((line) => line.name === name)!;
|
||||
// function LineInner({ name, lines }: LineProps) {
|
||||
// const line = lines.find((line) => line.name === name)!;
|
||||
|
||||
return chunk(line.stages, maxModulesPerRow).map((chunk, wrapIndex) => (
|
||||
<div className={styles["line-wrap"]}>
|
||||
{wrapIndex > 0 && (
|
||||
<Text size="minor" className={styles["wrap-marker"]} lowContrast>
|
||||
<CornerBottomLeftIcon />
|
||||
</Text>
|
||||
)}
|
||||
// return chunk(line.stages, maxModulesPerRow).map((chunk, wrapIndex) => (
|
||||
// <div className={styles["line-wrap"]}>
|
||||
// {wrapIndex > 0 && (
|
||||
// <Text size="minor" className={styles["wrap-marker"]} lowContrast>
|
||||
// <CornerBottomLeftIcon />
|
||||
// </Text>
|
||||
// )}
|
||||
|
||||
{chunk.map((stage, elementIndex) => (
|
||||
<LineElement
|
||||
key={elementIndex}
|
||||
index={wrapIndex * maxModulesPerRow + elementIndex}
|
||||
lineName={name}
|
||||
stage={stage}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
// {chunk.map((stage, elementIndex) => (
|
||||
// <LineElement
|
||||
// key={elementIndex}
|
||||
// index={wrapIndex * maxModulesPerRow + elementIndex}
|
||||
// lineName={name}
|
||||
// stage={stage}
|
||||
// />
|
||||
// ))}
|
||||
// </div>
|
||||
// ));
|
||||
// }
|
||||
|
||||
interface LineElementProps {
|
||||
index: number;
|
||||
lineName: string;
|
||||
stage: StageParameters;
|
||||
}
|
||||
// interface LineElementProps {
|
||||
// index: number;
|
||||
// lineName: string;
|
||||
// stage: StageParameters;
|
||||
// }
|
||||
|
||||
function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const tank = useProtagonistTank();
|
||||
const strings = useStrings();
|
||||
const gameStrings = useGameStrings("TankEntity");
|
||||
const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
|
||||
// function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
// const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
// const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
// const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
// const strings = useStrings();
|
||||
// const gameStrings = useGameStrings("TankEntity");
|
||||
// const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
|
||||
|
||||
let price: StandardPrice | undefined;
|
||||
// let price: StandardPrice | undefined;
|
||||
|
||||
switch (stage.grade) {
|
||||
case Grade.GRADE_COMMON:
|
||||
price = upgradePreset.common_grade_price;
|
||||
break;
|
||||
// switch (stage.grade) {
|
||||
// case Grade.GRADE_COMMON:
|
||||
// price = upgradePreset.common_grade_price;
|
||||
// break;
|
||||
|
||||
case Grade.GRADE_RARE:
|
||||
price = upgradePreset.rare_grade_price;
|
||||
break;
|
||||
// case Grade.GRADE_RARE:
|
||||
// price = upgradePreset.rare_grade_price;
|
||||
// break;
|
||||
|
||||
case Grade.GRADE_EPIC:
|
||||
price = upgradePreset.unique_grade_price;
|
||||
break;
|
||||
// case Grade.GRADE_EPIC:
|
||||
// price = upgradePreset.unique_grade_price;
|
||||
// break;
|
||||
|
||||
case Grade.GRADE_LEGENDARY:
|
||||
price = upgradePreset.legendary_grade_price;
|
||||
break;
|
||||
}
|
||||
// case Grade.GRADE_LEGENDARY:
|
||||
// price = upgradePreset.legendary_grade_price;
|
||||
// break;
|
||||
// }
|
||||
|
||||
for (const override of upgradePreset.prices_overrides) {
|
||||
if (override.stage_tech_name !== stage.tech_name) continue;
|
||||
price = override.price;
|
||||
}
|
||||
// for (const override of upgradePreset.prices_overrides) {
|
||||
// if (override.stage_tech_name !== stage.tech_name) continue;
|
||||
// price = override.price;
|
||||
// }
|
||||
|
||||
price ??= StandardPrice.create();
|
||||
// price ??= StandardPrice.create();
|
||||
|
||||
let isSelected: boolean;
|
||||
// let isSelected: boolean;
|
||||
|
||||
if (isAlternativeLine(lineName)) {
|
||||
const originalLine = originalLineName(lineName);
|
||||
isSelected = alternates[originalLine!];
|
||||
} else {
|
||||
if (alternates[lineName]) {
|
||||
isSelected = false;
|
||||
} else {
|
||||
isSelected = upgrades[lineName] === index;
|
||||
}
|
||||
}
|
||||
// if (isAlternativeLine(lineName)) {
|
||||
// const originalLine = originalLineName(lineName);
|
||||
// isSelected = alternates[originalLine!];
|
||||
// } else {
|
||||
// if (alternates[lineName]) {
|
||||
// isSelected = false;
|
||||
// } else {
|
||||
// isSelected = upgrades[lineName] === index;
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Tooltip tooltip={gameStrings[stage.display_name]}>
|
||||
<Button
|
||||
className={styles.stage}
|
||||
color={isSelected ? undefined : "gray"}
|
||||
variant={isSelected ? "surface" : "soft"}
|
||||
radius="1"
|
||||
onClick={() => {
|
||||
// TODO: make minimum selectable upgrade the last free module for tanks like the destiny
|
||||
// return (
|
||||
// <div className={styles.wrapper}>
|
||||
// <Tooltip tooltip={gameStrings[stage.display_name]}>
|
||||
// <Button
|
||||
// className={styles.stage}
|
||||
// color={isSelected ? undefined : "gray"}
|
||||
// variant={isSelected ? "surface" : "soft"}
|
||||
// radius="1"
|
||||
// onClick={() => {
|
||||
// // TODO: make minimum selectable upgrade the last free module for tanks like the destiny
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
const tankData = tank.tank!;
|
||||
// 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];
|
||||
// let i = draft.protagonist.upgrades[line.name];
|
||||
|
||||
while (i > 0) {
|
||||
const currentStage = line.stages[i];
|
||||
// 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];
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// if (otherLine.stages[idx]?.tech_name === required) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
return false;
|
||||
},
|
||||
);
|
||||
// return false;
|
||||
// },
|
||||
// );
|
||||
|
||||
if (valid) break;
|
||||
// if (valid) break;
|
||||
|
||||
i--;
|
||||
}
|
||||
// i--;
|
||||
// }
|
||||
|
||||
draft.protagonist.upgrades[line.name] = i;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.icon}
|
||||
src={`/media/modules/${stage.stage_type}.webp`}
|
||||
/>
|
||||
// draft.protagonist.upgrades[line.name] = i;
|
||||
// }
|
||||
// });
|
||||
// }}
|
||||
// >
|
||||
// <img
|
||||
// className={styles.icon}
|
||||
// src={`/media/modules/${stage.stage_type}.webp`}
|
||||
// />
|
||||
|
||||
<Text weight="light" lowContrast size="minor" className={styles.tier}>
|
||||
{isAlternativeLine(lineName)
|
||||
? strings.tanks.loadout.alternative
|
||||
: romanize(stage.number)}
|
||||
</Text>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
// <Text weight="light" lowContrast size="minor" className={styles.tier}>
|
||||
// {isAlternativeLine(lineName)
|
||||
// ? strings.tanks.loadout.alternative
|
||||
// : romanize(stage.number)}
|
||||
// </Text>
|
||||
// </Button>
|
||||
// </Tooltip>
|
||||
|
||||
{<Price className={styles.price} price={price} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// {<Price className={styles.price} price={price} />}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
import { applyPitchYawLimits } from "../../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../../core/blitzkit/modelTransform";
|
||||
import { Pose, poseEvent } from "../../core/blitzkit/pose";
|
||||
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||
import { useEquipment } from "../../hooks/useEquipment";
|
||||
import { useModel } from "../../hooks/useModel";
|
||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||
import { useProtagonistTurret } from "../../hooks/useProtagonistTurret";
|
||||
import { Tankopedia } from "../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||
@@ -41,12 +39,12 @@ export const TankopediaSandbox = forwardRef<
|
||||
TankSandboxProps
|
||||
>(({ thicknessRange }, ref) => {
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||
const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124);
|
||||
|
||||
const tank = useProtagonistTank();
|
||||
const gun = useProtagonistGun();
|
||||
const turret = useProtagonistTurret();
|
||||
const tank = useDuel("protagonist", DuelModule.Tank);
|
||||
const gun = useDuel("protagonist", DuelModule.Gun);
|
||||
const turret = useDuel("protagonist", DuelModule.Turret);
|
||||
|
||||
const model = Tankopedia.use((state) => state.protagonist.model);
|
||||
const turretModelDefinition = model.turrets[turret.id];
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const equipmentColumns = 3;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isTankCompatible } from "@blitzkit/core";
|
||||
import type { TankDefinition } from "@blitzkit/protos";
|
||||
import { times } from "lodash-es";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { isTankCompatible } from "../../hooks/useTankCompatibility";
|
||||
import type { TankFilters } from "../../stores/tankFilters";
|
||||
|
||||
const SHELLS = times(3, (index) => index);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { getTankStats, idToRegion, TankDefinition } from "@blitzkit/core";
|
||||
import { getTankStats, idToRegion } from "@blitzkit/core";
|
||||
import type { TankDefinition } from "@blitzkit/protos";
|
||||
import { TankFilters } from "../../stores/tankFilters";
|
||||
import { filterTank } from "./filterTank";
|
||||
|
||||
export async function filterTanks(
|
||||
filters: TankFilters,
|
||||
tanks: TankDefinition[],
|
||||
player?: number
|
||||
player?: number,
|
||||
) {
|
||||
const filtered: TankDefinition[] = [];
|
||||
let owned: number[] | undefined;
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { api } from "../../api/dynamic";
|
||||
import type { EquipmentMatrix } from "../../stores/duel";
|
||||
|
||||
const equipmentDefinitions = await api.equipment();
|
||||
|
||||
export function hasEquipment(
|
||||
id: number,
|
||||
equipmentPreset: string,
|
||||
equipmentMatrix: EquipmentMatrix,
|
||||
) {
|
||||
const preset = equipmentDefinitions.presets[equipmentPreset];
|
||||
return preset.slots.some((slot, index) => {
|
||||
const row = Math.floor(index / 3);
|
||||
const column = index % 3;
|
||||
const choice = equipmentMatrix[row][column];
|
||||
|
||||
if (choice === 0) return false;
|
||||
|
||||
const equipped = slot[choice === -1 ? "left" : "right"];
|
||||
|
||||
return equipped === id;
|
||||
});
|
||||
}
|
||||
@@ -72,3 +72,55 @@ export function useDuel(side: DuelSide, module: DuelModule) {
|
||||
const shellId = Tankopedia.use((state) => state[side].shell);
|
||||
return gun.shells.find((shell) => shell.id === shellId)!;
|
||||
}
|
||||
|
||||
export function getDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Tank,
|
||||
): TankDefinition;
|
||||
export function getDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Track,
|
||||
): TrackDefinition;
|
||||
export function getDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Engine,
|
||||
): EngineDefinition;
|
||||
export function getDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Turret,
|
||||
): TurretDefinition;
|
||||
export function getDuel(side: DuelSide, module: DuelModule.Gun): GunDefinition;
|
||||
export function getDuel(
|
||||
side: DuelSide,
|
||||
module: DuelModule.Shell,
|
||||
): ShellDefinition;
|
||||
|
||||
export function getDuel(side: DuelSide, module: DuelModule) {
|
||||
const tank = tanks.tanks[Tankopedia.state[side].tank];
|
||||
|
||||
if (module === DuelModule.Tank) return tank;
|
||||
|
||||
if (module === DuelModule.Track) {
|
||||
return tank.tracks.find(
|
||||
(track) => track.id === Tankopedia.state[side].track,
|
||||
)!;
|
||||
}
|
||||
|
||||
if (module === DuelModule.Engine) {
|
||||
return tank.engines.find(
|
||||
(engine) => engine.id === Tankopedia.state[side].engine,
|
||||
)!;
|
||||
}
|
||||
|
||||
const turret = tank.turrets.find(
|
||||
(turret) => turret.id === Tankopedia.state[side].turret,
|
||||
)!;
|
||||
|
||||
if (module === DuelModule.Turret) return turret;
|
||||
|
||||
const gun = turret.guns.find((gun) => gun.id === Tankopedia.state[side].gun)!;
|
||||
|
||||
if (module === DuelModule.Gun) return gun;
|
||||
|
||||
return gun.shells.find((shell) => shell.id === Tankopedia.state[side].shell)!;
|
||||
}
|
||||
@@ -7,27 +7,32 @@ const tanks = await api.tanks();
|
||||
|
||||
export type DuelSide = "protagonist" | "antagonist";
|
||||
|
||||
export function useEquipment(id: number, side: DuelSide) {
|
||||
export function useEquipment(side: DuelSide, id: number) {
|
||||
const member = Tankopedia.use((state) => state[side]);
|
||||
const appliedEquipment = Tankopedia.use((state) => state[side].equipment);
|
||||
|
||||
const tank = tanks.tanks[member.tank];
|
||||
const preset = equipment.presets[tank.equipment_preset];
|
||||
const equipmentMatrix = Tankopedia.use(
|
||||
(state) => state[side].equipment_matrix,
|
||||
|
||||
const value = useMemo(
|
||||
() => hasEquipment(id, tank.equipment_preset, appliedEquipment),
|
||||
[appliedEquipment, member.tank],
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
return preset.slots.some((slot, index) => {
|
||||
const row = Math.floor(index / 3);
|
||||
const column = index % 3;
|
||||
const choice = equipmentMatrix[row][column];
|
||||
|
||||
if (choice === 0) return false;
|
||||
|
||||
const equipped = slot[choice === -1 ? "left" : "right"];
|
||||
|
||||
return equipped === id;
|
||||
});
|
||||
}, [equipmentMatrix, member.tank]);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function hasEquipment(
|
||||
id: number,
|
||||
_preset: string,
|
||||
applied: Record<number, number>,
|
||||
) {
|
||||
const preset = equipment.presets[_preset];
|
||||
|
||||
return preset.slots.some((slot, index) => {
|
||||
if (!(index in applied)) return false;
|
||||
|
||||
const choice = applied[index];
|
||||
|
||||
return slot.options[choice] === id;
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { api } from "../api/dynamic";
|
||||
|
||||
const equipmentDefinitions = await api.equipment();
|
||||
const equipment = await api.equipment();
|
||||
|
||||
export function useEquipmentPreset(preset: string) {
|
||||
return equipmentDefinitions.presets[preset];
|
||||
return equipment.presets[preset];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CamouflageDefinitions } from "@blitzkit/core";
|
||||
import { CamouflageDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConsumableDefinitions } from "@blitzkit/core";
|
||||
import { ConsumableDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GameDefinitions } from "@blitzkit/core";
|
||||
import { GameDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModelDefinitions } from "@blitzkit/core";
|
||||
import { ModelDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProvisionDefinitions } from "@blitzkit/core";
|
||||
import { ProvisionDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TankDefinitions } from "@blitzkit/core";
|
||||
import { TankDefinitions } from "@blitzkit/protos";
|
||||
import { api } from "../../../api/dynamic";
|
||||
|
||||
export { getStaticPaths } from "../_index";
|
||||
|
||||
@@ -90,15 +90,21 @@ function Content() {
|
||||
session.tracking && tankStatsB
|
||||
? deltaTankStats(session.player.stats, tankStatsB)
|
||||
.sort((a, b) => b.last_battle_time - a.last_battle_time)
|
||||
.map((entry) => {
|
||||
.flatMap((entry) => {
|
||||
if (!(entry.tank_id in tankDefinitions.tanks)) return [];
|
||||
|
||||
const tank = tankDefinitions.tanks[entry.tank_id];
|
||||
|
||||
if (!(tank.id in averageDefinitions.averages)) return [];
|
||||
|
||||
const average = averageDefinitions.averages[tank.id];
|
||||
|
||||
const composite = compositeStats(
|
||||
{ ...entry.all, battle_life_time: entry.battle_life_time },
|
||||
average?.mu!,
|
||||
average.mu!,
|
||||
);
|
||||
|
||||
return { tank, composite };
|
||||
return [{ tank, composite }];
|
||||
})
|
||||
: undefined,
|
||||
[session.tracking && session.player, tankStatsB],
|
||||
|
||||
@@ -20,7 +20,7 @@ export const Page = withErrorWrapper(
|
||||
Tankopedia.useInitialization(protagonistTank);
|
||||
|
||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
||||
// const protagonistEquipment = useEquipment(protagonistTank.tank!);
|
||||
// const protagonistEquipment = useEquipment("protagonist", protagonistTank.tank!);
|
||||
|
||||
const characteristics = useMemo(
|
||||
() => computeCharacteristics(),
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { createDefaultSkills } from "@blitzkit/core";
|
||||
import type { ShellDefinition, TankDefinition } from "@blitzkit/protos";
|
||||
import type {
|
||||
ModelDefinition,
|
||||
ShellDefinition,
|
||||
TankDefinition,
|
||||
} from "@blitzkit/protos";
|
||||
import type { Vector3 } from "three";
|
||||
import { Varuna } from "varuna";
|
||||
import { api } from "../api/dynamic";
|
||||
@@ -81,6 +85,9 @@ interface Tankopedia {
|
||||
|
||||
protagonist: TankState;
|
||||
antagonist: TankState;
|
||||
model: ModelDefinition;
|
||||
|
||||
equalize: boolean;
|
||||
|
||||
shot?: Shot;
|
||||
skills: Record<string, number>;
|
||||
@@ -118,6 +125,8 @@ export const Tankopedia = new Varuna<Tankopedia, TankDefinition>((tank) => ({
|
||||
antagonist: createTankState(tank),
|
||||
model: models.models[tank.id],
|
||||
|
||||
equalize: false,
|
||||
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
editStatic: false,
|
||||
skills: createDefaultSkills(skills),
|
||||
|
||||
@@ -13,12 +13,6 @@ const models = await api.models();
|
||||
const provisions = await api.provisions();
|
||||
const scripts = await api.scripts();
|
||||
|
||||
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
|
||||
[-1, 1, -1],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
];
|
||||
|
||||
export function createTankState(tank: TankDefinition) {
|
||||
const turret = tank.turrets.at(-1)!;
|
||||
const gun = turret.guns.at(-1)!;
|
||||
@@ -35,7 +29,7 @@ export function createTankState(tank: TankDefinition) {
|
||||
assault_distance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
|
||||
speed: 0,
|
||||
|
||||
equipment_matrix: genericDefaultEquipmentMatrix,
|
||||
equipment: {},
|
||||
status: createTankStatus(),
|
||||
|
||||
model: models.models[turret.id],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ModelDefinition } from "@blitzkit/core";
|
||||
import type { EquipmentMatrix } from "./createTankState";
|
||||
import type { ModelDefinition } from "@blitzkit/protos";
|
||||
|
||||
export interface TankState {
|
||||
tank: number;
|
||||
@@ -13,7 +12,7 @@ export interface TankState {
|
||||
assault_distance: number;
|
||||
speed: number;
|
||||
|
||||
equipment_matrix: EquipmentMatrix;
|
||||
equipment: Record<number, number>;
|
||||
status: Record<VehicleStatusKey, boolean>;
|
||||
|
||||
model: ModelDefinition;
|
||||
|
||||
Reference in new issue
Block a user