feat: crew basics

This commit is contained in:
TrèsAbhi committed 2024-02-16 21:07:48 -06:00
1 parent b1e5502b04
commit 2c8c04387b
11 files changed
+183 -42

No files matched your search

@@ -1,25 +0,0 @@
import { Flex, Heading, Slider, TextField } from '@radix-ui/themes';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Crew() {
return (
<ConfigurationChildWrapper>
<Heading size="4">Crew</Heading>
<Flex
align="center"
gap="4"
justify="end"
style={{
width: 240,
}}
>
<Slider min={50} max={100} defaultValue={[100]} style={{ flex: 1 }} />
<TextField.Root style={{ width: 64 }}>
<TextField.Input defaultValue={100} />
<TextField.Slot>%</TextField.Slot>
</TextField.Root>
</Flex>
</ConfigurationChildWrapper>
);
}
@@ -1,4 +1,6 @@
import { Flex, Heading } from '@radix-ui/themes';
import { Flex, Heading, Slider, TextField } from '@radix-ui/themes';
import { clamp, debounce } from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react';
import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
@@ -12,12 +14,77 @@ export function Miscellaneous() {
const cooldownBooster = useTankopediaTemporary(
(state) => state.cooldownBooster,
);
const crew = useTankopediaTemporary((state) => state.crew);
const crewInput = useRef<HTMLInputElement>(null);
const [crewDraft, setCrewDraft] = useState(crew);
const debouncedApplyDraft = useMemo(
() =>
debounce((value) => {
mutateTankopediaTemporary((draft) => {
draft.crew = value;
});
}, 500),
[],
);
useEffect(() => {
setCrewDraft(crew);
if (!crewInput.current) return;
crewInput.current.value = `${Math.round(crew * 100)}`;
}, [crew]);
return (
<ConfigurationChildWrapper>
<Heading size="4">Miscellaneous</Heading>
<Flex gap="2">
<Flex gap="2" align="center">
<Flex
align="center"
direction="column"
gap="1"
justify="end"
style={{ width: 180 }}
>
<TextField.Root>
<TextField.Slot>Crew</TextField.Slot>
<TextField.Input
ref={crewInput}
defaultValue={Math.round(crew * 100)}
style={{ textAlign: 'right' }}
onBlur={() => {
const newValue = Number(crewInput.current!.value) / 100;
if (isNaN(newValue)) {
crewInput.current!.value = `${Math.round(crew * 100)}`;
return;
}
mutateTankopediaTemporary((draft) => {
draft.crew = clamp(newValue / 100, 0.5, 1);
});
}}
onKeyUp={(event) => {
if (event.key === 'Enter') {
crewInput.current?.blur();
}
}}
/>
<TextField.Slot>%</TextField.Slot>
</TextField.Root>
<Slider
min={50}
max={100}
value={[crewDraft * 100]}
style={{ width: '100%' }}
onValueChange={([value]) => {
setCrewDraft(value / 100);
debouncedApplyDraft(value / 100);
}}
/>
</Flex>
<GenericTankComponentButton
icon={asset('icons/camo.webp')}
selected={camouflage}
@@ -7,7 +7,11 @@ import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNear
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
import { normalizeBoundingBox } from '../../../../../../core/blitzkrieg/normalizeBoundingBox';
import { resolveDpm } from '../../../../../../core/blitzkrieg/resolveDpm';
import { GUN_TYPE_NAMES } from '../../../../../../core/blitzkrieg/tankDefinitions';
import {
CREW_MEMBER_NAMES,
CREW_MEMBER_NAMES_PLURAL,
GUN_TYPE_NAMES,
} from '../../../../../../core/blitzkrieg/tankDefinitions';
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
import { useConsumable } from '../../../../../../core/blitzkrieg/useConsumable';
import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipment';
@@ -21,6 +25,7 @@ export function Characteristics() {
const { tank, turret, gun, engine, track, shell } = useDuel(
(state) => state.protagonist!,
);
const crew = useTankopediaTemporary((state) => state.crew);
const stockEngine = tank.engines[0];
const stockTrack = tank.tracks[0];
const stockTurret = tank.turrets[0];
@@ -497,6 +502,34 @@ export function Characteristics() {
{size[0].toFixed(2)} x {size[2].toFixed(2)} x {size[1].toFixed(2)}
</Info>
</Flex>
<Flex direction="column" gap="2">
<Heading size="5">Crew</Heading>
{tank.crew.map((member) => {
const count = member.count ?? 1;
return (
<>
<InfoWithDelta
name={`${count > 1 ? `${member.count} ` : ''}${
(count > 1 ? CREW_MEMBER_NAMES_PLURAL : CREW_MEMBER_NAMES)[
member.type
]
}`}
unit="%"
decimals={0}
>
{crew * (member.type === 'commander' ? 1 : 1.1) * 100}
</InfoWithDelta>
{member.substitute &&
member.substitute.map((substitute) => (
<Info indent name={`Substitutes ${substitute}`} />
))}
</>
);
})}
</Flex>
</Flex>
);
}
@@ -3,6 +3,7 @@ import { Badge, Button, Card, Flex, Tabs, Theme } from '@radix-ui/themes';
import { PerspectiveCamera } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { Suspense, use, useEffect, useRef, useState } from 'react';
import { isMobile } from 'react-device-detect';
import { ShotDisplay } from '../../../../../../components/ShotDisplay';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
@@ -10,6 +11,7 @@ import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTran
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
import { tankIcon } from '../../../../../../core/blitzkrieg/tankIcon';
import { useModel } from '../../../../../../hooks/useModel';
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
import { useDuel } from '../../../../../../stores/duel';
import {
TankopediaMode,
@@ -38,7 +40,8 @@ export function TankSandbox() {
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const [loadModel, setLoadModel] = useState(true); // set to false someday?
const wideFormat = useWideFormat();
const [loadModel, setLoadModel] = useState(wideFormat || !isMobile);
const duel = useDuel();
const { hasPbr } = useModel(protagonist.tank.id);
@@ -204,7 +207,6 @@ export function TankSandbox() {
style={{ height: '100%', position: 'relative' }}
>
<Button
variant="soft"
style={{
zIndex: 1,
}}
@@ -215,21 +217,21 @@ export function TankSandbox() {
<LightningBoltIcon /> Load model
</Button>
<div
<Flex
style={{
filter: 'blur(16px)',
width: 256,
height: 128,
display: 'flex',
justifyContent: 'end',
position: 'absolute',
top: '50%',
left: '50%',
transformOrigin: 'center center',
transform: 'translate(25%, -25%) scale(300%)',
}}
>
<img src={tankIcon(duel.antagonist!.tank.id, 'big')} />
</div>
</Flex>
</Flex>
)}
</div>
+2 -3
View File
@@ -4,12 +4,12 @@ import { Flex } from '@radix-ui/themes';
import { use, useEffect } from 'react';
import PageWrapper from '../../../../components/PageWrapper';
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
import { useWideFormat } from '../../../../hooks/useWideFormat';
import { mutateDuel, useDuel } from '../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
import { AntagonistBar } from './components/AntagonistBar';
import { Characteristics } from './components/Characteristics';
import { Consumables } from './components/Characteristics/components/Consumables';
import { Crew } from './components/Characteristics/components/Crew';
import { Equipments } from './components/Characteristics/components/Equipments';
import { Miscellaneous } from './components/Characteristics/components/Miscellaneous';
import { Modules } from './components/Characteristics/components/Modules';
@@ -21,7 +21,7 @@ export default function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
const awaitedTankDefinitions = use(tankDefinitions);
const assigned = useDuel((state) => state.assigned);
const wideFormat = typeof window !== 'undefined' && window.innerWidth > 880;
const wideFormat = useWideFormat();
useEffect(() => {
mutateDuel((draft) => {
@@ -121,7 +121,6 @@ export default function Page({ params }: { params: { id: string } }) {
<Modules />
<Equipments />
<Consumables />
<Crew />
<Provisions />
<Miscellaneous />
</Flex>