mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +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%",
|
"percentage": "%s1%",
|
||||||
"ghost_factory": "Ghost Factory",
|
"ghost_factory": "Ghost Factory",
|
||||||
"dead_rail": "Dead Rail"
|
"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 { 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 { Duel } from '../../../../../../../stores/duel';
|
||||||
import { VisualizerCard } from '../VisualizerCard';
|
import { VisualizerCard } from '../VisualizerCard';
|
||||||
|
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||||
|
|
||||||
const ANGLE_COEFFICIENT = 1 / 10;
|
const ANGLE_COEFFICIENT = 1 / 10;
|
||||||
|
|
||||||
@@ -17,6 +28,17 @@ function c(thetaDeg: number, m = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GunFlexibilityVisualizer() {
|
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 tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
@@ -66,7 +88,6 @@ export function GunFlexibilityVisualizer() {
|
|||||||
} else if (b) {
|
} else if (b) {
|
||||||
d += `A ${mo} ${mo} 0 0 0 ${c(-90 - b.range / 2 - t, mo)}`;
|
d += `A ${mo} ${mo} 0 0 0 ${c(-90 - b.range / 2 - t, mo)}`;
|
||||||
} else {
|
} else {
|
||||||
console.log('this should log 0');
|
|
||||||
d += `A ${mo} ${mo} 0 0 0 ${c(-90, mo)}`;
|
d += `A ${mo} ${mo} 0 0 0 ${c(-90, mo)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +146,76 @@ export function GunFlexibilityVisualizer() {
|
|||||||
d += `Z`;
|
d += `Z`;
|
||||||
|
|
||||||
return (
|
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
|
<svg
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -138,15 +228,15 @@ export function GunFlexibilityVisualizer() {
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
fill="var(--gray-5)"
|
fill="var(--gray-6)"
|
||||||
stroke="var(--gray-7)"
|
stroke="var(--gray-8)"
|
||||||
strokeWidth="1px"
|
strokeWidth="1px"
|
||||||
vectorEffect="non-scaling-stroke"
|
vectorEffect="non-scaling-stroke"
|
||||||
d={d}
|
d={d}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
stroke="var(--gray-10)"
|
stroke="var(--gray-11)"
|
||||||
fill="transparent"
|
fill="transparent"
|
||||||
strokeWidth="1px"
|
strokeWidth="1px"
|
||||||
strokeDasharray="0.5rem 0.5rem"
|
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"
|
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>
|
</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>
|
</VisualizerCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+47
-80
@@ -1,6 +1,6 @@
|
|||||||
import { asset, isExplosive } from '@blitzkit/core';
|
import { asset, isExplosive } from '@blitzkit/core';
|
||||||
import { literals } from '@blitzkit/i18n';
|
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 { useRef, useState } from 'react';
|
||||||
import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||||
import { Var } from '../../../../../../../core/radix/var';
|
import { Var } from '../../../../../../../core/radix/var';
|
||||||
@@ -8,6 +8,7 @@ import { useLocale } from '../../../../../../../hooks/useLocale';
|
|||||||
import { Duel } from '../../../../../../../stores/duel';
|
import { Duel } from '../../../../../../../stores/duel';
|
||||||
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
||||||
import { VisualizerCard } from '../VisualizerCard';
|
import { VisualizerCard } from '../VisualizerCard';
|
||||||
|
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||||
import { ShellSwitcher } from './components/ShellSwitcher';
|
import { ShellSwitcher } from './components/ShellSwitcher';
|
||||||
|
|
||||||
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||||
@@ -157,85 +158,51 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Flex
|
<VisualizerCornerStat
|
||||||
style={{ userSelect: 'none' }}
|
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||||
gap="4"
|
value={literals(strings.common.units.deg, [
|
||||||
position="absolute"
|
radToDeg(Math.abs(angle)).toFixed(0),
|
||||||
top="0"
|
])}
|
||||||
left="0"
|
side="top-left"
|
||||||
p="3"
|
/>
|
||||||
width="100%"
|
<VisualizerCornerStat
|
||||||
justify="between"
|
label={
|
||||||
>
|
strings.website.tools.tankopedia.visualizers.ricochet.normalization
|
||||||
<Flex gap="2">
|
}
|
||||||
<Text color="gray" size="1">
|
value={
|
||||||
{strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
doesRicochet
|
||||||
</Text>
|
? '-'
|
||||||
<Code size="1" variant="ghost">
|
: literals(strings.common.units.deg, [
|
||||||
{literals(strings.common.units.deg, [
|
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
|
||||||
radToDeg(Math.abs(angle)).toFixed(0),
|
])
|
||||||
])}
|
}
|
||||||
</Code>
|
side="top-right"
|
||||||
</Flex>
|
/>
|
||||||
|
<VisualizerCornerStat
|
||||||
<Flex gap="2">
|
label={
|
||||||
<Text color="gray" size="1">
|
strings.website.tools.tankopedia.visualizers.ricochet
|
||||||
{
|
.effective_angle
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet
|
}
|
||||||
.normalization
|
value={
|
||||||
}
|
doesRicochet
|
||||||
</Text>
|
? '-'
|
||||||
<Code size="1" variant="ghost">
|
: literals(strings.common.units.deg, [
|
||||||
{doesRicochet
|
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||||
? '-'
|
])
|
||||||
: literals(strings.common.units.deg, [
|
}
|
||||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(
|
side="bottom-left"
|
||||||
0,
|
/>
|
||||||
),
|
<VisualizerCornerStat
|
||||||
])}
|
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||||
</Code>
|
value={
|
||||||
</Flex>
|
doesRicochet
|
||||||
</Flex>
|
? '-'
|
||||||
|
: literals(strings.common.units.percentage, [
|
||||||
<Flex
|
armorPercentage.toFixed(0),
|
||||||
style={{ userSelect: 'none' }}
|
])
|
||||||
gap="4"
|
}
|
||||||
position="absolute"
|
side="bottom-right"
|
||||||
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>
|
|
||||||
|
|
||||||
{explodes && (
|
{explodes && (
|
||||||
<Flex
|
<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);
|
newPitch = clamp(newPitch, lowerPitch, upperPitch);
|
||||||
|
|
||||||
return [newPitch, newYaw];
|
return [newPitch, newYaw] as const;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import type { EquipmentMatrix } from '../../stores/duel';
|
import type { EquipmentMatrix } from '../../stores/duel';
|
||||||
import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions';
|
import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions';
|
||||||
|
|
||||||
export async function hasEquipment(
|
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||||
|
|
||||||
|
export function hasEquipment(
|
||||||
id: number,
|
id: number,
|
||||||
equipmentPreset: string,
|
equipmentPreset: string,
|
||||||
equipmentMatrix: EquipmentMatrix,
|
equipmentMatrix: EquipmentMatrix,
|
||||||
) {
|
) {
|
||||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
|
||||||
const preset = equipmentDefinitions.presets[equipmentPreset];
|
const preset = equipmentDefinitions.presets[equipmentPreset];
|
||||||
return preset.slots.some((slot, index) => {
|
return preset.slots.some((slot, index) => {
|
||||||
const row = Math.floor(index / 3);
|
const row = Math.floor(index / 3);
|
||||||
|
|||||||
Reference in new issue
Block a user