mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: cursor
This commit is contained in:
1 parent
21130b29f8
commit
40f5042896
6 files changed
+230
-89
No files matched your search
@@ -913,6 +913,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"depression": "Depression",
|
||||
"elevation": "Elevation"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+144
-6
@@ -1,7 +1,18 @@
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Box } from '@radix-ui/themes';
|
||||
import { useRef, useState } from 'react';
|
||||
import { radToDeg } from 'three/src/math/MathUtils.js';
|
||||
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
|
||||
import { DEFAULT_PITCH_TRANSITION } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import {
|
||||
applyPitchYawLimits,
|
||||
DEFAULT_PITCH_TRANSITION,
|
||||
} from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../../stores/duel';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||
|
||||
const ANGLE_COEFFICIENT = 1 / 10;
|
||||
|
||||
@@ -17,6 +28,17 @@ function c(thetaDeg: number, m = 1) {
|
||||
}
|
||||
|
||||
export function GunFlexibilityVisualizer() {
|
||||
const { strings } = useLocale();
|
||||
|
||||
const [yaw, setYaw] = useState(0);
|
||||
const [minPitch, setMinPitch] = useState(0);
|
||||
const [maxPitch, setMaxPitch] = useState(0);
|
||||
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const highlighter = useRef<HTMLDivElement>(null);
|
||||
const pointer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const duelStore = Duel.useStore();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
@@ -66,7 +88,6 @@ export function GunFlexibilityVisualizer() {
|
||||
} else if (b) {
|
||||
d += `A ${mo} ${mo} 0 0 0 ${c(-90 - b.range / 2 - t, mo)}`;
|
||||
} else {
|
||||
console.log('this should log 0');
|
||||
d += `A ${mo} ${mo} 0 0 0 ${c(-90, mo)}`;
|
||||
}
|
||||
|
||||
@@ -125,7 +146,76 @@ export function GunFlexibilityVisualizer() {
|
||||
d += `Z`;
|
||||
|
||||
return (
|
||||
<VisualizerCard>
|
||||
<VisualizerCard
|
||||
ref={container}
|
||||
onPointerMove={(event) => {
|
||||
if (!container.current || !highlighter.current || !pointer.current)
|
||||
return;
|
||||
|
||||
const rect = container.current.getBoundingClientRect();
|
||||
const u = event.clientX - rect.left - rect.width / 2;
|
||||
const v = event.clientY - rect.top - rect.height / 2;
|
||||
const yaw = Math.atan2(u, -v);
|
||||
|
||||
const {
|
||||
equipmentMatrix,
|
||||
tank: { equipment_preset },
|
||||
} = duelStore.getState().protagonist;
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
equipment_preset,
|
||||
equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
equipment_preset,
|
||||
equipmentMatrix,
|
||||
);
|
||||
|
||||
const min = applyPitchYawLimits(
|
||||
-Math.PI,
|
||||
yaw,
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
const max = applyPitchYawLimits(
|
||||
Math.PI,
|
||||
yaw,
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
setYaw(min[1]);
|
||||
setMinPitch(min[0]);
|
||||
setMaxPitch(max[0]);
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={highlighter}
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
borderRadius: Var('radius-2'),
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
opacity: 2 ** -3,
|
||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<svg
|
||||
style={{
|
||||
position: 'absolute',
|
||||
@@ -138,15 +228,15 @@ export function GunFlexibilityVisualizer() {
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-7)"
|
||||
fill="var(--gray-6)"
|
||||
stroke="var(--gray-8)"
|
||||
strokeWidth="1px"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
d={d}
|
||||
/>
|
||||
|
||||
<path
|
||||
stroke="var(--gray-10)"
|
||||
stroke="var(--gray-11)"
|
||||
fill="transparent"
|
||||
strokeWidth="1px"
|
||||
strokeDasharray="0.5rem 0.5rem"
|
||||
@@ -154,6 +244,54 @@ export function GunFlexibilityVisualizer() {
|
||||
d="M 0.5 0 A 0.5 0.5 0 1 1 -0.5 0 A 0.5 0.5 0 1 1 0.5 0 Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<Box
|
||||
ref={pointer}
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
width="0"
|
||||
height="0"
|
||||
style={{
|
||||
transform: `translate(-50%, -50%) rotate(${yaw + Math.PI}rad)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
width="1pt"
|
||||
height="14rem"
|
||||
style={{
|
||||
borderRadius: Var('radius-1'),
|
||||
background: `linear-gradient(${Var('gray-a6')}, ${Var('gray-a11')})`,
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={
|
||||
strings.website.tools.tankopedia.visualizers.flexibility.elevation
|
||||
}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(maxPitch).toFixed(0),
|
||||
])}
|
||||
side="top-left"
|
||||
/>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={
|
||||
strings.website.tools.tankopedia.visualizers.flexibility.depression
|
||||
}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(-minPitch).toFixed(0),
|
||||
])}
|
||||
side="top-right"
|
||||
/>
|
||||
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.flexibility.yaw}
|
||||
value={literals(strings.common.units.deg, [radToDeg(yaw).toFixed(0)])}
|
||||
side="bottom-left"
|
||||
/>
|
||||
</VisualizerCard>
|
||||
);
|
||||
}
|
||||
+47
-80
@@ -1,6 +1,6 @@
|
||||
import { asset, isExplosive } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Box, Code, Flex, Text } from '@radix-ui/themes';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { useRef, useState } from 'react';
|
||||
import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
@@ -8,6 +8,7 @@ import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../../stores/duel';
|
||||
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||
import { ShellSwitcher } from './components/ShellSwitcher';
|
||||
|
||||
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
@@ -157,85 +158,51 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Flex
|
||||
style={{ userSelect: 'none' }}
|
||||
gap="4"
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
p="3"
|
||||
width="100%"
|
||||
justify="between"
|
||||
>
|
||||
<Flex gap="2">
|
||||
<Text color="gray" size="1">
|
||||
{strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(angle)).toFixed(0),
|
||||
])}
|
||||
</Code>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2">
|
||||
<Text color="gray" size="1">
|
||||
{
|
||||
strings.website.tools.tankopedia.visualizers.ricochet
|
||||
.normalization
|
||||
}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(
|
||||
0,
|
||||
),
|
||||
])}
|
||||
</Code>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
style={{ userSelect: 'none' }}
|
||||
gap="4"
|
||||
position="absolute"
|
||||
bottom="0"
|
||||
left="0"
|
||||
p="3"
|
||||
width="100%"
|
||||
justify="between"
|
||||
>
|
||||
<Flex gap="2">
|
||||
<Text color="gray" size="1">
|
||||
{
|
||||
strings.website.tools.tankopedia.visualizers.ricochet
|
||||
.effective_angle
|
||||
}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||
])}
|
||||
</Code>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2">
|
||||
<Text color="gray" size="1">
|
||||
{strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.percentage, [
|
||||
armorPercentage.toFixed(0),
|
||||
])}
|
||||
</Code>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||
value={literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(angle)).toFixed(0),
|
||||
])}
|
||||
side="top-left"
|
||||
/>
|
||||
<VisualizerCornerStat
|
||||
label={
|
||||
strings.website.tools.tankopedia.visualizers.ricochet.normalization
|
||||
}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
|
||||
])
|
||||
}
|
||||
side="top-right"
|
||||
/>
|
||||
<VisualizerCornerStat
|
||||
label={
|
||||
strings.website.tools.tankopedia.visualizers.ricochet
|
||||
.effective_angle
|
||||
}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.deg, [
|
||||
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||
])
|
||||
}
|
||||
side="bottom-left"
|
||||
/>
|
||||
<VisualizerCornerStat
|
||||
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||
value={
|
||||
doesRicochet
|
||||
? '-'
|
||||
: literals(strings.common.units.percentage, [
|
||||
armorPercentage.toFixed(0),
|
||||
])
|
||||
}
|
||||
side="bottom-right"
|
||||
/>
|
||||
|
||||
{explodes && (
|
||||
<Flex
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Code, Flex, Text } from '@radix-ui/themes';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value: string;
|
||||
side: `${'top' | 'bottom'}-${'left' | 'right'}`;
|
||||
}
|
||||
|
||||
export function VisualizerCornerStat({ label, value, side }: Props) {
|
||||
return (
|
||||
<Flex
|
||||
gap="2"
|
||||
position="absolute"
|
||||
top={side === 'top-left' || side === 'top-right' ? '3' : undefined}
|
||||
bottom={
|
||||
side === 'bottom-left' || side === 'bottom-right' ? '3' : undefined
|
||||
}
|
||||
left={side === 'top-left' || side === 'bottom-left' ? '3' : undefined}
|
||||
right={side === 'top-right' || side === 'bottom-right' ? '3' : undefined}
|
||||
>
|
||||
<Text color="gray" size="1">
|
||||
{label}
|
||||
</Text>
|
||||
<Code size="1" variant="ghost">
|
||||
{value}
|
||||
</Code>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -86,5 +86,5 @@ export function applyPitchYawLimits(
|
||||
|
||||
newPitch = clamp(newPitch, lowerPitch, upperPitch);
|
||||
|
||||
return [newPitch, newYaw];
|
||||
return [newPitch, newYaw] as const;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { EquipmentMatrix } from '../../stores/duel';
|
||||
import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions';
|
||||
|
||||
export async function hasEquipment(
|
||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||
|
||||
export function hasEquipment(
|
||||
id: number,
|
||||
equipmentPreset: string,
|
||||
equipmentMatrix: EquipmentMatrix,
|
||||
) {
|
||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||
const preset = equipmentDefinitions.presets[equipmentPreset];
|
||||
return preset.slots.some((slot, index) => {
|
||||
const row = Math.floor(index / 3);
|
||||
|
||||
Reference in new issue
Block a user