mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: auto reloader compatibility
This commit is contained in:
1 parent
8033e8f7cc
commit
bdbfce0e18
3 files changed
+49
-123
No files matched your search
+1
-77
@@ -5,18 +5,9 @@ import {
|
|||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from '@blitzkit/i18n';
|
||||||
import {
|
import { Flex, Heading, IconButton, Tooltip } from '@radix-ui/themes';
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
IconButton,
|
|
||||||
Slider,
|
|
||||||
Text,
|
|
||||||
TextField,
|
|
||||||
Tooltip,
|
|
||||||
} from '@radix-ui/themes';
|
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { lerp } from 'three/src/math/MathUtils.js';
|
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||||
@@ -187,73 +178,6 @@ export function Firepower({
|
|||||||
)}
|
)}
|
||||||
<ReloadVisualizer stats={stats} />
|
<ReloadVisualizer stats={stats} />
|
||||||
|
|
||||||
{typeof shell.penetration !== 'number' && (
|
|
||||||
<>
|
|
||||||
<InfoWithDelta
|
|
||||||
stats={stats}
|
|
||||||
indent
|
|
||||||
decimals={0}
|
|
||||||
name={literals(
|
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
|
||||||
.at_distance,
|
|
||||||
[`${penetrationDistance}`],
|
|
||||||
)}
|
|
||||||
noRanking
|
|
||||||
value={() =>
|
|
||||||
lerp(
|
|
||||||
shell.penetration.near,
|
|
||||||
shell.penetration.far,
|
|
||||||
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
|
|
||||||
500,
|
|
||||||
) * penetrationCoefficient
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
|
||||||
<Text color="gray">
|
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
|
||||||
.at_distance_slider_label
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<Slider
|
|
||||||
variant="classic"
|
|
||||||
key={penetrationDistance}
|
|
||||||
min={0}
|
|
||||||
max={500}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
defaultValue={[penetrationDistance]}
|
|
||||||
onValueChange={([value]) => {
|
|
||||||
setPenetrationDistanceDebounced(value);
|
|
||||||
|
|
||||||
if (!penetrationDistanceInput.current) return;
|
|
||||||
|
|
||||||
penetrationDistanceInput.current.value = value.toString();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField.Root
|
|
||||||
variant="classic"
|
|
||||||
style={{ width: 64 }}
|
|
||||||
ref={penetrationDistanceInput}
|
|
||||||
defaultValue={penetrationDistance}
|
|
||||||
onBlur={() => {
|
|
||||||
setPenetrationDistance(
|
|
||||||
Math.min(
|
|
||||||
parseInt(penetrationDistanceInput.current!.value),
|
|
||||||
500,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.currentTarget.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot side="right">m</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isExplosive(shell.type) && (
|
{isExplosive(shell.type) && (
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
stats={stats}
|
stats={stats}
|
||||||
|
|||||||
+48
-44
@@ -24,21 +24,7 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
const totalTime = useRef<HTMLSpanElement>(null);
|
const totalTime = useRef<HTMLSpanElement>(null);
|
||||||
const progressTime = useRef<HTMLSpanElement>(null);
|
const progressTime = useRef<HTMLSpanElement>(null);
|
||||||
|
|
||||||
/**
|
|
||||||
* 3-shell gun example:
|
|
||||||
*
|
|
||||||
* 0-1: clip reload
|
|
||||||
* ~~~: shell shot
|
|
||||||
* 1-2: intra-clip
|
|
||||||
* ~~~: shell shot
|
|
||||||
* 2-3: intra-clip
|
|
||||||
* ~~~: shell shot & reset to 0
|
|
||||||
*
|
|
||||||
* intra-clip time = intraClip * (n - 1)
|
|
||||||
*/
|
|
||||||
const state = useRef(0);
|
const state = useRef(0);
|
||||||
const dpm = useRef(0);
|
|
||||||
const shotPool = useRef(new Set<number>());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancel = false;
|
let cancel = false;
|
||||||
@@ -46,55 +32,73 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
let lastState = state.current;
|
let lastState = state.current;
|
||||||
const shellArray = new Array(stats.shells);
|
const shellArray = new Array(stats.shells);
|
||||||
|
|
||||||
function shoot() {
|
|
||||||
shotPool.current.add(Date.now() / 1000);
|
|
||||||
dpm.current += stats.damage;
|
|
||||||
}
|
|
||||||
|
|
||||||
function frame() {
|
function frame() {
|
||||||
if (!totalTime.current || !progressTime.current) return;
|
if (!totalTime.current || !progressTime.current) return;
|
||||||
|
|
||||||
|
const reloadThreshold = stats.shellReloads
|
||||||
|
? stats.shellReloads.length
|
||||||
|
: 1;
|
||||||
const now = Date.now() / 1000;
|
const now = Date.now() / 1000;
|
||||||
const dt = now - lastT;
|
const dt = now - lastT;
|
||||||
let reload;
|
let reload;
|
||||||
let shells;
|
let shells;
|
||||||
|
|
||||||
shotPool.current.forEach((t) => {
|
if (state.current < reloadThreshold) {
|
||||||
if (now - t > 60) shotPool.current.delete(t);
|
const shellReload =
|
||||||
});
|
reloadThreshold === 1
|
||||||
|
? stats.shellReload!
|
||||||
|
: stats.shellReloads![Math.floor(state.current)];
|
||||||
|
|
||||||
dpm.current = shotPool.current.size * stats.damage;
|
state.current = Math.min(
|
||||||
|
reloadThreshold,
|
||||||
if (state.current < 1) {
|
state.current + dt / shellReload,
|
||||||
state.current = Math.min(1, state.current + dt / stats.shellReload!);
|
);
|
||||||
reload = state.current;
|
reload = state.current % 1;
|
||||||
shells = shellArray.fill(reload);
|
shells = shellArray.fill(reload);
|
||||||
|
|
||||||
totalTime.current.innerHTML = stats.shellReload!.toFixed(PRECISION);
|
if (reloadThreshold > 1) {
|
||||||
progressTime.current.innerHTML = (
|
shells = shells.map((_, index) =>
|
||||||
(1 - reload) *
|
Math.max(0, state.current - shellArray.length + index + 1),
|
||||||
stats.shellReload!
|
);
|
||||||
).toFixed(PRECISION);
|
}
|
||||||
|
|
||||||
if (state.current >= 1) shoot();
|
totalTime.current.innerHTML = shellReload.toFixed(PRECISION);
|
||||||
} else if (state.current < stats.shells) {
|
progressTime.current.innerHTML = ((1 - reload) * shellReload).toFixed(
|
||||||
const interval =
|
PRECISION,
|
||||||
stats.burstShells === 1
|
);
|
||||||
? stats.intraClip!
|
} else if (state.current < stats.shells + reloadThreshold - 1) {
|
||||||
: Math.floor(state.current) % stats.burstShells === 0
|
let interval;
|
||||||
? stats.intraClip!
|
|
||||||
: stats.burstInterShell!;
|
|
||||||
|
|
||||||
state.current = Math.min(stats.shells, state.current + dt / interval);
|
if (
|
||||||
shells = times(stats.shells, (index) => index > state.current - 1);
|
stats.burstShells === 1 ||
|
||||||
|
Math.floor(state.current) % stats.burstShells === 0
|
||||||
|
) {
|
||||||
|
interval = stats.intraClip!;
|
||||||
|
} else {
|
||||||
|
interval = stats.burstInterShell!;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.current = Math.min(
|
||||||
|
stats.shells + reloadThreshold,
|
||||||
|
state.current + dt / interval,
|
||||||
|
);
|
||||||
reload = state.current % 1;
|
reload = state.current % 1;
|
||||||
|
|
||||||
|
if (reloadThreshold > 1) {
|
||||||
|
shells = shellArray.map((_, index) =>
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
Math.floor(index + 1 + reloadThreshold - state.current),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
shells = times(stats.shells, (index) => index > state.current - 1);
|
||||||
|
}
|
||||||
|
|
||||||
totalTime.current.innerHTML = interval.toFixed(PRECISION);
|
totalTime.current.innerHTML = interval.toFixed(PRECISION);
|
||||||
progressTime.current.innerHTML = ((1 - reload) * interval).toFixed(
|
progressTime.current.innerHTML = ((1 - reload) * interval).toFixed(
|
||||||
PRECISION,
|
PRECISION,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastState % 1 > state.current % 1) shoot();
|
|
||||||
} else {
|
} else {
|
||||||
state.current = 0;
|
state.current = 0;
|
||||||
reload = 0;
|
reload = 0;
|
||||||
|
|||||||
-2
@@ -87,8 +87,6 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}, [hasImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer]);
|
}, [hasImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer]);
|
||||||
|
|
||||||
console.log(tank.slug, gun.burst, stats.burstShells);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
gap="8"
|
gap="8"
|
||||||
|
|||||||
Reference in new issue
Block a user