diff --git a/scripts/buildAssets/constants.ts b/scripts/buildAssets/constants.ts index 4e3132b80..a0c049ffc 100644 --- a/scripts/buildAssets/constants.ts +++ b/scripts/buildAssets/constants.ts @@ -21,9 +21,10 @@ export const POI = { optionalDevicesImage: 'Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp', consumablesCommon: 'XML/item_defs/vehicles/common/consumables/common.xml', - provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml', consumableIcons: 'Gfx/UI/InventoryIcons/Big/Consumables', + provisionIcons: 'Gfx/UI/InventoryIcons/Big/Provisions', equipmentItemImageMappings: 'UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml', + provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml', defaultCamoIcon: 'Gfx/UI/Hangar/IconCamouflage.webp', } as const; diff --git a/scripts/buildAssets/consumableIcons.ts b/scripts/buildAssets/consumableProvisionIcons.ts similarity index 51% rename from scripts/buildAssets/consumableIcons.ts rename to scripts/buildAssets/consumableProvisionIcons.ts index 591f134fd..65dc49727 100644 --- a/scripts/buildAssets/consumableIcons.ts +++ b/scripts/buildAssets/consumableProvisionIcons.ts @@ -16,15 +16,20 @@ interface Mappings { }[]; } -export async function consumableIcons(production: boolean) { - console.log('Building consumable icons...'); +export async function consumableProvisionIcons(production: boolean) { + console.log('Building consumable and provision icons...'); const changes: FileChange[] = []; - const texture = sharp( + const consumablesTexture = sharp( await readDVPLFile( `${DATA}/${POI.consumableIcons}/texture0.packed.webp.dvpl`, ), ); + const provisionsTexture = sharp( + await readDVPLFile( + `${DATA}/${POI.provisionIcons}/texture0.packed.webp.dvpl`, + ), + ); const equipmentItemImageMappings = await readYAMLDVPL( `${DATA}/${POI.equipmentItemImageMappings}.dvpl`, @@ -32,6 +37,9 @@ export async function consumableIcons(production: boolean) { const consumablesCommon = await readXMLDVPL<{ root: ConsumablesProvisionsCommon; }>(`${DATA}/${POI.consumablesCommon}.dvpl`); + const provisionsCommon = await readXMLDVPL<{ + root: ConsumablesProvisionsCommon; + }>(`${DATA}/${POI.provisionsCommon}.dvpl`); const styleSheets = Object.values(equipmentItemImageMappings.StyleSheets); await Promise.all( @@ -43,20 +51,21 @@ export async function consumableIcons(production: boolean) { ); if (!styleSheet) { - throw new Error( - `No style sheet found for consumable ${consumable.icon}`, + console.warn( + `No style sheet found for consumable ${consumable.icon}; skipping...`, ); + return; } const configPath = styleSheet.properties['bg-sprite'] .replace('~res:/', '') - .replace('.psd', '.txt'); + .replace('.psd', '') + .replace('.txt', ''); if (configPath.startsWith('Gfx/Shared')) { - const imagePath = `${configPath}.packed.webp.dvpl`; - const content = (await readDVPLFile(`${DATA}/${imagePath}`)).toString( - 'base64', - ); + const content = ( + await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`) + ).toString('base64'); changes.push({ path: `icons/consumables/${consumable.id}.webp`, @@ -64,12 +73,12 @@ export async function consumableIcons(production: boolean) { content, }); } else { - const sizes = (await readStringDVPL(`${DATA}/${configPath}.dvpl`)) + const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt.dvpl`)) .split('\n')[4] .split(' ') .map(Number); const content = ( - await texture + await consumablesTexture .clone() .extract({ left: sizes[0], @@ -89,5 +98,61 @@ export async function consumableIcons(production: boolean) { }), ); - await commitAssets('consumable icons', changes, production); + await Promise.all( + Object.values(provisionsCommon.root).map(async (provision) => { + const styleSheet = styleSheets.find((styleSheet) => + styleSheet.selectors.includes( + `.equipment_item.${provision.icon} * .equipment-img`, + ), + ); + + if (!styleSheet) { + console.warn( + `No style sheet found for provision ${provision.icon}; skipping...`, + ); + return; + } + + const configPath = styleSheet.properties['bg-sprite'] + .replace('~res:/', '') + .replace('.psd', '') + .replace('.txt', ''); + + if (configPath.startsWith('Gfx/Shared')) { + const content = ( + await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`) + ).toString('base64'); + + changes.push({ + path: `icons/provisions/${provision.id}.webp`, + encoding: 'base64', + content, + }); + } else { + const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt.dvpl`)) + .split('\n')[4] + .split(' ') + .map(Number); + const content = ( + await provisionsTexture + .clone() + .extract({ + left: sizes[0], + top: sizes[1], + width: sizes[2], + height: sizes[3], + }) + .toBuffer() + ).toString('base64'); + + changes.push({ + path: `icons/provisions/${provision.id}.webp`, + encoding: 'base64', + content, + }); + } + }), + ); + + await commitAssets('consumable and provision icons', changes, production); } diff --git a/scripts/buildAssets/definitions.ts b/scripts/buildAssets/definitions.ts index 01fff3bd0..11e0df5dc 100644 --- a/scripts/buildAssets/definitions.ts +++ b/scripts/buildAssets/definitions.ts @@ -201,8 +201,8 @@ export interface ConsumablesProvisionsCommon { category: string; tags: string; vehicleFilter?: { - include: { vehicle: ConsumablesFilter }; - exclude?: { vehicle: ConsumablesFilter }; + include: { vehicle: ConsumablesVehicleFilter; nations?: string }; + exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string }; }; script: { '#text': string; @@ -215,7 +215,7 @@ export interface ConsumablesProvisionsCommon { }; } -type ConsumablesFilter = +type ConsumablesVehicleFilter = | { minLevel: number; maxLevel: number } | { name: string } | { extendedTags: string }; @@ -669,6 +669,13 @@ export async function definitions(production: boolean) { ids: includeRaw.name.split(' ').map((key) => tankStringIdMap[key]), }); } else throw new SyntaxError('Unhandled include type'); + + if (consumable.vehicleFilter?.include.nations) { + consumableDefinitions[consumable.id].include.push({ + type: 'nation', + nations: consumable.vehicleFilter.include.nations.split(' '), + }); + } } if (excludeRaw) { @@ -687,6 +694,13 @@ export async function definitions(production: boolean) { ) as TankFilterDefinitionCategory[], }); } else throw new SyntaxError('Unhandled exclude type'); + + if (consumable.vehicleFilter?.exclude?.nations) { + consumableDefinitions[consumable.id].exclude!.push({ + type: 'nation', + nations: consumable.vehicleFilter.exclude.nations.split(' '), + }); + } } Object.entries({ @@ -736,6 +750,13 @@ export async function definitions(production: boolean) { ids: includeRaw.name.split(' ').map((key) => tankStringIdMap[key]), }); } else throw new SyntaxError('Unhandled include type'); + + if (provision.vehicleFilter?.include.nations) { + provisionDefinitions[provision.id].include.push({ + type: 'nation', + nations: provision.vehicleFilter.include.nations.split(' '), + }); + } } if (excludeRaw) { @@ -754,6 +775,13 @@ export async function definitions(production: boolean) { ) as TankFilterDefinitionCategory[], }); } else throw new SyntaxError('Unhandled exclude type'); + + if (provision.vehicleFilter?.exclude?.nations) { + provisionDefinitions[provision.id].exclude!.push({ + type: 'nation', + nations: provision.vehicleFilter.exclude.nations.split(' '), + }); + } } Object.entries({ diff --git a/scripts/buildAssets/index.ts b/scripts/buildAssets/index.ts index 3aae2e49d..59a77eef3 100644 --- a/scripts/buildAssets/index.ts +++ b/scripts/buildAssets/index.ts @@ -2,7 +2,7 @@ import { config } from 'dotenv'; import { argv } from 'process'; import { camouflageIcons } from './camouflageIcons'; import { circleFlags } from './circleFlags'; -import { consumableIcons } from './consumableIcons'; +import { consumableProvisionIcons } from './consumableProvisionIcons'; import { definitions } from './definitions'; import { equipmentIcons } from './equipmentIcons'; import { buildModuleIcons } from './moduleIcons'; @@ -33,7 +33,7 @@ const methods = [ buildShellIcons, buildModuleIcons, equipmentIcons, - consumableIcons, + consumableProvisionIcons, camouflageIcons, ]; diff --git a/src/app/tools/tankopedia/[id]/components/Configure/index.tsx b/src/app/tools/tankopedia/[id]/components/Configure/index.tsx index 4c8181424..32323b636 100644 --- a/src/app/tools/tankopedia/[id]/components/Configure/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Configure/index.tsx @@ -17,11 +17,6 @@ export function Configure() { - - {/* - - Provisions - */} ); } diff --git a/src/core/blitzkrieg/checkConsumableInclusivity.ts b/src/core/blitzkrieg/checkConsumableInclusivity.ts index 05f16a1da..907e852f5 100644 --- a/src/core/blitzkrieg/checkConsumableInclusivity.ts +++ b/src/core/blitzkrieg/checkConsumableInclusivity.ts @@ -15,6 +15,9 @@ export function checkConsumableProvisionInclusivity( case 'ids': return rule.ids.includes(tank.id); + case 'nation': + return rule.nations.includes(tank.nation); + case 'category': throw new SyntaxError('Category filtering found in include rule'); } @@ -27,6 +30,9 @@ export function checkConsumableProvisionInclusivity( case 'ids': return rule.ids.includes(tank.id); + case 'nation': + return rule.nations.includes(tank.nation); + case 'category': return rule.categories.some((category) => { switch (category) { diff --git a/src/core/blitzkrieg/consumableDefinitions.ts b/src/core/blitzkrieg/consumableDefinitions.ts index 270d67429..9fb49b4f4 100644 --- a/src/core/blitzkrieg/consumableDefinitions.ts +++ b/src/core/blitzkrieg/consumableDefinitions.ts @@ -15,6 +15,10 @@ export type TankFilterDefinition = | { type: 'category'; categories: TankFilterDefinitionCategory[]; + } + | { + type: 'nation'; + nations: string[]; }; export interface ConsumableEntry {