mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: armor modes
This commit is contained in:
1 parent
ffa1395b50
commit
f1410cf321
42 files changed
+293
-264
No files matched your search
@@ -1,195 +0,0 @@
|
||||
import { modelDefinitions } from '@blitzkit/core';
|
||||
import { OrbitControls } from '@react-three/drei';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Vector3 } from 'three';
|
||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../core/blitzkit/hasEquipment';
|
||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||
import { useAwait } from '../../../../../hooks/useAwait';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
||||
|
||||
const initialPosition = [0, 4, -18] as const;
|
||||
const poseDistances: Record<Pose, number> = {
|
||||
[Pose.HullDown]: 15,
|
||||
[Pose.FaceHug]: 5,
|
||||
[Pose.Default]: -1,
|
||||
};
|
||||
|
||||
export function Controls() {
|
||||
const duelStore = Duel.useStore();
|
||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||
const camera = useThree((state) => state.camera);
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const antagonist = Duel.use((state) => state.antagonist);
|
||||
const protagonistModelDefinition =
|
||||
awaitedModelDefinitions[protagonist.tank.id];
|
||||
const protagonistTrackModelDefinition =
|
||||
awaitedModelDefinitions[protagonist.tank.id].tracks[protagonist.track.id];
|
||||
const antagonistModelDefinition = awaitedModelDefinitions[antagonist.tank.id];
|
||||
const protagonistTurretModelDefinition =
|
||||
protagonistModelDefinition.turrets[protagonist.turret.id];
|
||||
const antagonistTurretModelDefinition =
|
||||
antagonistModelDefinition.turrets[antagonist.turret.id];
|
||||
const protagonistGunModelDefinition =
|
||||
protagonistTurretModelDefinition.guns[protagonist.gun.id];
|
||||
const protagonistHullOrigin = new Vector3(
|
||||
protagonistTrackModelDefinition.origin[0],
|
||||
protagonistTrackModelDefinition.origin[1],
|
||||
-protagonistTrackModelDefinition.origin[2],
|
||||
);
|
||||
const protagonistTurretOrigin = new Vector3(
|
||||
protagonistModelDefinition.turretOrigin[0],
|
||||
protagonistModelDefinition.turretOrigin[1],
|
||||
-protagonistModelDefinition.turretOrigin[2],
|
||||
);
|
||||
const protagonistGunOrigin = new Vector3(
|
||||
protagonistTurretModelDefinition.gunOrigin[0],
|
||||
protagonistTurretModelDefinition.gunOrigin[1],
|
||||
-protagonistTurretModelDefinition.gunOrigin[2],
|
||||
);
|
||||
const antagonistGunHeight =
|
||||
protagonistTrackModelDefinition.origin[1] +
|
||||
antagonistModelDefinition.turretOrigin[1] +
|
||||
antagonistTurretModelDefinition.gunOrigin[1];
|
||||
const [autoRotate, setAutoRotate] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
camera.position.set(...initialPosition);
|
||||
orbitControls.current?.target.set(0, 1.25, 0);
|
||||
}, [camera]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe(
|
||||
(state) => state.controlsEnabled,
|
||||
(enabled) => {
|
||||
if (orbitControls.current) orbitControls.current.enabled = enabled;
|
||||
},
|
||||
);
|
||||
|
||||
async function handlePoseEvent(event: Pose) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment,
|
||||
duel.protagonist.equipmentMatrix,
|
||||
);
|
||||
|
||||
switch (event) {
|
||||
case Pose.HullDown: {
|
||||
const [pitch] = applyPitchYawLimits(
|
||||
-Infinity,
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
camera.position
|
||||
.set(0, 0, 0)
|
||||
.add(protagonistHullOrigin)
|
||||
.add(protagonistTurretOrigin)
|
||||
.add(protagonistGunOrigin)
|
||||
.add(
|
||||
new Vector3(
|
||||
0,
|
||||
poseDistances[event] * Math.sin(pitch),
|
||||
poseDistances[event] * -Math.cos(pitch),
|
||||
),
|
||||
);
|
||||
camera.lookAt(
|
||||
protagonistHullOrigin
|
||||
.clone()
|
||||
.add(protagonistTurretOrigin)
|
||||
.add(protagonistGunOrigin),
|
||||
);
|
||||
orbitControls.current?.target.set(0, antagonistGunHeight, 0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.FaceHug: {
|
||||
const [pitch] = applyPitchYawLimits(
|
||||
0,
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
camera.position
|
||||
.set(0, 0, 0)
|
||||
.add(protagonistHullOrigin)
|
||||
.add(protagonistTurretOrigin)
|
||||
.add(protagonistGunOrigin)
|
||||
.add(
|
||||
new Vector3(
|
||||
0,
|
||||
poseDistances[event] * Math.sin(pitch),
|
||||
poseDistances[event] * -Math.cos(pitch),
|
||||
),
|
||||
);
|
||||
orbitControls.current?.target
|
||||
.set(0, 0, 0)
|
||||
.add(protagonistHullOrigin)
|
||||
.add(protagonistTurretOrigin)
|
||||
.add(protagonistGunOrigin)
|
||||
.add(
|
||||
new Vector3(
|
||||
0,
|
||||
0.5 * poseDistances[event] * Math.sin(pitch),
|
||||
0.5 * poseDistances[event] * -Math.cos(pitch),
|
||||
),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Pose.Default: {
|
||||
camera.position.set(...initialPosition);
|
||||
orbitControls.current?.target.set(0, 1.25, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
poseEvent.on(handlePoseEvent);
|
||||
|
||||
return () => {
|
||||
unsubscribeTankopediaEphemeral();
|
||||
poseEvent.off(handlePoseEvent);
|
||||
};
|
||||
}, [camera, protagonist.tank.id, antagonist.tank.id]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleDisturbance() {
|
||||
setAutoRotate(false);
|
||||
}
|
||||
|
||||
poseEvent.on(handleDisturbance);
|
||||
canvas.addEventListener('pointerdown', handleDisturbance);
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener('pointerdown', handleDisturbance);
|
||||
poseEvent.off(handleDisturbance);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OrbitControls
|
||||
maxDistance={20}
|
||||
minDistance={5}
|
||||
ref={orbitControls}
|
||||
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
||||
rotateSpeed={0.25}
|
||||
enableDamping={false}
|
||||
autoRotate={autoRotate}
|
||||
autoRotateSpeed={1 / 4}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Environment } from '@react-three/drei';
|
||||
import { useModel } from '../../../../../hooks/useModel';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
||||
|
||||
export function Lighting() {
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const { hasPbr } = useModel(protagonist.tank.id);
|
||||
const showEnvironment = TankopediaPersistent.use(
|
||||
(state) => state.model.visual.showEnvironment,
|
||||
);
|
||||
const environment = TankopediaPersistent.use(
|
||||
(state) => state.model.visual.environment,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Environment preset={environment} background={showEnvironment} blur={0} />
|
||||
|
||||
<directionalLight
|
||||
position={[1, 1, -1]}
|
||||
intensity={hasPbr ? 1 : 5}
|
||||
castShadow
|
||||
color={'rgb(225, 225, 255)'}
|
||||
/>
|
||||
<directionalLight
|
||||
position={[-1, 1, 1]}
|
||||
intensity={hasPbr ? 1 : 4}
|
||||
castShadow
|
||||
color={'rgb(240, 255, 240)'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
|
||||
export function AutoClear() {
|
||||
useFrame(({ gl }) => {
|
||||
gl.clear();
|
||||
}, 0);
|
||||
|
||||
return null;
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import { Spinner } from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
|
||||
export function ModelLoader() {
|
||||
return (
|
||||
<Html center position={[0, 1.5, 0]}>
|
||||
<Spinner size="3" />
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
-332
@@ -1,332 +0,0 @@
|
||||
import { normalizeAngleRad } from '@blitzkit/core';
|
||||
import { invalidate, ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { memo, useRef } from 'react';
|
||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
||||
import { ModelTankWrapper } from '../../../../../../../components/Armor/components/ModelTankWrapper';
|
||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../../../../core/blitzkit/jsxTree';
|
||||
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
||||
import { useModel } from '../../../../../../../hooks/useModel';
|
||||
import { useTankModelDefinition } from '../../../../../../../hooks/useTankModelDefinition';
|
||||
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
||||
import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent';
|
||||
import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants';
|
||||
|
||||
export const TankModel = memo(() => {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const duelStore = Duel.useStore();
|
||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const turretContainer = useRef<Group>(null);
|
||||
const gunContainer = useRef<Group>(null);
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const { gltf } = useModel(protagonist.tank.id);
|
||||
const nodes = Object.values(gltf.nodes);
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
|
||||
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||
|
||||
return (
|
||||
<ModelTankWrapper ref={hullContainer}>
|
||||
{nodes.map((node) => {
|
||||
const isHull = node.name === 'hull';
|
||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||
const isTrack = node.name.startsWith('chassis_track_');
|
||||
const isVisible = isHull || isWheel || isTrack;
|
||||
const position = new Vector2();
|
||||
const delta = new Vector2();
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
function translateTexture(offset: number) {
|
||||
const mesh = node.children[0] as Mesh;
|
||||
const material = mesh?.material as MeshStandardMaterial;
|
||||
|
||||
if (!material) return;
|
||||
|
||||
invalidate();
|
||||
|
||||
if (material.map) material.map.offset.y += offset;
|
||||
if (material.aoMap) material.aoMap.offset.y += offset;
|
||||
if (material.normalMap) material.normalMap.offset.y += offset;
|
||||
if (material.roughnessMap) material.roughnessMap.offset.y += offset;
|
||||
if (material.metalnessMap) material.metalnessMap.offset.y += offset;
|
||||
}
|
||||
|
||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
if (
|
||||
isTrack &&
|
||||
tankopediaPersistentStore.getState().display ===
|
||||
TankopediaDisplay.Model
|
||||
) {
|
||||
position.set(event.clientX, event.clientY);
|
||||
event.stopPropagation();
|
||||
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = false;
|
||||
});
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
position.set(event.clientX, event.clientY);
|
||||
const deltaX = delta.x / window.innerWidth;
|
||||
const deltaY = delta.y / window.innerHeight;
|
||||
|
||||
translateTexture(deltaX + deltaY);
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = true;
|
||||
});
|
||||
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
onPointerDown,
|
||||
},
|
||||
// group(group) {
|
||||
// console.log(group.name);
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive && group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
})}
|
||||
|
||||
<group ref={turretContainer}>
|
||||
{nodes.map((node) => {
|
||||
const isTurret = node.name.startsWith('turret_');
|
||||
const isCurrentTurret =
|
||||
node.name ===
|
||||
`turret_${turretModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentTurret;
|
||||
const position = new Vector2();
|
||||
const delta = new Vector2();
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
let pitch = 0;
|
||||
let yaw = 0;
|
||||
|
||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (!isTurret) return;
|
||||
|
||||
position.set(event.clientX, event.clientY);
|
||||
yaw = protagonist.yaw;
|
||||
pitch = protagonist.pitch;
|
||||
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = false;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.shot = undefined;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
async function handlePointerMove(event: PointerEvent) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment,
|
||||
duel.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
position.set(event.clientX, event.clientY);
|
||||
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch,
|
||||
yaw + delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateDuel((draft) => {
|
||||
draft.protagonist.pitch = normalizeAngleRad(pitch);
|
||||
draft.protagonist.yaw = normalizeAngleRad(yaw);
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = true;
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
onPointerDown,
|
||||
},
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
})}
|
||||
|
||||
<group ref={gunContainer}>
|
||||
{nodes.map((node) => {
|
||||
const isCurrentMantlet =
|
||||
node.name ===
|
||||
`gun_${gunModelDefinition.model
|
||||
.toString()
|
||||
.padStart(2, '0')}_mask`;
|
||||
const isCurrentGun =
|
||||
node.name ===
|
||||
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
|
||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||
const position = new Vector2();
|
||||
const delta = new Vector2();
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
let pitch = 0;
|
||||
let yaw = 0;
|
||||
|
||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||
event.stopPropagation();
|
||||
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = false;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.shot = undefined;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
|
||||
position.set(event.clientX, event.clientY);
|
||||
pitch = protagonist.pitch;
|
||||
yaw = protagonist.yaw;
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
async function handlePointerMove(event: PointerEvent) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment,
|
||||
duel.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
position.set(event.clientX, event.clientY);
|
||||
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch - delta.y * (Math.PI / boundingRect.height),
|
||||
yaw + delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
function handlePointerUp() {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.controlsEnabled = true;
|
||||
});
|
||||
mutateDuel((draft) => {
|
||||
draft.protagonist.pitch = normalizeAngleRad(pitch);
|
||||
draft.protagonist.yaw = normalizeAngleRad(yaw);
|
||||
});
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
onPointerDown,
|
||||
},
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
</ModelTankWrapper>
|
||||
);
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useFrame, useLoader } from '@react-three/fiber';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { useRef } from 'react';
|
||||
import { MeshStandardMaterial, TextureLoader } from 'three';
|
||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
||||
|
||||
export function SceneProps() {
|
||||
const show = TankopediaPersistent.use(
|
||||
(state) =>
|
||||
state.model.visual.showGrid && !state.model.visual.showEnvironment,
|
||||
);
|
||||
const texture = useLoader(TextureLoader, 'https://i.imgur.com/C28Z8nU.png');
|
||||
const material = useRef<MeshStandardMaterial>(null);
|
||||
|
||||
useFrame(({ camera }) => {
|
||||
if (!material.current) return;
|
||||
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
|
||||
});
|
||||
|
||||
texture.anisotropy = 2;
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<planeGeometry args={[10, 10]} />
|
||||
<meshStandardMaterial ref={material} map={texture} transparent />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
||||
import { ThicknessRange } from '../../../../../components/Armor/components/StaticArmor';
|
||||
import { useArmor } from '../../../../../hooks/useArmor';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
||||
import { DynamicArmorSwitcher } from './Model/components/Options/components/DynamicArmorSwitcher';
|
||||
|
||||
interface ThicknessesProps {
|
||||
thicknessRange: ThicknessRange;
|
||||
}
|
||||
|
||||
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
||||
const showExternalModules = TankopediaPersistent.use(
|
||||
(state) => state.model.visual.showExternalModules,
|
||||
);
|
||||
const showSpacedArmor = TankopediaPersistent.use(
|
||||
(state) => state.model.visual.showSpacedArmor,
|
||||
);
|
||||
const showPrimaryArmor = TankopediaPersistent.use(
|
||||
(state) => state.model.visual.showPrimaryArmor,
|
||||
);
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
|
||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||
const { hasDynamicArmor } = useArmor(protagonistTank.id);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
position="absolute"
|
||||
right="0"
|
||||
top="50%"
|
||||
mr="4"
|
||||
style={{ transform: 'translateY(-50%)', userSelect: 'none' }}
|
||||
direction="column"
|
||||
gap="2"
|
||||
align="end"
|
||||
>
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.model.visual.showPrimaryArmor =
|
||||
!draft.model.visual.showPrimaryArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{(thicknessRange.value * 1.5).toFixed(0)}
|
||||
</Text>
|
||||
<Flex gap="1">
|
||||
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">Primary</Text>
|
||||
</Flex>
|
||||
<Text color="gray" size="1">
|
||||
0
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(64, 0, 0), rgb(255, 0, 0) 33%, rgb(255, 255, 0) 66%, rgb(0, 255, 0))`,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.model.visual.showSpacedArmor =
|
||||
!draft.model.visual.showSpacedArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{thicknessRange.value.toFixed(0)}
|
||||
</Text>
|
||||
<Flex gap="1">
|
||||
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">Spaced</Text>
|
||||
</Flex>
|
||||
<Text color="gray" size="1">
|
||||
0
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.model.visual.showExternalModules =
|
||||
!draft.model.visual.showExternalModules;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Flex gap="1">
|
||||
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">External</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
height="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#3eb0c6',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="2" mt="2">
|
||||
<Button
|
||||
color="red"
|
||||
size="1"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.editStatic = false;
|
||||
draft.model = tankopediaEphemeralStore.getInitialState().model;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.editStatic = !draft.editStatic;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text size="1">Edit</Text>
|
||||
<Checkbox size="2" checked={editStatic} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<DynamicArmorSwitcher />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user