import { alias, TankType } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons"; import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes"; import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { api } from "../../../core/blitzkit/api"; import { useLocale } from "../../../hooks/useLocale"; import { Duel } from "../../../stores/duel"; import { Tankopedia } from "../../../stores/tankopedia"; import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; import { Arrow } from "./components/Arrow"; import { Node } from "./components/Node"; type Line = number[]; const tankDefinitions = await api.tankDefinitions(); export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10]; export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) { const { locale, strings } = useLocale(); const master = Duel.use((state) => state.protagonist.tank); const xpMultiplier = Tankopedia.use((state) => state.xpMultiplier); const container = useRef(null); const lines = useMemo(() => { function extend(line: Line): Line[] { const root = tankDefinitions.tanks[line.at(-1)!]; if ( root.ancestors === undefined || root.ancestors.length === 0 || root.tier === 1 ) { return [line]; } else { if (root.ancestors.length === 1 || root.tier === 2) { line.push( root.ancestors.find( (ancestor) => !tankDefinitions.tanks[ancestor].deprecated, ) ?? root.ancestors[0], ); return extend(line); } else { return root.ancestors .map((ancestor) => { const newLine = [...line, ancestor]; return extend(newLine); }) .flat(); } } } return extend([master.id]); }, [master]); const [lineIndex, setLineIndex] = useState(0); const line = useMemo( () => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()), [master, lineIndex], ); const totalXp = line.reduce( (xp, id) => xp + (tankDefinitions.tanks[id].research_cost === undefined || tankDefinitions.tanks[id].tier === 1 ? 0 : (tankDefinitions.tanks[id].research_cost.research_cost_type! .value as number)), 0, ); const totalCredits = line.reduce( (credits, id) => credits + tankDefinitions.tanks[id].price!.value, 0, ); useEffect(() => { if (!container.current) return; container.current.scrollLeft = container.current.scrollWidth; }); if ( master.type !== TankType.TANK_TYPE_RESEARCHABLE || master.ancestors === undefined || master.ancestors.length === 0 ) { return null; } return ( {strings.website.tools.tankopedia.tech_tree.title} {master.tier > 1 && ( XP {Math.round(totalXp / xpMultiplier).toLocaleString(locale)} Silver {totalCredits.toLocaleString(locale)} )} {XP_MULTIPLIERS.map((multiplier) => { const selected = xpMultiplier === multiplier; return ( { Tankopedia.mutate((draft) => { draft.xpMultiplier = multiplier; }); }} width="2.5rem" justify="center" align="center" pt="1" pl="1" > {literals(strings.common.units.x, { value: multiplier })} ); })} {line.map((id, index) => { const last = index === line.length - 1; const tank = tankDefinitions.tanks[id]; return ( {index > 0 && } ); })} {master.successors.length > 0 && ( <> {master.successors!.map((id, index) => ( {index > 0 && ( )} ))} )} {lines.length > 1 && ( setLineIndex((lines.length + lineIndex - 1) % lines.length) } > {literals(strings.website.tools.tankopedia.tech_tree.route, { index: lineIndex + 1, count: lines.length, })} setLineIndex((lineIndex + 1) % lines.length)} > )} ); }