mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: consumable definitions
This commit is contained in:
1 parent
3e3ed35fb0
commit
d4e8ec125f
6 files changed
+247
-90
No files matched your search
Vendored
+3
@@ -11,11 +11,14 @@
|
|||||||
"bepaint",
|
"bepaint",
|
||||||
"BINORMAL",
|
"BINORMAL",
|
||||||
"Binormals",
|
"Binormals",
|
||||||
|
"bkwd",
|
||||||
"blitzstars",
|
"blitzstars",
|
||||||
"Camo",
|
"Camo",
|
||||||
"classname",
|
"classname",
|
||||||
"cleandist",
|
"cleandist",
|
||||||
"cockroackdb",
|
"cockroackdb",
|
||||||
|
"Coef",
|
||||||
|
"cooldown",
|
||||||
"corsproxy",
|
"corsproxy",
|
||||||
"CUBEMAP",
|
"CUBEMAP",
|
||||||
"CUBETEXCOORD",
|
"CUBETEXCOORD",
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ export const POI = {
|
|||||||
'UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml',
|
'UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml',
|
||||||
optionalDevicesImage:
|
optionalDevicesImage:
|
||||||
'Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp',
|
'Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp',
|
||||||
|
consumables: 'XML/item_defs/vehicles/common/consumables/common.xml',
|
||||||
} as const;
|
} as const;
|
||||||
@@ -6,6 +6,7 @@ import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
|
|||||||
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
|
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
|
||||||
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
|
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
|
||||||
import commitMultipleFiles from '../../src/core/blitzkrieg/commitMultipleFiles';
|
import commitMultipleFiles from '../../src/core/blitzkrieg/commitMultipleFiles';
|
||||||
|
import { ConsumableDefinitions } from '../../src/core/blitzkrieg/consumablesDefinitions';
|
||||||
import {
|
import {
|
||||||
EquipmentDefinitions,
|
EquipmentDefinitions,
|
||||||
EquipmentRow,
|
EquipmentRow,
|
||||||
@@ -182,6 +183,41 @@ interface OptionalDeviceSlotRow {
|
|||||||
[key: string]: { device0: string; device1: string };
|
[key: string]: { device0: string; device1: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Consumables {
|
||||||
|
[key: string]: {
|
||||||
|
id: number;
|
||||||
|
userString: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
category: string;
|
||||||
|
tags: string;
|
||||||
|
vehicleFilter: {
|
||||||
|
include: { vehicle: ConsumablesFilter };
|
||||||
|
exclude?: { vehicle: ConsumablesFilter };
|
||||||
|
};
|
||||||
|
script: {
|
||||||
|
'#text': string;
|
||||||
|
automatic?: boolean;
|
||||||
|
duration?: number;
|
||||||
|
cooldown?: number;
|
||||||
|
shotEffect?: string;
|
||||||
|
bonusValues?: { [key: string]: number };
|
||||||
|
} & Record<string, string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConsumablesFilter =
|
||||||
|
| { minLevel: number; maxLevel: number }
|
||||||
|
| { name: string }
|
||||||
|
| { extendedTags: string };
|
||||||
|
|
||||||
|
const consumableEffectSuffixes = [
|
||||||
|
'Increase',
|
||||||
|
'Factor',
|
||||||
|
'Bias',
|
||||||
|
'Coef',
|
||||||
|
'Percent',
|
||||||
|
];
|
||||||
const blitzTankTypeToBlitzkrieg: Record<BlitzTankType, TankType> = {
|
const blitzTankTypeToBlitzkrieg: Record<BlitzTankType, TankType> = {
|
||||||
'AT-SPG': 'tank_destroyer',
|
'AT-SPG': 'tank_destroyer',
|
||||||
lightTank: 'light',
|
lightTank: 'light',
|
||||||
@@ -196,6 +232,10 @@ const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = {
|
|||||||
};
|
};
|
||||||
const LANGUAGE = 'en';
|
const LANGUAGE = 'en';
|
||||||
|
|
||||||
|
const missingStrings: Record<string, string> = {
|
||||||
|
'#artefacts:tungstentip/name': 'Tungsten Shells',
|
||||||
|
};
|
||||||
|
|
||||||
export async function buildDefinitions() {
|
export async function buildDefinitions() {
|
||||||
console.log('Building tank definitions...');
|
console.log('Building tank definitions...');
|
||||||
|
|
||||||
@@ -205,9 +245,11 @@ export async function buildDefinitions() {
|
|||||||
presets: {},
|
presets: {},
|
||||||
equipments: {},
|
equipments: {},
|
||||||
};
|
};
|
||||||
|
const consumableDefinitions: ConsumableDefinitions = {};
|
||||||
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
|
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
|
||||||
(nations) => nations.filter((nation) => nation !== 'common'),
|
(nations) => nations.filter((nation) => nation !== 'common'),
|
||||||
);
|
);
|
||||||
|
const tankStringIdMap: Record<string, number> = {};
|
||||||
const strings = await readYAMLDVPL<Strings>(
|
const strings = await readYAMLDVPL<Strings>(
|
||||||
`${DATA}/${POI.strings}/${LANGUAGE}.yaml.dvpl`,
|
`${DATA}/${POI.strings}/${LANGUAGE}.yaml.dvpl`,
|
||||||
);
|
);
|
||||||
@@ -217,6 +259,9 @@ export async function buildDefinitions() {
|
|||||||
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: Consumables }>(
|
||||||
|
`${DATA}/${POI.consumables}.dvpl`,
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
nations.map(async (nation) => {
|
nations.map(async (nation) => {
|
||||||
@@ -257,6 +302,7 @@ export async function buildDefinitions() {
|
|||||||
const trackArmorRaw = Object.values(tankDefinition.root.chassis).at(-1)!
|
const trackArmorRaw = Object.values(tankDefinition.root.chassis).at(-1)!
|
||||||
.armor.leftTrack;
|
.armor.leftTrack;
|
||||||
const equipment = tankDefinition.root.optDevicePreset;
|
const equipment = tankDefinition.root.optDevicePreset;
|
||||||
|
tankStringIdMap[`${nation}:${tankKey}`] = tankId;
|
||||||
|
|
||||||
Object.keys(tankDefinition.root.hull.armor)
|
Object.keys(tankDefinition.root.hull.armor)
|
||||||
.filter((name) => name.startsWith('armor_'))
|
.filter((name) => name.startsWith('armor_'))
|
||||||
@@ -563,6 +609,67 @@ export async function buildDefinitions() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Object.values(consumables.root).forEach((consumable) => {
|
||||||
|
consumableDefinitions[consumable.id] = {
|
||||||
|
id: consumable.id,
|
||||||
|
name:
|
||||||
|
strings[consumable.userString] ??
|
||||||
|
missingStrings[consumable.userString] ??
|
||||||
|
`Unknown ${consumable.id}`,
|
||||||
|
include: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const includeRaw = consumable.vehicleFilter.include.vehicle;
|
||||||
|
const excludeRaw = consumable.vehicleFilter.exclude?.vehicle;
|
||||||
|
|
||||||
|
if ('minLevel' in includeRaw) {
|
||||||
|
consumableDefinitions[consumable.id].include.push({
|
||||||
|
type: 'tier',
|
||||||
|
min: includeRaw.minLevel,
|
||||||
|
max: includeRaw.maxLevel,
|
||||||
|
});
|
||||||
|
} else if ('name' in includeRaw) {
|
||||||
|
consumableDefinitions[consumable.id].include.push({
|
||||||
|
type: 'ids',
|
||||||
|
ids: includeRaw.name.split(' ').map((key) => tankStringIdMap[key]),
|
||||||
|
});
|
||||||
|
} else throw new SyntaxError('Unhandled include type');
|
||||||
|
|
||||||
|
if (excludeRaw) {
|
||||||
|
consumableDefinitions[consumable.id].exclude = [];
|
||||||
|
|
||||||
|
if ('name' in excludeRaw) {
|
||||||
|
consumableDefinitions[consumable.id].exclude!.push({
|
||||||
|
type: 'ids',
|
||||||
|
ids: excludeRaw.name.split(' ').map((key) => tankStringIdMap[key]),
|
||||||
|
});
|
||||||
|
} else if ('extendedTags' in excludeRaw) {
|
||||||
|
consumableDefinitions[consumable.id].exclude!.push({
|
||||||
|
type: 'category',
|
||||||
|
categories: excludeRaw.extendedTags.split(' '),
|
||||||
|
});
|
||||||
|
} else throw new SyntaxError('Unhandled exclude type');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries({
|
||||||
|
...consumable.script,
|
||||||
|
...consumable.script.bonusValues,
|
||||||
|
}).forEach(([effectName, effect]) => {
|
||||||
|
if (
|
||||||
|
!consumableEffectSuffixes.some((suffix) => effectName.endsWith(suffix))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!consumableDefinitions[consumable.id].effects) {
|
||||||
|
consumableDefinitions[consumable.id].effects = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
consumableDefinitions[consumable.id].effects![effectName] =
|
||||||
|
effect as number;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Committing definitions...');
|
console.log('Committing definitions...');
|
||||||
await commitMultipleFiles(
|
await commitMultipleFiles(
|
||||||
'tresabhi',
|
'tresabhi',
|
||||||
@@ -585,6 +692,11 @@ export async function buildDefinitions() {
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
path: 'definitions/equipment.json',
|
path: 'definitions/equipment.json',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
content: JSON.stringify(consumableDefinitions),
|
||||||
|
encoding: 'utf-8',
|
||||||
|
path: 'definitions/consumables.json',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,103 +23,110 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
|
|||||||
<Flex gap="4" direction="column">
|
<Flex gap="4" direction="column">
|
||||||
<Heading>Configure</Heading>
|
<Heading>Configure</Heading>
|
||||||
|
|
||||||
<Heading size="4">Modules</Heading>
|
<Flex gap="4" justify="between" wrap="wrap">
|
||||||
|
<Flex gap="2" direction="column">
|
||||||
|
<Heading size="4">Modules</Heading>
|
||||||
|
|
||||||
<Flex gap="2">
|
|
||||||
<Flex>
|
|
||||||
{duel.protagonist.tank.turrets.map((thisTurret, index) => {
|
|
||||||
return (
|
|
||||||
<ModuleButton
|
|
||||||
key={thisTurret.id}
|
|
||||||
selected={duel.protagonist.turret.id === thisTurret.id}
|
|
||||||
type="module"
|
|
||||||
module="turret"
|
|
||||||
tier={thisTurret.tier}
|
|
||||||
first={index === 0}
|
|
||||||
last={index === duel.protagonist.tank.turrets.length - 1}
|
|
||||||
rowChild
|
|
||||||
onClick={() => {
|
|
||||||
setDuel(
|
|
||||||
produce<Duel>((draft) => {
|
|
||||||
draft.protagonist.turret = thisTurret;
|
|
||||||
draft.protagonist.gun = thisTurret.guns.at(-1)!;
|
|
||||||
draft.protagonist.shell =
|
|
||||||
thisTurret.guns.at(-1)!.shells[0];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex>
|
|
||||||
{duel.protagonist.turret.guns.map((thisGun, index) => {
|
|
||||||
return (
|
|
||||||
<ModuleButton
|
|
||||||
key={thisGun.id}
|
|
||||||
type="module"
|
|
||||||
module="gun"
|
|
||||||
tier={thisGun.tier}
|
|
||||||
selected={duel.protagonist.gun.id === thisGun.id}
|
|
||||||
first={index === 0}
|
|
||||||
last={index === duel.protagonist.turret.guns.length - 1}
|
|
||||||
rowChild
|
|
||||||
onClick={() => {
|
|
||||||
setDuel(
|
|
||||||
produce<Duel>((draft) => {
|
|
||||||
draft.protagonist.gun = thisGun;
|
|
||||||
draft.protagonist.shell = thisGun.shells[0];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Heading size="4">Equipment</Heading>
|
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
|
||||||
{equipmentRows.map((equipmentRow, rowIndex) => (
|
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
{equipmentRow.map((equipment, columnIndex) => (
|
<Flex>
|
||||||
<Flex>
|
{duel.protagonist.tank.turrets.map((thisTurret, index) => {
|
||||||
<ModuleButton
|
return (
|
||||||
type="equipment"
|
<ModuleButton
|
||||||
equipment={equipment[0]}
|
key={thisTurret.id}
|
||||||
first
|
selected={duel.protagonist.turret.id === thisTurret.id}
|
||||||
rowChild
|
type="module"
|
||||||
selected={equipmentMatrix[rowIndex][columnIndex] === 0}
|
module="turret"
|
||||||
onClick={() => {
|
tier={thisTurret.tier}
|
||||||
mutateTankopedia((draft) => {
|
first={index === 0}
|
||||||
draft.equipmentMatrix[rowIndex][columnIndex] = 0;
|
last={index === duel.protagonist.tank.turrets.length - 1}
|
||||||
});
|
rowChild
|
||||||
}}
|
onClick={() => {
|
||||||
/>
|
setDuel(
|
||||||
<ModuleButton
|
produce<Duel>((draft) => {
|
||||||
type="equipment"
|
draft.protagonist.turret = thisTurret;
|
||||||
equipment={equipment[1]}
|
draft.protagonist.gun = thisTurret.guns.at(-1)!;
|
||||||
last
|
draft.protagonist.shell =
|
||||||
rowChild
|
thisTurret.guns.at(-1)!.shells[0];
|
||||||
selected={equipmentMatrix[rowIndex][columnIndex] === 1}
|
}),
|
||||||
onClick={() => {
|
);
|
||||||
mutateTankopedia((draft) => {
|
}}
|
||||||
draft.equipmentMatrix[rowIndex][columnIndex] = 1;
|
/>
|
||||||
});
|
);
|
||||||
}}
|
})}
|
||||||
/>
|
</Flex>
|
||||||
|
|
||||||
|
<Flex>
|
||||||
|
{duel.protagonist.turret.guns.map((thisGun, index) => {
|
||||||
|
return (
|
||||||
|
<ModuleButton
|
||||||
|
key={thisGun.id}
|
||||||
|
type="module"
|
||||||
|
module="gun"
|
||||||
|
tier={thisGun.tier}
|
||||||
|
selected={duel.protagonist.gun.id === thisGun.id}
|
||||||
|
first={index === 0}
|
||||||
|
last={index === duel.protagonist.turret.guns.length - 1}
|
||||||
|
rowChild
|
||||||
|
onClick={() => {
|
||||||
|
setDuel(
|
||||||
|
produce<Duel>((draft) => {
|
||||||
|
draft.protagonist.gun = thisGun;
|
||||||
|
draft.protagonist.shell = thisGun.shells[0];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex gap="2" direction="column">
|
||||||
|
<Heading size="4">Equipment</Heading>
|
||||||
|
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
{equipmentRows.map((equipmentRow, rowIndex) => (
|
||||||
|
<Flex gap="2" key={rowIndex}>
|
||||||
|
{equipmentRow.map((equipment, columnIndex) => (
|
||||||
|
<Flex key={columnIndex}>
|
||||||
|
<ModuleButton
|
||||||
|
type="equipment"
|
||||||
|
equipment={equipment[0]}
|
||||||
|
first
|
||||||
|
rowChild
|
||||||
|
selected={equipmentMatrix[rowIndex][columnIndex] === 0}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopedia((draft) => {
|
||||||
|
draft.equipmentMatrix[rowIndex][columnIndex] = 0;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ModuleButton
|
||||||
|
type="equipment"
|
||||||
|
equipment={equipment[1]}
|
||||||
|
last
|
||||||
|
rowChild
|
||||||
|
selected={equipmentMatrix[rowIndex][columnIndex] === 1}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopedia((draft) => {
|
||||||
|
draft.equipmentMatrix[rowIndex][columnIndex] = 1;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
</Flex>
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* <Flex gap="4" direction="column">
|
<Flex gap="2" direction="column">
|
||||||
<Heading size="5">Modules</Heading>
|
<Heading size="4">Consumables</Heading>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{/*
|
||||||
<Flex gap="4" direction="column">
|
<Flex gap="4" direction="column">
|
||||||
<Heading size="5">Camouflage</Heading>
|
<Heading size="5">Camouflage</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ export function ModuleButton({
|
|||||||
<Button
|
<Button
|
||||||
radius="medium"
|
radius="medium"
|
||||||
color={selected ? undefined : 'gray'}
|
color={selected ? undefined : 'gray'}
|
||||||
variant={selected ? 'solid' : 'soft'}
|
variant={selected ? 'surface' : 'soft'}
|
||||||
|
// variant="soft"
|
||||||
style={{
|
style={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
width: 48,
|
width: 48,
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { asset } from './asset';
|
||||||
|
|
||||||
|
type ConsumableDefinitionFilter =
|
||||||
|
| {
|
||||||
|
type: 'tier';
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'ids';
|
||||||
|
ids: number[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'category';
|
||||||
|
categories: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ConsumableDefinitions {
|
||||||
|
[key: string]: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
effects?: Record<string, number>;
|
||||||
|
include: ConsumableDefinitionFilter[];
|
||||||
|
exclude?: ConsumableDefinitionFilter[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const consumableDefinitions = fetch(
|
||||||
|
asset('definitions/consumables.json'),
|
||||||
|
{
|
||||||
|
cache: 'no-cache',
|
||||||
|
},
|
||||||
|
).then(async (response) => response.json() as Promise<ConsumableDefinitions>);
|
||||||
Reference in new issue
Block a user