feat: renaming

This commit is contained in:
TrèsAbhi committed 2025-09-01 22:56:39 -05:00
1 parent 8b527f2333
commit 96482d4268
38 files changed
+931 -943

No files matched your search

@@ -1,4 +1,4 @@
import { ReloadIcon } from '@radix-ui/react-icons';
import { ReloadIcon } from "@radix-ui/react-icons";
import {
Card,
Code,
@@ -6,21 +6,19 @@ import {
IconButton,
Text,
TextField,
} from '@radix-ui/themes';
import { Html } from '@react-three/drei';
import { useRef } from 'react';
import { radToDeg } from 'three/src/math/MathUtils.js';
import { resolveArmorIndex } from '../../../core/blitzkit/resolveArmorIndex';
import { App } from '../../../stores/app';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { layerTypeNames } from './ShotDisplayCard';
import { ArmorType } from './SpacedArmorScene';
} from "@radix-ui/themes";
import { Html } from "@react-three/drei";
import { useRef } from "react";
import { radToDeg } from "three/src/math/MathUtils.js";
import { resolveArmorIndex } from "../../../core/blitzkit/resolveArmorIndex";
import { App } from "../../../stores/app";
import { Duel } from "../../../stores/duel";
import { Tankopedia } from "../../../stores/tankopedia";
import { layerTypeNames } from "./ShotDisplayCard";
import { ArmorType } from "./SpacedArmorScene";
export function ArmorPlateDisplay() {
const highlightArmor = TankopediaEphemeral.use(
(state) => state.highlightArmor,
);
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
const developerMode = App.useDeferred((state) => state.developerMode, false);
const duelStore = Duel.useStore();
const input = useRef<HTMLInputElement>(null);
@@ -31,20 +29,20 @@ export function ArmorPlateDisplay() {
<group position={highlightArmor.point}>
<Html
center
style={{ pointerEvents: highlightArmor.editingPlate ? 'auto' : 'none' }}
style={{ pointerEvents: highlightArmor.editingPlate ? "auto" : "none" }}
>
<Card
variant="classic"
mb="4"
style={{
whiteSpace: 'nowrap',
whiteSpace: "nowrap",
color: highlightArmor.color,
transform: 'translateY(-50%)',
transform: "translateY(-50%)",
}}
>
<Flex direction="column">
<Text weight="bold">
{layerTypeNames[highlightArmor.type]}{' '}
{layerTypeNames[highlightArmor.type]}{" "}
{highlightArmor.thickness.toFixed(0)}
<Text size="1" weight="regular">
mm
@@ -90,10 +88,10 @@ export function ArmorPlateDisplay() {
if (isNaN(thickness) || thickness < 0) return;
if (event.key === 'Enter') {
if (event.key === "Enter") {
input.current?.blur();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
const { protagonist } = duelStore.getState();
const tank = draft.model;
@@ -101,23 +99,23 @@ export function ArmorPlateDisplay() {
const gun = turret.guns[protagonist.gun.id];
const track = tank.tracks[protagonist.track.id];
if (highlightArmor.name.startsWith('hull_')) {
if (highlightArmor.name.startsWith("hull_")) {
tank.armor.thickness[index] = thickness;
} else if (highlightArmor.name.startsWith('turret_')) {
} else if (highlightArmor.name.startsWith("turret_")) {
turret.armor.thickness[index] = thickness;
} else if (highlightArmor.name.startsWith('gun_')) {
if (highlightArmor.name.includes('_armor_')) {
} else if (highlightArmor.name.startsWith("gun_")) {
if (highlightArmor.name.includes("_armor_")) {
gun.armor.thickness[index] = thickness;
} else {
gun.thickness = thickness;
}
} else if (highlightArmor.name.startsWith('chassis_')) {
} else if (highlightArmor.name.startsWith("chassis_")) {
track.thickness = thickness;
}
});
} else if (event.key === 'Escape') {
} else if (event.key === "Escape") {
input.current?.blur();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}
@@ -135,7 +133,7 @@ export function ArmorPlateDisplay() {
mt="2"
color="red"
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
if (!highlightArmor.editingPlate) return;
const index = resolveArmorIndex(highlightArmor.name);
@@ -143,7 +141,7 @@ export function ArmorPlateDisplay() {
if (index === undefined) return;
draft.highlightArmor = undefined;
const initialState = TankopediaEphemeral.initial;
const initialState = Tankopedia.initial;
const { protagonist } = duelStore.getState();
const tank = draft.model;
const initialTank = initialState.model;
@@ -156,20 +154,20 @@ export function ArmorPlateDisplay() {
const initialTrack =
initialTank.tracks[protagonist.track.id];
if (highlightArmor.name.startsWith('hull_')) {
if (highlightArmor.name.startsWith("hull_")) {
tank.armor.thickness[index] =
initialTank.armor.thickness[index];
} else if (highlightArmor.name.startsWith('turret_')) {
} else if (highlightArmor.name.startsWith("turret_")) {
turret.armor.thickness[index] =
initialTurret.armor.thickness[index];
} else if (highlightArmor.name.startsWith('gun_')) {
if (highlightArmor.name.includes('_armor_')) {
} else if (highlightArmor.name.startsWith("gun_")) {
if (highlightArmor.name.includes("_armor_")) {
gun.armor.thickness[index] =
initialGun.armor.thickness[index];
} else {
gun.thickness = initialGun.thickness;
}
} else if (highlightArmor.name.startsWith('chassis_')) {
} else if (highlightArmor.name.startsWith("chassis_")) {
track.thickness = initialTrack.thickness;
}
});
@@ -2,9 +2,9 @@ import {
canSplash,
isExplosive,
resolvePenetrationCoefficient,
} from '@blitzkit/core';
import { invalidate, useFrame } from '@react-three/fiber';
import { useEffect } from 'react';
} from "@blitzkit/core";
import { invalidate, useFrame } from "@react-three/fiber";
import { useEffect } from "react";
import {
MeshBasicMaterial,
NormalBlending,
@@ -14,16 +14,16 @@ import {
UniformsLib,
UniformsUtils,
Vector2,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils.js';
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
import { Duel, type EquipmentMatrix } from '../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
import fragmentShader from './shaders/fragment.glsl?raw';
import vertexShader from './shaders/vertex.glsl?raw';
import { spacedArmorRenderTarget } from './target';
} from "three";
import { degToRad } from "three/src/math/MathUtils.js";
import { hasEquipment } from "../../../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../../../core/blitzkit/jsxTree";
import { Duel, type EquipmentMatrix } from "../../../../stores/duel";
import { Tankopedia } from "../../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
import { spacedArmorRenderTarget } from "./target";
interface PrimaryArmorSceneComponentProps {
thickness: number;
@@ -81,15 +81,15 @@ export function PrimaryArmorSceneComponent({
useEffect(() => {
function handleShellChange() {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.caliber.value = shell.caliber;
material.uniforms.ricochet.value = degToRad(
isExplosive(shell.type) ? 90 : shell.ricochet!,
isExplosive(shell.type) ? 90 : shell.ricochet!
);
material.uniforms.normalization.value = degToRad(
shell.normalization ?? 0,
shell.normalization ?? 0
);
material.uniforms.isExplosive.value = isExplosive(shell.type);
material.uniforms.canSplash.value = canSplash(shell.type);
@@ -116,13 +116,13 @@ export function PrimaryArmorSceneComponent({
}
function handleProtagonistEquipmentChange(
equipment: EquipmentMatrix,
noInvalidate = false,
noInvalidate = false
) {
const duel = duelStore.getState();
const hasEnhancedArmor = hasEquipment(
110,
duel.protagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.thickness.value = hasEnhancedArmor
? thickness * 1.03
@@ -132,16 +132,16 @@ export function PrimaryArmorSceneComponent({
}
function handleAntagonistEquipmentChange(
equipment: EquipmentMatrix,
noInvalidate = false,
noInvalidate = false
) {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment(
103,
duel.antagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.penetration.value =
@@ -159,34 +159,34 @@ export function PrimaryArmorSceneComponent({
handleOpaqueChange(initialState.opaque);
handleWireframeChange(initialState.wireframe);
handleProtagonistEquipmentChange(
duelStore.getState().protagonist.equipmentMatrix,
duelStore.getState().protagonist.equipmentMatrix
);
handleAntagonistEquipmentChange(
duelStore.getState().antagonist.equipmentMatrix,
duelStore.getState().antagonist.equipmentMatrix
);
const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
tankopediaPersistentStore.subscribe(
(state) => state.greenPenetration,
handleGreenPenetrationChange,
handleGreenPenetrationChange
),
tankopediaPersistentStore.subscribe(
(state) => state.advancedHighlighting,
handleAdvancedHighlightingChange,
handleAdvancedHighlightingChange
),
tankopediaPersistentStore.subscribe(
(state) => state.opaque,
handleOpaqueChange,
handleOpaqueChange
),
duelStore.subscribe(
(state) => state.protagonist.equipmentMatrix,
(equipment) => handleProtagonistEquipmentChange(equipment),
(equipment) => handleProtagonistEquipmentChange(equipment)
),
duelStore.subscribe(
(state) => state.antagonist.equipmentMatrix,
(equipment) => handleAntagonistEquipmentChange(equipment),
(equipment) => handleAntagonistEquipmentChange(equipment)
),
];
@@ -197,7 +197,7 @@ export function PrimaryArmorSceneComponent({
useFrame(({ gl, camera }) => {
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
gl.getPixelRatio(),
gl.getPixelRatio()
);
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
material.uniforms.spacedArmorDepth.value =
@@ -1,9 +1,9 @@
import { J_HAT, K_HAT } from '@blitzkit/core';
import { Box, Flex, Text } from '@radix-ui/themes';
import { Html } from '@react-three/drei';
import { invalidate, useFrame } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useEffect, useRef, type ComponentProps } from 'react';
import { J_HAT, K_HAT } from "@blitzkit/core";
import { Box, Flex, Text } from "@radix-ui/themes";
import { Html } from "@react-three/drei";
import { invalidate, useFrame } from "@react-three/fiber";
import { clamp } from "lodash-es";
import { useEffect, useRef, type ComponentProps } from "react";
import {
BufferGeometry,
DoubleSide,
@@ -15,24 +15,24 @@ import {
MeshBasicMaterial,
Path,
Quaternion,
} from 'three';
import { isHalloween } from '../../../core/blitzkit/isHalloween';
import { LocaleProvider, useLocale } from '../../../hooks/useLocale';
} from "three";
import { isHalloween } from "../../../core/blitzkit/isHalloween";
import { LocaleProvider, useLocale } from "../../../hooks/useLocale";
import {
TankopediaEphemeral,
Tankopedia,
type ShotLayerNonExternal,
type ShotStatus,
} from '../../../stores/tankopediaEphemeral';
import { ShotDisplayCard } from './ShotDisplayCard';
} from "../../../stores/tankopedia";
import { ShotDisplayCard } from "./ShotDisplayCard";
export const shotStatusColors: Record<
ShotStatus,
ComponentProps<typeof Text>['color']
ComponentProps<typeof Text>["color"]
> = {
blocked: 'red',
penetration: 'green',
ricochet: 'yellow',
splash: 'orange',
blocked: "red",
penetration: "green",
ricochet: "yellow",
splash: "orange",
};
const SURFACE_POINT_SIZE = 12;
@@ -41,7 +41,7 @@ const TRACER_THICK = 1 / 32;
const TRACER_THIN = TRACER_THICK / 2;
export function ShotDisplay() {
const shot = TankopediaEphemeral.use((state) => state.shot);
const shot = Tankopedia.use((state) => state.shot);
const inTracer = useRef<Mesh>(null);
const outTracer = useRef<Mesh>(null);
const splashRadiusWrapper = useRef<Group>(null);
@@ -52,16 +52,16 @@ export function ShotDisplay() {
useEffect(() => {
if (shot?.splashRadius !== undefined) {
const outlineGeometry = new BufferGeometry().setFromPoints(
new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50),
new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50)
);
const outlineMaterial = new LineDashedMaterial({
color: 'orange',
color: "orange",
dashSize: 0.05,
gapSize: 0.025,
});
const outline = new Line(
outlineGeometry,
outlineMaterial,
outlineMaterial
).computeLineDistances();
splashRadiusWrapper.current?.add(outline);
@@ -76,9 +76,9 @@ export function ShotDisplay() {
if (
isHalloween() &&
shot?.splashRadius !== undefined &&
shot.in.status === 'penetration'
shot.in.status === "penetration"
) {
const audio = new Audio('/assets/audio/lotta-damage.mp3');
const audio = new Audio("/assets/audio/lotta-damage.mp3");
audio.currentTime = 0.7;
audio.volume = 0.25;
@@ -134,11 +134,11 @@ export function ShotDisplay() {
const outLength =
shot.out && shot.out.layers.length > 0
? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo(
(shot.out.layers.at(-1) as ShotLayerNonExternal).point,
(shot.out.layers.at(-1) as ShotLayerNonExternal).point
)
: SHOT_DISPLAY_LENGTH_INFINITY;
const inLast = shot.in.layers.findLast(
(layer) => layer.type !== null,
(layer) => layer.type !== null
) as ShotLayerNonExternal;
const outTitleColor = shot.out
? shotStatusColors[shot.out.status]
@@ -154,7 +154,7 @@ export function ShotDisplay() {
position={inLast.point}
ref={splashRadiusWrapper}
rotation={new Euler().setFromQuaternion(
new Quaternion().setFromUnitVectors(K_HAT, shot.in.surfaceNormal),
new Quaternion().setFromUnitVectors(K_HAT, shot.in.surfaceNormal)
)}
>
<mesh>
@@ -172,13 +172,13 @@ export function ShotDisplay() {
<Html position={inLast.point} center>
<Box
style={{
pointerEvents: 'none',
pointerEvents: "none",
transform: `translateY(${tracerGoingUp ? 50 : -50}%)`,
}}
mb={tracerGoingUp ? '0' : '9'}
pb={tracerGoingUp ? '0' : '4'}
mt={tracerGoingUp ? '9' : '0'}
pt={tracerGoingUp ? '4' : '0'}
mb={tracerGoingUp ? "0" : "9"}
pb={tracerGoingUp ? "0" : "4"}
mt={tracerGoingUp ? "9" : "0"}
pt={tracerGoingUp ? "4" : "0"}
>
<LocaleProvider locale={locale}>
<ShotDisplayCard shot={shot} />
@@ -190,7 +190,7 @@ export function ShotDisplay() {
if (layer.type === null) return null;
const shotStatusColor =
shot.in.status === 'splash'
shot.in.status === "splash"
? shotStatusColors.splash
: shotStatusColors[layer.status];
@@ -199,8 +199,8 @@ export function ShotDisplay() {
position={layer.point}
key={index}
style={{
pointerEvents: 'none',
transform: 'translateY(-50%)',
pointerEvents: "none",
transform: "translateY(-50%)",
}}
>
<Flex
@@ -225,7 +225,7 @@ export function ShotDisplay() {
<group
position={inLast.point}
rotation={new Euler().setFromQuaternion(
new Quaternion().setFromUnitVectors(J_HAT, shot.in.surfaceNormal),
new Quaternion().setFromUnitVectors(J_HAT, shot.in.surfaceNormal)
)}
>
<mesh position={[0, inLength / 2, 0]} renderOrder={2}>
@@ -253,8 +253,8 @@ export function ShotDisplay() {
rotation={new Euler().setFromQuaternion(
new Quaternion().setFromUnitVectors(
J_HAT,
shot.out.surfaceNormal.clone(),
),
shot.out.surfaceNormal.clone()
)
)}
>
<mesh position={[0, outLength / 2, 0]} renderOrder={2}>
@@ -1,4 +1,4 @@
import { literals } from '@blitzkit/i18n/src/literals';
import { literals } from "@blitzkit/i18n/src/literals";
import {
Card,
Flex,
@@ -6,19 +6,19 @@ import {
Separator,
Text,
type CardProps,
} from '@radix-ui/themes';
import type { ComponentProps } from 'react';
import { radToDeg } from 'three/src/math/MathUtils.js';
import { useLocale } from '../../../hooks/useLocale';
import type { Shot, ShotLayer } from '../../../stores/tankopediaEphemeral';
import { shotStatusColors } from './ShotDisplay';
import { ArmorType } from './SpacedArmorScene';
} from "@radix-ui/themes";
import type { ComponentProps } from "react";
import { radToDeg } from "three/src/math/MathUtils.js";
import { useLocale } from "../../../hooks/useLocale";
import type { Shot, ShotLayer } from "../../../stores/tankopedia";
import { shotStatusColors } from "./ShotDisplay";
import { ArmorType } from "./SpacedArmorScene";
export const layerTypeNames = {
[ArmorType.Primary]: 'primary',
[ArmorType.Spaced]: 'spaced',
[ArmorType.External]: 'external',
null: 'gap',
[ArmorType.Primary]: "primary",
[ArmorType.Spaced]: "spaced",
[ArmorType.External]: "external",
null: "gap",
} as const;
function LayerEntry({
@@ -29,7 +29,7 @@ function LayerEntry({
angle,
}: {
layerIndex?: string;
shotStatusColor: ComponentProps<typeof Text>['color'];
shotStatusColor: ComponentProps<typeof Text>["color"];
layerName: string;
layer: ShotLayer;
angle?: number;
@@ -77,7 +77,7 @@ function LayerEntry({
[
Math.round(layer.thicknessAngled).toLocaleString(locale),
radToDeg(angle ?? 0).toFixed(0),
],
]
)}
</Text>
)}
@@ -89,7 +89,7 @@ function LayerEntry({
{literals(
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
.ricochet_loss,
[Math.max(-100, -50 * layer.distance).toFixed(0)],
[Math.max(-100, -50 * layer.distance).toFixed(0)]
)}
</Text>
)}
@@ -100,7 +100,7 @@ function LayerEntry({
{literals(
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
.nominal,
[Math.round(layer.thickness).toLocaleString(locale)],
[Math.round(layer.thickness).toLocaleString(locale)]
)}
</Text>
)}
@@ -132,14 +132,14 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
shot.in.status
],
[Math.round(shot.damage).toLocaleString(locale)],
[Math.round(shot.damage).toLocaleString(locale)]
)}
</Text>
<table
style={{
whiteSpace: 'nowrap',
borderSpacing: 'var(--space-1) 0',
whiteSpace: "nowrap",
borderSpacing: "var(--space-1) 0",
}}
>
{shot.in.layers.map((layer, index) => {
@@ -155,7 +155,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
? undefined
: `${(shot.containsGaps ? index / 2 : index) + 1}.`;
const shotStatusColor =
shot.in.status === 'splash'
shot.in.status === "splash"
? shotStatusColors.splash
: shotStatusColors[layer.status];
@@ -202,7 +202,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
{literals(
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
.status[shot.out.status],
[Math.round(shot.damage).toLocaleString(locale)],
[Math.round(shot.damage).toLocaleString(locale)]
)}
</Text>
@@ -224,7 +224,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
1
}.`;
const shotStatusColor =
shot.out!.status === 'splash'
shot.out!.status === "splash"
? shotStatusColors.splash
: shotStatusColors[layer.status];
@@ -1,20 +1,20 @@
import { resolvePenetrationCoefficient } from '@blitzkit/core';
import { useEffect } from 'react';
import { resolvePenetrationCoefficient } from "@blitzkit/core";
import { useEffect } from "react";
import {
AdditiveBlending,
MeshBasicMaterial,
Object3D,
Plane,
ShaderMaterial,
} from 'three';
import type { ArmorUserData, ExternalModuleVariant } from '../..';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { ArmorType } from '../../../SpacedArmorScene';
import fragmentShader from './shaders/fragment.glsl?raw';
import vertexShader from './shaders/vertex.glsl?raw';
} from "three";
import type { ArmorUserData, ExternalModuleVariant } from "../..";
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
import { Duel, type EquipmentMatrix } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { ArmorType } from "../../../SpacedArmorScene";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
interface SpacedArmorSubExternalProps {
node: Object3D;
@@ -49,7 +49,7 @@ export function SpacedArmorSubExternal({
useEffect(() => {
function handleShellChange() {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.penetration.value = shell.penetration.near;
}
@@ -58,7 +58,7 @@ export function SpacedArmorSubExternal({
const hasEnhancedArmor = hasEquipment(
110,
duel.protagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.thickness.value = hasEnhancedArmor
? thickness * 1.03
@@ -66,13 +66,13 @@ export function SpacedArmorSubExternal({
}
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment(
103,
duel.antagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.penetration.value =
@@ -82,22 +82,22 @@ export function SpacedArmorSubExternal({
handleShellChange();
handleProtagonistEquipmentChange(
duelStore.getState().protagonist.equipmentMatrix,
duelStore.getState().protagonist.equipmentMatrix
);
handleAntagonistEquipmentChange(
duelStore.getState().antagonist.equipmentMatrix,
duelStore.getState().antagonist.equipmentMatrix
);
const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
duelStore.subscribe(
(state) => state.protagonist.equipmentMatrix,
handleProtagonistEquipmentChange,
handleProtagonistEquipmentChange
),
duelStore.subscribe(
(state) => state.antagonist.equipmentMatrix,
handleAntagonistEquipmentChange,
handleAntagonistEquipmentChange
),
];
@@ -1,20 +1,20 @@
import { isExplosive, resolvePenetrationCoefficient } from '@blitzkit/core';
import { useEffect } from 'react';
import { isExplosive, resolvePenetrationCoefficient } from "@blitzkit/core";
import { useEffect } from "react";
import {
AdditiveBlending,
MeshBasicMaterial,
ShaderMaterial,
type Object3D,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils.js';
import type { ArmorUserData } from '../..';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { ArmorType } from '../../../SpacedArmorScene';
import fragmentShader from './shaders/fragment.glsl?raw';
import vertexShader from './shaders/vertex.glsl?raw';
} from "three";
import { degToRad } from "three/src/math/MathUtils.js";
import type { ArmorUserData } from "../..";
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
import { Duel, type EquipmentMatrix } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { ArmorType } from "../../../SpacedArmorScene";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
interface SpacedArmorSubSpacedProps {
node: Object3D;
@@ -51,16 +51,16 @@ export function SpacedArmorSubSpaced({
useEffect(() => {
function handleShellChange() {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
material.uniforms.penetration.value = shell.penetration.near;
material.uniforms.caliber.value = shell.caliber;
material.uniforms.ricochet.value = degToRad(
isExplosive(shell.type) ? 90 : shell.ricochet!,
isExplosive(shell.type) ? 90 : shell.ricochet!
);
material.uniforms.normalization.value = degToRad(
shell.normalization ?? 0,
shell.normalization ?? 0
);
}
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
@@ -68,7 +68,7 @@ export function SpacedArmorSubSpaced({
const hasEnhancedArmor = hasEquipment(
110,
duel.protagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.thickness.value = hasEnhancedArmor
? thickness * 1.03
@@ -76,13 +76,13 @@ export function SpacedArmorSubSpaced({
}
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = duelStore.getState();
const tankopediaEphemeral = TankopediaEphemeral.state;
const tankopediaEphemeral = Tankopedia.state;
const shell = tankopediaEphemeral.customShell ?? duel.antagonist.shell;
const penetration = shell.penetration.near;
const hasCalibratedShells = hasEquipment(
103,
duel.antagonist.tank.equipment_preset,
equipment,
equipment
);
material.uniforms.penetration.value =
@@ -92,22 +92,22 @@ export function SpacedArmorSubSpaced({
handleShellChange();
handleProtagonistEquipmentChange(
duelStore.getState().protagonist.equipmentMatrix,
duelStore.getState().protagonist.equipmentMatrix
);
handleAntagonistEquipmentChange(
duelStore.getState().antagonist.equipmentMatrix,
duelStore.getState().antagonist.equipmentMatrix
);
const unsubscribes = [
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
TankopediaEphemeral.on((state) => state.customShell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
duelStore.subscribe(
(state) => state.protagonist.equipmentMatrix,
handleProtagonistEquipmentChange,
handleProtagonistEquipmentChange
),
duelStore.subscribe(
(state) => state.antagonist.equipmentMatrix,
handleAntagonistEquipmentChange,
handleAntagonistEquipmentChange
),
];
@@ -2,9 +2,9 @@ import {
ShellType,
isExplosive,
resolvePenetrationCoefficient,
} from '@blitzkit/core';
import { useThree } from '@react-three/fiber';
import { useCallback } from 'react';
} from "@blitzkit/core";
import { useThree } from "@react-three/fiber";
import { useCallback } from "react";
import {
type Intersection,
MeshBasicMaterial,
@@ -14,21 +14,21 @@ import {
Raycaster,
Scene,
Vector3,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils.js';
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
import { discardClippingPlane } from '../../../../core/three/discardClippingPlane';
import { Duel } from '../../../../stores/duel';
} from "three";
import { degToRad } from "three/src/math/MathUtils.js";
import { hasEquipment } from "../../../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../../../core/blitzkit/jsxTree";
import { discardClippingPlane } from "../../../../core/three/discardClippingPlane";
import { Duel } from "../../../../stores/duel";
import {
type Shot,
type ShotLayerBase,
type ShotLayerNonExternal,
TankopediaEphemeral,
} from '../../../../stores/tankopediaEphemeral';
import { ArmorType } from '../SpacedArmorScene';
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
Tankopedia,
} from "../../../../stores/tankopedia";
import { ArmorType } from "../SpacedArmorScene";
import { SpacedArmorSubExternal } from "./components/SpacedArmorSubExternal";
import { SpacedArmorSubSpaced } from "./components/SpacedArmorSubSpaced";
type SpacedArmorSceneComponentProps = {
node: Object3D;
@@ -45,7 +45,7 @@ type SpacedArmorSceneComponentProps = {
}
);
export type ExternalModuleVariant = 'gun' | 'track';
export type ExternalModuleVariant = "gun" | "track";
export type ArmorUserData = {
thickness: number;
} & (
@@ -84,10 +84,10 @@ export function SpacedArmorSceneComponent({
point: Vector3,
intersections: Intersection[],
allowRicochet: boolean,
remainingPenetrationInput?: number,
remainingPenetrationInput?: number
) => {
const { antagonist, protagonist } = duelStore.getState();
const { customShell } = TankopediaEphemeral.state;
const { customShell } = Tankopedia.state;
const shell = customShell ?? antagonist.shell;
const cameraNormal = camera.position.clone().sub(point).normalize();
const shot: Shot = {
@@ -98,19 +98,19 @@ export function SpacedArmorSceneComponent({
in: {
surfaceNormal: cameraNormal,
layers: [],
status: 'blocked',
status: "blocked",
},
};
const hasCalibratedShells = hasEquipment(
103,
antagonist.tank.equipment_preset,
antagonist.equipmentMatrix,
antagonist.equipmentMatrix
);
const hasEnhancedArmor = hasEquipment(
110,
protagonist.tank.equipment_preset,
protagonist.equipmentMatrix,
protagonist.equipmentMatrix
);
const penetration =
shell.penetration.near *
@@ -118,8 +118,8 @@ export function SpacedArmorSceneComponent({
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
const filteredIntersections = intersections.filter(
(intersection) =>
'type' in intersection.object.userData &&
!discardClippingPlane(intersection.object, intersection.point),
"type" in intersection.object.userData &&
!discardClippingPlane(intersection.object, intersection.point)
) as unknown as Intersection<Object3D & { userData: ArmorUserData }>[];
const encounteredExternalModuleVariants: ExternalModuleVariant[] = [];
const noDuplicateIntersections: typeof filteredIntersections = [];
@@ -128,12 +128,12 @@ export function SpacedArmorSceneComponent({
if (intersection.object.userData.type === ArmorType.External) {
if (
!encounteredExternalModuleVariants.includes(
intersection.object.userData.variant,
intersection.object.userData.variant
)
) {
noDuplicateIntersections.push(intersection);
encounteredExternalModuleVariants.push(
intersection.object.userData.variant,
intersection.object.userData.variant
);
}
} else {
@@ -161,7 +161,7 @@ export function SpacedArmorSceneComponent({
const surfaceNormal = intersection
.normal!.clone()
.applyQuaternion(
intersection.object.getWorldQuaternion(new Quaternion()),
intersection.object.getWorldQuaternion(new Quaternion())
);
if (shell.type === ShellType.HEAT && index > 0) {
@@ -173,7 +173,7 @@ export function SpacedArmorSceneComponent({
shot.in.layers.push({
type: null,
distance,
status: blocked ? 'blocked' : 'penetration',
status: blocked ? "blocked" : "penetration",
});
if (blocked) break;
@@ -193,16 +193,16 @@ export function SpacedArmorSceneComponent({
surfaceNormal,
status:
shell.type === ShellType.HE
? 'blocked'
? "blocked"
: blocked
? 'blocked'
: 'penetration',
? "blocked"
: "penetration",
variant: layer.variant,
});
} else {
const thickness = layer.thickness * thicknessCoefficient;
const ricochet = degToRad(
isExplosive(shell.type) ? 90 : shell.ricochet!,
isExplosive(shell.type) ? 90 : shell.ricochet!
);
const angle = surfaceNormal.angleTo(cameraNormal);
@@ -226,7 +226,7 @@ export function SpacedArmorSceneComponent({
thicknessAngled: finalThickness,
point: intersection.point,
surfaceNormal,
status: 'ricochet',
status: "ricochet",
angle,
});
@@ -244,10 +244,10 @@ export function SpacedArmorSceneComponent({
surfaceNormal: intersection.face!.normal,
status:
shell.type === ShellType.HE && layer.type !== ArmorType.Primary
? 'blocked'
? "blocked"
: blocked
? 'blocked'
: 'penetration',
? "blocked"
: "penetration",
angle,
});
}
@@ -281,10 +281,10 @@ export function SpacedArmorSceneComponent({
return acc + layer.thicknessAngled;
},
0,
0
);
const distanceFromSpacedArmor = firstLayer.point.distanceTo(
lastLayer.point,
lastLayer.point
);
const finalDamage = Math.max(
0,
@@ -293,7 +293,7 @@ export function SpacedArmorSceneComponent({
(1 - distanceFromSpacedArmor / shell.explosion_radius!) -
1.1 *
(lastLayer.thicknessAngled +
Math.min(penetration, totalSpacedArmorThickness)),
Math.min(penetration, totalSpacedArmorThickness))
);
/**
@@ -307,19 +307,19 @@ export function SpacedArmorSceneComponent({
* for parity's sake, I am capping the value here.
*/
if (shot.in.layers.length > 1 || lastLayer.status === 'blocked') {
shot.in.status = finalDamage > 0 ? 'splash' : 'blocked';
if (shot.in.layers.length > 1 || lastLayer.status === "blocked") {
shot.in.status = finalDamage > 0 ? "splash" : "blocked";
shot.damage = finalDamage;
} else {
shot.in.status = 'penetration';
shot.in.status = "penetration";
shot.damage = shell.armor_damage;
}
} else {
if (lastLayer.status === 'blocked') {
shot.in.status = 'blocked';
if (lastLayer.status === "blocked") {
shot.in.status = "blocked";
shot.damage = 0;
} else if (lastLayer.status === 'penetration') {
shot.in.status = 'penetration';
} else if (lastLayer.status === "penetration") {
shot.in.status = "penetration";
shot.damage = shell.armor_damage;
} else {
const caster = new Raycaster();
@@ -330,29 +330,29 @@ export function SpacedArmorSceneComponent({
.sub(
lastLayer.surfaceNormal
.clone()
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal)),
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal))
);
caster.set(lastLayer.point, ricochetNormal);
const ricochetIntersections = caster.intersectObjects(
props.scene.children,
true,
true
);
const outShot = shoot(
lastLayer.point,
ricochetIntersections,
false,
remainingPenetration,
remainingPenetration
);
shot.in.status = 'ricochet';
shot.in.status = "ricochet";
if (outShot === null) {
shot.damage = 0;
shot.out = {
surfaceNormal: ricochetNormal,
layers: [],
status: 'ricochet',
status: "ricochet",
};
} else {
shot.damage = outShot.damage;
@@ -365,7 +365,7 @@ export function SpacedArmorSceneComponent({
return shot;
},
[camera],
[camera]
);
return (
@@ -390,7 +390,7 @@ export function SpacedArmorSceneComponent({
const shot = shoot(event.point, event.intersections, true)!;
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = shot;
});
}}
@@ -1,22 +1,22 @@
import { useThree, type ThreeEvent } from '@react-three/fiber';
import { memo, useRef } from 'react';
import { Group, Plane, Vector2, Vector3 } from 'three';
import { applyPitchYawLimits } from '../../../core/blitz/applyPitchYawLimits';
import { correctZYTuple } from '../../../core/blitz/correctZYTuple';
import { hasEquipment } from '../../../core/blitzkit/hasEquipment';
import { modelTransformEvent } from '../../../core/blitzkit/modelTransform';
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';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
import { ModelTankWrapper } from './ModelTankWrapper';
import { ArmorType } from './SpacedArmorScene';
import { StaticArmorSceneComponent } from './StaticArmorSceneComponent';
import { useThree, type ThreeEvent } from "@react-three/fiber";
import { memo, useRef } from "react";
import { Group, Plane, Vector2, Vector3 } from "three";
import { applyPitchYawLimits } from "../../../core/blitz/applyPitchYawLimits";
import { correctZYTuple } from "../../../core/blitz/correctZYTuple";
import { hasEquipment } from "../../../core/blitzkit/hasEquipment";
import { modelTransformEvent } from "../../../core/blitzkit/modelTransform";
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";
import { Tankopedia } from "../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../stores/tankopediaPersistent";
import { ModelTankWrapper } from "./ModelTankWrapper";
import { ArmorType } from "./SpacedArmorScene";
import { StaticArmorSceneComponent } from "./StaticArmorSceneComponent";
interface ArmorSceneProps {
thicknessRange: ThicknessRange;
@@ -52,16 +52,16 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
? undefined
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
(state) => state.showPrimaryArmor
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
(state) => state.showSpacedArmor
);
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
(state) => state.showExternalModules
);
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
state.protagonist.consumables.includes(73)
);
useTankTransform(track, turret, turretContainer, gunContainer);
@@ -70,12 +70,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
<ModelTankWrapper ref={wrapper}>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isHull = node.name.startsWith('hull_');
const isHull = node.name.startsWith("hull_");
const isVisible = isHull;
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
tankModelDefinition.armor,
armorId,
armorId
);
if (
@@ -83,8 +83,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
(isDynamicArmorActive && node.name.includes("state_00")) ||
(!isDynamicArmorActive && node.name.includes("state_01"))
)
return null;
@@ -103,8 +103,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
</group>
{modelNodes.map((node) => {
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isWheel = node.name.startsWith("chassis_wheel_");
const isTrack = node.name.startsWith("chassis_track_");
const isVisible = isWheel || isTrack;
if (!isVisible || !showExternalModules) return null;
@@ -126,13 +126,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
<group ref={turretContainer}>
{armorNodes.map((node) => {
const isCurrentTurret = node.name.startsWith(
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`,
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
);
const isVisible = isCurrentTurret;
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
turretModelDefinition.armor,
armorId,
armorId
);
if (
@@ -140,8 +140,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
(isDynamicArmorActive && node.name.includes("state_00")) ||
(!isDynamicArmorActive && node.name.includes("state_01"))
) {
return null;
}
@@ -154,27 +154,27 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
position.set(event.clientX, event.clientY);
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const boundingRect = canvas.getBoundingClientRect();
@@ -188,16 +188,16 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return (
@@ -219,13 +219,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
<group ref={gunContainer}>
{armorNodes.map((node) => {
const isCurrentGun = node.name.startsWith(
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`,
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
);
const isVisible = isCurrentGun;
const armorId = nameToArmorId(node.name);
const { spaced, thickness } = resolveArmor(
gunModelDefinition.armor,
armorId,
armorId
);
if (
@@ -233,8 +233,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
thickness === undefined ||
(!showPrimaryArmor && !spaced) ||
(!showSpacedArmor && spaced) ||
(isDynamicArmorActive && node.name.includes('state_00')) ||
(!isDynamicArmorActive && node.name.includes('state_01'))
(isDynamicArmorActive && node.name.includes("state_00")) ||
(!isDynamicArmorActive && node.name.includes("state_01"))
)
return null;
@@ -244,30 +244,30 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
position.set(event.clientX, event.clientY);
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -281,16 +281,16 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return (
@@ -312,7 +312,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
{modelNodes.map((node) => {
const gunString = `gun_${gunModelDefinition.model_id
.toString()
.padStart(2, '0')}`;
.padStart(2, "0")}`;
const isCurrentGun = gunModelDefinition.mask
? node.name.startsWith(gunString)
: node.name === gunString;
@@ -326,29 +326,29 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
position.set(event.clientX, event.clientY);
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -362,17 +362,17 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return (
@@ -1,8 +1,8 @@
import { I_HAT, J_HAT } from '@blitzkit/core';
import { type MeshProps, useThree } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import type { QuicklimeEvent } from 'quicklime';
import { useEffect, useMemo } from 'react';
import { I_HAT, J_HAT } from "@blitzkit/core";
import { type MeshProps, useThree } from "@react-three/fiber";
import { clamp } from "lodash-es";
import type { QuicklimeEvent } from "quicklime";
import { useEffect, useMemo } from "react";
import {
Box3,
Color,
@@ -15,28 +15,28 @@ import {
Plane,
Quaternion,
Vector3,
} from 'three';
import { unrotateDavaVector } from '../../../core/blitz/unrotateDavaVector';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
} 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';
} from "../../../core/blitzkit/modelTransform";
import { discardClippingPlane } from "../../../core/three/discardClippingPlane";
import { Tankopedia } from "../../../stores/tankopedia";
import { ArmorType } from "./SpacedArmorScene";
import type {
ArmorUserData,
ExternalModuleVariant,
} from './SpacedArmorSceneComponent';
import type { ThicknessRange } from './StaticArmor';
} from "./SpacedArmorSceneComponent";
import type { ThicknessRange } from "./StaticArmor";
type StaticArmorSceneComponentProps = {
name: string;
thickness: number;
thicknessRange: ThicknessRange;
node: Object3D;
onPointerDown?: MeshProps['onPointerDown'];
onPointerDown?: MeshProps["onPointerDown"];
} & (
| {
type: Exclude<ArmorType, ArmorType.External>;
@@ -90,7 +90,7 @@ export function StaticArmorSceneComponent({
color = new Color(
clamp(1 - (7 / 8) * xClamped, 0, 1),
0,
clamp(1 - (1 / 8) * xClamped, 0, 1),
clamp(1 - (1 / 8) * xClamped, 0, 1)
);
opacity = clamp(x + 1 / 2, 0, 1);
break;
@@ -113,7 +113,7 @@ export function StaticArmorSceneComponent({
depthWrite,
...(props.clip ? { clippingPlanes: [props.clip] } : {}),
}),
[thickness],
[thickness]
);
const outlineMaterial = useMemo(
() =>
@@ -122,7 +122,7 @@ export function StaticArmorSceneComponent({
.clone()
.multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1),
}),
[thickness],
[thickness]
);
useEffect(() => {
@@ -137,11 +137,11 @@ export function StaticArmorSceneComponent({
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_'))
(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;
@@ -164,9 +164,9 @@ export function StaticArmorSceneComponent({
}
const unsubscribes = [
TankopediaEphemeral.on(
Tankopedia.on(
(state) => state.highlightArmor?.name,
handleHighlightArmor,
handleHighlightArmor
),
];
@@ -179,14 +179,14 @@ export function StaticArmorSceneComponent({
const { clip } = props;
const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
unrotateDavaVector(props.turretOrigin.clone()),
unrotateDavaVector(props.turretOrigin.clone())
);
const barrelOrigin = neckOrigin.clone().add(gunOrigin);
const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
const point = new Vector3();
function handleModelTransform(
event: QuicklimeEvent<ModelTransformEventData>,
event: QuicklimeEvent<ModelTransformEventData>
) {
clip.normal
.set(0, 0, -1)
@@ -227,7 +227,7 @@ export function StaticArmorSceneComponent({
{
type: props.type,
variant:
props.type === ArmorType.External ? props.variant : 'gun',
props.type === ArmorType.External ? props.variant : "gun",
thickness,
} satisfies ArmorUserData
}
@@ -236,7 +236,7 @@ export function StaticArmorSceneComponent({
event.stopPropagation();
const { editStatic } = TankopediaEphemeral.state;
const { editStatic } = Tankopedia.state;
const bounds = new Box3().setFromObject(event.object);
const point = bounds.min
@@ -251,13 +251,13 @@ export function StaticArmorSceneComponent({
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(
event.object.getWorldQuaternion(new Quaternion()),
event.object.getWorldQuaternion(new Quaternion())
);
const angle = surfaceNormal.angleTo(cameraNormal);
const thicknessAngled =
thickness / Math.sin(Math.PI / 2 - angle);
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = {
type: props.type,
name,