mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: new layout
This commit is contained in:
1 parent
281549add3
commit
9f2ced6bfd
5 files changed
+223
-225
No files matched your search
@@ -5,7 +5,7 @@ import { theme } from '../../../../../../../stitches.config';
|
||||
interface InfoProps {
|
||||
name: string;
|
||||
children: ReactNode;
|
||||
unit: string;
|
||||
unit?: string;
|
||||
indent?: boolean;
|
||||
highlight?: boolean;
|
||||
}
|
||||
@@ -24,10 +24,15 @@ export function Info({
|
||||
gap="4"
|
||||
>
|
||||
<Text color={highlight ? 'amber' : undefined}>
|
||||
{name}{' '}
|
||||
<Text color="gray" size="1">
|
||||
{unit}
|
||||
</Text>
|
||||
{name}
|
||||
{unit != undefined && (
|
||||
<>
|
||||
{' '}
|
||||
<Text color="gray" size="1">
|
||||
{unit}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { Flex, Heading, Tabs } from '@radix-ui/themes';
|
||||
import { Flex, Heading } from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { normalizeBoundingBox } from '../../../../../../core/blitzkrieg/normalizeBoundingBox';
|
||||
import { SHELL_NAMES } from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import {
|
||||
GUN_TYPE_NAMES,
|
||||
SHELL_NAMES,
|
||||
} from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
|
||||
import { useDuel } from '../../../../../../stores/duel';
|
||||
import { Camouflage } from './components/Camouflage';
|
||||
import { Consumables } from './components/Consumables';
|
||||
import { Crew } from './components/Crew';
|
||||
import { Equipments } from './components/Equipments';
|
||||
import { Info } from './components/Info';
|
||||
import { Modules } from './components/Modules';
|
||||
import { Provisions } from './components/Provisions';
|
||||
|
||||
export function Characteristics() {
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
@@ -47,216 +44,192 @@ export function Characteristics() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs.Root defaultValue="configure" style={{ width: '100%' }}>
|
||||
<Flex gap="4" direction="column">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="configure">Configure</Tabs.Trigger>
|
||||
<Tabs.Trigger value="statistics">Statistics</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="statistics">
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Survivability</Heading>
|
||||
<Info name="Health" unit="hp">
|
||||
{tank.health + turret.health}
|
||||
</Info>
|
||||
<Info name="Fire chance" unit="%">
|
||||
{Math.round(engine.fire_chance * 100)}
|
||||
</Info>
|
||||
<Info name="View range" unit="m">
|
||||
{turret.view_range}
|
||||
</Info>
|
||||
<Info name="Camouflage when still" unit="%">
|
||||
{(tank.camouflage.still * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Moving" unit="%">
|
||||
{(tank.camouflage.moving * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Shooting still" unit="%">
|
||||
{(tank.camouflage.still * gun.camouflageLoss * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Shooting on move" unit="%">
|
||||
{(tank.camouflage.moving * gun.camouflageLoss * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="On fire penalty" unit="%">
|
||||
{(tank.camouflage.onFire * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Size" unit="m">
|
||||
{size.map((component) => component.toFixed(2)).join(' x ')}
|
||||
</Info>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Fire</Heading>
|
||||
<Info name="Damage per minute" unit="hp / min">
|
||||
{dpm.toFixed(0)}
|
||||
</Info>
|
||||
{gun.type === 'auto_reloader' && (
|
||||
<>
|
||||
<Info indent name="Maximum" unit="hp / min">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Effective" unit="hp / min">
|
||||
TODO
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{gun.type === 'auto_reloader' ? (
|
||||
gun.reload.map((reload, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={
|
||||
index > 0 ? `Shell ${index + 1}` : 'Reload on shell 1'
|
||||
}
|
||||
unit="s"
|
||||
>
|
||||
{reload.toFixed(2)}
|
||||
</Info>
|
||||
))
|
||||
) : (
|
||||
<Info name="Reload" unit="s">
|
||||
{gun.reload.toFixed(2)}
|
||||
</Info>
|
||||
)}
|
||||
<Info name="Caliber" unit="mm">
|
||||
{shell.caliber}
|
||||
</Info>
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' penetration' : ''}`}
|
||||
unit="mm"
|
||||
>
|
||||
{resolveNearPenetration(shell.penetration)}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' damage' : ''}`}
|
||||
unit="hp"
|
||||
>
|
||||
{shell.damage.armor}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' module damage' : ''}`}
|
||||
unit="hp"
|
||||
>
|
||||
{shell.damage.module}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' velocity' : ''}`}
|
||||
unit="m/s"
|
||||
>
|
||||
{shell.speed}
|
||||
</Info>
|
||||
))}
|
||||
<Info name="Aim time" unit="s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Dispersion at 100m" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On move" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On hull traverse" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On turret traverse" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On shoot" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Gun depression" unit="°">
|
||||
{gunModelDefinition.pitch.max}
|
||||
</Info>
|
||||
<Info indent name="Elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.min}
|
||||
</Info>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
<>
|
||||
<Info indent name="Frontal depression" unit="°">
|
||||
{gunModelDefinition.pitch.front.max}
|
||||
</Info>
|
||||
<Info indent name="Frontal elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.front.min}
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
<>
|
||||
<Info indent name="Rear depression" unit="°">
|
||||
{gunModelDefinition.pitch.back.max}
|
||||
</Info>
|
||||
<Info indent name="Rear elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.back.min}
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{turretModelDefinition.yaw && (
|
||||
<Info indent name="Azimuth" unit="°">
|
||||
{-turretModelDefinition.yaw.min},{' '}
|
||||
{turretModelDefinition.yaw.max}
|
||||
</Info>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Maneuverability</Heading>
|
||||
<Info name="Speed forwards" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Average" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Backwards" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Effective power on hard terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On medium terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On soft terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Weight" unit="mt">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Traverse speed on hard terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On medium terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On soft terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="configure">
|
||||
<Flex direction="column" gap="4">
|
||||
<Modules />
|
||||
<Equipments />
|
||||
<Crew />
|
||||
<Consumables />
|
||||
<Provisions />
|
||||
<Camouflage />
|
||||
</Flex>
|
||||
</Tabs.Content>
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Survivability</Heading>
|
||||
<Info name="Health" unit="hp">
|
||||
{tank.health + turret.health}
|
||||
</Info>
|
||||
<Info name="Fire chance" unit="%">
|
||||
{Math.round(engine.fire_chance * 100)}
|
||||
</Info>
|
||||
<Info name="View range" unit="m">
|
||||
{turret.view_range}
|
||||
</Info>
|
||||
<Info name="Camouflage when still" unit="%">
|
||||
{(tank.camouflage.still * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Moving" unit="%">
|
||||
{(tank.camouflage.moving * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Shooting still" unit="%">
|
||||
{(tank.camouflage.still * gun.camouflageLoss * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Shooting on move" unit="%">
|
||||
{(tank.camouflage.moving * gun.camouflageLoss * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="On fire penalty" unit="%">
|
||||
{(tank.camouflage.onFire * 100).toFixed(2)}
|
||||
</Info>
|
||||
<Info indent name="Size" unit="m">
|
||||
{size.map((component) => component.toFixed(2)).join(' x ')}
|
||||
</Info>
|
||||
</Flex>
|
||||
</Tabs.Root>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Fire</Heading>
|
||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||
<Info name="Damage per minute" unit="hp / min">
|
||||
{dpm.toFixed(0)}
|
||||
</Info>
|
||||
{gun.type === 'auto_reloader' && (
|
||||
<>
|
||||
<Info indent name="Maximum" unit="hp / min">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Effective" unit="hp / min">
|
||||
TODO
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{gun.type === 'auto_reloader' ? (
|
||||
gun.reload.map((reload, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={index > 0 ? `Shell ${index + 1}` : 'Reload on shell 1'}
|
||||
unit="s"
|
||||
>
|
||||
{reload.toFixed(2)}
|
||||
</Info>
|
||||
))
|
||||
) : (
|
||||
<Info name="Reload" unit="s">
|
||||
{gun.reload.toFixed(2)}
|
||||
</Info>
|
||||
)}
|
||||
<Info name="Caliber" unit="mm">
|
||||
{shell.caliber}
|
||||
</Info>
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' penetration' : ''}`}
|
||||
unit="mm"
|
||||
>
|
||||
{resolveNearPenetration(shell.penetration)}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' damage' : ''}`}
|
||||
unit="hp"
|
||||
>
|
||||
{shell.damage.armor}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' module damage' : ''}`}
|
||||
unit="hp"
|
||||
>
|
||||
{shell.damage.module}
|
||||
</Info>
|
||||
))}
|
||||
{gun.shells.map((shell, index) => (
|
||||
<Info
|
||||
indent={index > 0}
|
||||
name={`${SHELL_NAMES[shell.type]}${index === 0 ? ' velocity' : ''}`}
|
||||
unit="m/s"
|
||||
>
|
||||
{shell.speed}
|
||||
</Info>
|
||||
))}
|
||||
<Info name="Aim time" unit="s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Dispersion at 100m" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On move" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On hull traverse" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On turret traverse" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On shoot" unit="m">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Gun depression" unit="°">
|
||||
{gunModelDefinition.pitch.max}
|
||||
</Info>
|
||||
<Info indent name="Elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.min}
|
||||
</Info>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
<>
|
||||
<Info indent name="Frontal depression" unit="°">
|
||||
{gunModelDefinition.pitch.front.max}
|
||||
</Info>
|
||||
<Info indent name="Frontal elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.front.min}
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
<>
|
||||
<Info indent name="Rear depression" unit="°">
|
||||
{gunModelDefinition.pitch.back.max}
|
||||
</Info>
|
||||
<Info indent name="Rear elevation" unit="°">
|
||||
{-gunModelDefinition.pitch.back.min}
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{turretModelDefinition.yaw && (
|
||||
<Info indent name="Azimuth" unit="°">
|
||||
{-turretModelDefinition.yaw.min}, {turretModelDefinition.yaw.max}
|
||||
</Info>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Maneuverability</Heading>
|
||||
<Info name="Speed forwards" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Average" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="Backwards" unit="km/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Effective power on hard terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On medium terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On soft terrain" unit="hp/kg">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Weight" unit="mt">
|
||||
TODO
|
||||
</Info>
|
||||
<Info name="Traverse speed on hard terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On medium terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
<Info indent name="On soft terrain" unit="°/s">
|
||||
TODO
|
||||
</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,12 @@ import { mutateDuel, useDuel } from '../../../../stores/duel';
|
||||
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
|
||||
import { AntagonistBar } from './components/AntagonistBar';
|
||||
import { Characteristics } from './components/Characteristics';
|
||||
import { Camouflage } from './components/Characteristics/components/Camouflage';
|
||||
import { Consumables } from './components/Characteristics/components/Consumables';
|
||||
import { Crew } from './components/Characteristics/components/Crew';
|
||||
import { Equipments } from './components/Characteristics/components/Equipments';
|
||||
import { Modules } from './components/Characteristics/components/Modules';
|
||||
import { Provisions } from './components/Characteristics/components/Provisions';
|
||||
import { TankSandbox } from './components/Model/TankSandbox';
|
||||
import { Title } from './components/Title';
|
||||
|
||||
@@ -92,13 +98,21 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
direction="column"
|
||||
style={{
|
||||
flex: 1,
|
||||
position: wideFormat ? 'sticky' : undefined,
|
||||
width: '100%',
|
||||
top: 64,
|
||||
}}
|
||||
>
|
||||
<TankSandbox />
|
||||
<AntagonistBar />
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Modules />
|
||||
<Equipments />
|
||||
<Crew />
|
||||
<Consumables />
|
||||
<Provisions />
|
||||
<Camouflage />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex style={{ width: wideFormat ? 320 : '100%' }}>
|
||||
|
||||
@@ -5,7 +5,7 @@ export class EventManager<Type> {
|
||||
this.callbacks.add(callback);
|
||||
}
|
||||
off(callback: (data: Type) => void) {
|
||||
this.callbacks.delete(callback);
|
||||
return this.callbacks.delete(callback);
|
||||
}
|
||||
|
||||
emit(data: Type) {
|
||||
|
||||
@@ -91,6 +91,12 @@ export type ShellDefinition = {
|
||||
explosionRadius?: number;
|
||||
};
|
||||
|
||||
export const GUN_TYPE_NAMES: Record<GunDefinition['type'], string> = {
|
||||
regular: 'Regular',
|
||||
auto_loader: 'Auto loader',
|
||||
auto_reloader: 'Auto reloader',
|
||||
};
|
||||
|
||||
export const tankDefinitions = fetch(asset('definitions/tanks.json'), {
|
||||
cache: 'no-cache',
|
||||
}).then(async (response) => response.json() as Promise<TankDefinitions>);
|
||||
|
||||
Reference in new issue
Block a user