feat: new tab

This commit is contained in:
tresabhi committed 2025-03-21 19:30:37 -05:00
1 parent 7f056500ca
commit 7605e1d6f2
10 files changed
+136 -55

No files matched your search

+4
View File
@@ -855,6 +855,10 @@
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"sandbox": {
"dissector": {
"name": "Dissector"
},
"poses": {
"hull_down": "Hull down",
"face_hug": "Face hug",
@@ -0,0 +1,17 @@
import { memo } from 'react';
import { DissectorSceneComponent } from './DissectorSceneComponent';
export const Dissector = memo(() => {
return (
<group>
<DissectorSceneComponent group="hull" />
<DissectorSceneComponent group="chassis" />
<group position={[0.7, 1.6, 0]}>
<DissectorSceneComponent group="turret_01" />
<group position={[1, 0.36, 0]}>
<DissectorSceneComponent group="gun_01" />
</group>
</group>
</group>
);
});
@@ -0,0 +1,89 @@
import { asset } from '@blitzkit/core';
import { useGLTF } from '@react-three/drei';
import {
Color,
DoubleSide,
LineBasicMaterial,
Material,
MeshStandardMaterial,
} from 'three';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { groupNameRegex } from './StaticArmorSceneComponent';
interface DissectorSceneComponentProps {
group: string;
}
const armorColor = new Color().multiplyScalar(1 / 16);
const moduleColor: Record<string, Color> = {
Armor: armorColor,
ArmorScreen: armorColor,
Commander: new Color('white'),
Driver: new Color('white'),
Gunner1: new Color('white'),
Loader1: new Color('white'),
SurveyingDevice: new Color('purple'),
Radio: new Color('indigo'),
Gun: new Color('pink'),
FuelTank: new Color('yellow'),
Engine: new Color('orange'),
AmmoBay: new Color('gold'),
LeftTrack: new Color('green'),
RightTrack: new Color('green'),
TurretRotator: new Color('blue'),
Transmission: new Color('cyan'),
};
const moduleOutlineColor: Record<string, Color> = {};
for (const key in moduleColor) {
moduleOutlineColor[key] = moduleColor[key].clone().multiplyScalar(4);
}
export function DissectorSceneComponent({
group,
}: DissectorSceneComponentProps) {
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 groupName = group.match(groupNameRegex)![1];
const groupArmor = armor.groups[groupName];
return jsxTree(gltf.scene, {
mesh(mesh, props) {
if (!(mesh.material instanceof Material)) {
return null;
}
const armorName = mesh.material.name;
const armorPlate = groupArmor.armors[armorName];
if (!(armorPlate.type in moduleColor)) {
throw new TypeError(`Unhandled armor type: ${armorPlate.type}`);
}
const material = new MeshStandardMaterial({
side: DoubleSide,
color: moduleColor[armorPlate.type],
});
const outlineMaterial = new LineBasicMaterial({
side: DoubleSide,
color: moduleOutlineColor[armorPlate.type],
});
return (
<>
<mesh {...props} material={material} />
<lineSegments material={outlineMaterial}>
<edgesGeometry args={[props.geometry, 45]} />
</lineSegments>
</>
);
},
});
}
@@ -1,62 +1,19 @@
import { memo } from 'react';
import { StaticArmorSceneComponents } from './StaticArmorSceneComponents';
import { StaticArmorSceneComponent } from './StaticArmorSceneComponent';
export interface ThicknessRange {
value: number;
}
export const StaticArmor = memo(() => {
// const wrapper = useRef<Group>(null);
// const turretContainer = useRef<Group>(null);
// const gunContainer = useRef<Group>(null);
// const protagonist = Duel.use((draft) => draft.protagonist);
// 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);
// const { gltf: armorGltf } = useArmor(tank.id);
// const { gltf: modelGltf } = useModel(tank.id);
// const armorNodes = Object.values(armorGltf.nodes);
// const modelNodes = Object.values(modelGltf.nodes);
// const tankModelDefinition = useTankModelDefinition();
// const trackModelDefinition = tankModelDefinition.tracks[track.id];
// const turretModelDefinition = tankModelDefinition.turrets[turret.id];
// const gunModelDefinition =
// turretModelDefinition.guns[gun.gun_type!.value.base.id];
// const hullOrigin = correctZYTuple(trackModelDefinition.origin);
// const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
// const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
// const mutateDuel = Duel.useMutation();
// const canvas = useThree((state) => state.gl.domElement);
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
// const duelStore = Duel.useStore();
// const maskOrigin =
// gunModelDefinition.mask === undefined
// ? undefined
// : gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
// const showPrimaryArmor = TankopediaPersistent.use(
// (state) => state.showPrimaryArmor,
// );
// const showSpacedArmor = TankopediaPersistent.use(
// (state) => state.showSpacedArmor,
// );
// const showExternalModules = TankopediaPersistent.use(
// (state) => state.showExternalModules,
// );
// const isDynamicArmorActive = Duel.use((state) =>
// state.protagonist.consumables.includes(73),
// );
// useTankTransform(track, turret, turretContainer, gunContainer);
return (
<group>
<StaticArmorSceneComponents group="hull" />
<StaticArmorSceneComponents group="chassis" />
<StaticArmorSceneComponent group="hull" />
<StaticArmorSceneComponent group="chassis" />
<group position={[0.7, 1.6, 0]}>
<StaticArmorSceneComponents group="turret_01" />
<StaticArmorSceneComponent group="turret_01" />
<group position={[1, 0.36, 0]}>
<StaticArmorSceneComponents group="gun_01" />
<StaticArmorSceneComponent group="gun_01" />
</group>
</group>
</group>
@@ -12,16 +12,16 @@ import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
interface StaticArmorSceneComponentsProps {
interface StaticArmorSceneComponentsProp {
group: string;
}
const moduleColor = new Color(192 / 255, 192 / 255, 192 / 255);
const groupNameRegex = /([a-z]+)(_\d+)?/;
export const groupNameRegex = /([a-z]+)(_\d+)?/;
export function StaticArmorSceneComponents({
export function StaticArmorSceneComponent({
group,
}: StaticArmorSceneComponentsProps) {
}: StaticArmorSceneComponentsProp) {
const tank = Duel.use((state) => state.protagonist.tank);
const showModules = TankopediaPersistent.use((state) => state.showModules);
const showArmorScreen = TankopediaPersistent.use(
@@ -395,6 +395,15 @@ export function Options({ canvas }: OptionsProps) {
</Flex>
</Tooltip>
</SegmentedControl.Item>
<SegmentedControl.Item value={`${TankopediaDisplay.Dissector}`}>
<Tooltip
content={strings.website.tools.tanks.sandbox.dissector.name}
>
<Flex height="100%" align="center">
<img src={imgur('ErJEWbY')} style={{ height: '1.25em' }} />
</Flex>
</Tooltip>
</SegmentedControl.Item>
{/* <SegmentedControl.Item value={`${TankopediaDisplay.ShootingRange}`}>
<Tooltip content="Shooting range">
<Flex height="100%" align="center">
@@ -20,7 +20,8 @@ export function Lighting() {
environmentRotation={new Euler(0, degToRad(180), 0)}
/>
)}
{display === TankopediaDisplay.StaticArmor && (
{(display === TankopediaDisplay.StaticArmor ||
display === TankopediaDisplay.Dissector) && (
<Environment preset="lobby" />
)}
{display === TankopediaDisplay.ShootingRange && (
@@ -1,12 +1,14 @@
import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core';
import { Canvas } from '@react-three/fiber';
import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react';
import { Plane, Vector3 } from 'three';
import { useOnScreen } from '../../../../../hooks/useOnScreen';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
import { Armor } from '../../../../Armor';
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
import { Dissector } from '../../../../Armor/components/Dissector';
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
import { StaticArmor } from '../../../../Armor/components/StaticArmor';
import { AutoClear } from './components/AutoClear';
@@ -158,7 +160,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
: 'never'
}
gl={{
clippingPlanes: [],
clippingPlanes: [new Plane(new Vector3(0, 0, 1))],
localClippingEnabled: true,
preserveDrawingBuffer: true,
}}
@@ -190,6 +192,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<Suspense fallback={<ModelLoader />}>
{display === TankopediaDisplay.DynamicArmor && <Armor />}
{display === TankopediaDisplay.StaticArmor && <StaticArmor />}
{display === TankopediaDisplay.Dissector && <Dissector />}
<Lighting />
</Suspense>
</Canvas>
@@ -137,7 +137,7 @@ export const TankopediaEphemeral = createContextualStore(
armor,
controlsEnabled: true,
xpMultiplier: 1,
display: TankopediaDisplay.StaticArmor,
display: TankopediaDisplay.Dissector,
})),
);
},
@@ -37,5 +37,6 @@ export enum TankopediaDisplay {
Model,
DynamicArmor,
StaticArmor,
Dissector,
ShootingRange,
}