diff --git a/.vscode/settings.json b/.vscode/settings.json index b2a9e4d06..860908502 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,6 +43,7 @@ "Everytime", "FASTNAME", "FOURCC", + "fsformat", "fullstats", "fuzzysort", "gamemode", diff --git a/docs/guide/cdon.md b/docs/guide/cdon.md index 701bc62f0..025126b93 100644 --- a/docs/guide/cdon.md +++ b/docs/guide/cdon.md @@ -1,6 +1,6 @@ # Cedilla Object Notation -A compact binary representation similar to JSON objects. This documentation uses some funky (yet concise) syntax I developed (don't worry, my C++ isn't that bad!). +This documentation uses an experimental binary notation syntax called "Buffer Up". 1. JSON compressed as CDON, written directly to disk (a direct 50% reduction). 2. CDON further compressed with LZ4 (a clean 90% reduction). @@ -9,62 +9,43 @@ A compact binary representation similar to JSON objects. This documentation uses ![an image of file sizes](https://i.imgur.com/AHJiFpq.png) -## Primitives +FS stands for "Fast String". ```cpp -struct String { - uint32 length; - utf8[length] value; -} -``` - -## Reading - -- Everything is little endian -- Magic is always `"CDON"` -- The index of fast strings must be read as mentioned by the fast string format - -```cpp -struct BlitzkriegObjectNotation { - ascii[4] magic; +primary CedillaObjectNotation { + utf8"CDON" magic; Header header; - Body body; + primary Body body; } -``` -### Header - -- Version is always `1` - -```cpp -struct Header { +Header { uint16 version; - FastStringFormat fastStringFormat; - StringTable stringTable; + FSFormat fsFormat; + StringTable stringTable; } -// read as uint8 -enum FastStringFormat { +FSFormat enum { Format8, Format16, Format32, } -struct StringTable { - (uint8 | uint16 | uint32) count; +FSPrimitive match(fsFormat) { + Format8: uint8; + Format16: uint16; + Format32: uint32; +} + +StringTable { + FSPrimitive count; String[count] strings; } -``` -### Body - -```cpp -struct Body { - Value object; +Body { + primary Value object; } -// read as uint8 -enum ValueType { +ValueType enum { Null, Boolean, Uint8, @@ -78,96 +59,43 @@ enum ValueType { Float32, Float64, String, - FastString, + FS, Array, Object, } -struct Value = `Value${ValueType}` - -struct ValueNull { - ValueType.Null type; +String { + uint32 length; + primary utf8 value; } -struct ValueBoolean { - ValueType.Boolean type; - uint8 value; -} +StringValue Value & { (ValueType.String | ValueType.FS) type } -struct ValueUint8 { - ValueType.Uint8 type; - uint8 value; -} - -struct ValueUint16 { - ValueType.Uint16 type; - uint16 value; -} - -struct ValueUint32 { - ValueType.Uint32 type; - uint32 value; -} - -struct ValueUint64 { - ValueType.Uint64 type; - uint64 value; -} - -struct ValueInt8 { - ValueType.Int8 type; - int8 value; -} - -struct ValueInt16 { - ValueType.Int16 type; - int16 value; -} - -struct ValueInt32 { - ValueType.Int32 type; - int32 value; -} - -struct ValueInt64 { - ValueType.Int64 type; - int64 value; -} - -struct ValueFloat32 { - ValueType.Float32 type; - float32 value; -} - -struct ValueFloat64 { - ValueType.Float64 type; - float64 value; -} - -struct ValueString { - ValueType.String type; - String value; -} - -struct ValueFastString { - ValueType.FastString type; - (uint8 | uint16 | uint32) index; -} - -struct ValueArray { - ValueType.Array type; - uint32 count; - Value[count] values; -} - -struct ValueObject { - ValueType.Object type; - uint32 count; - (ValueString | ValueFastString)[count] keys; - Value[count] values; +Value { + ValueType type; + match { + Boolean: { uint8 value; } + Uint8: { uint8 value; } + Uint16: { uint16 value; } + Uint32: { uint32 value; } + Uint64: { uint64 value; } + Int8: { int8 value; } + Int16: { int16 value; } + Int32: { int32 value; } + Int64: { int64 value; } + Float32: { float32 value; } + Float64: { float64 value; } + String: { String value; } + FS: { FSPrimitive index; } + Array: { + uint32 count; + Value[count] values; + } + Object: { + uint32 count; + StringValue[count] keys; + Value[count] values; + } + } } ``` - -## Writing - -It's pretty obvious how to write. Remember that a `FastString` should only be applied to the string table if the string shows up more than once in the body. Otherwise, there's no point and `String` should be used. diff --git a/scripts/buildAssets/boosterIcons.ts b/scripts/buildAssets/boosterIcons.ts new file mode 100644 index 000000000..2b1069ba8 --- /dev/null +++ b/scripts/buildAssets/boosterIcons.ts @@ -0,0 +1,48 @@ +import { readdir } from 'fs/promises'; +import sharp from 'sharp'; +import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; +import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; +import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; +import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; +import { DATA, POI } from './constants'; + +export async function boosterIcons(production: boolean) { + const boosterFiles = (await readdir(`${DATA}/${POI.boosterIcons}`)).filter( + (file) => !file.endsWith('@2x.txt.dvpl') && !file.startsWith('texture0'), + ); + const image = sharp( + await readDVPLFile(`${DATA}/${POI.boosterIcons}/texture0.packed.webp.dvpl`), + ); + const changes: FileChange[] = []; + + await Promise.all( + boosterFiles.map(async (file) => { + const name = file.match(/booster_(.+).txt.dvpl/)![1]; + const sizes = ( + await readStringDVPL(`${DATA}/${POI.boosterIcons}/${file}`) + ) + .split('\n')[4] + .split(' ') + .map(Number); + const content = ( + await image + .clone() + .extract({ + left: sizes[0], + top: sizes[1], + width: sizes[2], + height: sizes[3], + }) + .toBuffer() + ).toString('base64'); + + changes.push({ + path: `icons/boosters/${name}.webp`, + content, + encoding: 'base64', + }); + }), + ); + + await commitAssets('booster icons', changes, production); +} diff --git a/scripts/buildAssets/constants.ts b/scripts/buildAssets/constants.ts index a0c049ffc..503a7399c 100644 --- a/scripts/buildAssets/constants.ts +++ b/scripts/buildAssets/constants.ts @@ -27,4 +27,5 @@ export const POI = { 'UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml', provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml', defaultCamoIcon: 'Gfx/UI/Hangar/IconCamouflage.webp', + boosterIcons: 'Gfx/Shared/boosters', } as const; diff --git a/scripts/buildAssets/consumableProvisionIcons.ts b/scripts/buildAssets/consumableProvisionIcons.ts index fb866d7c3..3919b483d 100644 --- a/scripts/buildAssets/consumableProvisionIcons.ts +++ b/scripts/buildAssets/consumableProvisionIcons.ts @@ -6,7 +6,7 @@ import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { DATA, POI } from './constants'; -import { ConsumablesProvisionsCommon } from './definitions'; +import { ConsumablesCommon, ProvisionsCommon } from './definitions'; interface Mappings { Header: { version: number }; @@ -35,10 +35,10 @@ export async function consumableProvisionIcons(production: boolean) { `${DATA}/${POI.equipmentItemImageMappings}.dvpl`, ); const consumablesCommon = await readXMLDVPL<{ - root: ConsumablesProvisionsCommon; + root: ConsumablesCommon; }>(`${DATA}/${POI.consumablesCommon}.dvpl`); const provisionsCommon = await readXMLDVPL<{ - root: ConsumablesProvisionsCommon; + root: ProvisionsCommon; }>(`${DATA}/${POI.provisionsCommon}.dvpl`); const styleSheets = Object.values(equipmentItemImageMappings.StyleSheets); diff --git a/scripts/buildAssets/index.ts b/scripts/buildAssets/index.ts index 41e5a1d06..cb1751c7a 100644 --- a/scripts/buildAssets/index.ts +++ b/scripts/buildAssets/index.ts @@ -1,5 +1,6 @@ import { config } from 'dotenv'; import { argv } from 'process'; +import { boosterIcons } from './boosterIcons'; import { camouflageIcons } from './camouflageIcons'; import { circleFlags } from './circleFlags'; import { consumableProvisionIcons } from './consumableProvisionIcons'; @@ -35,10 +36,9 @@ const methods = [ equipmentIcons, consumableProvisionIcons, camouflageIcons, + boosterIcons, ]; for (const method of methods) { - if (allTargets || targets?.includes(method.name)) { - await method(production); - } + if (allTargets || targets?.includes(method.name)) method(production); } diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Consumables.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Consumables.tsx index 01ccc1d99..d822fd3fc 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Consumables.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Consumables.tsx @@ -23,6 +23,9 @@ export function Consumables() { protagonist.gun, ), ); + const cooldownBooster = useTankopediaTemporary( + (state) => state.cooldownBooster, + ); const hasConsumableDeliverySystem = useEquipment(118); const hasHighEndConsumables = useEquipment(101); @@ -42,7 +45,9 @@ export function Consumables() { : undefined } cooldown={ - consumable.cooldown * (hasConsumableDeliverySystem ? 0.85 : 1) + consumable.cooldown * + (hasConsumableDeliverySystem ? 0.85 : 1) * + (1 - cooldownBooster * 0.1) } key={consumable.id} first={index === 0} diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Miscellaneous.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Miscellaneous.tsx index cd4c20ed2..8b1d92c81 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Miscellaneous.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Miscellaneous.tsx @@ -1,5 +1,6 @@ import { Flex, Heading } from '@radix-ui/themes'; -import { CamouflageButton } from '../../../../../../../components/ModuleButtons/CamouflageButton'; +import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton'; +import { asset } from '../../../../../../../core/blitzkrieg/asset'; import { mutateTankopediaTemporary, useTankopediaTemporary, @@ -8,14 +9,23 @@ import { ConfigurationChildWrapper } from './ConfigurationChildWrapper'; export function Miscellaneous() { const camouflage = useTankopediaTemporary((state) => state.camouflage); + const cooldownBooster = useTankopediaTemporary( + (state) => state.cooldownBooster, + ); return ( Miscellaneous - { @@ -24,6 +34,31 @@ export function Miscellaneous() { }); }} /> + + 0} + first + last + banner={ + cooldownBooster === 0 + ? undefined + : cooldownBooster === 1 + ? '#afb0abc0' + : cooldownBooster === 2 + ? '#5f72cbc0' + : '#9b3cc0c0' + } + iconStyles={{ + transform: 'translate(-50%, -50%) scale(0.8)', + }} + onClick={() => { + mutateTankopediaTemporary((draft) => { + draft.cooldownBooster++; + if (draft.cooldownBooster === 4) draft.cooldownBooster = 0; + }); + }} + /> ); diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index f0869d9a8..766a3ff00 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -119,9 +119,9 @@ export default function Page({ params }: { params: { id: string } }) { - + diff --git a/src/components/ModuleButtons/CamouflageButton.tsx b/src/components/ModuleButtons/CamouflageButton.tsx deleted file mode 100644 index 0964d85af..000000000 --- a/src/components/ModuleButtons/CamouflageButton.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { asset } from '../../core/blitzkrieg/asset'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import { TankComponentButtonProps } from './TankComponentButton'; - -export function CamouflageButton(props: TankComponentButtonProps) { - return ( - - ); -} diff --git a/src/components/ModuleButtons/GenericTankComponentButton.tsx b/src/components/ModuleButtons/GenericTankComponentButton.tsx index c4df4bf6d..a8057673c 100644 --- a/src/components/ModuleButtons/GenericTankComponentButton.tsx +++ b/src/components/ModuleButtons/GenericTankComponentButton.tsx @@ -28,6 +28,7 @@ export function GenericTankComponentButton({ draggable={false} src={icon} style={{ + objectFit: 'contain', width: 32, height: 32, position: 'absolute', diff --git a/src/components/ModuleButtons/TankComponentButton.tsx b/src/components/ModuleButtons/TankComponentButton.tsx index cbc87affb..69e9bd503 100644 --- a/src/components/ModuleButtons/TankComponentButton.tsx +++ b/src/components/ModuleButtons/TankComponentButton.tsx @@ -10,6 +10,7 @@ export interface TankComponentButtonProps children?: ReactNode; discriminator?: ReactNode; disabled?: boolean; + banner?: string; } export function TankComponentButton({ @@ -19,6 +20,7 @@ export function TankComponentButton({ rowChild, discriminator, children, + banner, ...props }: TankComponentButtonProps) { return ( @@ -36,9 +38,24 @@ export function TankComponentButton({ borderBottomLeftRadius: first ? undefined : 0, borderBottomRightRadius: last ? undefined : 0, margin: rowChild ? -0.5 : 'unset', + overflow: 'hidden', }} {...props} > + {banner !== undefined && ( +
+ )} + {discriminator !== undefined && ( ( asset('definitions/equipment.cdon.lz4'), ); - -equipmentDefinitions.then(console.log); diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index c756054b5..3695d7918 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -69,6 +69,7 @@ interface TankopediaTemporary { consumables: number[]; provisions: number[]; camouflage: boolean; + cooldownBooster: number; shot?: Shot; } @@ -178,6 +179,7 @@ export const useTankopediaTemporary = create()( consumables: [], provisions: [], camouflage: false, + cooldownBooster: 0, })), );