2025-09-27 16:07:45 -05:00
|
|
|
import { memo, useRef } from "react";
|
|
|
|
|
import { Group, Plane, Scene, Vector3 } from "three";
|
2026-09-27 16:46:22 -05:00
|
|
|
import { correctZYTuple } from "../core/blitz/correctZYTuple";
|
|
|
|
|
import { nameToArmorId } from "../core/blitzkit/nameToArmorId";
|
|
|
|
|
import { resolveArmor } from "../core/blitzkit/resolveThickness";
|
|
|
|
|
import { useArmor } from "../hooks/useArmor";
|
|
|
|
|
import { useModel } from "../hooks/useModel";
|
|
|
|
|
import { useTankModelDefinition } from "../hooks/useTankModelDefinition";
|
|
|
|
|
import { useTankTransform } from "../hooks/useTankTransform";
|
|
|
|
|
import { Duel } from "../stores/duel";
|
2025-09-27 16:07:45 -05:00
|
|
|
import { ModelTankWrapper } from "./ModelTankWrapper";
|
|
|
|
|
import { SpacedArmorSceneComponent } from "./SpacedArmorSceneComponent";
|
2024-01-15 21:55:09 -06:00
|
|
|
|
2024-02-26 13:29:05 -06:00
|
|
|
export enum ArmorType {
|
2024-07-29 20:46:57 -05:00
|
|
|
Primary,
|
2024-02-26 13:29:05 -06:00
|
|
|
Spaced,
|
|
|
|
|
External,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-28 22:29:21 -06:00
|
|
|
interface SpacedArmorSceneProps {
|
|
|
|
|
scene: Scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
2024-02-28 17:41:59 -06:00
|
|
|
const wrapper = useRef<Group>(null);
|
2025-09-27 16:07:45 -05:00
|
|
|
const turretContainer = useRef<Group>(null!);
|
|
|
|
|
const gunContainer = useRef<Group>(null!);
|
2024-07-22 20:47:29 -05:00
|
|
|
const tank = Duel.use((state) => state.protagonist.tank);
|
|
|
|
|
const track = Duel.use((state) => state.protagonist.track);
|
|
|
|
|
const turret = Duel.use((state) => state.protagonist.turret);
|
|
|
|
|
const gun = Duel.use((state) => state.protagonist.gun);
|
2024-09-04 21:15:57 -05:00
|
|
|
const { gltf: armorGltf } = useArmor(tank.id);
|
2024-07-22 20:47:29 -05:00
|
|
|
const { gltf: modelGltf } = useModel(tank.id);
|
|
|
|
|
const armorNodes = Object.values(armorGltf.nodes);
|
|
|
|
|
const modelNodes = Object.values(modelGltf.nodes);
|
2024-07-30 13:24:55 -05:00
|
|
|
const tankModelDefinition = useTankModelDefinition();
|
2024-07-22 20:47:29 -05:00
|
|
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
|
|
|
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
2025-08-18 12:06:14 -05:00
|
|
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
2026-03-31 14:11:45 -05:00
|
|
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
|
|
|
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
|
|
|
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
2024-07-22 20:47:29 -05:00
|
|
|
const maskOrigin =
|
|
|
|
|
gunModelDefinition.mask === undefined
|
|
|
|
|
? undefined
|
|
|
|
|
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
2024-09-04 21:15:57 -05:00
|
|
|
const isDynamicArmorActive = Duel.use((state) =>
|
2026-03-31 14:11:45 -05:00
|
|
|
state.protagonist.consumables.includes(73),
|
2024-08-28 21:16:25 -05:00
|
|
|
);
|
2024-02-02 10:31:40 -06:00
|
|
|
|
2024-11-10 21:54:20 -06:00
|
|
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
2024-02-28 17:41:59 -06:00
|
|
|
|
|
|
|
|
return (
|
2024-07-24 20:46:42 -05:00
|
|
|
<ModelTankWrapper ref={wrapper}>
|
2024-02-28 17:41:59 -06:00
|
|
|
<group position={hullOrigin}>
|
|
|
|
|
{armorNodes.map((node) => {
|
2025-09-27 16:07:45 -05:00
|
|
|
const isHull = node.name.startsWith("hull_");
|
2024-02-28 17:41:59 -06:00
|
|
|
const isVisible = isHull;
|
|
|
|
|
const armorId = nameToArmorId(node.name);
|
|
|
|
|
const { spaced, thickness } = resolveArmor(
|
2026-03-31 14:11:45 -05:00
|
|
|
tankModelDefinition.armor!,
|
|
|
|
|
armorId,
|
2024-02-28 17:41:59 -06:00
|
|
|
);
|
|
|
|
|
|
2024-08-28 21:16:25 -05:00
|
|
|
if (
|
|
|
|
|
!isVisible ||
|
|
|
|
|
thickness === undefined ||
|
2025-09-27 16:07:45 -05:00
|
|
|
(isDynamicArmorActive && node.name.includes("state_00")) ||
|
|
|
|
|
(!isDynamicArmorActive && node.name.includes("state_01"))
|
2024-08-28 21:16:25 -05:00
|
|
|
)
|
|
|
|
|
return null;
|
2024-02-28 17:41:59 -06:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<SpacedArmorSceneComponent
|
2024-02-28 22:29:21 -06:00
|
|
|
scene={scene}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${node.uuid}-hull`}
|
2024-07-29 20:46:57 -05:00
|
|
|
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
2024-02-28 17:41:59 -06:00
|
|
|
thickness={thickness}
|
|
|
|
|
node={node}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</group>
|
2024-07-24 20:46:42 -05:00
|
|
|
|
2024-02-28 17:41:59 -06:00
|
|
|
{modelNodes.map((node) => {
|
2025-09-27 16:07:45 -05:00
|
|
|
const isWheel = node.name.startsWith("chassis_wheel_");
|
|
|
|
|
const isTrack = node.name.startsWith("chassis_track_");
|
2024-02-28 17:41:59 -06:00
|
|
|
const isVisible = isWheel || isTrack;
|
|
|
|
|
|
|
|
|
|
if (!isVisible) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<SpacedArmorSceneComponent
|
2024-02-28 22:29:21 -06:00
|
|
|
scene={scene}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${node.uuid}-track`}
|
2024-02-28 17:41:59 -06:00
|
|
|
type={ArmorType.External}
|
|
|
|
|
thickness={trackModelDefinition.thickness}
|
|
|
|
|
variant="track"
|
|
|
|
|
node={node}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
<group ref={turretContainer}>
|
2024-07-24 20:46:42 -05:00
|
|
|
{armorNodes.map((node) => {
|
|
|
|
|
const isCurrentTurret = node.name.startsWith(
|
2026-03-31 14:11:45 -05:00
|
|
|
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
2024-07-24 20:46:42 -05:00
|
|
|
);
|
|
|
|
|
const isVisible = isCurrentTurret;
|
|
|
|
|
const armorId = nameToArmorId(node.name);
|
|
|
|
|
const { spaced, thickness } = resolveArmor(
|
2026-03-31 14:11:45 -05:00
|
|
|
turretModelDefinition.armor!,
|
|
|
|
|
armorId,
|
2024-07-24 20:46:42 -05:00
|
|
|
);
|
|
|
|
|
|
2024-08-28 21:16:25 -05:00
|
|
|
if (
|
|
|
|
|
!isVisible ||
|
|
|
|
|
thickness === undefined ||
|
2025-09-27 16:07:45 -05:00
|
|
|
(isDynamicArmorActive && node.name.includes("state_00")) ||
|
|
|
|
|
(!isDynamicArmorActive && node.name.includes("state_01"))
|
2024-08-28 21:16:25 -05:00
|
|
|
)
|
|
|
|
|
return null;
|
2024-07-24 20:46:42 -05:00
|
|
|
|
|
|
|
|
return (
|
2024-09-22 21:27:46 -05:00
|
|
|
<group position={hullOrigin} key={node.uuid}>
|
|
|
|
|
<group position={turretOrigin}>
|
2024-07-27 13:46:36 -05:00
|
|
|
<SpacedArmorSceneComponent
|
|
|
|
|
scene={scene}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${node.uuid}-turret`}
|
2024-07-29 20:46:57 -05:00
|
|
|
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
2024-07-27 13:46:36 -05:00
|
|
|
thickness={thickness}
|
|
|
|
|
node={node}
|
|
|
|
|
/>
|
|
|
|
|
</group>
|
2024-07-24 20:46:42 -05:00
|
|
|
</group>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
<group ref={gunContainer}>
|
2024-01-15 21:55:09 -06:00
|
|
|
{armorNodes.map((node) => {
|
2024-07-24 20:46:42 -05:00
|
|
|
const isCurrentGun = node.name.startsWith(
|
2026-03-31 14:11:45 -05:00
|
|
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
2024-02-28 17:41:59 -06:00
|
|
|
);
|
2024-07-24 20:46:42 -05:00
|
|
|
const isVisible = isCurrentGun;
|
2024-01-15 21:55:09 -06:00
|
|
|
const armorId = nameToArmorId(node.name);
|
|
|
|
|
const { spaced, thickness } = resolveArmor(
|
2026-03-31 14:11:45 -05:00
|
|
|
gunModelDefinition.armor!,
|
|
|
|
|
armorId,
|
2024-01-15 21:55:09 -06:00
|
|
|
);
|
|
|
|
|
|
2024-08-28 21:16:25 -05:00
|
|
|
if (
|
|
|
|
|
!isVisible ||
|
|
|
|
|
thickness === undefined ||
|
2025-09-27 16:07:45 -05:00
|
|
|
(isDynamicArmorActive && node.name.includes("state_00")) ||
|
|
|
|
|
(!isDynamicArmorActive && node.name.includes("state_01"))
|
2024-08-28 21:16:25 -05:00
|
|
|
)
|
|
|
|
|
return null;
|
2024-01-15 21:55:09 -06:00
|
|
|
|
|
|
|
|
return (
|
2025-04-15 16:29:14 -05:00
|
|
|
<group position={hullOrigin} key={`${node.uuid}-gun-spaced}`}>
|
2024-09-22 21:27:46 -05:00
|
|
|
<group position={turretOrigin.clone().add(gunOrigin)}>
|
2024-07-27 13:46:36 -05:00
|
|
|
<SpacedArmorSceneComponent
|
|
|
|
|
scene={scene}
|
2024-07-29 20:46:57 -05:00
|
|
|
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
2024-07-27 13:46:36 -05:00
|
|
|
thickness={thickness}
|
|
|
|
|
node={node}
|
|
|
|
|
/>
|
|
|
|
|
</group>
|
2024-02-28 17:41:59 -06:00
|
|
|
</group>
|
2024-01-15 21:55:09 -06:00
|
|
|
);
|
|
|
|
|
})}
|
2024-01-23 13:37:32 -06:00
|
|
|
|
2024-02-28 17:41:59 -06:00
|
|
|
{modelNodes.map((node) => {
|
2024-10-07 18:45:11 -05:00
|
|
|
const gunString = `gun_${gunModelDefinition.model_id
|
2024-09-06 23:17:53 -05:00
|
|
|
.toString()
|
2025-09-27 16:07:45 -05:00
|
|
|
.padStart(2, "0")}`;
|
2024-06-03 13:47:55 -05:00
|
|
|
const isCurrentGun = gunModelDefinition.mask
|
|
|
|
|
? node.name.startsWith(gunString)
|
|
|
|
|
: node.name === gunString;
|
2024-02-28 17:41:59 -06:00
|
|
|
const isVisible = isCurrentGun;
|
2024-01-20 12:01:05 -06:00
|
|
|
|
2024-02-28 17:41:59 -06:00
|
|
|
if (!isVisible) return null;
|
2024-01-20 12:01:05 -06:00
|
|
|
|
2024-02-28 17:41:59 -06:00
|
|
|
return (
|
|
|
|
|
<SpacedArmorSceneComponent
|
2024-02-28 22:29:21 -06:00
|
|
|
scene={scene}
|
2025-04-15 16:29:14 -05:00
|
|
|
key={`${node.uuid}-gun-external`}
|
2024-02-28 17:41:59 -06:00
|
|
|
type={ArmorType.External}
|
|
|
|
|
thickness={gunModelDefinition.thickness}
|
|
|
|
|
variant="gun"
|
|
|
|
|
node={node}
|
2024-04-13 10:38:13 -05:00
|
|
|
clip={
|
|
|
|
|
maskOrigin === undefined
|
|
|
|
|
? undefined
|
|
|
|
|
: new Plane(new Vector3(0, 0, -1), -maskOrigin)
|
|
|
|
|
}
|
2024-02-28 17:41:59 -06:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2024-01-15 21:55:09 -06:00
|
|
|
</group>
|
|
|
|
|
</group>
|
2024-07-24 20:46:42 -05:00
|
|
|
</ModelTankWrapper>
|
2024-02-28 17:41:59 -06:00
|
|
|
);
|
|
|
|
|
});
|