feat: burst compatiblity

This commit is contained in:
TrèsAbhi committed 2025-08-21 20:22:12 -05:00
1 parent 339663adaf
commit 8033e8f7cc
8 files changed
+78 -50

No files matched your search

@@ -136,7 +136,6 @@ message GunDefinitionAutoLoader {
required float clip_reload = 1; required float clip_reload = 1;
required float intra_clip = 2; required float intra_clip = 2;
required float shell_count = 3; required float shell_count = 3;
optional Burst burst = 4;
} }
message Burst { message Burst {
+1 -21
View File
@@ -408,7 +408,6 @@ export interface GunDefinitionAutoLoader {
clip_reload: number; clip_reload: number;
intra_clip: number; intra_clip: number;
shell_count: number; shell_count: number;
burst?: Burst;
} }
export interface Burst { export interface Burst {
@@ -3023,7 +3022,7 @@ export const GunDefinitionRegular: MessageFns<GunDefinitionRegular> = {
}; };
function createBaseGunDefinitionAutoLoader(): GunDefinitionAutoLoader { function createBaseGunDefinitionAutoLoader(): GunDefinitionAutoLoader {
return { clip_reload: 0, intra_clip: 0, shell_count: 0, burst: undefined }; return { clip_reload: 0, intra_clip: 0, shell_count: 0 };
} }
export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = { export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
@@ -3040,9 +3039,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
if (message.shell_count !== 0) { if (message.shell_count !== 0) {
writer.uint32(29).float(message.shell_count); writer.uint32(29).float(message.shell_count);
} }
if (message.burst !== undefined) {
Burst.encode(message.burst, writer.uint32(34).fork()).join();
}
return writer; return writer;
}, },
@@ -3081,14 +3077,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
message.shell_count = reader.float(); message.shell_count = reader.float();
continue; continue;
} }
case 4: {
if (tag !== 34) {
break;
}
message.burst = Burst.decode(reader, reader.uint32());
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@@ -3109,7 +3097,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
shell_count: isSet(object.shell_count) shell_count: isSet(object.shell_count)
? globalThis.Number(object.shell_count) ? globalThis.Number(object.shell_count)
: 0, : 0,
burst: isSet(object.burst) ? Burst.fromJSON(object.burst) : undefined,
}; };
}, },
@@ -3124,9 +3111,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
if (message.shell_count !== 0) { if (message.shell_count !== 0) {
obj.shell_count = message.shell_count; obj.shell_count = message.shell_count;
} }
if (message.burst !== undefined) {
obj.burst = Burst.toJSON(message.burst);
}
return obj; return obj;
}, },
@@ -3142,10 +3126,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
message.clip_reload = object.clip_reload ?? 0; message.clip_reload = object.clip_reload ?? 0;
message.intra_clip = object.intra_clip ?? 0; message.intra_clip = object.intra_clip ?? 0;
message.shell_count = object.shell_count ?? 0; message.shell_count = object.shell_count ?? 0;
message.burst =
object.burst !== undefined && object.burst !== null
? Burst.fromPartial(object.burst)
: undefined;
return message; return message;
}, },
}; };
+3 -1
View File
@@ -1155,7 +1155,9 @@
"shellRange": "Shell range", "shellRange": "Shell range",
"shellCapacity": "Shell capacity", "shellCapacity": "Shell capacity",
"gunType": "Gun type", "gunType": "Gun type",
"penetrationAt250m": "Penetration at 250m" "penetrationAt250m": "Penetration at 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
} }
}, },
@@ -1324,7 +1324,13 @@ export async function definitions() {
unlocks: resolveUnlocks(gun.unlocks), unlocks: resolveUnlocks(gun.unlocks),
shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo,
assault_ranges, assault_ranges,
burst:
gun.burst && gun.burst.count > 1
? {
count: gun.burst.count,
interval: 60 / gun.burst.rate,
}
: undefined,
gun_type: gun_type:
gunType === 'regular' gunType === 'regular'
? { ? {
@@ -1350,13 +1356,6 @@ export async function definitions() {
intra_clip: 60 / gun.clip!.rate, intra_clip: 60 / gun.clip!.rate,
clip_reload: gun.reloadTime, clip_reload: gun.reloadTime,
shell_count: gunClipCount, shell_count: gunClipCount,
burst:
gun.burst && gun.burst.count > 1
? {
count: gun.burst.count,
interval: 60 / gun.burst.rate,
}
: undefined,
}, },
}, },
} satisfies GunDefinition); } satisfies GunDefinition);
@@ -41,7 +41,9 @@ export function Firepower({
const setPenetrationDistanceDebounced = debounce((value: number) => { const setPenetrationDistanceDebounced = debounce((value: number) => {
setPenetrationDistance(value); setPenetrationDistance(value);
}, 500); }, 500);
const { gun, shell, turret } = Duel.use((state) => state.protagonist); const turret = Duel.use((state) => state.protagonist.turret);
const gun = Duel.use((state) => state.protagonist.gun);
const shell = Duel.use((state) => state.protagonist.shell);
const penetrationDistanceInput = useRef<HTMLInputElement>(null); const penetrationDistanceInput = useRef<HTMLInputElement>(null);
const hasSupercharger = useEquipment(107); const hasSupercharger = useEquipment(107);
const hasCalibratedShells = useEquipment(103); const hasCalibratedShells = useEquipment(103);
@@ -1,5 +1,4 @@
import { type Datum, type Serie } from '@nivo/line'; import { Box, Code } from '@radix-ui/themes';
import { Box } from '@radix-ui/themes';
import { times } from 'lodash-es'; import { times } from 'lodash-es';
import { Quicklime, type QuicklimeCallback } from 'quicklime'; import { Quicklime, type QuicklimeCallback } from 'quicklime';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
@@ -9,6 +8,8 @@ import { VisualizerCard } from '../VisualizerCard';
import { Shell } from './components/Shell'; import { Shell } from './components/Shell';
import { Target } from './components/Target'; import { Target } from './components/Target';
const PRECISION = 2;
export interface ReloadUpdateData { export interface ReloadUpdateData {
reload: number; reload: number;
shells: number[]; shells: number[];
@@ -16,12 +17,12 @@ export interface ReloadUpdateData {
export const reloadUpdate = new Quicklime<ReloadUpdateData>(); export const reloadUpdate = new Quicklime<ReloadUpdateData>();
const T = 60;
export function ReloadVisualizer({ stats }: StatsAcceptorProps) { export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
const reloadCircle = useRef<HTMLDivElement>(null); const reloadCircle = useRef<HTMLDivElement>(null);
const reloadCore = useRef<HTMLDivElement>(null); const reloadCore = useRef<HTMLDivElement>(null);
const reloadGlow = useRef<HTMLDivElement>(null); const reloadGlow = useRef<HTMLDivElement>(null);
const totalTime = useRef<HTMLSpanElement>(null);
const progressTime = useRef<HTMLSpanElement>(null);
/** /**
* 3-shell gun example: * 3-shell gun example:
@@ -38,7 +39,6 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
const state = useRef(0); const state = useRef(0);
const dpm = useRef(0); const dpm = useRef(0);
const shotPool = useRef(new Set<number>()); const shotPool = useRef(new Set<number>());
const data = useRef<Serie[]>([{ id: 'value', data: [] }]);
useEffect(() => { useEffect(() => {
let cancel = false; let cancel = false;
@@ -52,6 +52,8 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
} }
function frame() { function frame() {
if (!totalTime.current || !progressTime.current) return;
const now = Date.now() / 1000; const now = Date.now() / 1000;
const dt = now - lastT; const dt = now - lastT;
let reload; let reload;
@@ -68,15 +70,30 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
reload = state.current; reload = state.current;
shells = shellArray.fill(reload); shells = shellArray.fill(reload);
totalTime.current.innerHTML = stats.shellReload!.toFixed(PRECISION);
progressTime.current.innerHTML = (
(1 - reload) *
stats.shellReload!
).toFixed(PRECISION);
if (state.current >= 1) shoot(); if (state.current >= 1) shoot();
} else if (state.current < stats.shells) { } else if (state.current < stats.shells) {
state.current = Math.min( const interval =
stats.shells, stats.burstShells === 1
state.current + dt / stats.intraClip!, ? stats.intraClip!
); : Math.floor(state.current) % stats.burstShells === 0
? stats.intraClip!
: stats.burstInterShell!;
state.current = Math.min(stats.shells, state.current + dt / interval);
shells = times(stats.shells, (index) => index > state.current - 1); shells = times(stats.shells, (index) => index > state.current - 1);
reload = state.current % 1; reload = state.current % 1;
totalTime.current.innerHTML = interval.toFixed(PRECISION);
progressTime.current.innerHTML = ((1 - reload) * interval).toFixed(
PRECISION,
);
if (lastState % 1 > state.current % 1) shoot(); if (lastState % 1 > state.current % 1) shoot();
} else { } else {
state.current = 0; state.current = 0;
@@ -90,15 +107,6 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
lastT = now; lastT = now;
lastState = state.current; lastState = state.current;
(data.current[0].data as Datum[]).push({
x: now,
y: dpm.current / (stats.dpm + stats.damage),
});
(data.current[0].data as Datum[]).splice(
0,
data.current[0].data.findIndex((d) => now - (d.x as number) < T),
);
} }
frame(); frame();
@@ -216,6 +224,38 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
}} }}
/> />
<Code
ref={progressTime}
size="1"
variant="ghost"
weight="bold"
color="gray"
highContrast
style={{
position: 'absolute',
top: '50%',
right: 'calc(50% + var(--space-8))',
transform: 'translateX(50%)',
}}
>
6.9s
</Code>
<Code
ref={totalTime}
size="1"
variant="ghost"
weight="bold"
color="gray"
style={{
position: 'absolute',
top: '50%',
left: 'calc(50% + var(--space-8))',
transform: 'translateX(-50%)',
}}
>
10.0s
</Code>
{stats.shells > 1 && {stats.shells > 1 &&
times(stats.shells, (index) => ( times(stats.shells, (index) => (
<Shell stats={stats} index={index} key={index} /> <Shell stats={stats} index={index} key={index} />
@@ -87,6 +87,8 @@ 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"
@@ -151,6 +151,8 @@ export function tankCharacteristics(
commanderMastery * commanderMastery *
(tank.crew.some(({ type }) => type === CrewType.DRIVER) ? 1.1 : 1.05); (tank.crew.some(({ type }) => type === CrewType.DRIVER) ? 1.1 : 1.05);
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]); const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
const burstShells = gun.burst?.count ?? 1;
const burstInterShell = gun.burst?.interval;
const armorDamageCoefficient = const armorDamageCoefficient =
coefficient([hasTungsten, 0.15]) * coefficient([hasTungsten, 0.15]) *
coefficient( coefficient(
@@ -581,6 +583,8 @@ export function tankCharacteristics(
shellCapacity, shellCapacity,
gunType, gunType,
penetrationAt250m, penetrationAt250m,
burstShells,
burstInterShell,
}; };
} }