From 1a008a48527168ebf0af792e508a6025ddfcd981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:31:46 -0500 Subject: [PATCH] feat: equalizer for visual armor sandbox --- .../PrimaryArmorSceneComponent/index.tsx | 56 ++++++++++--------- .../Armor/components/ShotDisplay.tsx | 16 +++--- .../SpacedArmorSubExternal/index.tsx | 36 +++++++++--- .../components/SpacedArmorSubSpaced/index.tsx | 36 +++++++++--- .../SpacedArmorSceneComponent/index.tsx | 7 +-- 5 files changed, 96 insertions(+), 55 deletions(-) diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx index 3fa4d6509..0daf46b1d 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx @@ -19,7 +19,7 @@ import { 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 { Duel } from "../../../../stores/duel"; import { Tankopedia } from "../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent"; import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; @@ -98,14 +98,8 @@ export function PrimaryArmorSceneComponent({ material.uniforms.damage.value = shell.armor_damage; material.uniforms.explosionRadius.value = shell.explosion_radius; - handleProtagonistEquipmentChange( - Duel.state.protagonist.equipmentMatrix, - true, - ); - handleAntagonistEquipmentChange( - Duel.state.antagonist.equipmentMatrix, - true, - ); + handleProtagonistEquipmentChange(true); + handleAntagonistEquipmentChange(true); invalidate(); } @@ -122,25 +116,25 @@ export function PrimaryArmorSceneComponent({ function handleWireframeChange(wireframe: boolean) { material.wireframe = wireframe; } - function handleProtagonistEquipmentChange( - equipment: EquipmentMatrix, - noInvalidate = false, - ) { + function handleProtagonistEquipmentChange(noInvalidate = false) { + const equipment = Duel.state.protagonist.equipmentMatrix; const hasEnhancedArmor = hasEquipment( 110, Duel.state.protagonist.tank.equipment_preset, equipment, ); - material.uniforms.thickness.value = hasEnhancedArmor - ? thickness * 1.03 - : thickness; + const equalizer = + (Duel.state.equalize + ? Duel.state.protagonist.tank.equalizer?.armor + : undefined) ?? 1; + + material.uniforms.thickness.value = + thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; if (!noInvalidate) invalidate(); } - function handleAntagonistEquipmentChange( - equipment: EquipmentMatrix, - noInvalidate = false, - ) { + function handleAntagonistEquipmentChange(noInvalidate = false) { + const equipment = Duel.state.antagonist.equipmentMatrix; const tankopediaEphemeral = Tankopedia.state; const shell = tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; @@ -150,10 +144,15 @@ export function PrimaryArmorSceneComponent({ Duel.state.antagonist.tank.equipment_preset, equipment, ); + const equalize = + (Duel.state.equalize + ? Duel.state.antagonist.tank.equalizer?.penetration + : undefined) ?? 1; material.uniforms.penetration.value = penetration * - resolvePenetrationCoefficient(hasCalibratedShells, shell.type); + resolvePenetrationCoefficient(hasCalibratedShells, shell.type) * + equalize; if (!noInvalidate) invalidate(); } @@ -169,8 +168,8 @@ export function PrimaryArmorSceneComponent({ ); handleOpaqueChange(TankopediaPersistent.state.opaque); handleWireframeChange(TankopediaPersistent.state.wireframe); - handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix); - handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix); + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); transitionEvent.on(handleTransitionEvent); @@ -188,11 +187,18 @@ export function PrimaryArmorSceneComponent({ TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange), Duel.on( (state) => state.protagonist.equipmentMatrix, - (equipment) => handleProtagonistEquipmentChange(equipment), + () => handleProtagonistEquipmentChange(), ), Duel.on( (state) => state.antagonist.equipmentMatrix, - (equipment) => handleAntagonistEquipmentChange(equipment), + () => handleAntagonistEquipmentChange(), + ), + Duel.on( + (state) => state.equalize, + () => { + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); + }, ), () => transitionEvent.off(handleTransitionEvent), ]; diff --git a/packages/website/src/components/Armor/components/ShotDisplay.tsx b/packages/website/src/components/Armor/components/ShotDisplay.tsx index 2832d2b6d..b7f8f9d2a 100644 --- a/packages/website/src/components/Armor/components/ShotDisplay.tsx +++ b/packages/website/src/components/Armor/components/ShotDisplay.tsx @@ -52,7 +52,7 @@ 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", @@ -61,7 +61,7 @@ export function ShotDisplay() { }); const outline = new Line( outlineGeometry, - outlineMaterial + outlineMaterial, ).computeLineDistances(); splashRadiusWrapper.current?.add(outline); @@ -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), )} > @@ -225,7 +225,7 @@ export function ShotDisplay() { @@ -253,8 +253,8 @@ export function ShotDisplay() { rotation={new Euler().setFromQuaternion( new Quaternion().setFromUnitVectors( J_HAT, - shot.out.surfaceNormal.clone() - ) + shot.out.surfaceNormal.clone(), + ), )} > diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx index b7b0e6434..36edc60d9 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx @@ -10,7 +10,7 @@ import { 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 { Duel } from "../../../../../../stores/duel"; import { Tankopedia } from "../../../../../../stores/tankopedia"; import { ArmorType } from "../../../SpacedArmorScene"; import fragmentShader from "./shaders/fragment.glsl?raw"; @@ -52,17 +52,23 @@ export function SpacedArmorSubExternal({ tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; material.uniforms.penetration.value = shell.penetration!.near; } - function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) { + function handleProtagonistEquipmentChange() { + const equipment = Duel.state.protagonist.equipmentMatrix; const hasEnhancedArmor = hasEquipment( 110, Duel.state.protagonist.tank.equipment_preset, equipment, ); - material.uniforms.thickness.value = hasEnhancedArmor - ? thickness * 1.03 - : thickness; + const equalizer = + (Duel.state.equalize + ? Duel.state.protagonist.tank.equalizer?.armor + : undefined) ?? 1; + + material.uniforms.thickness.value = + thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; } - function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) { + function handleAntagonistEquipmentChange() { + const equipment = Duel.state.antagonist.equipmentMatrix; const tankopediaEphemeral = Tankopedia.state; const shell = tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; @@ -72,15 +78,20 @@ export function SpacedArmorSubExternal({ Duel.state.antagonist.tank.equipment_preset, equipment, ); + const equalize = + (Duel.state.equalize + ? Duel.state.antagonist.tank.equalizer?.penetration + : undefined) ?? 1; material.uniforms.penetration.value = penetration * - resolvePenetrationCoefficient(hasCalibratedShells, shell.type); + resolvePenetrationCoefficient(hasCalibratedShells, shell.type) * + equalize; } handleShellChange(); - handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix); - handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix); + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); const unsubscribes = [ Duel.on((state) => state.antagonist.shell, handleShellChange), @@ -93,6 +104,13 @@ export function SpacedArmorSubExternal({ (state) => state.antagonist.equipmentMatrix, handleAntagonistEquipmentChange, ), + Duel.on( + (state) => state.equalize, + () => { + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); + }, + ), ]; return () => { diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx index f72519b55..fa1a6007e 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx @@ -10,7 +10,7 @@ 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 { Duel } from "../../../../../../stores/duel"; import { Tankopedia } from "../../../../../../stores/tankopedia"; import { ArmorType } from "../../../SpacedArmorScene"; import fragmentShader from "./shaders/fragment.glsl?raw"; @@ -62,17 +62,23 @@ export function SpacedArmorSubSpaced({ shell.normalization ?? 0, ); } - function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) { + function handleProtagonistEquipmentChange() { + const equipment = Duel.state.protagonist.equipmentMatrix; const hasEnhancedArmor = hasEquipment( 110, Duel.state.protagonist.tank.equipment_preset, equipment, ); - material.uniforms.thickness.value = hasEnhancedArmor - ? thickness * 1.03 - : thickness; + const equalizer = + (Duel.state.equalize + ? Duel.state.protagonist.tank.equalizer?.armor + : undefined) ?? 1; + + material.uniforms.thickness.value = + thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; } - function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) { + function handleAntagonistEquipmentChange() { + const equipment = Duel.state.antagonist.equipmentMatrix; const tankopediaEphemeral = Tankopedia.state; const shell = tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; @@ -82,15 +88,20 @@ export function SpacedArmorSubSpaced({ Duel.state.antagonist.tank.equipment_preset, equipment, ); + const equalize = + (Duel.state.equalize + ? Duel.state.antagonist.tank.equalizer?.penetration + : undefined) ?? 1; material.uniforms.penetration.value = penetration * - resolvePenetrationCoefficient(hasCalibratedShells, shell.type); + resolvePenetrationCoefficient(hasCalibratedShells, shell.type) * + equalize; } handleShellChange(); - handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix); - handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix); + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); const unsubscribes = [ Duel.on((state) => state.antagonist.shell, handleShellChange), @@ -103,6 +114,13 @@ export function SpacedArmorSubSpaced({ (state) => state.antagonist.equipmentMatrix, handleAntagonistEquipmentChange, ), + Duel.on( + (state) => state.equalize, + () => { + handleProtagonistEquipmentChange(); + handleAntagonistEquipmentChange(); + }, + ), ]; return () => { diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index c0a8789b4..38b42f959 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -18,7 +18,6 @@ import { import { degToRad } from "three/src/math/MathUtils.js"; import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; import { jsxTree } from "../../../../core/blitzkit/jsxTree"; -import { defaultEqualizer } from "../../../../core/blitzkit/tankToDuelMember"; import { discardClippingPlane } from "../../../../core/three/discardClippingPlane"; import { Duel } from "../../../../stores/duel"; import { @@ -90,8 +89,8 @@ export function SpacedArmorSceneComponent({ const shell = customShell ?? Duel.state.antagonist.shell; const { equalize } = Duel.state; const equalizer = - (equalize ? Duel.state.antagonist.tank.equalizer : undefined) ?? - defaultEqualizer; + (equalize ? Duel.state.antagonist.tank.equalizer?.damage : undefined) ?? + 1; const cameraNormal = camera.position.clone().sub(point).normalize(); const shot: Shot = { splashRadius: @@ -372,7 +371,7 @@ export function SpacedArmorSceneComponent({ } } - shot.damage *= equalizer.damage; + shot.damage *= equalizer; return shot; },