mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: consumable timings
This commit is contained in:
1 parent
8f098d435a
commit
809f807da8
11 files changed
+145
-46
No files matched your search
@@ -254,8 +254,7 @@ interface OptionalDeviceSlots {
|
|||||||
interface OptionalDeviceSlotRow {
|
interface OptionalDeviceSlotRow {
|
||||||
[key: string]: { device0: string; device1: string };
|
[key: string]: { device0: string; device1: string };
|
||||||
}
|
}
|
||||||
|
export interface ConsumablesCommon {
|
||||||
export interface ConsumablesProvisionsCommon {
|
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
id: number;
|
id: number;
|
||||||
userString: string;
|
userString: string;
|
||||||
@@ -270,8 +269,28 @@ export interface ConsumablesProvisionsCommon {
|
|||||||
script: {
|
script: {
|
||||||
'#text': string;
|
'#text': string;
|
||||||
automatic?: boolean;
|
automatic?: boolean;
|
||||||
|
cooldown: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
cooldown?: number;
|
shotEffect?: string;
|
||||||
|
bonusValues?: { [key: string]: number };
|
||||||
|
} & Record<string, string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface ProvisionsCommon {
|
||||||
|
[key: string]: {
|
||||||
|
id: number;
|
||||||
|
userString: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
category: string;
|
||||||
|
tags: string;
|
||||||
|
vehicleFilter?: {
|
||||||
|
include: { vehicle: ConsumablesVehicleFilter; nations?: string };
|
||||||
|
exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string };
|
||||||
|
};
|
||||||
|
script: {
|
||||||
|
'#text': string;
|
||||||
|
automatic?: boolean;
|
||||||
shotEffect?: string;
|
shotEffect?: string;
|
||||||
bonusValues?: { [key: string]: number };
|
bonusValues?: { [key: string]: number };
|
||||||
} & Record<string, string>;
|
} & Record<string, string>;
|
||||||
@@ -337,10 +356,10 @@ export async function definitions(production: boolean) {
|
|||||||
const optionalDeviceSlots = await readXMLDVPL<{
|
const optionalDeviceSlots = await readXMLDVPL<{
|
||||||
root: OptionalDeviceSlots;
|
root: OptionalDeviceSlots;
|
||||||
}>(`${DATA}/${POI.optionalDeviceSlots}.dvpl`);
|
}>(`${DATA}/${POI.optionalDeviceSlots}.dvpl`);
|
||||||
const consumables = await readXMLDVPL<{ root: ConsumablesProvisionsCommon }>(
|
const consumables = await readXMLDVPL<{ root: ConsumablesCommon }>(
|
||||||
`${DATA}/${POI.consumablesCommon}.dvpl`,
|
`${DATA}/${POI.consumablesCommon}.dvpl`,
|
||||||
);
|
);
|
||||||
const provisions = await readXMLDVPL<{ root: ConsumablesProvisionsCommon }>(
|
const provisions = await readXMLDVPL<{ root: ProvisionsCommon }>(
|
||||||
`${DATA}/${POI.provisionsCommon}.dvpl`,
|
`${DATA}/${POI.provisionsCommon}.dvpl`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -793,6 +812,8 @@ export async function definitions(production: boolean) {
|
|||||||
Object.values(consumables.root).forEach((consumable) => {
|
Object.values(consumables.root).forEach((consumable) => {
|
||||||
consumableDefinitions[consumable.id] = {
|
consumableDefinitions[consumable.id] = {
|
||||||
id: consumable.id,
|
id: consumable.id,
|
||||||
|
cooldown: consumable.script.cooldown,
|
||||||
|
duration: consumable.script.duration,
|
||||||
name:
|
name:
|
||||||
strings[consumable.userString] ??
|
strings[consumable.userString] ??
|
||||||
missingStrings[consumable.userString] ??
|
missingStrings[consumable.userString] ??
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { use } from 'react';
|
|||||||
import { ConsumableButton } from '../../../../../../../components/ModuleButtons/ConsumableButton';
|
import { ConsumableButton } from '../../../../../../../components/ModuleButtons/ConsumableButton';
|
||||||
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity';
|
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity';
|
||||||
import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions';
|
import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions';
|
||||||
|
import { useEquipment } from '../../../../../../../core/blitzkrieg/useEquipment';
|
||||||
import { useDuel } from '../../../../../../../stores/duel';
|
import { useDuel } from '../../../../../../../stores/duel';
|
||||||
import {
|
import {
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
@@ -22,6 +23,8 @@ export function Consumables() {
|
|||||||
protagonist.gun,
|
protagonist.gun,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const hasConsumableDeliverySystem = useEquipment(118);
|
||||||
|
const hasHighEndConsumables = useEquipment(101);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigurationChildWrapper>
|
<ConfigurationChildWrapper>
|
||||||
@@ -33,6 +36,14 @@ export function Consumables() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ConsumableButton
|
<ConsumableButton
|
||||||
|
duration={
|
||||||
|
typeof consumable.duration === 'number'
|
||||||
|
? consumable.duration * (hasHighEndConsumables ? 0.7 : 1)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
cooldown={
|
||||||
|
consumable.cooldown * (hasConsumableDeliverySystem ? 0.85 : 1)
|
||||||
|
}
|
||||||
key={consumable.id}
|
key={consumable.id}
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === consumablesList.length - 1}
|
last={index === consumablesList.length - 1}
|
||||||
|
|||||||
+3
-7
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useDelta } from '../../../../../../../hooks/useDelta';
|
||||||
import { Info, InfoProps } from './Info';
|
import { Info, InfoProps } from './Info';
|
||||||
|
|
||||||
interface InfoWithDeltaProps extends InfoProps {
|
interface InfoWithDeltaProps extends InfoProps {
|
||||||
@@ -6,14 +6,10 @@ interface InfoWithDeltaProps extends InfoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function InfoWithDelta({ children, ...props }: InfoWithDeltaProps) {
|
export function InfoWithDelta({ children, ...props }: InfoWithDeltaProps) {
|
||||||
const lastValue = useRef(children);
|
const delta = useDelta(children);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
lastValue.current = children;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Info {...props} delta={children - lastValue.current}>
|
<Info {...props} delta={delta}>
|
||||||
{children}
|
{children}
|
||||||
</Info>
|
</Info>
|
||||||
);
|
);
|
||||||
|
|||||||
+15
-13
@@ -1,4 +1,4 @@
|
|||||||
import { Heading } from '@radix-ui/themes';
|
import { Flex, Heading } from '@radix-ui/themes';
|
||||||
import { CamouflageButton } from '../../../../../../../components/ModuleButtons/CamouflageButton';
|
import { CamouflageButton } from '../../../../../../../components/ModuleButtons/CamouflageButton';
|
||||||
import {
|
import {
|
||||||
mutateTankopediaTemporary,
|
mutateTankopediaTemporary,
|
||||||
@@ -6,23 +6,25 @@ import {
|
|||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||||
|
|
||||||
export function Camouflage() {
|
export function Miscellaneous() {
|
||||||
const camouflage = useTankopediaTemporary((state) => state.camouflage);
|
const camouflage = useTankopediaTemporary((state) => state.camouflage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigurationChildWrapper>
|
<ConfigurationChildWrapper>
|
||||||
<Heading size="4">Camouflage</Heading>
|
<Heading size="4">Miscellaneous</Heading>
|
||||||
|
|
||||||
<CamouflageButton
|
<Flex gap="2">
|
||||||
selected={camouflage}
|
<CamouflageButton
|
||||||
first
|
selected={camouflage}
|
||||||
last
|
first
|
||||||
onClick={() => {
|
last
|
||||||
mutateTankopediaTemporary((draft) => {
|
onClick={() => {
|
||||||
draft.camouflage = !camouflage;
|
mutateTankopediaTemporary((draft) => {
|
||||||
});
|
draft.camouflage = !camouflage;
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
</ConfigurationChildWrapper>
|
</ConfigurationChildWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,10 @@ import { mutateDuel, useDuel } from '../../../../stores/duel';
|
|||||||
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
|
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
|
||||||
import { AntagonistBar } from './components/AntagonistBar';
|
import { AntagonistBar } from './components/AntagonistBar';
|
||||||
import { Characteristics } from './components/Characteristics';
|
import { Characteristics } from './components/Characteristics';
|
||||||
import { Camouflage } from './components/Characteristics/components/Camouflage';
|
|
||||||
import { Consumables } from './components/Characteristics/components/Consumables';
|
import { Consumables } from './components/Characteristics/components/Consumables';
|
||||||
import { Crew } from './components/Characteristics/components/Crew';
|
import { Crew } from './components/Characteristics/components/Crew';
|
||||||
import { Equipments } from './components/Characteristics/components/Equipments';
|
import { Equipments } from './components/Characteristics/components/Equipments';
|
||||||
|
import { Miscellaneous } from './components/Characteristics/components/Miscellaneous';
|
||||||
import { Modules } from './components/Characteristics/components/Modules';
|
import { Modules } from './components/Characteristics/components/Modules';
|
||||||
import { Provisions } from './components/Characteristics/components/Provisions';
|
import { Provisions } from './components/Characteristics/components/Provisions';
|
||||||
import { TankSandbox } from './components/Model/TankSandbox';
|
import { TankSandbox } from './components/Model/TankSandbox';
|
||||||
@@ -119,11 +119,11 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<Flex direction="column" gap="4">
|
<Flex direction="column" gap="4">
|
||||||
<Modules />
|
<Modules />
|
||||||
<Equipments />
|
|
||||||
<Crew />
|
<Crew />
|
||||||
|
<Equipments />
|
||||||
<Consumables />
|
<Consumables />
|
||||||
<Provisions />
|
<Provisions />
|
||||||
<Camouflage />
|
<Miscellaneous />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,62 @@
|
|||||||
|
import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Flex, Text } from '@radix-ui/themes';
|
||||||
import { asset } from '../../core/blitzkrieg/asset';
|
import { asset } from '../../core/blitzkrieg/asset';
|
||||||
|
import { useDelta } from '../../hooks/useDelta';
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||||
import { TankComponentButtonProps } from './TankComponentButton';
|
import { TankComponentButtonProps } from './TankComponentButton';
|
||||||
|
|
||||||
interface ConsumableButtonProps extends TankComponentButtonProps {
|
interface ConsumableButtonProps extends TankComponentButtonProps {
|
||||||
consumable: number;
|
consumable: number;
|
||||||
|
duration?: number;
|
||||||
|
cooldown: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConsumableButton({
|
export function ConsumableButton({
|
||||||
consumable,
|
consumable,
|
||||||
|
cooldown,
|
||||||
|
duration,
|
||||||
...props
|
...props
|
||||||
}: ConsumableButtonProps) {
|
}: ConsumableButtonProps) {
|
||||||
|
const cooldownDelta = useDelta(cooldown);
|
||||||
|
const durationDelta = useDelta(duration ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={asset(`icons/consumables/${consumable}.webp`)}
|
icon={asset(`icons/consumables/${consumable}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
<Flex direction="column" style={{ transform: 'translateX(-10px)' }}>
|
||||||
|
{duration && (
|
||||||
|
<Text
|
||||||
|
color={
|
||||||
|
durationDelta < 0
|
||||||
|
? 'green'
|
||||||
|
: durationDelta > 0
|
||||||
|
? 'tomato'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap="1" style={{ marginBottom: -4 }}>
|
||||||
|
<ClockIcon />
|
||||||
|
{Math.round(duration)}s
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
color={
|
||||||
|
cooldownDelta < 0
|
||||||
|
? 'green'
|
||||||
|
: cooldownDelta > 0
|
||||||
|
? 'tomato'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<ReloadIcon />
|
||||||
|
{Math.round(cooldown)}s
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</GenericTankComponentButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -12,22 +12,35 @@ interface GenericTankComponentButtonProps extends TankComponentButtonProps {
|
|||||||
export function GenericTankComponentButton({
|
export function GenericTankComponentButton({
|
||||||
icon,
|
icon,
|
||||||
iconStyles,
|
iconStyles,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: GenericTankComponentButtonProps) {
|
}: GenericTankComponentButtonProps) {
|
||||||
return (
|
return (
|
||||||
<TankComponentButton disabled={props.disabled} {...props}>
|
<TankComponentButton {...props}>
|
||||||
<img
|
<div
|
||||||
draggable={false}
|
|
||||||
src={icon}
|
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 48,
|
||||||
height: 32,
|
height: 40,
|
||||||
position: 'absolute',
|
position: 'relative',
|
||||||
opacity: props.disabled ? 0.5 : 1,
|
|
||||||
transform: 'scale(0.75)',
|
|
||||||
...iconStyles,
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<img
|
||||||
|
draggable={false}
|
||||||
|
src={icon}
|
||||||
|
style={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
position: 'absolute',
|
||||||
|
opacity: props.disabled ? 0.5 : 1,
|
||||||
|
transform: 'translate(-50%, -50%) scale(0.75)',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
...iconStyles,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{children}
|
||||||
</TankComponentButton>
|
</TankComponentButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,19 +18,17 @@ export function TankComponentButton({
|
|||||||
last = false,
|
last = false,
|
||||||
rowChild,
|
rowChild,
|
||||||
discriminator,
|
discriminator,
|
||||||
disabled,
|
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: TankComponentButtonProps) {
|
}: TankComponentButtonProps) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
|
||||||
radius="medium"
|
radius="medium"
|
||||||
color={selected ? undefined : 'gray'}
|
color={selected ? undefined : 'gray'}
|
||||||
variant={selected ? 'surface' : 'soft'}
|
variant={selected ? 'surface' : 'soft'}
|
||||||
style={{
|
style={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
width: 48,
|
minWidth: 48,
|
||||||
height: 40,
|
height: 40,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
borderTopLeftRadius: first ? undefined : 0,
|
borderTopLeftRadius: first ? undefined : 0,
|
||||||
@@ -39,10 +37,8 @@ export function TankComponentButton({
|
|||||||
borderBottomRightRadius: last ? undefined : 0,
|
borderBottomRightRadius: last ? undefined : 0,
|
||||||
margin: rowChild ? -0.5 : 'unset',
|
margin: rowChild ? -0.5 : 'unset',
|
||||||
}}
|
}}
|
||||||
{...(props as unknown as ComponentProps<typeof Button>)}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
|
||||||
|
|
||||||
{discriminator !== undefined && (
|
{discriminator !== undefined && (
|
||||||
<Text
|
<Text
|
||||||
size="1"
|
size="1"
|
||||||
@@ -59,6 +55,8 @@ export function TankComponentButton({
|
|||||||
{discriminator}
|
{discriminator}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,8 @@ export type TankFilterDefinition =
|
|||||||
export interface ConsumableEntry {
|
export interface ConsumableEntry {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
cooldown: number;
|
||||||
|
duration?: number;
|
||||||
include: TankFilterDefinition[];
|
include: TankFilterDefinition[];
|
||||||
exclude?: TankFilterDefinition[];
|
exclude?: TankFilterDefinition[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,3 +25,5 @@ export interface EquipmentDefinitions {
|
|||||||
export const equipmentDefinitions = fetchCdonLz4<EquipmentDefinitions>(
|
export const equipmentDefinitions = fetchCdonLz4<EquipmentDefinitions>(
|
||||||
asset('definitions/equipment.cdon.lz4'),
|
asset('definitions/equipment.cdon.lz4'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
equipmentDefinitions.then(console.log);
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export function useDelta(value: number) {
|
||||||
|
const lastValue = useRef(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
lastValue.current = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return value - lastValue.current;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user