mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: renaming
This commit is contained in:
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;
|
||||
}
|
||||
});
|
||||
|
||||
+30
-30
@@ -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];
|
||||
|
||||
|
||||
+20
-20
@@ -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
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
+23
-23
@@ -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
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
+51
-51
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user