mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: isolated duel
This commit is contained in:
1 parent
fce5f19100
commit
0b5c2e42fb
15 files changed
+218
-249
No files matched your search
@@ -9,25 +9,17 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { produce } from 'immer';
|
import { useState } from 'react';
|
||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
|
||||||
import { ModuleButton } from '../../../../../../components/ModuleButton';
|
import { ModuleButton } from '../../../../../../components/ModuleButton';
|
||||||
import { SmallTankIcon } from '../../../../../../components/SmallTankIcon';
|
import { SmallTankIcon } from '../../../../../../components/SmallTankIcon';
|
||||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||||
import { SHELL_NAMES } from '../../../../../../core/blitzkrieg/tankDefinitions';
|
import { SHELL_NAMES } from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||||
|
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
||||||
import { TankSearch } from '../../../components/TankSearch';
|
import { TankSearch } from '../../../components/TankSearch';
|
||||||
import { Duel } from '../../page';
|
|
||||||
|
|
||||||
interface AntagonistBarProps {
|
export function AntagonistBar() {
|
||||||
duel: Duel;
|
const antagonist = useDuel((state) => state.antagonist!);
|
||||||
setDuel: Dispatch<SetStateAction<Duel>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AntagonistBar({
|
|
||||||
duel: { antagonist },
|
|
||||||
setDuel,
|
|
||||||
}: AntagonistBarProps) {
|
|
||||||
const mode = useTankopediaTemporary((state) => state.mode);
|
const mode = useTankopediaTemporary((state) => state.mode);
|
||||||
const [tab, setTab] = useState('search');
|
const [tab, setTab] = useState('search');
|
||||||
const [antagonistSelectorOpen, setAntagonistSelectorVisible] =
|
const [antagonistSelectorOpen, setAntagonistSelectorVisible] =
|
||||||
@@ -50,11 +42,9 @@ export function AntagonistBar({
|
|||||||
key={shell.id}
|
key={shell.id}
|
||||||
last={index === antagonist.gun.shells.length - 1}
|
last={index === antagonist.gun.shells.length - 1}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.antagonist!.shell = shell;
|
||||||
draft.antagonist.shell = shell;
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -109,16 +99,14 @@ export function AntagonistBar({
|
|||||||
<TankSearch
|
<TankSearch
|
||||||
compact
|
compact
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.antagonist!.tank = tank;
|
||||||
draft.antagonist.tank = tank;
|
draft.antagonist!.turret = tank.turrets.at(-1)!;
|
||||||
draft.antagonist.turret = tank.turrets.at(-1)!;
|
draft.antagonist!.gun =
|
||||||
draft.antagonist.gun =
|
draft.antagonist!.turret.guns.at(-1)!;
|
||||||
draft.antagonist.turret.guns.at(-1)!;
|
draft.antagonist!.shell =
|
||||||
draft.antagonist.shell =
|
draft.antagonist!.gun.shells[0];
|
||||||
draft.antagonist.gun.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
setAntagonistSelectorVisible(false);
|
setAntagonistSelectorVisible(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -144,15 +132,13 @@ export function AntagonistBar({
|
|||||||
}
|
}
|
||||||
key={turret.id}
|
key={turret.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.antagonist!.turret = turret;
|
||||||
draft.antagonist.turret = turret;
|
draft.antagonist!.gun =
|
||||||
draft.antagonist.gun =
|
turret.guns.at(-1)!;
|
||||||
turret.guns.at(-1)!;
|
draft.antagonist!.shell =
|
||||||
draft.antagonist.shell =
|
draft.antagonist!.gun.shells[0];
|
||||||
draft.antagonist.gun.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
selected={antagonist.turret.id === turret.id}
|
selected={antagonist.turret.id === turret.id}
|
||||||
tier={turret.tier}
|
tier={turret.tier}
|
||||||
@@ -172,12 +158,10 @@ export function AntagonistBar({
|
|||||||
index === antagonist.turret.guns.length - 1
|
index === antagonist.turret.guns.length - 1
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.antagonist!.gun = gun;
|
||||||
draft.antagonist.gun = gun;
|
draft.antagonist!.shell = gun.shells[0];
|
||||||
draft.antagonist.shell = gun.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
selected={antagonist.gun.id === gun.id}
|
selected={antagonist.gun.id === gun.id}
|
||||||
tier={gun.tier}
|
tier={gun.tier}
|
||||||
@@ -226,14 +210,12 @@ export function AntagonistBar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
[draft.protagonist, draft.antagonist] = [
|
||||||
[draft.protagonist, draft.antagonist] = [
|
draft.antagonist,
|
||||||
draft.antagonist,
|
draft.protagonist,
|
||||||
draft.protagonist,
|
];
|
||||||
];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ShuffleIcon /> Swap tanks
|
<ShuffleIcon /> Swap tanks
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import { Flex, Heading } from '@radix-ui/themes';
|
import { Flex, Heading } from '@radix-ui/themes';
|
||||||
import { produce } from 'immer';
|
import { use } from 'react';
|
||||||
import { Dispatch, SetStateAction, use } from 'react';
|
|
||||||
import { ModuleButton } from '../../../../../components/ModuleButton';
|
import { ModuleButton } from '../../../../../components/ModuleButton';
|
||||||
import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions';
|
import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions';
|
||||||
|
import { mutateDuel, useDuel } from '../../../../../stores/duel';
|
||||||
import {
|
import {
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../../../stores/tankopedia';
|
} from '../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../page';
|
|
||||||
|
|
||||||
interface ConfigureProps {
|
export function Configure() {
|
||||||
duel: Duel;
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
setDuel: Dispatch<SetStateAction<Duel>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Configure({ duel, setDuel }: ConfigureProps) {
|
|
||||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||||
const equipmentRows =
|
const equipmentRows =
|
||||||
awaitedEquipmentDefinitions.presets[duel.protagonist.tank.equipment];
|
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
|
||||||
const equipmentMatrix = useTankopediaTemporary(
|
const equipmentMatrix = useTankopediaTemporary(
|
||||||
(state) => state.equipmentMatrix,
|
(state) => state.equipmentMatrix,
|
||||||
);
|
);
|
||||||
@@ -32,26 +27,24 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
|
|||||||
|
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Flex>
|
<Flex>
|
||||||
{duel.protagonist.tank.turrets.map((thisTurret, index) => {
|
{protagonist.tank.turrets.map((thisTurret, index) => {
|
||||||
return (
|
return (
|
||||||
<ModuleButton
|
<ModuleButton
|
||||||
key={thisTurret.id}
|
key={thisTurret.id}
|
||||||
selected={duel.protagonist.turret.id === thisTurret.id}
|
selected={protagonist.turret.id === thisTurret.id}
|
||||||
type="module"
|
type="module"
|
||||||
module="turret"
|
module="turret"
|
||||||
tier={thisTurret.tier}
|
tier={thisTurret.tier}
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === duel.protagonist.tank.turrets.length - 1}
|
last={index === protagonist.tank.turrets.length - 1}
|
||||||
rowChild
|
rowChild
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.protagonist!.turret = thisTurret;
|
||||||
draft.protagonist.turret = thisTurret;
|
draft.protagonist!.gun = thisTurret.guns.at(-1)!;
|
||||||
draft.protagonist.gun = thisTurret.guns.at(-1)!;
|
draft.protagonist!.shell =
|
||||||
draft.protagonist.shell =
|
thisTurret.guns.at(-1)!.shells[0];
|
||||||
thisTurret.guns.at(-1)!.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -59,24 +52,22 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
{duel.protagonist.turret.guns.map((thisGun, index) => {
|
{protagonist.turret.guns.map((thisGun, index) => {
|
||||||
return (
|
return (
|
||||||
<ModuleButton
|
<ModuleButton
|
||||||
key={thisGun.id}
|
key={thisGun.id}
|
||||||
type="module"
|
type="module"
|
||||||
module="gun"
|
module="gun"
|
||||||
tier={thisGun.tier}
|
tier={thisGun.tier}
|
||||||
selected={duel.protagonist.gun.id === thisGun.id}
|
selected={protagonist.gun.id === thisGun.id}
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === duel.protagonist.turret.guns.length - 1}
|
last={index === protagonist.turret.guns.length - 1}
|
||||||
rowChild
|
rowChild
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.protagonist!.gun = thisGun;
|
||||||
draft.protagonist.gun = thisGun;
|
draft.protagonist!.shell = thisGun.shells[0];
|
||||||
draft.protagonist.shell = thisGun.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimi
|
|||||||
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose';
|
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose';
|
||||||
import { useAwait } from '../../../../../hooks/useAwait';
|
import { useAwait } from '../../../../../hooks/useAwait';
|
||||||
|
import { useDuel } from '../../../../../stores/duel';
|
||||||
import { useTankopediaPersistent } from '../../../../../stores/tankopedia';
|
import { useTankopediaPersistent } from '../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../page';
|
|
||||||
|
|
||||||
const poseDistances: Record<Pose, number> = {
|
const poseDistances: Record<Pose, number> = {
|
||||||
[Pose.HullDown]: 15,
|
[Pose.HullDown]: 15,
|
||||||
@@ -16,24 +16,21 @@ const poseDistances: Record<Pose, number> = {
|
|||||||
[Pose.Default]: -1,
|
[Pose.Default]: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ControlsProps {
|
export function Controls() {
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Controls({ duel }: ControlsProps) {
|
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||||
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
||||||
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
|
const antagonist = useDuel((state) => state.antagonist!);
|
||||||
const protagonistModelDefinition =
|
const protagonistModelDefinition =
|
||||||
awaitedModelDefinitions[duel.protagonist.tank.id];
|
awaitedModelDefinitions[protagonist.tank.id];
|
||||||
const antagonistModelDefinition =
|
const antagonistModelDefinition = awaitedModelDefinitions[antagonist.tank.id];
|
||||||
awaitedModelDefinitions[duel.antagonist.tank.id];
|
|
||||||
const protagonistTurretModelDefinition =
|
const protagonistTurretModelDefinition =
|
||||||
protagonistModelDefinition.turrets[duel.protagonist.turret.id];
|
protagonistModelDefinition.turrets[protagonist.turret.id];
|
||||||
const antagonistTurretModelDefinition =
|
const antagonistTurretModelDefinition =
|
||||||
antagonistModelDefinition.turrets[duel.antagonist.turret.id];
|
antagonistModelDefinition.turrets[antagonist.turret.id];
|
||||||
const protagonistGunModelDefinition =
|
const protagonistGunModelDefinition =
|
||||||
protagonistTurretModelDefinition.guns[duel.protagonist.gun.id];
|
protagonistTurretModelDefinition.guns[protagonist.gun.id];
|
||||||
const protagonistHullOrigin = new Vector3(
|
const protagonistHullOrigin = new Vector3(
|
||||||
protagonistModelDefinition.hullOrigin[0],
|
protagonistModelDefinition.hullOrigin[0],
|
||||||
protagonistModelDefinition.hullOrigin[1],
|
protagonistModelDefinition.hullOrigin[1],
|
||||||
@@ -148,7 +145,7 @@ export function Controls({ duel }: ControlsProps) {
|
|||||||
unsubscribeTankopedia();
|
unsubscribeTankopedia();
|
||||||
poseEvent.off(handlePoseEvent);
|
poseEvent.off(handlePoseEvent);
|
||||||
};
|
};
|
||||||
}, [camera, duel.protagonist.tank.id, duel.antagonist.tank.id]);
|
}, [camera, protagonist.tank.id, antagonist.tank.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { useModel } from '../../../../../hooks/useModel';
|
import { useModel } from '../../../../../hooks/useModel';
|
||||||
import { Duel } from '../page';
|
import { useDuel } from '../../../../../stores/duel';
|
||||||
|
|
||||||
interface LightingProps {
|
export function Lighting() {
|
||||||
duel: Duel;
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
}
|
const { hasPbr } = useModel(protagonist.tank.id);
|
||||||
|
|
||||||
export function Lighting({ duel }: LightingProps) {
|
|
||||||
const { hasPbr } = useModel(duel.protagonist.tank.id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawL
|
|||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
|
||||||
|
import { useDuel } from '../../../../../../stores/duel';
|
||||||
import {
|
import {
|
||||||
TankopediaMode,
|
TankopediaMode,
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../../../../stores/tankopedia';
|
} from '../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../page';
|
|
||||||
import { Controls } from '../Control';
|
import { Controls } from '../Control';
|
||||||
import { Lighting } from '../Lighting';
|
import { Lighting } from '../Lighting';
|
||||||
import { RotationInputs } from '../QuickInputs';
|
import { RotationInputs } from '../QuickInputs';
|
||||||
@@ -20,21 +20,17 @@ import { Options } from './components/Options';
|
|||||||
import { TankArmor } from './components/TankArmor';
|
import { TankArmor } from './components/TankArmor';
|
||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
interface TankSandboxProps {
|
export function TankSandbox() {
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TankSandbox({ duel }: TankSandboxProps) {
|
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||||
const mode = useTankopediaTemporary((state) => state.mode);
|
const mode = useTankopediaTemporary((state) => state.mode);
|
||||||
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition =
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
turretModelDefinition.guns[duel.protagonist.gun.id];
|
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
@@ -119,8 +115,6 @@ export function TankSandbox({ duel }: TankSandboxProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!duel.protagonist) return void 0;
|
|
||||||
|
|
||||||
mutateTankopediaTemporary((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
[draft.model.pose.pitch, draft.model.pose.yaw] = applyPitchYawLimits(
|
[draft.model.pose.pitch, draft.model.pose.yaw] = applyPitchYawLimits(
|
||||||
draft.model.pose.pitch,
|
draft.model.pose.pitch,
|
||||||
@@ -129,7 +123,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
|
|||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [duel.protagonist.gun, duel.protagonist.turret]);
|
}, [protagonist.gun, protagonist.turret]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Theme radius={isFullScreen ? 'none' : undefined}>
|
<Theme radius={isFullScreen ? 'none' : undefined}>
|
||||||
@@ -170,21 +164,20 @@ export function TankSandbox({ duel }: TankSandboxProps) {
|
|||||||
camera={{ fov: 25 }}
|
camera={{ fov: 25 }}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
>
|
>
|
||||||
<Controls duel={duel} />
|
<Controls />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
|
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
<Lighting duel={duel} />
|
<Lighting />
|
||||||
<TankModel duel={duel} />
|
<TankModel />
|
||||||
<TankArmor duel={duel} />
|
<TankArmor />
|
||||||
{/* <SpacedArmorDepth duel={duel} /> */}
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
|
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
|
||||||
|
|
||||||
<RotationInputs duel={duel} />
|
<RotationInputs />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Theme>
|
</Theme>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
+7
-13
@@ -11,14 +11,11 @@ import { nameToArmorId } from '../../../../../../../../../core/blitzkrieg/nameTo
|
|||||||
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
|
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
|
||||||
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
||||||
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
||||||
|
import { useDuel } from '../../../../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../../../../page';
|
|
||||||
|
|
||||||
interface ArmorHighlightingProps {
|
export function ArmorHighlighting() {
|
||||||
duel: Duel;
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
}
|
|
||||||
|
|
||||||
export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
|
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const modelDefinitions = useModelDefinitions();
|
const modelDefinitions = useModelDefinitions();
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
@@ -88,13 +85,12 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
});
|
});
|
||||||
|
|
||||||
const armorGltf = useArmor(duel.protagonist.tank.id);
|
const armorGltf = useArmor(protagonist.tank.id);
|
||||||
const armorNodes = Object.values(armorGltf.nodes);
|
const armorNodes = Object.values(armorGltf.nodes);
|
||||||
const tankModelDefinition = modelDefinitions[duel.protagonist.tank.id];
|
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition =
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
turretModelDefinition.guns[duel.protagonist.gun.id];
|
|
||||||
const hullOrigin = new Vector3(
|
const hullOrigin = new Vector3(
|
||||||
tankModelDefinition.hullOrigin[0],
|
tankModelDefinition.hullOrigin[0],
|
||||||
tankModelDefinition.hullOrigin[1],
|
tankModelDefinition.hullOrigin[1],
|
||||||
@@ -137,7 +133,6 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
|
|||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
maxThickness={maxThickness}
|
maxThickness={maxThickness}
|
||||||
duel={duel}
|
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
@@ -162,7 +157,6 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
|
|||||||
return (
|
return (
|
||||||
<ArmorMesh
|
<ArmorMesh
|
||||||
maxThickness={maxThickness}
|
maxThickness={maxThickness}
|
||||||
duel={duel}
|
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
geometry={(node as Mesh).geometry}
|
geometry={(node as Mesh).geometry}
|
||||||
position={turretOrigin}
|
position={turretOrigin}
|
||||||
|
|||||||
+8
-12
@@ -12,12 +12,8 @@ import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolve
|
|||||||
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
||||||
import { useModel } from '../../../../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../../../../hooks/useModel';
|
||||||
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
||||||
|
import { useDuel } from '../../../../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../../../../page';
|
|
||||||
|
|
||||||
interface ExternalModuleMaskProps {
|
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When rendered, generates a mask and thickness buffer for external modules.
|
* When rendered, generates a mask and thickness buffer for external modules.
|
||||||
@@ -26,7 +22,8 @@ interface ExternalModuleMaskProps {
|
|||||||
* - B: no data
|
* - B: no data
|
||||||
* - A: 1 means is armor; 0 is background
|
* - A: 1 means is armor; 0 is background
|
||||||
*/
|
*/
|
||||||
export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
|
export const ExternalModuleMask = memo(() => {
|
||||||
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const modelDefinitions = useModelDefinitions();
|
const modelDefinitions = useModelDefinitions();
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
@@ -117,16 +114,15 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
});
|
});
|
||||||
|
|
||||||
const armorGltf = useArmor(duel.protagonist.tank.id);
|
const armorGltf = useArmor(protagonist.tank.id);
|
||||||
const { gltf: modelGltf } = useModel(duel.protagonist.tank.id);
|
const { gltf: modelGltf } = useModel(protagonist.tank.id);
|
||||||
|
|
||||||
const armorNodes = Object.values(armorGltf.nodes);
|
const armorNodes = Object.values(armorGltf.nodes);
|
||||||
const modelNodes = Object.values(modelGltf.nodes);
|
const modelNodes = Object.values(modelGltf.nodes);
|
||||||
const tankModelDefinition = modelDefinitions[duel.protagonist.tank.id];
|
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition =
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
turretModelDefinition.guns[duel.protagonist.gun.id];
|
|
||||||
const maxThickness = Math.max(
|
const maxThickness = Math.max(
|
||||||
tankModelDefinition.trackThickness,
|
tankModelDefinition.trackThickness,
|
||||||
gunModelDefinition.barrelThickness,
|
gunModelDefinition.barrelThickness,
|
||||||
|
|||||||
+8
-12
@@ -12,12 +12,8 @@ import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolve
|
|||||||
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
import { useArmor } from '../../../../../../../../../hooks/useArmor';
|
||||||
import { useModel } from '../../../../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../../../../hooks/useModel';
|
||||||
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
|
||||||
|
import { useDuel } from '../../../../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../../../../page';
|
|
||||||
|
|
||||||
interface SpacedArmorDepthProps {
|
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When rendered, this component provides the depth buffer for the armor which
|
* When rendered, this component provides the depth buffer for the armor which
|
||||||
@@ -30,7 +26,8 @@ interface SpacedArmorDepthProps {
|
|||||||
* - B: no data
|
* - B: no data
|
||||||
* - A: 1 means is armor; 0 is background
|
* - A: 1 means is armor; 0 is background
|
||||||
*/
|
*/
|
||||||
export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
|
export const SpacedArmorDepth = memo(() => {
|
||||||
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const modelDefinitions = useModelDefinitions();
|
const modelDefinitions = useModelDefinitions();
|
||||||
const turretContainer = useRef<Group>(null);
|
const turretContainer = useRef<Group>(null);
|
||||||
@@ -123,16 +120,15 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
});
|
});
|
||||||
|
|
||||||
const armorGltf = useArmor(duel.protagonist.tank.id);
|
const armorGltf = useArmor(protagonist.tank.id);
|
||||||
const { gltf: modelGltf } = useModel(duel.protagonist.tank.id);
|
const { gltf: modelGltf } = useModel(protagonist.tank.id);
|
||||||
|
|
||||||
const armorNodes = Object.values(armorGltf.nodes);
|
const armorNodes = Object.values(armorGltf.nodes);
|
||||||
const modelNodes = Object.values(modelGltf.nodes);
|
const modelNodes = Object.values(modelGltf.nodes);
|
||||||
const tankModelDefinition = modelDefinitions[duel.protagonist.tank.id];
|
const tankModelDefinition = modelDefinitions[protagonist.tank.id];
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[duel.protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition =
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
turretModelDefinition.guns[duel.protagonist.gun.id];
|
|
||||||
const maxThickness = Math.max(
|
const maxThickness = Math.max(
|
||||||
tankModelDefinition.trackThickness,
|
tankModelDefinition.trackThickness,
|
||||||
gunModelDefinition.barrelThickness,
|
gunModelDefinition.barrelThickness,
|
||||||
|
|||||||
@@ -3,29 +3,24 @@ import { memo } from 'react';
|
|||||||
import { DepthTexture, Scene } from 'three';
|
import { DepthTexture, Scene } from 'three';
|
||||||
import { externalModuleMaskRenderTarget } from '../../../../../../../../components/ArmorMesh';
|
import { externalModuleMaskRenderTarget } from '../../../../../../../../components/ArmorMesh';
|
||||||
import { spacedArmorDepthRenderTarget } from '../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth';
|
import { spacedArmorDepthRenderTarget } from '../../../../../../../../components/ArmorMesh/components/SpacedArmorDepth';
|
||||||
import { Duel } from '../../../../page';
|
|
||||||
import { ArmorHighlighting } from './components/ArmorHighlighting';
|
import { ArmorHighlighting } from './components/ArmorHighlighting';
|
||||||
import { ExternalModuleMask } from './components/ExternalModuleMask';
|
import { ExternalModuleMask } from './components/ExternalModuleMask';
|
||||||
import { SpacedArmorDepth } from './components/SpacedArmorDepth';
|
import { SpacedArmorDepth } from './components/SpacedArmorDepth';
|
||||||
|
|
||||||
interface TankArmorProps {
|
export const TankArmor = memo(() => {
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TankArmor = memo<TankArmorProps>(({ duel }) => {
|
|
||||||
const externalModuleMaskScene = new Scene();
|
const externalModuleMaskScene = new Scene();
|
||||||
const spacedArmorDepthScene = new Scene();
|
const spacedArmorDepthScene = new Scene();
|
||||||
const armorHighlightingScene = new Scene();
|
const armorHighlightingScene = new Scene();
|
||||||
const externalModuleMaskPortal = createPortal(
|
const externalModuleMaskPortal = createPortal(
|
||||||
<ExternalModuleMask duel={duel} />,
|
<ExternalModuleMask />,
|
||||||
externalModuleMaskScene,
|
externalModuleMaskScene,
|
||||||
);
|
);
|
||||||
const spacedArmorDepthPortal = createPortal(
|
const spacedArmorDepthPortal = createPortal(
|
||||||
<SpacedArmorDepth duel={duel} />,
|
<SpacedArmorDepth />,
|
||||||
spacedArmorDepthScene,
|
spacedArmorDepthScene,
|
||||||
);
|
);
|
||||||
const armorHighlightingPortal = createPortal(
|
const armorHighlightingPortal = createPortal(
|
||||||
<ArmorHighlighting duel={duel} />,
|
<ArmorHighlighting />,
|
||||||
armorHighlightingScene,
|
armorHighlightingScene,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,21 +22,17 @@ import {
|
|||||||
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
|
||||||
import { useModel } from '../../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../../hooks/useModel';
|
||||||
|
import { useDuel } from '../../../../../../../stores/duel';
|
||||||
import mutateTankopediaPersistent, {
|
import mutateTankopediaPersistent, {
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../../page';
|
|
||||||
|
|
||||||
interface TankModelProps {
|
export const TankModel = memo(() => {
|
||||||
duel: Duel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
|
|
||||||
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
|
||||||
ModelDefinitions | undefined
|
ModelDefinitions | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
const protagonist = useDuel((draft) => draft.protagonist!);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const physical = useTankopediaTemporary((state) => state.model.pose);
|
const physical = useTankopediaTemporary((state) => state.model.pose);
|
||||||
const hullContainer = useRef<Group>(null);
|
const hullContainer = useRef<Group>(null);
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ import { use, useEffect, useRef } from 'react';
|
|||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
||||||
|
import { useDuel } from '../../../../../../stores/duel';
|
||||||
import mutateTankopediaPersistent, {
|
import mutateTankopediaPersistent, {
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
useTankopediaPersistent,
|
useTankopediaPersistent,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../../../../stores/tankopedia';
|
} from '../../../../../../stores/tankopedia';
|
||||||
import { Duel } from '../../page';
|
|
||||||
import { QuickEquipmentButton } from './components/QuickEquipmentButton';
|
import { QuickEquipmentButton } from './components/QuickEquipmentButton';
|
||||||
|
|
||||||
interface RotationInputsProps {
|
export function RotationInputs() {
|
||||||
duel: Duel;
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
}
|
|
||||||
|
|
||||||
export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
|
|
||||||
const physical = useTankopediaTemporary((state) => state.model.pose);
|
const physical = useTankopediaTemporary((state) => state.model.pose);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { ArrowLeftIcon } from '@radix-ui/react-icons';
|
import { ArrowLeftIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, Heading, Link, Text } from '@radix-ui/themes';
|
import { Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||||
import { Flag } from '../../../../../components/Flag';
|
import { Flag } from '../../../../../components/Flag';
|
||||||
import { Duel } from '../page';
|
import { useDuel } from '../../../../../stores/duel';
|
||||||
|
|
||||||
interface TitleProps {
|
export function Title() {
|
||||||
duel: Duel;
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
}
|
|
||||||
|
|
||||||
export function Title({ duel }: TitleProps) {
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="between" align="center">
|
<Flex justify="between" align="center">
|
||||||
<Link
|
<Link
|
||||||
@@ -21,8 +19,8 @@ export function Title({ duel }: TitleProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
<Flag nation={duel.protagonist.tank.nation} />
|
<Flag nation={protagonist.tank.nation} />
|
||||||
<Heading>{duel.protagonist.tank.name}</Heading>
|
<Heading>{protagonist.tank.name}</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,43 +1,40 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Flex } from '@radix-ui/themes';
|
import { Flex } from '@radix-ui/themes';
|
||||||
import { produce } from 'immer';
|
import { use, useEffect } from 'react';
|
||||||
import { use, useEffect, useState } from 'react';
|
|
||||||
import PageWrapper from '../../../../components/PageWrapper';
|
import PageWrapper from '../../../../components/PageWrapper';
|
||||||
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
|
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import {
|
import { mutateDuel, useDuel } from '../../../../stores/duel';
|
||||||
DuelMember,
|
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
|
||||||
mutateTankopediaTemporary,
|
|
||||||
} from '../../../../stores/tankopedia';
|
|
||||||
import { AntagonistBar } from './components/AntagonistBar';
|
import { AntagonistBar } from './components/AntagonistBar';
|
||||||
import { Configure } from './components/Configure';
|
import { Configure } from './components/Configure';
|
||||||
import { TankSandbox } from './components/Model/TankSandbox';
|
import { TankSandbox } from './components/Model/TankSandbox';
|
||||||
import { Title } from './components/Title';
|
import { Title } from './components/Title';
|
||||||
|
|
||||||
export interface Duel {
|
|
||||||
protagonist: DuelMember;
|
|
||||||
antagonist: DuelMember;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Page({ params }: { params: { id: string } }) {
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
const id = parseInt(params.id);
|
const id = parseInt(params.id);
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const [duel, setDuel] = useState<Duel>({
|
const assigned = useDuel((state) => state.assigned);
|
||||||
protagonist: {
|
|
||||||
tank: awaitedTankDefinitions[id],
|
|
||||||
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
|
||||||
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
|
||||||
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
|
|
||||||
},
|
|
||||||
antagonist: {
|
|
||||||
tank: awaitedTankDefinitions[id],
|
|
||||||
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
|
||||||
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
|
||||||
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
mutateDuel((draft) => {
|
||||||
|
draft.assigned = true;
|
||||||
|
draft.protagonist = {
|
||||||
|
tank: awaitedTankDefinitions[id],
|
||||||
|
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
||||||
|
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
||||||
|
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!
|
||||||
|
.shells[0],
|
||||||
|
};
|
||||||
|
draft.antagonist = {
|
||||||
|
tank: awaitedTankDefinitions[id],
|
||||||
|
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
|
||||||
|
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
|
||||||
|
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!
|
||||||
|
.shells[0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
mutateTankopediaTemporary((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.model.pose.yaw = 0;
|
draft.model.pose.yaw = 0;
|
||||||
draft.model.pose.pitch = 0;
|
draft.model.pose.pitch = 0;
|
||||||
@@ -48,27 +45,21 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
if (document.activeElement instanceof HTMLInputElement) return;
|
if (document.activeElement instanceof HTMLInputElement) return;
|
||||||
|
|
||||||
if (event.key === '1') {
|
if (event.key === '1') {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
draft.antagonist!.shell = draft.antagonist!.gun.shells[0];
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[0];
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (event.key === '2') {
|
} else if (event.key === '2') {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
if (draft.antagonist!.gun.shells[1]) {
|
||||||
if (draft.antagonist.gun.shells[1]) {
|
draft.antagonist!.shell = draft.antagonist!.gun.shells[1];
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[1];
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (event.key === '3') {
|
} else if (event.key === '3') {
|
||||||
setDuel(
|
mutateDuel((draft) => {
|
||||||
produce<Duel>((draft) => {
|
if (draft.antagonist!.gun.shells[2]) {
|
||||||
if (draft.antagonist.gun.shells[2]) {
|
draft.antagonist!.shell = draft.antagonist!.gun.shells[2];
|
||||||
draft.antagonist.shell = draft.antagonist.gun.shells[2];
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,15 +72,17 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper color="purple">
|
<PageWrapper color="purple">
|
||||||
<Flex gap="8" direction="column">
|
{assigned && (
|
||||||
<Flex gap="4" direction="column">
|
<Flex gap="8" direction="column">
|
||||||
<Title duel={duel} />
|
<Flex gap="4" direction="column">
|
||||||
<TankSandbox duel={duel} />
|
<Title />
|
||||||
<AntagonistBar duel={duel} setDuel={setDuel} />
|
<TankSandbox />
|
||||||
</Flex>
|
<AntagonistBar />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Configure duel={duel} setDuel={setDuel} />
|
<Configure />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
)}
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { MeshProps, useFrame, useThree } from '@react-three/fiber';
|
import { MeshProps, useFrame, useThree } from '@react-three/fiber';
|
||||||
import { useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { MeshStandardMaterial, ShaderMaterial, Vector2 } from 'three';
|
import { MeshStandardMaterial, ShaderMaterial, Vector2 } from 'three';
|
||||||
import ThreeCustomShaderMaterial from 'three-custom-shader-material';
|
import ThreeCustomShaderMaterial from 'three-custom-shader-material';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
import { Duel } from '../../../../app/tools/tankopedia/[id]/page';
|
|
||||||
import { canSplash } from '../../../../core/blitz/canSplash';
|
import { canSplash } from '../../../../core/blitz/canSplash';
|
||||||
import { isExplosive } from '../../../../core/blitz/isExplosive';
|
import { isExplosive } from '../../../../core/blitz/isExplosive';
|
||||||
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
|
||||||
|
import { useDuel } from '../../../../stores/duel';
|
||||||
import { useTankopediaPersistent } from '../../../../stores/tankopedia';
|
import { useTankopediaPersistent } from '../../../../stores/tankopedia';
|
||||||
import { externalModuleMaskRenderTarget } from '../ExternalModuleMask';
|
import { externalModuleMaskRenderTarget } from '../ExternalModuleMask';
|
||||||
import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth';
|
import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth';
|
||||||
@@ -14,7 +14,6 @@ import fragmentShader from './shaders/fragment.glsl';
|
|||||||
import vertexShader from './shaders/vertex.glsl';
|
import vertexShader from './shaders/vertex.glsl';
|
||||||
|
|
||||||
interface ArmorMeshProps extends MeshProps {
|
interface ArmorMeshProps extends MeshProps {
|
||||||
duel: Duel;
|
|
||||||
thickness: number;
|
thickness: number;
|
||||||
maxThickness: number;
|
maxThickness: number;
|
||||||
}
|
}
|
||||||
@@ -22,23 +21,43 @@ interface ArmorMeshProps extends MeshProps {
|
|||||||
export function ArmorMesh({
|
export function ArmorMesh({
|
||||||
thickness,
|
thickness,
|
||||||
maxThickness,
|
maxThickness,
|
||||||
duel,
|
|
||||||
...props
|
...props
|
||||||
}: ArmorMeshProps) {
|
}: ArmorMeshProps) {
|
||||||
const equipment = useTankopediaPersistent((draft) => draft.model.equipment);
|
const initialEquipment = useTankopediaPersistent.getState().model.equipment;
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const { shell } = duel.antagonist;
|
const shell = useDuel((state) => state.antagonist!.shell);
|
||||||
const greenPenetration = useTankopediaPersistent(
|
const greenPenetration = useTankopediaPersistent(
|
||||||
(state) => state.model.visual.greenPenetration,
|
(state) => state.model.visual.greenPenetration,
|
||||||
);
|
);
|
||||||
const material = useRef<ShaderMaterial>(null);
|
const material = useRef<ShaderMaterial>(null);
|
||||||
const resolution = new Vector2();
|
const resolution = new Vector2();
|
||||||
const explosionCapable = isExplosive(shell.type);
|
const explosionCapable = isExplosive(shell.type);
|
||||||
let mutatedThickness = thickness;
|
|
||||||
let penetration = resolveNearPenetration(shell.penetration);
|
|
||||||
|
|
||||||
if (equipment.calibratedShells) penetration *= explosionCapable ? 1.1 : 1.05;
|
useEffect(() => {
|
||||||
if (equipment.enhancedArmor) mutatedThickness *= 1.04;
|
function updateQuickEquipments() {
|
||||||
|
const equipment = useTankopediaPersistent.getState().model.equipment;
|
||||||
|
const nearPenetration = resolveNearPenetration(shell.penetration);
|
||||||
|
|
||||||
|
if (material.current) {
|
||||||
|
material.current.uniforms.thickness.value =
|
||||||
|
thickness * (equipment.enhancedArmor ? 1.04 : 1);
|
||||||
|
material.current.uniforms.penetration.value =
|
||||||
|
nearPenetration *
|
||||||
|
(equipment.calibratedShells ? (explosionCapable ? 1.1 : 1.05) : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribes = [
|
||||||
|
useTankopediaPersistent.subscribe(
|
||||||
|
(state) => state.model.equipment,
|
||||||
|
updateQuickEquipments,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
useFrame(({ gl, camera }) => {
|
useFrame(({ gl, camera }) => {
|
||||||
if (material.current) {
|
if (material.current) {
|
||||||
@@ -84,9 +103,17 @@ export function ArmorMesh({
|
|||||||
maxThickness: { value: maxThickness },
|
maxThickness: { value: maxThickness },
|
||||||
isExplosive: { value: explosionCapable },
|
isExplosive: { value: explosionCapable },
|
||||||
canSplash: { value: canSplash(shell.type) },
|
canSplash: { value: canSplash(shell.type) },
|
||||||
thickness: { value: mutatedThickness },
|
thickness: {
|
||||||
|
value: thickness * (initialEquipment.enhancedArmor ? 1.04 : 1),
|
||||||
|
},
|
||||||
penetration: {
|
penetration: {
|
||||||
value: penetration,
|
value:
|
||||||
|
resolveNearPenetration(shell.penetration) *
|
||||||
|
(initialEquipment.calibratedShells
|
||||||
|
? explosionCapable
|
||||||
|
? 1.1
|
||||||
|
: 1.05
|
||||||
|
: 1),
|
||||||
},
|
},
|
||||||
caliber: { value: shell.caliber },
|
caliber: { value: shell.caliber },
|
||||||
ricochetAngle: { value: degToRad(shell.ricochet ?? 90) },
|
ricochetAngle: { value: degToRad(shell.ricochet ?? 90) },
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { DuelMember } from './tankopedia';
|
||||||
|
|
||||||
|
export interface Duel {
|
||||||
|
assigned: boolean;
|
||||||
|
protagonist?: DuelMember;
|
||||||
|
antagonist?: DuelMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDuel = create<Duel>()(() => ({
|
||||||
|
assigned: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function mutateDuel(recipe: (draft: Duel) => void) {
|
||||||
|
useDuel.setState(produce(recipe));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user