Merge branch 'main' into spall-liner-in-tankopdiea-model-viewer

This commit is contained in:
TrèsAbhi authored and GitHub committed 2026-10-02 15:18:53 -07:00
commit 576b4f7c73
38 files changed
+661 -1024

No files matched your search

@@ -5,7 +5,7 @@ import {
} from "@blitzkit/core";
import { invalidate, useFrame } from "@react-three/fiber";
import type { QuicklimeEvent } from "quicklime";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import {
MeshBasicMaterial,
NormalBlending,
@@ -43,42 +43,44 @@ export function PrimaryArmorSceneComponent({
node,
thickness,
}: PrimaryArmorSceneComponentProps) {
const material = new ShaderMaterial({
fragmentShader,
vertexShader,
const material = useRef(
new ShaderMaterial({
fragmentShader,
vertexShader,
fog: true,
transparent: true,
fog: true,
transparent: true,
blending: isHalloween ? SubtractiveBlending : NormalBlending,
blending: isHalloween ? SubtractiveBlending : NormalBlending,
uniforms: UniformsUtils.merge([
UniformsLib.common,
UniformsLib.fog,
uniforms: UniformsUtils.merge([
UniformsLib.common,
UniformsLib.fog,
{
thickness: { value: null },
penetration: { value: null },
caliber: { value: null },
ricochet: { value: null },
normalization: { value: null },
isExplosive: { value: null },
canSplash: { value: null },
damage: { value: null },
explosionRadius: { value: null },
greenPenetration: { value: null },
advancedHighlighting: { value: null },
opaque: { value: null },
{
thickness: { value: null },
penetration: { value: null },
caliber: { value: null },
ricochet: { value: null },
normalization: { value: null },
isExplosive: { value: null },
canSplash: { value: null },
damage: { value: null },
explosionRadius: { value: null },
greenPenetration: { value: null },
advancedHighlighting: { value: null },
opaque: { value: null },
inverseProjectionMatrix: { value: null },
resolution: { value: new Vector2() },
spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null },
inverseProjectionMatrix: { value: null },
resolution: { value: new Vector2() },
spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null },
opacity: { value: 0 },
},
]),
});
opacity: { value: 0 },
},
]),
}),
);
useEffect(() => {
function handleShellChange() {
@@ -86,17 +88,17 @@ export function PrimaryArmorSceneComponent({
const shell =
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
material.uniforms.caliber.value = shell.caliber;
material.uniforms.ricochet.value = degToRad(
material.current.uniforms.caliber.value = shell.caliber;
material.current.uniforms.ricochet.value = degToRad(
isExplosive(shell.type) ? 90 : shell.ricochet!,
);
material.uniforms.normalization.value = degToRad(
material.current.uniforms.normalization.value = degToRad(
shell.normalization ?? 0,
);
material.uniforms.isExplosive.value = isExplosive(shell.type);
material.uniforms.canSplash.value = canSplash(shell.type);
material.uniforms.damage.value = shell.armor_damage;
material.uniforms.explosionRadius.value = shell.explosion_radius;
material.current.uniforms.isExplosive.value = isExplosive(shell.type);
material.current.uniforms.canSplash.value = canSplash(shell.type);
material.current.uniforms.damage.value = shell.armor_damage;
material.current.uniforms.explosionRadius.value = shell.explosion_radius;
handleProtagonistEquipmentChange(true);
handleAntagonistEquipmentChange(true);
@@ -104,17 +106,18 @@ export function PrimaryArmorSceneComponent({
invalidate();
}
function handleGreenPenetrationChange(greenPenetration: boolean) {
material.uniforms.greenPenetration.value = greenPenetration;
material.current.uniforms.greenPenetration.value = greenPenetration;
}
function handleAdvancedHighlightingChange(advancedHighlighting: boolean) {
material.uniforms.advancedHighlighting.value = advancedHighlighting;
material.current.uniforms.advancedHighlighting.value =
advancedHighlighting;
invalidate();
}
function handleOpaqueChange(opaque: boolean) {
material.uniforms.opaque.value = opaque;
material.current.uniforms.opaque.value = opaque;
}
function handleWireframeChange(wireframe: boolean) {
material.wireframe = wireframe;
material.current.wireframe = wireframe;
}
function handleProtagonistEquipmentChange(noInvalidate = false) {
const equipment = Duel.state.protagonist.equipmentMatrix;
@@ -128,7 +131,7 @@ export function PrimaryArmorSceneComponent({
? Duel.state.protagonist.tank.equalizer?.armor
: undefined) ?? 1;
material.uniforms.thickness.value =
material.current.uniforms.thickness.value =
thickness * (hasEnhancedArmor ? 1.04 : 1) * equalizer;
if (!noInvalidate) invalidate();
@@ -145,7 +148,7 @@ export function PrimaryArmorSceneComponent({
equipment,
);
material.uniforms.penetration.value =
material.current.uniforms.penetration.value =
penetration *
resolvePenetrationCoefficient(
hasCalibratedShells,
@@ -158,7 +161,7 @@ export function PrimaryArmorSceneComponent({
}
function handleTransitionEvent(event: QuicklimeEvent<number>) {
material.uniforms.opacity.value = event.data;
material.current.uniforms.opacity.value = event.data;
}
handleShellChange();
@@ -209,13 +212,14 @@ export function PrimaryArmorSceneComponent({
}, []);
useFrame(({ gl, camera }) => {
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
gl.getSize(material.current.uniforms.resolution.value).multiplyScalar(
gl.getPixelRatio(),
);
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
material.uniforms.spacedArmorDepth.value =
material.current.uniforms.spacedArmorBuffer.value =
spacedArmorRenderTarget.texture;
material.current.uniforms.spacedArmorDepth.value =
spacedArmorRenderTarget.depthTexture;
material.uniforms.inverseProjectionMatrix.value =
material.current.uniforms.inverseProjectionMatrix.value =
camera.projectionMatrixInverse;
});
@@ -240,7 +244,7 @@ export function PrimaryArmorSceneComponent({
{...props}
key={`${key}-include`}
renderOrder={1}
material={material}
material={material.current}
/>
);
},
@@ -27,8 +27,6 @@ export const Armor = memo(() => {
const newRenderSize = new Vector2();
useFrame(({ gl, camera, scene }) => {
gl.autoClear = true;
gl.getSize(newRenderSize).multiplyScalar(gl.getPixelRatio());
if (!newRenderSize.equals(renderSize)) {
@@ -50,6 +48,8 @@ export const Armor = memo(() => {
gl.clearDepth();
gl.render(primaryArmorScene, camera);
gl.autoClear = true;
}, 1);
return (
@@ -45,14 +45,13 @@ const consumableDefinitions = await api.consumableDefinitions();
const provisionDefinitions = await api.provisionDefinitions();
const tankDefinitions = await api.tankDefinitions();
const gameModeRoleSets: Record<number, Set<number>> = {};
const gameModeRoleSets: Record<string, Set<number>> = {};
for (const tankIdString in tankDefinitions.tanks) {
const gameMode = tankDefinitions.tanks[tankIdString];
for (const gameModeIdString in gameMode.roles) {
const role = gameMode.roles[gameModeIdString];
const gameModeId = Number(gameModeIdString);
for (const gameModeId in gameMode.roles) {
const role = gameMode.roles[gameModeId];
if (gameModeId in gameModeRoleSets) {
gameModeRoleSets[gameModeId].add(role);
@@ -63,7 +62,7 @@ for (const tankIdString in tankDefinitions.tanks) {
}
const gameModeRoles: {
gameModeId: number;
gameModeId: string;
consumables: number[];
provisions: number[];
}[] = [];
@@ -72,8 +71,7 @@ const allGameModeConsumables = new Set<number>();
const allGameModeProvisions = new Set<number>();
for (const gameModeIdString in gameModeRoleSets) {
const gameModeId = Number(gameModeIdString);
const roles = gameModeRoleSets[gameModeId];
const roles = gameModeRoleSets[gameModeIdString];
const consumables = new Set<number>();
const provisions = new Set<number>();
@@ -116,7 +114,7 @@ for (const gameModeIdString in gameModeRoleSets) {
}
gameModeRoles.push({
gameModeId: gameModeId,
gameModeId: gameModeIdString,
consumables: Array.from(consumables.values()),
provisions: Array.from(provisions.values()),
});
@@ -1,5 +1,5 @@
import { alias } from "@blitzkit/core";
import { Badge, Box, Flex, Heading, Switch, Text } from "@radix-ui/themes";
import { Box, Flex, Heading, Switch, Text } from "@radix-ui/themes";
import { useLocale } from "../../../../hooks/useLocale";
import { Duel } from "../../../../stores/duel";
import { Tankopedia } from "../../../../stores/tankopedia";
@@ -12,12 +12,7 @@ export function Equalizer() {
return (
<ConfigurationChildWrapper>
<Heading size="4">
<Flex gap="2" align="center">
{strings.website.tools.tankopedia.configuration.equalizer.title}{" "}
<Badge variant="outline">
{strings.website.tools.tankopedia.configuration.equalizer.beta}
</Badge>
</Flex>
{strings.website.tools.tankopedia.configuration.equalizer.title}
</Heading>
<Box
@@ -34,9 +29,9 @@ export function Equalizer() {
style={{
cursor: "pointer",
borderRadius: "var(--radius-3)",
backgroundImage: `url(${alias("api", `/gamemodes/45/banner.webp`)})`,
backgroundImage: `url(${alias("api", `/gamemodes/Insanity/banner.webp`)})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundPosition: "50% 25%",
overflow: "hidden",
}}
>
@@ -25,8 +25,7 @@ export function GameModeSection() {
</Heading>
<Flex justify="center" gap="4" wrap="wrap" px="4">
{roles.map(([key, value]) => {
const id = Number(key);
{roles.map(([id, value]) => {
const gameMode = gameDefinitions.gameModes[id];
return (
@@ -17,15 +17,15 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
const bannerMatches: { name: string; path: string }[] = [];
for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
/eGameMode\.([a-zA-Z]+) -> "~res:([^"]+)"/g,
/eGameMode\.(\w+) -> "~res:([^"]+)"/g,
)) {
bannerMatches.push({ name: match[1], path: match[2] });
}
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
/"(\d+)" -> "battleType\/([a-zA-Z]+)"/g,
/eGameMode\.(\w+) -> "battleType\/([a-zA-Z]+)"/g,
)) {
const id = Number(match[1]);
const id = match[1];
const name = match[2];
let path = bannerMatches.find(
(bannerMatch) => bannerMatch.name.toLowerCase() === name.toLowerCase(),
@@ -11,13 +11,6 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
.dir(`Data/Gfx/Shared/tank-supply/ammunition/big`)
.then((files) => files.filter((file) => file.endsWith("_l.txt")));
console.log(
"Shell files raw",
await vfs.dir(`Data/Gfx/Shared/tank-supply/ammunition/big`),
);
console.log("Shell file paths", files);
for (const file of files) {
const id = file.match(/(.+)_l\.txt/)![1];
const sizes = (
@@ -33,8 +26,6 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
});
}
console.log("Shell paths", paths);
return paths;
});
@@ -90,15 +90,21 @@ function Content() {
session.tracking && tankStatsB
? deltaTankStats(session.player.stats, tankStatsB)
.sort((a, b) => b.last_battle_time - a.last_battle_time)
.map((entry) => {
.flatMap((entry) => {
if (!(entry.tank_id in tankDefinitions.tanks)) return [];
const tank = tankDefinitions.tanks[entry.tank_id];
if (!(tank.id in averageDefinitions.averages)) return [];
const average = averageDefinitions.averages[tank.id];
const composite = compositeStats(
{ ...entry.all, battle_life_time: entry.battle_life_time },
average?.mu!,
average.mu!,
);
return { tank, composite };
return [{ tank, composite }];
})
: undefined,
[session.tracking && session.player, tankStatsB],