mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-30 05:59:51 +01:00
feat: battle count
This commit is contained in:
1 parent
22831cc268
commit
9a686230c6
3 files changed
+89
-13
No files matched your search
@@ -2,13 +2,36 @@ import { Box, Flex, Text } from '@radix-ui/themes';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { use } from 'react';
|
import { use } from 'react';
|
||||||
import { asset } from '../../../../../../../../core/blitzkit/asset';
|
import { asset } from '../../../../../../../../core/blitzkit/asset';
|
||||||
|
import { averageDefinitions } from '../../../../../../../../core/blitzkit/averageDefinitions';
|
||||||
import { tankDefinitions } from '../../../../../../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../../../../../../core/blitzkit/tankDefinitions';
|
||||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../../../core/blitzkit/tankDefinitions/constants';
|
import { TIER_ROMAN_NUMERALS } from '../../../../../../../../core/blitzkit/tankDefinitions/constants';
|
||||||
import { formatCompact } from '../../../../../../../../core/math/formatCompact';
|
import { formatCompact } from '../../../../../../../../core/math/formatCompact';
|
||||||
|
import * as TankopediaEphemeral from '../../../../../../../../stores/tankopediaEphemeral';
|
||||||
|
|
||||||
export function Node({ id, highlight }: { id: number; highlight?: boolean }) {
|
interface NodeProps {
|
||||||
|
id: number;
|
||||||
|
nextIds?: number[];
|
||||||
|
highlight?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Node({ id, highlight, nextIds }: NodeProps) {
|
||||||
|
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const tank = awaitedTankDefinitions[id];
|
const tank = awaitedTankDefinitions[id];
|
||||||
|
const nextTanks = nextIds?.map((id) => awaitedTankDefinitions[id]);
|
||||||
|
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||||
|
const thisTankXp =
|
||||||
|
tank.xp === undefined || tank.tier === 1 ? 0 : tank.xp! / xpMultiplier;
|
||||||
|
const nextTanksXp = nextTanks?.reduce(
|
||||||
|
(xp, tank) =>
|
||||||
|
xp +
|
||||||
|
(tank.xp === undefined || tank.tier === 1 ? 0 : tank.xp! / xpMultiplier),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const averages = awaitedAverageDefinitions.averages[tank.id]?.mu;
|
||||||
|
const averageXp = averages ? averages.xp / averages.battles : undefined;
|
||||||
|
const games =
|
||||||
|
averages && nextTanks ? Math.round(nextTanksXp! / averageXp!) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -60,7 +83,6 @@ export function Node({ id, highlight }: { id: number; highlight?: boolean }) {
|
|||||||
</Text>
|
</Text>
|
||||||
<Text wrap="nowrap">{tank.name}</Text>
|
<Text wrap="nowrap">{tank.name}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
<Text color="gray" size="1" wrap="nowrap">
|
<Text color="gray" size="1" wrap="nowrap">
|
||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
@@ -74,12 +96,9 @@ export function Node({ id, highlight }: { id: number; highlight?: boolean }) {
|
|||||||
objectPosition: 'center',
|
objectPosition: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{tank.xp === undefined || tank.tier === 1
|
{formatCompact(thisTankXp)}
|
||||||
? 0
|
|
||||||
: formatCompact(tank.xp!)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text color="gray" size="1" wrap="nowrap">
|
<Text color="gray" size="1" wrap="nowrap">
|
||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
<img
|
<img
|
||||||
@@ -96,6 +115,12 @@ export function Node({ id, highlight }: { id: number; highlight?: boolean }) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{averages && nextTanks && (
|
||||||
|
<Text color="gray" size="1" mt="2">
|
||||||
|
{games!} {games === 1 ? 'battle' : 'battles'}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
|
|||||||
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
|
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
|
||||||
import { use, useEffect, useMemo, useRef, useState } from 'react';
|
import { use, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { asset } from '../../../../../../../core/blitzkit/asset';
|
import { asset } from '../../../../../../../core/blitzkit/asset';
|
||||||
|
import { imgur } from '../../../../../../../core/blitzkit/imgur';
|
||||||
import { tankDefinitions } from '../../../../../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../../../../../core/blitzkit/tankDefinitions';
|
||||||
import * as Duel from '../../../../../../../stores/duel';
|
import * as Duel from '../../../../../../../stores/duel';
|
||||||
|
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
||||||
import { Arrow } from './components/Arrow';
|
import { Arrow } from './components/Arrow';
|
||||||
import { Node } from './components/Node';
|
import { Node } from './components/Node';
|
||||||
|
|
||||||
type Line = number[];
|
type Line = number[];
|
||||||
|
|
||||||
|
export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
|
||||||
|
|
||||||
export function TechTreeSection() {
|
export function TechTreeSection() {
|
||||||
|
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
||||||
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const master = Duel.use((state) => state.protagonist.tank);
|
const master = Duel.use((state) => state.protagonist.tank);
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
@@ -94,7 +100,7 @@ export function TechTreeSection() {
|
|||||||
objectPosition: 'center',
|
objectPosition: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{totalXp.toLocaleString()}
|
{Math.round(totalXp / xpMultiplier).toLocaleString()}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -115,16 +121,58 @@ export function TechTreeSection() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Flex gap="1" mt="2">
|
||||||
|
{XP_MULTIPLIERS.map((multiplier) => {
|
||||||
|
const selected = xpMultiplier === multiplier;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
background: `url(${imgur('7hDltb4')})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
opacity: selected ? 1 : 0.5,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.xpMultiplier = multiplier;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
key={multiplier}
|
||||||
|
width="2.5rem"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
pt="1"
|
||||||
|
pl="1"
|
||||||
|
>
|
||||||
|
<Text size="2">x{multiplier}</Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<ScrollArea type="hover" scrollbars="horizontal" ref={container}>
|
<ScrollArea type="hover" scrollbars="horizontal" ref={container}>
|
||||||
<Flex align="center" gap="2" justify="center" p="4">
|
<Flex align="center" gap="2" justify="center" p="4">
|
||||||
{line.map((id, index) => (
|
{line.map((id, index) => {
|
||||||
<>
|
const last = index === line.length - 1;
|
||||||
{index > 0 && <Arrow />}
|
const tank = awaitedTankDefinitions[id];
|
||||||
<Node key={id} id={id} highlight={index === line.length - 1} />
|
|
||||||
</>
|
return (
|
||||||
))}
|
<>
|
||||||
|
{index > 0 && <Arrow />}
|
||||||
|
<Node
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
nextIds={last ? tank.successors : [line[index + 1]]}
|
||||||
|
highlight={last}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{master.tier < 10 && (
|
{master.tier < 10 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { Vector3 } from 'three';
|
import { Vector3 } from 'three';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
import { subscribeWithSelector } from 'zustand/middleware';
|
||||||
|
import { XP_MULTIPLIERS } from '../app/tools/tankopedia/[id]/components/Model/TechTreeSection';
|
||||||
import { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
import { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
||||||
import { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
import { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
||||||
import { ModelDefinition } from '../core/blitzkit/modelDefinitions';
|
import { ModelDefinition } from '../core/blitzkit/modelDefinitions';
|
||||||
@@ -28,6 +29,7 @@ interface TankopediaEphemeral {
|
|||||||
}
|
}
|
||||||
| { type: ArmorType.External }
|
| { type: ArmorType.External }
|
||||||
);
|
);
|
||||||
|
xpMultiplier: (typeof XP_MULTIPLIERS)[number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShotLayerBase {
|
export interface ShotLayerBase {
|
||||||
@@ -97,6 +99,7 @@ export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
|||||||
skills: {},
|
skills: {},
|
||||||
model,
|
model,
|
||||||
controlsEnabled: true,
|
controlsEnabled: true,
|
||||||
|
xpMultiplier: 1,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Reference in new issue
Block a user