feat: custom shell customizer ui

This commit is contained in:
tresabhi committed 2024-11-10 20:50:46 -06:00
1 parent e364d1fda2
commit 9a1c66932a
3 files changed
+257 -57

No files matched your search

@@ -0,0 +1,220 @@
import { imgur, ShellType } from '@blitzkit/core';
import {
Button,
Dialog,
Flex,
IconButton,
Select,
Slider,
Text,
TextField,
} from '@radix-ui/themes';
import { invalidate } from '@react-three/fiber';
import { useEffect, useState, type ChangeEvent } from 'react';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
export function CustomShellButton() {
const customShell = TankopediaEphemeral.use((state) => state.customShell);
const antagonistShell = Duel.use((state) => state.antagonist.shell);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const [customShellDraft, setCustomShellDraft] = useState(
customShell ?? antagonistShell,
);
useEffect(() => {
setCustomShellDraft(customShell ?? antagonistShell);
}, [antagonistShell, customShell]);
return (
<Dialog.Root>
<Dialog.Trigger>
<IconButton
color={customShell ? undefined : 'gray'}
variant="soft"
size={{ initial: '2', sm: '3' }}
radius="none"
>
<img
alt="custom shell"
src={imgur('j2CoXak')}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Custom shell</Dialog.Title>
<Flex direction="column" gap="2">
<Flex align="center" justify="between">
<Text>Type</Text>
<Select.Root
value={`${customShellDraft.type}`}
onValueChange={(value) => {
const type = Number(value) as ShellType;
const modified = { ...customShellDraft, type };
switch (type) {
case ShellType.AP: {
modified.normalization = 5;
modified.ricochet = 70;
break;
}
case ShellType.APCR: {
modified.normalization = 2;
modified.ricochet = 70;
break;
}
case ShellType.HEAT:
case ShellType.HE: {
modified.normalization = undefined;
modified.ricochet = undefined;
break;
}
}
setCustomShellDraft(modified);
}}
>
<Select.Trigger />
<Select.Content>
<Select.Item value={`${ShellType.AP}`}>AP</Select.Item>
<Select.Item value={`${ShellType.APCR}`}>APCR</Select.Item>
<Select.Item value={`${ShellType.HEAT}`}>HEAT</Select.Item>
<Select.Item value={`${ShellType.HE}`}>HE</Select.Item>
</Select.Content>
</Select.Root>
</Flex>
<Flex align="center" justify="between">
<Text>Caliber</Text>
<TextField.Root
style={{ maxWidth: '6rem' }}
defaultValue={customShellDraft.caliber}
type="number"
onBlur={(event: ChangeEvent<HTMLInputElement>) => {
setCustomShellDraft({
...customShellDraft,
caliber: event.target.valueAsNumber,
});
}}
onFocus={(event) => event.target.select()}
onKeyDown={(event) => {
if (event.key === 'Enter') {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
(event.target as HTMLInputElement).valueAsNumber =
customShellDraft.caliber;
(event.target as HTMLInputElement).blur();
}
}}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</Flex>
<Flex align="center" justify="between">
<Text>Penetration</Text>
<TextField.Root
style={{ maxWidth: '6rem' }}
defaultValue={customShellDraft.penetration.near}
type="number"
onBlur={(event: ChangeEvent<HTMLInputElement>) => {
setCustomShellDraft({
...customShellDraft,
penetration: {
near: event.target.valueAsNumber,
far: event.target.valueAsNumber,
},
});
}}
onFocus={(event) => event.target.select()}
onKeyDown={(event) => {
if (event.key === 'Enter') {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
(event.target as HTMLInputElement).valueAsNumber =
customShellDraft.penetration.near;
(event.target as HTMLInputElement).blur();
}
}}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</Flex>
{(customShellDraft.type === ShellType.AP ||
customShellDraft.type === ShellType.APCR) && (
<>
<Flex align="center" justify="between" gap="4" py="1">
<Text>Normalization</Text>
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
<Text color="gray">
{customShellDraft.normalization ?? 0}°
</Text>
<Slider
min={0}
max={90}
value={[customShellDraft.normalization ?? 0]}
onValueChange={([value]) =>
setCustomShellDraft({
...customShellDraft,
normalization: value,
})
}
/>
</Flex>
</Flex>
<Flex align="center" justify="between" gap="4" py="1">
<Text>Ricochet</Text>
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
<Text color="gray">{customShellDraft.ricochet ?? 90}°</Text>
<Slider
min={0}
max={90}
value={[customShellDraft.ricochet ?? 90]}
onValueChange={([value]) =>
setCustomShellDraft({
...customShellDraft,
ricochet: value,
})
}
/>
</Flex>
</Flex>
</>
)}
</Flex>
<Flex justify="end" mt="6" gap="2">
<Dialog.Close>
<Button color="red" variant="outline">
Cancel
</Button>
</Dialog.Close>
<Dialog.Close>
<Button
onClick={() => {
invalidate();
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.customShell = customShellDraft;
});
}}
>
Apply
</Button>
</Dialog.Close>
</Flex>
</Dialog.Content>
</Dialog.Root>
);
}
@@ -42,6 +42,7 @@ import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
import { ModuleButton } from '../../../../ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../SmallTankIcon';
import { TankSearch } from '../../../../TankSearch';
import { CustomShellButton } from './components/CustomShellButton';
import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher';
import { QuickInputs } from './components/QuickInputs';
import { Thicknesses } from './components/Thicknesses';
@@ -52,6 +53,7 @@ interface OptionsProps {
}
export function Options({ thicknessRange, canvas }: OptionsProps) {
const customShell = TankopediaEphemeral.use((state) => state.customShell);
const display = TankopediaPersistent.useDeferred(
(state) => state.display,
TankopediaDisplay.Model,
@@ -184,64 +186,40 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
}}
overflow="hidden"
>
{antagonistGun.gun_type!.value.base.shells.map(
(thisShell, shellIndex) => (
<IconButton
color={
thisShell.id === antagonistShell.id ? undefined : 'gray'
}
variant="soft"
key={thisShell.id}
size={{ initial: '2', sm: '3' }}
radius="none"
onClick={() => {
invalidate();
mutateDuel((draft) => {
draft.antagonist.shell = thisShell;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt={thisShell.name}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
),
)}
<IconButton
// color={
// thisShell.id === antagonistShell.id ? undefined : 'gray'
// }
variant="soft"
size={{ initial: '2', sm: '3' }}
radius="none"
onClick={() => {
invalidate();
// mutateDuel((draft) => {
// draft.antagonist.shell = thisShell;
// });
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt="custom shell"
src={imgur('j2CoXak')}
style={{
width: '50%',
height: '50%',
{antagonistGun.gun_type!.value.base.shells.map((thisShell) => (
<IconButton
color={
thisShell.id === antagonistShell.id && !customShell
? undefined
: 'gray'
}
variant="soft"
key={thisShell.id}
size={{ initial: '2', sm: '3' }}
radius="none"
onClick={() => {
invalidate();
mutateDuel((draft) => {
draft.antagonist.shell = thisShell;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.customShell = undefined;
});
}}
/>
</IconButton>
>
<img
alt={thisShell.name}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
))}
<CustomShellButton />
</Flex>
<Flex
@@ -1,6 +1,7 @@
import {
createDefaultSkills,
fetchSkillDefinitions,
ShellDefinition,
type ModelDefinition,
} from '@blitzkit/core';
import type { Vector3 } from 'three';
@@ -91,6 +92,7 @@ interface TankopediaEphemeral {
| { type: ArmorType.External }
);
xpMultiplier: (typeof XP_MULTIPLIERS)[number];
customShell?: ShellDefinition;
}
const skillDefinitions = await fetchSkillDefinitions();