mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: armor modes
This commit is contained in:
1 parent
ffa1395b50
commit
f1410cf321
42 files changed
+293
-264
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
export * from './assertSecret';
|
export * from './assertSecret';
|
||||||
export * from './asset';
|
export * from './asset';
|
||||||
// export * from './availableProvisions';
|
export * from './availableProvisions';
|
||||||
// export * from './averageDefinitions';
|
// export * from './averageDefinitions';
|
||||||
// export * from './camouflageDefinitions';
|
// export * from './camouflageDefinitions';
|
||||||
// export * from './checkConsumableProvisionInclusivity';
|
// export * from './checkConsumableProvisionInclusivity';
|
||||||
@@ -11,7 +11,7 @@ export * from './asset';
|
|||||||
// export * from './degressiveStat';
|
// export * from './degressiveStat';
|
||||||
// export * from './discoveredIdDefinitions';
|
// export * from './discoveredIdDefinitions';
|
||||||
// export * from './emblemURL';
|
// export * from './emblemURL';
|
||||||
// export * from './equipmentDefinitions';
|
export * from './equipmentDefinitions';
|
||||||
export * from './eventManager';
|
export * from './eventManager';
|
||||||
// export * from './fetchCdonLz4';
|
// export * from './fetchCdonLz4';
|
||||||
// export * from './flagUserActivity';
|
// export * from './flagUserActivity';
|
||||||
@@ -21,7 +21,7 @@ export * from './gameDefinitions';
|
|||||||
// export * from './getArchivedRatingLeaderboard';
|
// export * from './getArchivedRatingLeaderboard';
|
||||||
// export * from './getArchivedRatingMidnightLeaderboard';
|
// export * from './getArchivedRatingMidnightLeaderboard';
|
||||||
// export * from './getTimeDaysAgo';
|
// export * from './getTimeDaysAgo';
|
||||||
// export * from './isExplosive';
|
export * from './isExplosive';
|
||||||
// export * from './linkBlitzAndDiscord';
|
// export * from './linkBlitzAndDiscord';
|
||||||
// export * from './mapDefinitions';
|
// export * from './mapDefinitions';
|
||||||
export * from './modelDefinitions';
|
export * from './modelDefinitions';
|
||||||
@@ -42,7 +42,7 @@ export * from './tankDefinitions';
|
|||||||
export * from './tankIcon';
|
export * from './tankIcon';
|
||||||
export * from './tankNames';
|
export * from './tankNames';
|
||||||
export * from './unionBoundingBox';
|
export * from './unionBoundingBox';
|
||||||
// export * from './uniqueGuns';
|
export * from './uniqueGuns';
|
||||||
// export * from './unwrapBlitzkitResponse';
|
// export * from './unwrapBlitzkitResponse';
|
||||||
// export * from './votes';
|
// export * from './votes';
|
||||||
// export * from './withCORSProxy';
|
// export * from './withCORSProxy';
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"@blitzkit/core": "workspace:*",
|
"@blitzkit/core": "workspace:*",
|
||||||
"fuzzysort": "^2.0.4",
|
"fuzzysort": "^2.0.4",
|
||||||
"@nivo/bar": "^0.87.0",
|
"@nivo/bar": "^0.87.0",
|
||||||
"three": "0.159.0",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next": "^14.2.7",
|
"next": "^14.2.7",
|
||||||
"three-stdlib": "^2.32.2",
|
"three-stdlib": "^2.32.2",
|
||||||
@@ -26,13 +25,10 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-fuzzysort": "^1.0.1",
|
"react-fuzzysort": "^1.0.1",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"@vanilla-extract/dynamic": "^2.1.2",
|
"@vanilla-extract/dynamic": "^2.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-dom": "^18",
|
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
"@types/three": "^0.160.0",
|
"@vanilla-extract/next-plugin": "^2.4.5"
|
||||||
"@vanilla-extract/next-plugin": "^2.4.5",
|
|
||||||
"@types/react": "^18.3.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@radix-ui/themes": "^3.1.3",
|
"@radix-ui/themes": "^3.1.3",
|
||||||
"@vanilla-extract/css": "^1.15.5",
|
"@vanilla-extract/css": "^1.15.5",
|
||||||
"astro": "^4.15.6",
|
"astro": "^4.15.6",
|
||||||
|
"three": "0.159.0",
|
||||||
"@react-three/drei": "^9.111.5",
|
"@react-three/drei": "^9.111.5",
|
||||||
"@react-three/fiber": "^8.17.6",
|
"@react-three/fiber": "^8.17.6",
|
||||||
"astro-purgecss": "^4.6.0",
|
"astro-purgecss": "^4.6.0",
|
||||||
@@ -29,6 +30,9 @@
|
|||||||
"react-promise-suspense": "^0.3.4"
|
"react-promise-suspense": "^0.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vanilla-extract/vite-plugin": "^4.0.15"
|
"@types/three": "^0.160.0",
|
||||||
|
"@vanilla-extract/vite-plugin": "^4.0.15",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react": "^18.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,10 @@ import {
|
|||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { useRef } from 'react';
|
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 { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
import { layerTypeNames } from './ShotDisplayCard';
|
import { layerTypeNames } from './ShotDisplayCard';
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
|
|||||||
import { useArmor } from '../../../hooks/useArmor';
|
import { useArmor } from '../../../hooks/useArmor';
|
||||||
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
||||||
import { useTankTransform } from '../../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||||
|
import { Duel } from '../../../stores/duel';
|
||||||
import { ModelTankWrapper } from './ModelTankWrapper';
|
import { ModelTankWrapper } from './ModelTankWrapper';
|
||||||
import { PrimaryArmorSceneComponent } from './PrimaryArmorSceneComponent';
|
import { PrimaryArmorSceneComponent } from './PrimaryArmorSceneComponent';
|
||||||
|
|
||||||
|
|||||||
+26
-21
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ShellDefinition,
|
type ShellDefinition,
|
||||||
canSplash,
|
canSplash,
|
||||||
isExplosive,
|
isExplosive,
|
||||||
resolveNearPenetration,
|
resolveNearPenetration,
|
||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MeshBasicMaterial, Object3D, ShaderMaterial, Vector2 } from 'three';
|
import { MeshBasicMaterial, Object3D, ShaderMaterial, Vector2 } from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import { hasEquipment } from '../../../../../../website-legacy/src/core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
|
||||||
import { jsxTree } from '../../../../../../website-legacy/src/core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
|
||||||
import * as Duel from '../../../../../../website-legacy/src/stores/duel';
|
import { Duel, type EquipmentMatrix } from '../../../../stores/duel';
|
||||||
import * as TankopediaPersistent from '../../../../../../website-legacy/src/stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
|
||||||
import fragmentShader from './shaders/fragment.glsl';
|
import fragmentShader from './shaders/fragment.glsl?raw';
|
||||||
import vertexShader from './shaders/vertex.glsl';
|
import vertexShader from './shaders/vertex.glsl?raw';
|
||||||
import { spacedArmorRenderTarget } from './target';
|
import { spacedArmorRenderTarget } from './target';
|
||||||
|
|
||||||
interface PrimaryArmorSceneComponentProps {
|
interface PrimaryArmorSceneComponentProps {
|
||||||
@@ -76,15 +76,17 @@ export function PrimaryArmorSceneComponent({
|
|||||||
await handleProtagonistEquipmentChange(duel.protagonist.equipmentMatrix);
|
await handleProtagonistEquipmentChange(duel.protagonist.equipmentMatrix);
|
||||||
await handleAntagonistEquipmentChange(duel.antagonist.equipmentMatrix);
|
await handleAntagonistEquipmentChange(duel.antagonist.equipmentMatrix);
|
||||||
}
|
}
|
||||||
function handleVisualChange(
|
function handleGreenPenetrationChange(greenPenetration: boolean) {
|
||||||
visual: TankopediaPersistent.TankopediaPersistent['model']['visual'],
|
material.uniforms.greenPenetration.value = greenPenetration;
|
||||||
) {
|
}
|
||||||
material.uniforms.greenPenetration.value = visual.greenPenetration;
|
function handleOpaqueChange(opaque: boolean) {
|
||||||
material.uniforms.opaque.value = visual.opaque || visual.wireframe;
|
material.uniforms.opaque.value = opaque;
|
||||||
material.wireframe = visual.wireframe;
|
}
|
||||||
|
function handleWireframeChange(wireframe: boolean) {
|
||||||
|
material.wireframe = wireframe;
|
||||||
}
|
}
|
||||||
async function handleProtagonistEquipmentChange(
|
async function handleProtagonistEquipmentChange(
|
||||||
equipment: Duel.EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
) {
|
) {
|
||||||
const duel = duelStore.getState();
|
const duel = duelStore.getState();
|
||||||
const hasEnhancedArmor = await hasEquipment(
|
const hasEnhancedArmor = await hasEquipment(
|
||||||
@@ -96,9 +98,7 @@ export function PrimaryArmorSceneComponent({
|
|||||||
? thickness * 1.03
|
? thickness * 1.03
|
||||||
: thickness;
|
: thickness;
|
||||||
}
|
}
|
||||||
async function handleAntagonistEquipmentChange(
|
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
equipment: Duel.EquipmentMatrix,
|
|
||||||
) {
|
|
||||||
const duel = duelStore.getState();
|
const duel = duelStore.getState();
|
||||||
const shell = duel.antagonist.shell;
|
const shell = duel.antagonist.shell;
|
||||||
const penetration = resolveNearPenetration(shell.penetration);
|
const penetration = resolveNearPenetration(shell.penetration);
|
||||||
@@ -114,7 +114,11 @@ export function PrimaryArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleShellChange(duelStore.getState().antagonist.shell);
|
handleShellChange(duelStore.getState().antagonist.shell);
|
||||||
handleVisualChange(tankopediaPersistentStore.getState().model.visual);
|
handleGreenPenetrationChange(
|
||||||
|
tankopediaPersistentStore.getState().greenPenetration,
|
||||||
|
);
|
||||||
|
handleOpaqueChange(tankopediaPersistentStore.getState().opaque);
|
||||||
|
handleWireframeChange(tankopediaPersistentStore.getState().wireframe);
|
||||||
handleProtagonistEquipmentChange(
|
handleProtagonistEquipmentChange(
|
||||||
duelStore.getState().protagonist.equipmentMatrix,
|
duelStore.getState().protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
@@ -125,9 +129,10 @@ export function PrimaryArmorSceneComponent({
|
|||||||
const unsubscribes = [
|
const unsubscribes = [
|
||||||
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
duelStore.subscribe((state) => state.antagonist.shell, handleShellChange),
|
||||||
tankopediaPersistentStore.subscribe(
|
tankopediaPersistentStore.subscribe(
|
||||||
(state) => state.model.visual,
|
(state) => state.greenPenetration,
|
||||||
handleVisualChange,
|
handleGreenPenetrationChange,
|
||||||
),
|
),
|
||||||
|
|
||||||
duelStore.subscribe(
|
duelStore.subscribe(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
handleProtagonistEquipmentChange,
|
handleProtagonistEquipmentChange,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { J_HAT, K_HAT } from '@blitzkit/core';
|
import { K_HAT } from '@blitzkit/core';
|
||||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
import { Box, Text } from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { type ComponentProps, useEffect, useRef } from 'react';
|
import { useEffect, useRef, type ComponentProps } from 'react';
|
||||||
import {
|
import {
|
||||||
BufferGeometry,
|
BufferGeometry,
|
||||||
DoubleSide,
|
DoubleSide,
|
||||||
@@ -16,12 +16,16 @@ import {
|
|||||||
Path,
|
Path,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { useFullScreen } from '../../../../../website-legacy/src/hooks/useFullScreen';
|
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||||
import * as TankopediaEphemeral from '../../../../../website-legacy/src/stores/tankopediaEphemeral';
|
import {
|
||||||
|
TankopediaEphemeral,
|
||||||
|
type ShotLayerNonExternal,
|
||||||
|
type ShotStatus,
|
||||||
|
} from '../../../stores/tankopediaEphemeral';
|
||||||
import { ShotDisplayCard } from './ShotDisplayCard';
|
import { ShotDisplayCard } from './ShotDisplayCard';
|
||||||
|
|
||||||
export const shotStatusColors: Record<
|
export const shotStatusColors: Record<
|
||||||
TankopediaEphemeral.ShotStatus,
|
ShotStatus,
|
||||||
ComponentProps<typeof Text>['color']
|
ComponentProps<typeof Text>['color']
|
||||||
> = {
|
> = {
|
||||||
blocked: 'red',
|
blocked: 'red',
|
||||||
@@ -127,16 +131,13 @@ export function ShotDisplay() {
|
|||||||
}, 0) + SHOT_DISPLAY_LENGTH_INFINITY;
|
}, 0) + SHOT_DISPLAY_LENGTH_INFINITY;
|
||||||
const outLength =
|
const outLength =
|
||||||
shot.out && shot.out.layers.length > 0
|
shot.out && shot.out.layers.length > 0
|
||||||
? (
|
? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo(
|
||||||
shot.in.layers.at(-1) as TankopediaEphemeral.ShotLayerNonExternal
|
(shot.out.layers.at(-1) as ShotLayerNonExternal).point,
|
||||||
).point.distanceTo(
|
|
||||||
(shot.out.layers.at(-1) as TankopediaEphemeral.ShotLayerNonExternal)
|
|
||||||
.point,
|
|
||||||
)
|
)
|
||||||
: SHOT_DISPLAY_LENGTH_INFINITY;
|
: SHOT_DISPLAY_LENGTH_INFINITY;
|
||||||
const inLast = shot.in.layers.findLast(
|
const inLast = shot.in.layers.findLast(
|
||||||
(layer) => layer.type !== null,
|
(layer) => layer.type !== null,
|
||||||
) as TankopediaEphemeral.ShotLayerNonExternal;
|
) as ShotLayerNonExternal;
|
||||||
const outTitleColor = shot.out
|
const outTitleColor = shot.out
|
||||||
? shotStatusColors[shot.out.status]
|
? shotStatusColors[shot.out.status]
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -182,7 +183,7 @@ export function ShotDisplay() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
{[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => {
|
{/* {[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => {
|
||||||
if (layer.type === null) return null;
|
if (layer.type === null) return null;
|
||||||
|
|
||||||
const shotStatusColor =
|
const shotStatusColor =
|
||||||
@@ -275,7 +276,7 @@ export function ShotDisplay() {
|
|||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
)}
|
)} */}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
import {
|
import { Flex, Text } from '@radix-ui/themes';
|
||||||
Card,
|
|
||||||
CardProps,
|
|
||||||
Flex,
|
|
||||||
Inset,
|
|
||||||
Separator,
|
|
||||||
Text,
|
|
||||||
} from '@radix-ui/themes';
|
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
import { radToDeg } from 'three/src/math/MathUtils';
|
import type {
|
||||||
import * as TankopediaEphemeral from '../../../../../website-legacy/src/stores/tankopediaEphemeral';
|
ShotLayer,
|
||||||
import { shotStatusColors } from './ShotDisplay';
|
ShotStatus,
|
||||||
|
} from '../../../stores/tankopediaEphemeral';
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
import { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
import type { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
||||||
|
|
||||||
export const layerTypeNames: Record<ArmorType | 'null', string> = {
|
export const layerTypeNames: Record<ArmorType | 'null', string> = {
|
||||||
[ArmorType.Primary]: 'Primary',
|
[ArmorType.Primary]: 'Primary',
|
||||||
@@ -25,7 +19,7 @@ const externalLayerNames: Record<ExternalModuleVariant, string> = {
|
|||||||
track: 'Tracks',
|
track: 'Tracks',
|
||||||
};
|
};
|
||||||
|
|
||||||
const shotStatusNames: Record<TankopediaEphemeral.ShotStatus, string> = {
|
const shotStatusNames: Record<ShotStatus, string> = {
|
||||||
blocked: 'Blocked',
|
blocked: 'Blocked',
|
||||||
ricochet: 'Ricochet',
|
ricochet: 'Ricochet',
|
||||||
penetration: 'penetration',
|
penetration: 'penetration',
|
||||||
@@ -42,7 +36,7 @@ function LayerEntry({
|
|||||||
layerIndex?: string;
|
layerIndex?: string;
|
||||||
shotStatusColor: ComponentProps<typeof Text>['color'];
|
shotStatusColor: ComponentProps<typeof Text>['color'];
|
||||||
layerName: string;
|
layerName: string;
|
||||||
layer: TankopediaEphemeral.ShotLayer;
|
layer: ShotLayer;
|
||||||
angle?: number;
|
angle?: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -95,7 +89,7 @@ function LayerEntry({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ShotDisplayCardProps extends CardProps {
|
interface ShotDisplayCardProps extends CardProps {
|
||||||
shot: TankopediaEphemeral.Shot;
|
shot: Shot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useArmor } from '../../../hooks/useArmor';
|
|||||||
import { useModel } from '../../../hooks/useModel';
|
import { useModel } from '../../../hooks/useModel';
|
||||||
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
||||||
import { useTankTransform } from '../../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||||
|
import { Duel } from '../../../stores/duel';
|
||||||
import { ModelTankWrapper } from './ModelTankWrapper';
|
import { ModelTankWrapper } from './ModelTankWrapper';
|
||||||
import { SpacedArmorSceneComponent } from './SpacedArmorSceneComponent';
|
import { SpacedArmorSceneComponent } from './SpacedArmorSceneComponent';
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
|||||||
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||||
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
|
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
|
||||||
import { ArmorType } from '../../../SpacedArmorScene';
|
import { ArmorType } from '../../../SpacedArmorScene';
|
||||||
import fragmentShader from './shaders/fragment.glsl';
|
import fragmentShader from './shaders/fragment.glsl?raw';
|
||||||
import vertexShader from './shaders/vertex.glsl';
|
import vertexShader from './shaders/vertex.glsl?raw';
|
||||||
|
|
||||||
interface SpacedArmorSubExternalProps {
|
interface SpacedArmorSubExternalProps {
|
||||||
node: Object3D;
|
node: Object3D;
|
||||||
|
|||||||
+11
-13
@@ -1,24 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
ShellDefinition,
|
|
||||||
isExplosive,
|
isExplosive,
|
||||||
resolveNearPenetration,
|
resolveNearPenetration,
|
||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
|
type ShellDefinition,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
AdditiveBlending,
|
AdditiveBlending,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Object3D,
|
|
||||||
ShaderMaterial,
|
ShaderMaterial,
|
||||||
|
type Object3D,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import { ArmorUserData } from '../..';
|
import type { ArmorUserData } from '../..';
|
||||||
import { hasEquipment } from '../../../../../../../../website-legacy/src/core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
import { jsxTree } from '../../../../../../../../website-legacy/src/core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||||
import * as Duel from '../../../../../../../../website-legacy/src/stores/duel';
|
import { Duel, type EquipmentMatrix } from '../../../../../../stores/duel';
|
||||||
import { ArmorType } from '../../../SpacedArmorScene';
|
import { ArmorType } from '../../../SpacedArmorScene';
|
||||||
import fragmentShader from './shaders/fragment.glsl';
|
import fragmentShader from './shaders/fragment.glsl?raw';
|
||||||
import vertexShader from './shaders/vertex.glsl';
|
import vertexShader from './shaders/vertex.glsl?raw';
|
||||||
|
|
||||||
interface SpacedArmorSubSpacedProps {
|
interface SpacedArmorSubSpacedProps {
|
||||||
node: Object3D;
|
node: Object3D;
|
||||||
@@ -66,7 +66,7 @@ export function SpacedArmorSubSpaced({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
async function handleProtagonistEquipmentChange(
|
async function handleProtagonistEquipmentChange(
|
||||||
equipment: Duel.EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
) {
|
) {
|
||||||
const duel = duelStore.getState();
|
const duel = duelStore.getState();
|
||||||
const hasEnhancedArmor = await hasEquipment(
|
const hasEnhancedArmor = await hasEquipment(
|
||||||
@@ -78,9 +78,7 @@ export function SpacedArmorSubSpaced({
|
|||||||
? thickness * 1.03
|
? thickness * 1.03
|
||||||
: thickness;
|
: thickness;
|
||||||
}
|
}
|
||||||
async function handleAntagonistEquipmentChange(
|
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
equipment: Duel.EquipmentMatrix,
|
|
||||||
) {
|
|
||||||
const duel = duelStore.getState();
|
const duel = duelStore.getState();
|
||||||
const shell = duelStore.getState().antagonist.shell;
|
const shell = duelStore.getState().antagonist.shell;
|
||||||
const penetration = resolveNearPenetration(shell.penetration);
|
const penetration = resolveNearPenetration(shell.penetration);
|
||||||
|
|||||||
+16
-12
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
type ShellType,
|
ShellType,
|
||||||
isExplosive,
|
isExplosive,
|
||||||
resolveNearPenetration,
|
resolveNearPenetration,
|
||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
@@ -16,9 +16,17 @@ import {
|
|||||||
Scene,
|
Scene,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import * as Duel from '../../../../stores/duel';
|
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
|
||||||
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
|
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 { ArmorType } from '../SpacedArmorScene';
|
||||||
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
|
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
|
||||||
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
|
import { SpacedArmorSubSpaced } from './components/SpacedArmorSubSpaced';
|
||||||
@@ -83,7 +91,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
const { antagonist, protagonist } = duelStore.getState();
|
const { antagonist, protagonist } = duelStore.getState();
|
||||||
const shell = antagonist.shell;
|
const shell = antagonist.shell;
|
||||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||||
const shot: TankopediaEphemeral.Shot = {
|
const shot: Shot = {
|
||||||
splashRadius: shell.explosionRadius,
|
splashRadius: shell.explosionRadius,
|
||||||
damage: -1,
|
damage: -1,
|
||||||
containsGaps: shell.type === ShellType.HEAT,
|
containsGaps: shell.type === ShellType.HEAT,
|
||||||
@@ -157,9 +165,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (shell.type === ShellType.HEAT && index > 0) {
|
if (shell.type === ShellType.HEAT && index > 0) {
|
||||||
const lastLayer = shot.in.layers.at(
|
const lastLayer = shot.in.layers.at(-1) as ShotLayerBase;
|
||||||
-1,
|
|
||||||
) as TankopediaEphemeral.ShotLayerBase;
|
|
||||||
const distance = lastLayer.point.distanceTo(intersection.point);
|
const distance = lastLayer.point.distanceTo(intersection.point);
|
||||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||||
const blocked = remainingPenetration <= 0;
|
const blocked = remainingPenetration <= 0;
|
||||||
@@ -255,11 +261,9 @@ export function SpacedArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lastLayer = shot.in.layers.at(-1) as
|
const lastLayer = shot.in.layers.at(-1) as
|
||||||
| TankopediaEphemeral.ShotLayerNonExternal
|
| ShotLayerNonExternal
|
||||||
| undefined;
|
|
||||||
const firstLayer = shot.in.layers[0] as
|
|
||||||
| TankopediaEphemeral.ShotLayerBase
|
|
||||||
| undefined;
|
| undefined;
|
||||||
|
const firstLayer = shot.in.layers[0] as ShotLayerBase | undefined;
|
||||||
|
|
||||||
if (!lastLayer || !firstLayer) {
|
if (!lastLayer || !firstLayer) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useArmor } from '../../../hooks/useArmor';
|
|||||||
import { useModel } from '../../../hooks/useModel';
|
import { useModel } from '../../../hooks/useModel';
|
||||||
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
||||||
import { useTankTransform } from '../../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||||
|
import { Duel } from '../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||||
import { ModelTankWrapper } from './ModelTankWrapper';
|
import { ModelTankWrapper } from './ModelTankWrapper';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { I_HAT, J_HAT } from '@blitzkit/core';
|
import { I_HAT, J_HAT } from '@blitzkit/core';
|
||||||
import { MeshProps, useThree } from '@react-three/fiber';
|
import { type MeshProps, useThree } from '@react-three/fiber';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -15,20 +15,20 @@ import {
|
|||||||
Quaternion,
|
Quaternion,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { unrotateDavaVector } from '../../../../../website-legacy/src/core/blitz/unrotateDavaVector';
|
import { unrotateDavaVector } from '../../../core/blitz/unrotateDavaVector';
|
||||||
import { jsxTree } from '../../../../../website-legacy/src/core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
import {
|
import {
|
||||||
ModelTransformEventData,
|
type ModelTransformEventData,
|
||||||
modelTransformEvent,
|
modelTransformEvent,
|
||||||
} from '../../../../../website-legacy/src/core/blitzkit/modelTransform';
|
} from '../../../core/blitzkit/modelTransform';
|
||||||
import { discardClippingPlane } from '../../../../../website-legacy/src/core/three/discardClippingPlane';
|
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
||||||
import * as TankopediaEphemeral from '../../../../../website-legacy/src/stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
import {
|
import type {
|
||||||
ArmorUserData,
|
ArmorUserData,
|
||||||
ExternalModuleVariant,
|
ExternalModuleVariant,
|
||||||
} from './SpacedArmorSceneComponent';
|
} from './SpacedArmorSceneComponent';
|
||||||
import { ThicknessRange } from './StaticArmor';
|
import type { ThicknessRange } from './StaticArmor';
|
||||||
|
|
||||||
type StaticArmorSceneComponentProps = {
|
type StaticArmorSceneComponentProps = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import {
|
import {
|
||||||
TankComponentButton,
|
TankComponentButton,
|
||||||
TankComponentButtonProps,
|
type TankComponentButtonProps,
|
||||||
} from './TankComponentButton';
|
} from './TankComponentButton';
|
||||||
|
|
||||||
interface GenericTankComponentButtonProps extends TankComponentButtonProps {
|
interface GenericTankComponentButtonProps extends TankComponentButtonProps {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from '@blitzkit/core';
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||||
import { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||||
|
|
||||||
interface ModuleProps extends TankComponentButtonProps {
|
interface ModuleProps extends TankComponentButtonProps {
|
||||||
module: string;
|
module: string;
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@ import { asset } from '@blitzkit/core';
|
|||||||
import { IconButton } from '@radix-ui/themes';
|
import { IconButton } from '@radix-ui/themes';
|
||||||
import { invalidate } from '@react-three/fiber';
|
import { invalidate } from '@react-three/fiber';
|
||||||
import { useArmor } from '../../../../../../hooks/useArmor';
|
import { useArmor } from '../../../../../../hooks/useArmor';
|
||||||
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
|
|
||||||
export function DynamicArmorSwitcher() {
|
export function DynamicArmorSwitcher() {
|
||||||
|
|||||||
+24
-29
@@ -1,12 +1,15 @@
|
|||||||
import { HeightIcon, WidthIcon } from '@radix-ui/react-icons';
|
import { HeightIcon, WidthIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, TextField } from '@radix-ui/themes';
|
import { Flex, TextField } from '@radix-ui/themes';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils';
|
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelTransformEvent, ModelTransformEventData } from '../../../../../../core/blitzkit/modelTransform';
|
import {
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
type ModelTransformEventData,
|
||||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
modelTransformEvent,
|
||||||
import * as Duel from '../../../../../../stores/duel';
|
} from '../../../../../../../core/blitzkit/modelTransform';
|
||||||
|
import { useEquipment } from '../../../../../../../hooks/useEquipment';
|
||||||
|
import { useTankModelDefinition } from '../../../../../../../hooks/useTankModelDefinition';
|
||||||
|
import { Duel } from '../../../../../../../stores/duel';
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
export function QuickInputs() {
|
export function QuickInputs() {
|
||||||
@@ -22,13 +25,19 @@ export function QuickInputs() {
|
|||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
if (!yawInput.current) return;
|
||||||
|
|
||||||
|
yawInput.current.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
}, [protagonist.yaw]);
|
}, [protagonist.yaw]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pitchInput.current!.value = (
|
if (!pitchInput.current) return;
|
||||||
|
|
||||||
|
pitchInput.current.value = (
|
||||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
}, [protagonist.pitch]);
|
}, [protagonist.pitch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleTransformEvent({ pitch, yaw }: ModelTransformEventData) {
|
function handleTransformEvent({ pitch, yaw }: ModelTransformEventData) {
|
||||||
if (!pitchInput.current || !yawInput.current) return;
|
if (!pitchInput.current || !yawInput.current) return;
|
||||||
@@ -66,10 +75,8 @@ export function QuickInputs() {
|
|||||||
<TextField.Root
|
<TextField.Root
|
||||||
size="1"
|
size="1"
|
||||||
radius="full"
|
radius="full"
|
||||||
style={{
|
style={{ flex: 1, textAlign: 'right' }}
|
||||||
flex: 1,
|
defaultValue="0.0"
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const value = Number(yawInput.current!.value);
|
const value = Number(yawInput.current!.value);
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
@@ -98,23 +105,17 @@ export function QuickInputs() {
|
|||||||
onFocus={() => yawInput.current?.focus()}
|
onFocus={() => yawInput.current?.focus()}
|
||||||
ref={yawInput}
|
ref={yawInput}
|
||||||
>
|
>
|
||||||
<TextField.Slot
|
<TextField.Slot style={{ userSelect: 'none' }}>
|
||||||
style={{
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<WidthIcon />
|
<WidthIcon />
|
||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
<TextField.Slot />
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
size="1"
|
size="1"
|
||||||
radius="full"
|
radius="full"
|
||||||
style={{
|
style={{ flex: 1, textAlign: 'right' }}
|
||||||
flex: 1,
|
defaultValue="0.0"
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const value = Number(pitchInput.current!.value);
|
const value = Number(pitchInput.current!.value);
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
@@ -154,13 +155,7 @@ export function QuickInputs() {
|
|||||||
>
|
>
|
||||||
<HeightIcon />
|
<HeightIcon />
|
||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
<TextField.Slot
|
<TextField.Slot />
|
||||||
style={{
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
°
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
+12
-15
@@ -1,11 +1,11 @@
|
|||||||
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
||||||
import { ThicknessRange } from '../../../../../components/Armor/components/StaticArmor';
|
import { useArmor } from '../../../../../../hooks/useArmor';
|
||||||
import { useArmor } from '../../../../../hooks/useArmor';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import * as Duel from '../../../../../stores/duel';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
|
||||||
import { DynamicArmorSwitcher } from './Model/components/Options/components/DynamicArmorSwitcher';
|
import { DynamicArmorSwitcher } from './DynamicArmorSwitcher';
|
||||||
|
|
||||||
interface ThicknessesProps {
|
interface ThicknessesProps {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
@@ -13,13 +13,13 @@ interface ThicknessesProps {
|
|||||||
|
|
||||||
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
||||||
const showExternalModules = TankopediaPersistent.use(
|
const showExternalModules = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showExternalModules,
|
(state) => state.showExternalModules,
|
||||||
);
|
);
|
||||||
const showSpacedArmor = TankopediaPersistent.use(
|
const showSpacedArmor = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showSpacedArmor,
|
(state) => state.showSpacedArmor,
|
||||||
);
|
);
|
||||||
const showPrimaryArmor = TankopediaPersistent.use(
|
const showPrimaryArmor = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showPrimaryArmor,
|
(state) => state.showPrimaryArmor,
|
||||||
);
|
);
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||||
@@ -46,8 +46,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.showPrimaryArmor =
|
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
||||||
!draft.model.visual.showPrimaryArmor;
|
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
@@ -83,8 +82,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.showSpacedArmor =
|
draft.showSpacedArmor = !draft.showSpacedArmor;
|
||||||
!draft.model.visual.showSpacedArmor;
|
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
@@ -119,8 +117,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.showExternalModules =
|
draft.showExternalModules = !draft.showExternalModules;
|
||||||
!draft.model.visual.showExternalModules;
|
|
||||||
});
|
});
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
+25
-24
@@ -28,22 +28,24 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { invalidate } from '@react-three/fiber';
|
import { invalidate } from '@react-three/fiber';
|
||||||
import { type RefObject, useState } from 'react';
|
import { useState, type RefObject } from 'react';
|
||||||
import { ENVIRONMENTS } from '../../../../../constants/lightingEnvironments';
|
import { ENVIRONMENTS } from '../../../../../constants/lightingEnvironments';
|
||||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||||
import { useEquipment } from '../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||||
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
||||||
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
|
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
|
||||||
import { useStoreBoolean } from '../../../../../hooks/useStoreBoolean';
|
import { App } from '../../../../../stores/app';
|
||||||
import { useStoreKey } from '../../../../../hooks/useStoreKey';
|
import { Duel } from '../../../../../stores/duel';
|
||||||
import { $app } from '../../../../../stores/app';
|
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||||
import { $tankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
||||||
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
||||||
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
|
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
|
||||||
import { ModuleButton } from '../../../../ModuleButtons/ModuleButton';
|
import { ModuleButton } from '../../../../ModuleButtons/ModuleButton';
|
||||||
import { SmallTankIcon } from '../../../../SmallTankIcon';
|
import { SmallTankIcon } from '../../../../SmallTankIcon';
|
||||||
import { TankSearch } from '../../../../TankSearch';
|
import { TankSearch } from '../../../../TankSearch';
|
||||||
import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher';
|
import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher';
|
||||||
|
import { QuickInputs } from './components/QuickInputs';
|
||||||
|
import { Thicknesses } from './components/Thicknesses';
|
||||||
|
|
||||||
interface OptionsProps {
|
interface OptionsProps {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
@@ -51,23 +53,20 @@ interface OptionsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Options({ thicknessRange, canvas }: OptionsProps) {
|
export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||||
// const { display } = useStore($tankopediaPersistent, { keys: ['display'] });
|
const display = TankopediaPersistent.use((state) => state.display);
|
||||||
const display = useStoreKey($tankopediaPersistent, 'display');
|
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const showGrid = useStoreBoolean($tankopediaPersistent, 'showGrid');
|
const showGrid = TankopediaPersistent.use((state) => state.showGrid);
|
||||||
const showEnvironment = useStoreBoolean(
|
const showEnvironment = TankopediaPersistent.use(
|
||||||
$tankopediaPersistent,
|
(state) => state.showEnvironment,
|
||||||
'showEnvironment',
|
|
||||||
);
|
);
|
||||||
const greenPenetration = useStoreBoolean(
|
const greenPenetration = TankopediaPersistent.use(
|
||||||
$tankopediaPersistent,
|
(state) => state.greenPenetration,
|
||||||
'greenPenetration',
|
|
||||||
);
|
);
|
||||||
const wireframe = useStoreBoolean($tankopediaPersistent, 'wireframe');
|
const wireframe = TankopediaPersistent.use((state) => state.wireframe);
|
||||||
const opaque = useStoreBoolean($tankopediaPersistent, 'opaque');
|
const opaque = TankopediaPersistent.use((state) => state.opaque);
|
||||||
const fullScreenAvailable = useFullscreenAvailability();
|
const fullScreenAvailable = useFullscreenAvailability();
|
||||||
const environment = useStoreKey($tankopediaPersistent, 'environment');
|
const environment = TankopediaPersistent.use((state) => state.environment);
|
||||||
const developerMode = useStoreBoolean($app, 'developerMode');
|
const developerMode = App.use((state) => state.developerMode);
|
||||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||||
const antagonistGun = Duel.use((state) => state.antagonist.gun);
|
const antagonistGun = Duel.use((state) => state.antagonist.gun);
|
||||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||||
@@ -78,6 +77,8 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const hasEnhancedArmor = useEquipment(110);
|
const hasEnhancedArmor = useEquipment(110);
|
||||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||||
|
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||||
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -395,7 +396,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
checked={greenPenetration}
|
checked={greenPenetration}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.greenPenetration = checked;
|
draft.greenPenetration = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -406,7 +407,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
checked={opaque}
|
checked={opaque}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.opaque = checked;
|
draft.opaque = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -418,7 +419,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
checked={wireframe}
|
checked={wireframe}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.wireframe = checked;
|
draft.wireframe = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -434,7 +435,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
checked={showGrid}
|
checked={showGrid}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.showGrid = checked;
|
draft.showGrid = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -445,7 +446,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
checked={showEnvironment}
|
checked={showEnvironment}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.showEnvironment = checked;
|
draft.showEnvironment = checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -463,7 +464,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
value={environment}
|
value={environment}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaPersistent((draft) => {
|
||||||
draft.model.visual.environment = environment;
|
draft.environment = environment;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
File renamed without changes.
+6
-6
@@ -4,12 +4,12 @@ import { useThree } from '@react-three/fiber';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Vector3 } from 'three';
|
import { Vector3 } from 'three';
|
||||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { hasEquipment } from '../../../../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||||
import { useAwait } from '../../../../../hooks/useAwait';
|
import { useAwait } from '../../../../../../hooks/useAwait';
|
||||||
import * as Duel from '../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
|
|
||||||
const initialPosition = [0, 4, -18] as const;
|
const initialPosition = [0, 4, -18] as const;
|
||||||
const poseDistances: Record<Pose, number> = {
|
const poseDistances: Record<Pose, number> = {
|
||||||
+5
-7
@@ -1,17 +1,15 @@
|
|||||||
import { Environment } from '@react-three/drei';
|
import { Environment } from '@react-three/drei';
|
||||||
import { useModel } from '../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../hooks/useModel';
|
||||||
import * as Duel from '../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
export function Lighting() {
|
export function Lighting() {
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonist = Duel.use((state) => state.protagonist);
|
||||||
const { hasPbr } = useModel(protagonist.tank.id);
|
const { hasPbr } = useModel(protagonist.tank.id);
|
||||||
const showEnvironment = TankopediaPersistent.use(
|
const showEnvironment = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showEnvironment,
|
(state) => state.showEnvironment,
|
||||||
);
|
|
||||||
const environment = TankopediaPersistent.use(
|
|
||||||
(state) => state.model.visual.environment,
|
|
||||||
);
|
);
|
||||||
|
const environment = TankopediaPersistent.use((state) => state.environment);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
File renamed without changes.
+2
-3
@@ -2,12 +2,11 @@ import { useFrame, useLoader } from '@react-three/fiber';
|
|||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { MeshStandardMaterial, TextureLoader } from 'three';
|
import { MeshStandardMaterial, TextureLoader } from 'three';
|
||||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
export function SceneProps() {
|
export function SceneProps() {
|
||||||
const show = TankopediaPersistent.use(
|
const show = TankopediaPersistent.use(
|
||||||
(state) =>
|
(state) => state.showGrid && !state.showEnvironment,
|
||||||
state.model.visual.showGrid && !state.model.visual.showEnvironment,
|
|
||||||
);
|
);
|
||||||
const texture = useLoader(TextureLoader, 'https://i.imgur.com/C28Z8nU.png');
|
const texture = useLoader(TextureLoader, 'https://i.imgur.com/C28Z8nU.png');
|
||||||
const material = useRef<MeshStandardMaterial>(null);
|
const material = useRef<MeshStandardMaterial>(null);
|
||||||
+13
-13
@@ -1,19 +1,19 @@
|
|||||||
import { normalizeAngleRad } from '@blitzkit/core';
|
import { normalizeAngleRad } from '@blitzkit/core';
|
||||||
import { invalidate, ThreeEvent, useThree } from '@react-three/fiber';
|
import { invalidate, useThree, type ThreeEvent } from '@react-three/fiber';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
||||||
import { ModelTankWrapper } from '../../../../../../../components/Armor/components/ModelTankWrapper';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
|
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||||
import { jsxTree } from '../../../../../../../core/blitzkit/jsxTree';
|
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||||
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
import { useModel } from '../../../../../../hooks/useModel';
|
||||||
import { useModel } from '../../../../../../../hooks/useModel';
|
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||||
import { useTankModelDefinition } from '../../../../../../../hooks/useTankModelDefinition';
|
import { useTankTransform } from '../../../../../../hooks/useTankTransform';
|
||||||
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import * as Duel from '../../../../../../../stores/duel';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent';
|
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
||||||
import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants';
|
import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapper';
|
||||||
|
|
||||||
export const TankModel = memo(() => {
|
export const TankModel = memo(() => {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
+21
-22
@@ -6,29 +6,29 @@ import {
|
|||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Armor } from '../../../../../../components/Armor';
|
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { ArmorPlateDisplay } from '../../../../../../components/Armor/components/ArmorPlateDisplay';
|
import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform';
|
||||||
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
|
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||||
|
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||||
|
import { useOnScreen } from '../../../../../hooks/useOnScreen';
|
||||||
|
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
|
||||||
|
import { Duel } from '../../../../../stores/duel';
|
||||||
|
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||||
|
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
||||||
|
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
||||||
|
import { Armor } from '../../../../Armor';
|
||||||
|
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
|
||||||
|
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
|
||||||
import {
|
import {
|
||||||
StaticArmor,
|
StaticArmor,
|
||||||
ThicknessRange,
|
type ThicknessRange,
|
||||||
} from '../../../../../../components/Armor/components/StaticArmor';
|
} from '../../../../Armor/components/StaticArmor';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { AutoClear } from './components/AutoClear';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { Controls } from './components/Control';
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
import { Lighting } from './components/Lighting';
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { ModelLoader } from './components/ModelLoader';
|
||||||
import { useOnScreen } from '../../../../../../hooks/useOnScreen';
|
import { SceneProps } from './components/SceneProps';
|
||||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
import { TankModel } from './components/TankModel';
|
||||||
import * as Duel from '../../../../../../stores/duel';
|
|
||||||
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
|
||||||
import * as TankopediaPersistent from '../../../../../../stores/tankopediaPersistent';
|
|
||||||
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
|
||||||
import { Controls } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Control';
|
|
||||||
import { Lighting } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Lighting';
|
|
||||||
import { SceneProps } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/SceneProps';
|
|
||||||
import { AutoClear } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Model/AutoClear';
|
|
||||||
import { ModelLoader } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Model/components/ModelLoader';
|
|
||||||
import { TankModel } from '../../../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Model/components/TankModel';
|
|
||||||
|
|
||||||
interface TankSandboxProps {
|
interface TankSandboxProps {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
@@ -178,7 +178,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
<Controls />
|
<Controls />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
||||||
|
|
||||||
<ShotDisplay />
|
<ShotDisplay />
|
||||||
<ArmorPlateDisplay />
|
<ArmorPlateDisplay />
|
||||||
<AutoClear />
|
<AutoClear />
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { tankIcon } from '@blitzkit/core';
|
import { tankIcon } from '@blitzkit/core';
|
||||||
import { Flex, FlexProps, Progress } from '@radix-ui/themes';
|
import { Flex, type FlexProps, Progress } from '@radix-ui/themes';
|
||||||
import { useProgress } from '@react-three/drei';
|
import { useProgress } from '@react-three/drei';
|
||||||
|
|
||||||
type TankSandboxLoaderProps = Omit<FlexProps, 'id'> & {
|
type TankSandboxLoaderProps = Omit<FlexProps, 'id'> & {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||||
import { Var } from '../../../core/radix/var';
|
import { Var } from '../../../core/radix/var';
|
||||||
import { useFullScreen } from '../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||||
|
import { Duel } from '../../../stores/duel';
|
||||||
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
||||||
import { classIcons } from '../../ClassIcon';
|
import { classIcons } from '../../ClassIcon';
|
||||||
import { Options } from './components/Options';
|
import { Options } from './components/Options';
|
||||||
@@ -12,14 +13,10 @@ import { TankSandboxLoader } from './components/TankSandboxLoader';
|
|||||||
|
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
|
|
||||||
interface HeroSectionProps {
|
export function HeroSection() {
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeroSection({ id }: HeroSectionProps) {
|
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const protagonist = awaitedTankDefinitions[id];
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
const Icon = classIcons[protagonist.class];
|
const Icon = classIcons[protagonist.class];
|
||||||
const [dummyLoader, setDummyLoader] = useState(true);
|
const [dummyLoader, setDummyLoader] = useState(true);
|
||||||
const treeColor =
|
const treeColor =
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { normalizeAngleRad, PitchLimits, YawLimits } from '@blitzkit/core';
|
import {
|
||||||
|
normalizeAngleRad,
|
||||||
|
type PitchLimits,
|
||||||
|
type YawLimits,
|
||||||
|
} from '@blitzkit/core';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
|
|
||||||
export function applyPitchYawLimits(
|
export function applyPitchYawLimits(
|
||||||
pitch: number, // rad
|
pitch: number, // rad
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { equipmentDefinitions } from '@blitzkit/core';
|
import { equipmentDefinitions } from '@blitzkit/core';
|
||||||
import { EquipmentMatrix } from '../../stores/duel';
|
import type { EquipmentMatrix } from '../../stores/duel';
|
||||||
|
|
||||||
export async function hasEquipment(
|
export async function hasEquipment(
|
||||||
id: number,
|
id: number,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ModelArmor } from '@blitzkit/core';
|
import type { ModelArmor } from '@blitzkit/core';
|
||||||
|
|
||||||
export function resolveArmor(armor: ModelArmor, index: number) {
|
export function resolveArmor(armor: ModelArmor, index: number) {
|
||||||
const spaced = armor.spaced?.includes(index) ?? false;
|
const spaced = armor.spaced?.includes(index) ?? false;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
availableProvisions,
|
availableProvisions,
|
||||||
ProvisionDefinitions,
|
type ProvisionDefinitions,
|
||||||
TankDefinition,
|
type TankDefinition,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { DuelMember } from '../../stores/duel';
|
import type { DuelMember } from '../../stores/duel';
|
||||||
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
|
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
|
||||||
|
|
||||||
const PROVISION_PREFERENCES = [
|
const PROVISION_PREFERENCES = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { equipmentDefinitions } from '@blitzkit/core';
|
import { equipmentDefinitions } from '@blitzkit/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import * as Duel from '../stores/duel';
|
import { Duel } from '../stores/duel';
|
||||||
|
|
||||||
const awaitedEquipmentDefinitions = await equipmentDefinitions;
|
const awaitedEquipmentDefinitions = await equipmentDefinitions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as TankopediaEphemeral from '../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../stores/tankopediaEphemeral';
|
||||||
|
|
||||||
export function useTankModelDefinition() {
|
export function useTankModelDefinition() {
|
||||||
return TankopediaEphemeral.use((state) => state.model);
|
return TankopediaEphemeral.use((state) => state.model);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { I_HAT, J_HAT, K_HAT } from '@blitzkit/core';
|
import { I_HAT, J_HAT, K_HAT } from '@blitzkit/core';
|
||||||
import { type RefObject, useEffect } from 'react';
|
import { type RefObject, useEffect } from 'react';
|
||||||
import { Euler, Group, Vector3 } from 'three';
|
import { Euler, Group, Vector3 } from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import { correctZYTuple } from '../core/blitz/correctZYTuple';
|
import { correctZYTuple } from '../core/blitz/correctZYTuple';
|
||||||
import {
|
import {
|
||||||
ModelTransformEventData,
|
type ModelTransformEventData,
|
||||||
modelTransformEvent,
|
modelTransformEvent,
|
||||||
} from '../core/blitzkit/modelTransform';
|
} from '../core/blitzkit/modelTransform';
|
||||||
import { DuelMember } from '../stores/duel';
|
import type { DuelMember } from '../stores/duel';
|
||||||
import { useTankModelDefinition } from './useTankModelDefinition';
|
import { useTankModelDefinition } from './useTankModelDefinition';
|
||||||
|
|
||||||
export function useTankTransform(
|
export function useTankTransform(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { tankDefinitions } from '@blitzkit/core';
|
import { tankDefinitions } from '@blitzkit/core';
|
||||||
import type { GetStaticPaths } from 'astro';
|
import type { GetStaticPaths } from 'astro';
|
||||||
|
import ToolLayout from '../../../../layouts/ToolLayout.astro';
|
||||||
import { Page } from './index';
|
import { Page } from './index';
|
||||||
|
|
||||||
export const getStaticPaths = (async () => {
|
export const getStaticPaths = (async () => {
|
||||||
@@ -14,4 +15,6 @@ export const getStaticPaths = (async () => {
|
|||||||
const { id } = Astro.params;
|
const { id } = Astro.params;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Page id={id} client:load />
|
<ToolLayout>
|
||||||
|
<Page id={id} client:load />
|
||||||
|
</ToolLayout>
|
||||||
@@ -1,15 +1,37 @@
|
|||||||
|
import {
|
||||||
|
modelDefinitions,
|
||||||
|
provisionDefinitions,
|
||||||
|
tankDefinitions,
|
||||||
|
} from '@blitzkit/core';
|
||||||
import { PageWrapper } from '../../../../components/PageWrapper';
|
import { PageWrapper } from '../../../../components/PageWrapper';
|
||||||
import { HeroSection } from '../../../../components/Tankopedia/HeroSection';
|
import { HeroSection } from '../../../../components/Tankopedia/HeroSection';
|
||||||
|
import { App } from '../../../../stores/app';
|
||||||
|
import { Duel } from '../../../../stores/duel';
|
||||||
|
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||||
|
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
|
const awaitedProvisionDefinitions = await provisionDefinitions;
|
||||||
|
const awaitedModelDefinitions = await modelDefinitions;
|
||||||
|
|
||||||
export function Page({ id }: PageProps) {
|
export function Page({ id }: PageProps) {
|
||||||
|
const tank = awaitedTankDefinitions[id];
|
||||||
|
const model = awaitedModelDefinitions[id];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper p="0" noMaxWidth color="purple" size={1600} gap="9">
|
<TankopediaEphemeral.Provider data={model}>
|
||||||
<HeroSection id={id} />
|
<App.Provider>
|
||||||
{/* <ShotDisplaySection />
|
<TankopediaPersistent.Provider>
|
||||||
|
<Duel.Provider
|
||||||
|
data={{ tank, provisionDefinitions: awaitedProvisionDefinitions }}
|
||||||
|
>
|
||||||
|
<PageWrapper p="0" noMaxWidth color="purple" size={1600} gap="9">
|
||||||
|
<HeroSection />
|
||||||
|
{/* <ShotDisplaySection />
|
||||||
<MetaSection />
|
<MetaSection />
|
||||||
<TechTreeSection />
|
<TechTreeSection />
|
||||||
{!exempt && <AdMidSectionResponsive />}
|
{!exempt && <AdMidSectionResponsive />}
|
||||||
@@ -20,6 +42,10 @@ export function Page({ id }: PageProps) {
|
|||||||
<VideoSection />
|
<VideoSection />
|
||||||
<HistorySection />
|
<HistorySection />
|
||||||
{!exempt && <AdMidSectionResponsive mb="6" />} */}
|
{!exempt && <AdMidSectionResponsive mb="6" />} */}
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
</Duel.Provider>
|
||||||
|
</TankopediaPersistent.Provider>
|
||||||
|
</App.Provider>
|
||||||
|
</TankopediaEphemeral.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EquipmentMatrix } from '.';
|
import type { EquipmentMatrix } from '.';
|
||||||
|
|
||||||
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
|
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
|
||||||
[-1, 1, -1],
|
[-1, 1, -1],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import lodash from 'lodash-es';
|
import * as lodash from 'lodash-es';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { ModelDefinition } from '@blitzkit/core';
|
import type { ModelDefinition } from '@blitzkit/core';
|
||||||
import { Vector3 } from 'three';
|
import type { Vector3 } from 'three';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
import { subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
import type { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
||||||
import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
import type { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
||||||
import type { XP_MULTIPLIERS } from '../components/Tankopedia/TechTreeSection';
|
import type { XP_MULTIPLIERS } from '../components/Tankopedia/TechTreeSection';
|
||||||
import { createContextualStore } from '../core/zustand/createContextualStore';
|
import { createContextualStore } from '../core/zustand/createContextualStore';
|
||||||
@@ -66,19 +66,6 @@ export type ArmorPiercingLayer =
|
|||||||
gap: number;
|
gap: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TankopediaEphemeral = createContextualStore(
|
|
||||||
(model: ModelDefinition) =>
|
|
||||||
create<TankopediaEphemeral>()(
|
|
||||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
|
||||||
editStatic: false,
|
|
||||||
skills: {},
|
|
||||||
model,
|
|
||||||
controlsEnabled: true,
|
|
||||||
xpMultiplier: 1,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
interface TankopediaEphemeral {
|
interface TankopediaEphemeral {
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
@@ -101,3 +88,16 @@ interface TankopediaEphemeral {
|
|||||||
);
|
);
|
||||||
xpMultiplier: (typeof XP_MULTIPLIERS)[number];
|
xpMultiplier: (typeof XP_MULTIPLIERS)[number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TankopediaEphemeral = createContextualStore(
|
||||||
|
(model: ModelDefinition) =>
|
||||||
|
create<TankopediaEphemeral>()(
|
||||||
|
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||||
|
editStatic: false,
|
||||||
|
skills: {},
|
||||||
|
model,
|
||||||
|
controlsEnabled: true,
|
||||||
|
xpMultiplier: 1,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as lodash from 'lodash-es';
|
import * as lodash from 'lodash-es';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { ENVIRONMENTS } from '../../constants/lightingEnvironments';
|
import { ENVIRONMENTS } from '../../constants/lightingEnvironments';
|
||||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
||||||
import { SORT_NAMES, TankopediaDisplay } from './constants';
|
import { SORT_NAMES, TankopediaDisplay } from './constants';
|
||||||
@@ -31,13 +31,13 @@ export interface TankopediaPersistentStore {
|
|||||||
export const TankopediaPersistent = createContextualStore(() =>
|
export const TankopediaPersistent = createContextualStore(() =>
|
||||||
create<TankopediaPersistentStore>()(
|
create<TankopediaPersistentStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
subscribeWithSelector<TankopediaPersistentStore>(() => ({
|
||||||
wireframe: false,
|
wireframe: false,
|
||||||
opaque: false,
|
opaque: false,
|
||||||
environment: ENVIRONMENTS[0],
|
environment: ENVIRONMENTS[0],
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
greenPenetration: true,
|
greenPenetration: false,
|
||||||
showEnvironment: true,
|
showEnvironment: false,
|
||||||
showSpacedArmor: true,
|
showSpacedArmor: true,
|
||||||
showExternalModules: true,
|
showExternalModules: true,
|
||||||
showPrimaryArmor: true,
|
showPrimaryArmor: true,
|
||||||
@@ -46,7 +46,7 @@ export const TankopediaPersistent = createContextualStore(() =>
|
|||||||
direction: 'ascending',
|
direction: 'ascending',
|
||||||
},
|
},
|
||||||
display: TankopediaDisplay.Model,
|
display: TankopediaDisplay.Model,
|
||||||
}),
|
})),
|
||||||
{ name: 'tankopedia', merge: (a, b) => lodash.merge(b, a) },
|
{ name: 'tankopedia', merge: (a, b) => lodash.merge(b, a) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in new issue
Block a user