mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: more rendering
This commit is contained in:
1 parent
5b16e22750
commit
cbd2c2a719
10 files changed
+302
-366
No files matched your search
@@ -5,16 +5,13 @@ import { Color, Material, MeshBasicMaterial } from 'three';
|
|||||||
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
import { Duel } from '../../../stores/duel';
|
import { Duel } from '../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
|
import { StaticArmorSceneComponents } from './StaticArmorSceneComponents';
|
||||||
interface ArmorSceneProps {
|
|
||||||
thicknessRange: ThicknessRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThicknessRange {
|
export interface ThicknessRange {
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
export const StaticArmor = memo(() => {
|
||||||
// const wrapper = useRef<Group>(null);
|
// const wrapper = useRef<Group>(null);
|
||||||
// const turretContainer = useRef<Group>(null);
|
// const turretContainer = useRef<Group>(null);
|
||||||
// const gunContainer = useRef<Group>(null);
|
// const gunContainer = useRef<Group>(null);
|
||||||
@@ -58,44 +55,11 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
|
|
||||||
// useTankTransform(track, turret, turretContainer, gunContainer);
|
// useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
|
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
|
||||||
const armor = TankopediaEphemeral.use((state) => state.armor);
|
|
||||||
const hullArmor = armor.groups.hull;
|
|
||||||
const hullGltf = useGLTF(asset(`tanks/${tank.id}/collision/hull.glb`));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{jsxTree(hullGltf.scene, {
|
<StaticArmorSceneComponents group="hull" />
|
||||||
mesh(mesh) {
|
|
||||||
if (!(mesh.material instanceof Material)) {
|
|
||||||
return { visible: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const armorName = mesh.material.name;
|
|
||||||
const material = new MeshBasicMaterial({});
|
|
||||||
const armorPlate = hullArmor.armors[armorName];
|
|
||||||
|
|
||||||
switch (armorPlate.type) {
|
|
||||||
case 'Armor':
|
|
||||||
material.color = new Color('#ec6142');
|
|
||||||
material.transparent = true;
|
|
||||||
material.opacity = 0.5;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ArmorScreen':
|
|
||||||
material.color = new Color('#ff8dcc');
|
|
||||||
material.transparent = true;
|
|
||||||
material.opacity = 0.5;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
material.color = new Color('#70b8ff');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { material };
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
import { I_HAT, J_HAT } from '@blitzkit/core';
|
|
||||||
import { type MeshProps, useThree } from '@react-three/fiber';
|
|
||||||
import { clamp } from 'lodash-es';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
Box3,
|
|
||||||
Color,
|
|
||||||
DoubleSide,
|
|
||||||
FrontSide,
|
|
||||||
LineBasicMaterial,
|
|
||||||
Mesh,
|
|
||||||
MeshStandardMaterial,
|
|
||||||
Object3D,
|
|
||||||
Plane,
|
|
||||||
Quaternion,
|
|
||||||
Vector3,
|
|
||||||
} from 'three';
|
|
||||||
import { unrotateDavaVector } from '../../../core/blitz/unrotateDavaVector';
|
|
||||||
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
|
||||||
import {
|
|
||||||
type ModelTransformEventData,
|
|
||||||
modelTransformEvent,
|
|
||||||
} from '../../../core/blitzkit/modelTransform';
|
|
||||||
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
|
||||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
|
||||||
import type {
|
|
||||||
ArmorUserData,
|
|
||||||
ExternalModuleVariant,
|
|
||||||
} from './SpacedArmorSceneComponent';
|
|
||||||
import type { ThicknessRange } from './StaticArmor';
|
|
||||||
|
|
||||||
type StaticArmorSceneComponentProps = {
|
|
||||||
name: string;
|
|
||||||
thickness: number;
|
|
||||||
thicknessRange: ThicknessRange;
|
|
||||||
node: Object3D;
|
|
||||||
onPointerDown?: MeshProps['onPointerDown'];
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
type: Exclude<ArmorType, ArmorType.External>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: ArmorType.External;
|
|
||||||
variant: ExternalModuleVariant;
|
|
||||||
}
|
|
||||||
) &
|
|
||||||
(
|
|
||||||
| {
|
|
||||||
clip?: undefined;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
clip: Plane;
|
|
||||||
hullOrigin: Vector3;
|
|
||||||
turretOrigin: Vector3;
|
|
||||||
gunOrigin: Vector3;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const unselectedColor = new Color(0x404040);
|
|
||||||
const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
|
|
||||||
|
|
||||||
export function StaticArmorSceneComponent({
|
|
||||||
name,
|
|
||||||
thickness,
|
|
||||||
thicknessRange,
|
|
||||||
node,
|
|
||||||
onPointerDown,
|
|
||||||
...props
|
|
||||||
}: StaticArmorSceneComponentProps) {
|
|
||||||
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
|
|
||||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
|
||||||
const camera = useThree((state) => state.camera);
|
|
||||||
const x = thickness / thicknessRange.value;
|
|
||||||
const xClamped = clamp(x, 0, 1);
|
|
||||||
let color: Color;
|
|
||||||
let opacity: number;
|
|
||||||
let depthWrite = true;
|
|
||||||
|
|
||||||
switch (props.type) {
|
|
||||||
case ArmorType.Primary:
|
|
||||||
if (x > 1) {
|
|
||||||
color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
|
|
||||||
} else {
|
|
||||||
color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
|
|
||||||
}
|
|
||||||
opacity = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArmorType.Spaced:
|
|
||||||
color = new Color(
|
|
||||||
clamp(1 - (7 / 8) * xClamped, 0, 1),
|
|
||||||
0,
|
|
||||||
clamp(1 - (1 / 8) * xClamped, 0, 1),
|
|
||||||
);
|
|
||||||
opacity = clamp(x + 1 / 2, 0, 1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArmorType.External:
|
|
||||||
color = externalModuleColor;
|
|
||||||
opacity = 1 / 8;
|
|
||||||
depthWrite = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
opacity = clamp(opacity, 0, 1);
|
|
||||||
|
|
||||||
const surfaceMaterial = useMemo(
|
|
||||||
() =>
|
|
||||||
new MeshStandardMaterial({
|
|
||||||
color,
|
|
||||||
opacity,
|
|
||||||
transparent: opacity < 1,
|
|
||||||
depthWrite,
|
|
||||||
...(props.clip ? { clippingPlanes: [props.clip] } : {}),
|
|
||||||
}),
|
|
||||||
[thickness],
|
|
||||||
);
|
|
||||||
const outlineMaterial = useMemo(
|
|
||||||
() =>
|
|
||||||
new LineBasicMaterial({
|
|
||||||
color: color
|
|
||||||
.clone()
|
|
||||||
.multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1),
|
|
||||||
}),
|
|
||||||
[thickness],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handleHighlightArmor(selectedName?: string) {
|
|
||||||
if (selectedName === undefined) {
|
|
||||||
// nothing selected, go back to defaults
|
|
||||||
surfaceMaterial.opacity = opacity;
|
|
||||||
surfaceMaterial.transparent = opacity < 1;
|
|
||||||
surfaceMaterial.color = color;
|
|
||||||
surfaceMaterial.depthWrite = props.type !== ArmorType.External;
|
|
||||||
surfaceMaterial.side = FrontSide;
|
|
||||||
outlineMaterial.visible = true;
|
|
||||||
} else if (
|
|
||||||
selectedName === name ||
|
|
||||||
(name.startsWith('chassis_') && selectedName.startsWith('chassis_')) ||
|
|
||||||
(name.startsWith('gun_') &&
|
|
||||||
selectedName.startsWith('gun_') &&
|
|
||||||
!name.includes('_armor_') &&
|
|
||||||
!selectedName.includes('_armor_'))
|
|
||||||
) {
|
|
||||||
// this selected, stand out!
|
|
||||||
surfaceMaterial.opacity = 1;
|
|
||||||
surfaceMaterial.transparent = false;
|
|
||||||
surfaceMaterial.color = color;
|
|
||||||
surfaceMaterial.depthWrite = true;
|
|
||||||
surfaceMaterial.side = DoubleSide;
|
|
||||||
outlineMaterial.visible = true;
|
|
||||||
} else {
|
|
||||||
// something else selected, become background
|
|
||||||
surfaceMaterial.opacity = 1 / 4;
|
|
||||||
surfaceMaterial.transparent = true;
|
|
||||||
surfaceMaterial.color = unselectedColor;
|
|
||||||
surfaceMaterial.depthWrite = props.type !== ArmorType.External;
|
|
||||||
surfaceMaterial.side = FrontSide;
|
|
||||||
outlineMaterial.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
surfaceMaterial.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsubscribes = [
|
|
||||||
tankopediaEphemeralStore.subscribe(
|
|
||||||
(state) => state.highlightArmor?.name,
|
|
||||||
handleHighlightArmor,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (props.clip) {
|
|
||||||
/**
|
|
||||||
* hook inside an if statement?? don't panic! I assure you the clip prop
|
|
||||||
* never mutates :)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { clip } = props;
|
|
||||||
const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
|
|
||||||
const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
|
|
||||||
unrotateDavaVector(props.turretOrigin.clone()),
|
|
||||||
);
|
|
||||||
const barrelOrigin = neckOrigin.clone().add(gunOrigin);
|
|
||||||
const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
|
|
||||||
const point = new Vector3();
|
|
||||||
|
|
||||||
function handleModelTransform(data: ModelTransformEventData) {
|
|
||||||
clip.normal
|
|
||||||
.set(0, 0, -1)
|
|
||||||
.applyAxisAngle(I_HAT, data.pitch)
|
|
||||||
.applyAxisAngle(J_HAT, data.yaw ?? 0);
|
|
||||||
clip.constant = 0;
|
|
||||||
|
|
||||||
point
|
|
||||||
.copy(gunOrigin)
|
|
||||||
.applyAxisAngle(J_HAT, data.yaw ?? 0)
|
|
||||||
.add(clip.normal.multiplyScalar(-distanceToBarrel))
|
|
||||||
.add(neckOrigin);
|
|
||||||
clip.normal.normalize();
|
|
||||||
clip.constant -= clip.distanceToPoint(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
modelTransformEvent.on(handleModelTransform);
|
|
||||||
|
|
||||||
unsubscribes.push(() => modelTransformEvent.off(handleModelTransform));
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
||||||
};
|
|
||||||
}, [thickness]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{jsxTree(node, {
|
|
||||||
mesh: {
|
|
||||||
material: surfaceMaterial,
|
|
||||||
onPointerDown,
|
|
||||||
userData: {
|
|
||||||
type: props.type,
|
|
||||||
variant: props.type === ArmorType.External ? props.variant : 'gun',
|
|
||||||
thickness,
|
|
||||||
} satisfies ArmorUserData,
|
|
||||||
|
|
||||||
onClick(event) {
|
|
||||||
if (discardClippingPlane(event.object, event.point)) return;
|
|
||||||
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const { editStatic } = tankopediaEphemeralStore.getState();
|
|
||||||
|
|
||||||
const bounds = new Box3().setFromObject(event.object);
|
|
||||||
const point = bounds.min
|
|
||||||
.clone()
|
|
||||||
.add(bounds.max)
|
|
||||||
.divideScalar(2)
|
|
||||||
.setY(bounds.max.y);
|
|
||||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
|
||||||
const surfaceNormal = event
|
|
||||||
.normal!.clone()
|
|
||||||
.applyQuaternion(
|
|
||||||
event.object.getWorldQuaternion(new Quaternion()),
|
|
||||||
);
|
|
||||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
|
||||||
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
|
|
||||||
|
|
||||||
mutateTankopediaEphemeralStore((draft) => {
|
|
||||||
draft.highlightArmor = {
|
|
||||||
type: props.type,
|
|
||||||
name,
|
|
||||||
point,
|
|
||||||
thickness,
|
|
||||||
thicknessAngled,
|
|
||||||
angle,
|
|
||||||
color: `#${color.getHexString()}`,
|
|
||||||
editingPlate: editStatic,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
|
|
||||||
{node instanceof Mesh && (
|
|
||||||
<lineSegments material={outlineMaterial}>
|
|
||||||
<edgesGeometry args={[node.geometry, 45]} />
|
|
||||||
</lineSegments>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
import { asset } from '@blitzkit/core';
|
||||||
|
import { useGLTF } from '@react-three/drei';
|
||||||
|
import { Color, Material, MeshStandardMaterial } from 'three';
|
||||||
|
import { clamp } from 'three/src/math/MathUtils.js';
|
||||||
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
|
import { Duel } from '../../../stores/duel';
|
||||||
|
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
|
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
|
type StaticArmorSceneComponentsProps = {
|
||||||
|
group: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unselectedColor = new Color(0x404040);
|
||||||
|
const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
|
||||||
|
|
||||||
|
export function StaticArmorSceneComponents({
|
||||||
|
group,
|
||||||
|
}: StaticArmorSceneComponentsProps) {
|
||||||
|
const showModules = TankopediaPersistent.use((state) => state.showModules);
|
||||||
|
const showArmorScreen = TankopediaPersistent.use(
|
||||||
|
(state) => state.showArmorScreen,
|
||||||
|
);
|
||||||
|
const showArmor = TankopediaPersistent.use((state) => state.showArmor);
|
||||||
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const gltf = useGLTF(asset(`tanks/${tank.id}/collision/${group}.glb`));
|
||||||
|
const armor = TankopediaEphemeral.use((state) => state.armor);
|
||||||
|
const groupArmor = armor.groups[group];
|
||||||
|
const thicknessRange = TankopediaEphemeral.use(
|
||||||
|
(state) => state.thicknessRange,
|
||||||
|
);
|
||||||
|
|
||||||
|
return jsxTree(gltf.scene, {
|
||||||
|
mesh(mesh) {
|
||||||
|
if (!(mesh.material instanceof Material)) {
|
||||||
|
return { visible: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const armorName = mesh.material.name;
|
||||||
|
const armorPlate = groupArmor.armors[armorName];
|
||||||
|
const x = armorPlate.thickness / thicknessRange;
|
||||||
|
const xClamped = clamp(x, 0, 1);
|
||||||
|
const material = new MeshStandardMaterial();
|
||||||
|
|
||||||
|
switch (armorPlate.type) {
|
||||||
|
case 'Armor':
|
||||||
|
if (x > 1) {
|
||||||
|
material.color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
|
||||||
|
} else {
|
||||||
|
material.color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
|
||||||
|
}
|
||||||
|
material.opacity = 1;
|
||||||
|
material.visible = showArmor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArmorScreen':
|
||||||
|
material.color = new Color(
|
||||||
|
clamp(1 - (7 / 8) * xClamped, 0, 1),
|
||||||
|
0,
|
||||||
|
clamp(1 - (1 / 8) * xClamped, 0, 1),
|
||||||
|
);
|
||||||
|
material.opacity = clamp(x + 1 / 2, 0, 1);
|
||||||
|
material.visible = showArmorScreen;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
material.color = externalModuleColor;
|
||||||
|
material.opacity = 7 / 8;
|
||||||
|
material.depthWrite = false;
|
||||||
|
material.visible = showModules;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
material.opacity = clamp(material.opacity, 0, 1);
|
||||||
|
material.transparent = material.opacity < 1;
|
||||||
|
|
||||||
|
return { material };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
|
||||||
|
// const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
|
// const camera = useThree((state) => state.camera);
|
||||||
|
// const x = thickness / thicknessRange.value;
|
||||||
|
// const xClamped = clamp(x, 0, 1);
|
||||||
|
// let color: Color;
|
||||||
|
// let opacity: number;
|
||||||
|
// let depthWrite = true;
|
||||||
|
// switch (props.type) {
|
||||||
|
// case ArmorType.Primary:
|
||||||
|
// if (x > 1) {
|
||||||
|
// color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
|
||||||
|
// } else {
|
||||||
|
// color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
|
||||||
|
// }
|
||||||
|
// opacity = 1;
|
||||||
|
// break;
|
||||||
|
// case ArmorType.Spaced:
|
||||||
|
// color = new Color(
|
||||||
|
// clamp(1 - (7 / 8) * xClamped, 0, 1),
|
||||||
|
// 0,
|
||||||
|
// clamp(1 - (1 / 8) * xClamped, 0, 1),
|
||||||
|
// );
|
||||||
|
// opacity = clamp(x + 1 / 2, 0, 1);
|
||||||
|
// break;
|
||||||
|
// case ArmorType.External:
|
||||||
|
// color = externalModuleColor;
|
||||||
|
// opacity = 1 / 8;
|
||||||
|
// depthWrite = false;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// opacity = clamp(opacity, 0, 1);
|
||||||
|
// const surfaceMaterial = useMemo(
|
||||||
|
// () =>
|
||||||
|
// new MeshStandardMaterial({
|
||||||
|
// color,
|
||||||
|
// opacity,
|
||||||
|
// transparent: opacity < 1,
|
||||||
|
// depthWrite,
|
||||||
|
// ...(props.clip ? { clippingPlanes: [props.clip] } : {}),
|
||||||
|
// }),
|
||||||
|
// [thickness],
|
||||||
|
// );
|
||||||
|
// const outlineMaterial = useMemo(
|
||||||
|
// () =>
|
||||||
|
// new LineBasicMaterial({
|
||||||
|
// color: color
|
||||||
|
// .clone()
|
||||||
|
// .multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1),
|
||||||
|
// }),
|
||||||
|
// [thickness],
|
||||||
|
// );
|
||||||
|
// useEffect(() => {
|
||||||
|
// function handleHighlightArmor(selectedName?: string) {
|
||||||
|
// if (selectedName === undefined) {
|
||||||
|
// // nothing selected, go back to defaults
|
||||||
|
// surfaceMaterial.opacity = opacity;
|
||||||
|
// surfaceMaterial.transparent = opacity < 1;
|
||||||
|
// surfaceMaterial.color = color;
|
||||||
|
// surfaceMaterial.depthWrite = props.type !== ArmorType.External;
|
||||||
|
// surfaceMaterial.side = FrontSide;
|
||||||
|
// outlineMaterial.visible = true;
|
||||||
|
// } else if (
|
||||||
|
// selectedName === name ||
|
||||||
|
// (name.startsWith('chassis_') && selectedName.startsWith('chassis_')) ||
|
||||||
|
// (name.startsWith('gun_') &&
|
||||||
|
// selectedName.startsWith('gun_') &&
|
||||||
|
// !name.includes('_armor_') &&
|
||||||
|
// !selectedName.includes('_armor_'))
|
||||||
|
// ) {
|
||||||
|
// // this selected, stand out!
|
||||||
|
// surfaceMaterial.opacity = 1;
|
||||||
|
// surfaceMaterial.transparent = false;
|
||||||
|
// surfaceMaterial.color = color;
|
||||||
|
// surfaceMaterial.depthWrite = true;
|
||||||
|
// surfaceMaterial.side = DoubleSide;
|
||||||
|
// outlineMaterial.visible = true;
|
||||||
|
// } else {
|
||||||
|
// // something else selected, become background
|
||||||
|
// surfaceMaterial.opacity = 1 / 4;
|
||||||
|
// surfaceMaterial.transparent = true;
|
||||||
|
// surfaceMaterial.color = unselectedColor;
|
||||||
|
// surfaceMaterial.depthWrite = props.type !== ArmorType.External;
|
||||||
|
// surfaceMaterial.side = FrontSide;
|
||||||
|
// outlineMaterial.visible = false;
|
||||||
|
// }
|
||||||
|
// surfaceMaterial.needsUpdate = true;
|
||||||
|
// }
|
||||||
|
// const unsubscribes = [
|
||||||
|
// tankopediaEphemeralStore.subscribe(
|
||||||
|
// (state) => state.highlightArmor?.name,
|
||||||
|
// handleHighlightArmor,
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
// if (props.clip) {
|
||||||
|
// /**
|
||||||
|
// * hook inside an if statement?? don't panic! I assure you the clip prop
|
||||||
|
// * never mutates :)
|
||||||
|
// */
|
||||||
|
// const { clip } = props;
|
||||||
|
// const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
|
||||||
|
// const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
|
||||||
|
// unrotateDavaVector(props.turretOrigin.clone()),
|
||||||
|
// );
|
||||||
|
// const barrelOrigin = neckOrigin.clone().add(gunOrigin);
|
||||||
|
// const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
|
||||||
|
// const point = new Vector3();
|
||||||
|
// function handleModelTransform(data: ModelTransformEventData) {
|
||||||
|
// clip.normal
|
||||||
|
// .set(0, 0, -1)
|
||||||
|
// .applyAxisAngle(I_HAT, data.pitch)
|
||||||
|
// .applyAxisAngle(J_HAT, data.yaw ?? 0);
|
||||||
|
// clip.constant = 0;
|
||||||
|
// point
|
||||||
|
// .copy(gunOrigin)
|
||||||
|
// .applyAxisAngle(J_HAT, data.yaw ?? 0)
|
||||||
|
// .add(clip.normal.multiplyScalar(-distanceToBarrel))
|
||||||
|
// .add(neckOrigin);
|
||||||
|
// clip.normal.normalize();
|
||||||
|
// clip.constant -= clip.distanceToPoint(point);
|
||||||
|
// }
|
||||||
|
// modelTransformEvent.on(handleModelTransform);
|
||||||
|
// unsubscribes.push(() => modelTransformEvent.off(handleModelTransform));
|
||||||
|
// }
|
||||||
|
// return () => {
|
||||||
|
// unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
// };
|
||||||
|
// }, [thickness]);
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// {jsxTree(node, {
|
||||||
|
// mesh: {
|
||||||
|
// material: surfaceMaterial,
|
||||||
|
// onPointerDown,
|
||||||
|
// userData: {
|
||||||
|
// type: props.type,
|
||||||
|
// variant: props.type === ArmorType.External ? props.variant : 'gun',
|
||||||
|
// thickness,
|
||||||
|
// } satisfies ArmorUserData,
|
||||||
|
// onClick(event) {
|
||||||
|
// if (discardClippingPlane(event.object, event.point)) return;
|
||||||
|
// event.stopPropagation();
|
||||||
|
// const { editStatic } = tankopediaEphemeralStore.getState();
|
||||||
|
// const bounds = new Box3().setFromObject(event.object);
|
||||||
|
// const point = bounds.min
|
||||||
|
// .clone()
|
||||||
|
// .add(bounds.max)
|
||||||
|
// .divideScalar(2)
|
||||||
|
// .setY(bounds.max.y);
|
||||||
|
// const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||||
|
// const surfaceNormal = event
|
||||||
|
// .normal!.clone()
|
||||||
|
// .applyQuaternion(
|
||||||
|
// event.object.getWorldQuaternion(new Quaternion()),
|
||||||
|
// );
|
||||||
|
// const angle = surfaceNormal.angleTo(cameraNormal);
|
||||||
|
// const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
|
||||||
|
// mutateTankopediaEphemeralStore((draft) => {
|
||||||
|
// draft.highlightArmor = {
|
||||||
|
// type: props.type,
|
||||||
|
// name,
|
||||||
|
// point,
|
||||||
|
// thickness,
|
||||||
|
// thicknessAngled,
|
||||||
|
// angle,
|
||||||
|
// color: `#${color.getHexString()}`,
|
||||||
|
// editingPlate: editStatic,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })}
|
||||||
|
// {node instanceof Mesh && (
|
||||||
|
// <lineSegments material={outlineMaterial}>
|
||||||
|
// <edgesGeometry args={[node.geometry, 45]} />
|
||||||
|
// </lineSegments>
|
||||||
|
// )}
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
}
|
||||||
+20
-28
@@ -4,27 +4,21 @@ import { Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
|||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
|
|
||||||
|
|
||||||
interface ThicknessesProps {
|
export function Thicknesses() {
|
||||||
thicknessRange: ThicknessRange;
|
const showModules = TankopediaPersistent.use((state) => state.showModules);
|
||||||
}
|
const showArmorScreen = TankopediaPersistent.use(
|
||||||
|
(state) => state.showArmorScreen,
|
||||||
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|
||||||
const showExternalModules = TankopediaPersistent.use(
|
|
||||||
(state) => state.showExternalModules,
|
|
||||||
);
|
|
||||||
const showSpacedArmor = TankopediaPersistent.use(
|
|
||||||
(state) => state.showSpacedArmor,
|
|
||||||
);
|
|
||||||
const showPrimaryArmor = TankopediaPersistent.use(
|
|
||||||
(state) => state.showPrimaryArmor,
|
|
||||||
);
|
);
|
||||||
|
const showArmor = TankopediaPersistent.use((state) => state.showArmor);
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
|
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
|
||||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
const thicknessRange = TankopediaEphemeral.use(
|
||||||
|
(state) => state.thicknessRange,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -41,10 +35,10 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
<Flex
|
<Flex
|
||||||
height="64px"
|
height="64px"
|
||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
draft.showArmor = !draft.showArmor;
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
@@ -54,7 +48,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
<Flex direction="column" align="end" justify="between">
|
<Flex direction="column" align="end" justify="between">
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, [
|
||||||
(thicknessRange.value * 1.5).toFixed(0),
|
(thicknessRange * 1.5).toFixed(0),
|
||||||
])}
|
])}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="1">
|
<Text size="1">
|
||||||
@@ -75,17 +69,17 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
color: 'black',
|
color: 'black',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
{showArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
height="64px"
|
height="64px"
|
||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showArmorScreen ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.showSpacedArmor = !draft.showSpacedArmor;
|
draft.showArmorScreen = !draft.showArmorScreen;
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
@@ -94,9 +88,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
>
|
>
|
||||||
<Flex direction="column" align="end" justify="between">
|
<Flex direction="column" align="end" justify="between">
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, [thicknessRange.toFixed(0)])}
|
||||||
thicknessRange.value.toFixed(0),
|
|
||||||
])}
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="1">
|
<Text size="1">
|
||||||
{strings.website.tools.tanks.sandbox.static.spaced}
|
{strings.website.tools.tanks.sandbox.static.spaced}
|
||||||
@@ -115,16 +107,16 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
|
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
{showArmorScreen ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
gap="2"
|
gap="2"
|
||||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showModules ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.showExternalModules = !draft.showExternalModules;
|
draft.showModules = !draft.showModules;
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
@@ -148,7 +140,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
color: 'black',
|
color: 'black',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
{showModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -179,7 +171,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.editStatic = false;
|
draft.editStatic = false;
|
||||||
draft.model = tankopediaEphemeralStore.getInitialState().model;
|
draft.armor = tankopediaEphemeralStore.getInitialState().armor;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,15 +12,13 @@ import {
|
|||||||
} from '../../../../../stores/tankopediaEphemeral';
|
} from '../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
||||||
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
||||||
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
|
|
||||||
import { Thicknesses } from './components/Thicknesses';
|
import { Thicknesses } from './components/Thicknesses';
|
||||||
|
|
||||||
interface OptionsProps {
|
interface OptionsProps {
|
||||||
thicknessRange: ThicknessRange;
|
|
||||||
canvas: RefObject<HTMLCanvasElement>;
|
canvas: RefObject<HTMLCanvasElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Options({ thicknessRange, canvas }: OptionsProps) {
|
export function Options({ canvas }: OptionsProps) {
|
||||||
const hasCustomShell = TankopediaEphemeral.use(
|
const hasCustomShell = TankopediaEphemeral.use(
|
||||||
(state) => state.customShell !== undefined,
|
(state) => state.customShell !== undefined,
|
||||||
);
|
);
|
||||||
@@ -75,9 +73,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
{display === TankopediaDisplay.StaticArmor && <Thicknesses />}
|
||||||
<Thicknesses thicknessRange={thicknessRange} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* {display === TankopediaDisplay.DynamicArmor && (
|
{/* {display === TankopediaDisplay.DynamicArmor && (
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
+3
-9
@@ -8,10 +8,7 @@ import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/co
|
|||||||
import { Armor } from '../../../../Armor';
|
import { Armor } from '../../../../Armor';
|
||||||
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
|
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
|
||||||
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
|
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
|
||||||
import {
|
import { StaticArmor } from '../../../../Armor/components/StaticArmor';
|
||||||
StaticArmor,
|
|
||||||
type ThicknessRange,
|
|
||||||
} from '../../../../Armor/components/StaticArmor';
|
|
||||||
import { AutoClear } from './components/AutoClear';
|
import { AutoClear } from './components/AutoClear';
|
||||||
import { Controls } from './components/Control';
|
import { Controls } from './components/Control';
|
||||||
import { Lighting } from './components/Lighting';
|
import { Lighting } from './components/Lighting';
|
||||||
@@ -20,12 +17,11 @@ import { SceneProps } from './components/SceneProps';
|
|||||||
import { TankModel } from './components/TankModel';
|
import { TankModel } from './components/TankModel';
|
||||||
|
|
||||||
interface TankSandboxProps {
|
interface TankSandboxProps {
|
||||||
thicknessRange: ThicknessRange;
|
|
||||||
naked?: boolean;
|
naked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||||
({ thicknessRange, naked }, ref) => {
|
({ naked }, ref) => {
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
// const hasImprovedVerticalStabilizer = useEquipment(122);
|
// const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
@@ -193,9 +189,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
|
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
{display === TankopediaDisplay.StaticArmor && <StaticArmor />}
|
||||||
<StaticArmor thicknessRange={thicknessRange} />
|
|
||||||
)}
|
|
||||||
<Lighting />
|
<Lighting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
|
|
||||||
// return { value } satisfies ThicknessRange;
|
// return { value } satisfies ThicknessRange;
|
||||||
// }, [protagonist]);
|
// }, [protagonist]);
|
||||||
const thicknessRange = { value: 300 };
|
|
||||||
// const duelStore = Duel.useStore();
|
// const duelStore = Duel.useStore();
|
||||||
// const mutateDuel = Duel.useMutation();
|
// const mutateDuel = Duel.useMutation();
|
||||||
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
@@ -140,11 +139,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
{skeleton && <TankSandboxLoader id={protagonist.id} />}
|
{skeleton && <TankSandboxLoader id={protagonist.id} />}
|
||||||
|
|
||||||
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
||||||
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
<TankSandbox ref={canvas} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Options canvas={canvas} thicknessRange={thicknessRange} />
|
<Options canvas={canvas} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type PageProps = MaybeSkeletonComponentProps &
|
|||||||
export function Page({ tank, armor, skeleton, locale }: PageProps) {
|
export function Page({ tank, armor, skeleton, locale }: PageProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<TankopediaEphemeral.Provider data={armor}>
|
<TankopediaEphemeral.Provider data={{ armor, thicknessRange: 193 }}>
|
||||||
<App.Provider>
|
<App.Provider>
|
||||||
<TankopediaPersistent.Provider>
|
<TankopediaPersistent.Provider>
|
||||||
<Duel.Provider
|
<Duel.Provider
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TankopediaEphemeral {
|
interface TankopediaEphemeral {
|
||||||
|
thicknessRange: number;
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
shootingRangeZoom: ShootingRangeZoom;
|
shootingRangeZoom: ShootingRangeZoom;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
@@ -124,9 +125,11 @@ interface TankopediaEphemeral {
|
|||||||
|
|
||||||
const skillDefinitions = await awaitableSkillDefinitions;
|
const skillDefinitions = await awaitableSkillDefinitions;
|
||||||
|
|
||||||
export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
|
export const TankopediaEphemeral = createContextualStore(
|
||||||
|
({ armor, thicknessRange }: { armor: TankArmor; thicknessRange: number }) => {
|
||||||
return create<TankopediaEphemeral>()(
|
return create<TankopediaEphemeral>()(
|
||||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||||
|
thicknessRange,
|
||||||
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
||||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||||
editStatic: false,
|
editStatic: false,
|
||||||
@@ -137,4 +140,5 @@ export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
|
|||||||
display: TankopediaDisplay.StaticArmor,
|
display: TankopediaDisplay.StaticArmor,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
@@ -14,9 +14,9 @@ export interface TankopediaPersistentStore {
|
|||||||
advancedHighlighting: boolean;
|
advancedHighlighting: boolean;
|
||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
greenPenetration: boolean;
|
greenPenetration: boolean;
|
||||||
showSpacedArmor: boolean;
|
showArmorScreen: boolean;
|
||||||
showExternalModules: boolean;
|
showModules: boolean;
|
||||||
showPrimaryArmor: boolean;
|
showArmor: boolean;
|
||||||
recentlyViewed: number[];
|
recentlyViewed: number[];
|
||||||
hideTankModelUnderArmor: boolean;
|
hideTankModelUnderArmor: boolean;
|
||||||
|
|
||||||
@@ -36,9 +36,9 @@ export const TankopediaPersistent = createContextualStore(() =>
|
|||||||
advancedHighlighting: false,
|
advancedHighlighting: false,
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
greenPenetration: false,
|
greenPenetration: false,
|
||||||
showSpacedArmor: true,
|
showArmorScreen: true,
|
||||||
showExternalModules: true,
|
showModules: true,
|
||||||
showPrimaryArmor: true,
|
showArmor: true,
|
||||||
recentlyViewed: [],
|
recentlyViewed: [],
|
||||||
sort: {
|
sort: {
|
||||||
by: 'meta.none',
|
by: 'meta.none',
|
||||||
|
|||||||
Reference in new issue
Block a user