feat: consumables

This commit is contained in:
TrèsAbhi committed 2024-02-13 19:46:34 -06:00
1 parent 62bfab0960
commit 417385f231
14 files changed
+171 -152

No files matched your search

+1
View File
@@ -43,6 +43,7 @@
"Everytime", "Everytime",
"FASTNAME", "FASTNAME",
"FOURCC", "FOURCC",
"fsformat",
"fullstats", "fullstats",
"fuzzysort", "fuzzysort",
"gamemode", "gamemode",
+51 -123
View File
@@ -1,6 +1,6 @@
# Cedilla Object Notation # 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). 1. JSON compressed as CDON, written directly to disk (a direct 50% reduction).
2. CDON further compressed with LZ4 (a clean 90% 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) ![an image of file sizes](https://i.imgur.com/AHJiFpq.png)
## Primitives FS stands for "Fast String".
```cpp ```cpp
struct String { primary CedillaObjectNotation {
uint32 length; utf8"CDON" magic;
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;
Header header; Header header;
Body body; primary Body<header.fsFormat> body;
} }
```
### Header Header {
- Version is always `1`
```cpp
struct Header {
uint16 version; uint16 version;
FastStringFormat fastStringFormat; FSFormat fsFormat;
StringTable stringTable; StringTable<fsFormat> stringTable;
} }
// read as uint8 FSFormat enum<uint8> {
enum FastStringFormat {
Format8, Format8,
Format16, Format16,
Format32, Format32,
} }
struct StringTable { FSPrimitive<FSFormat fsFormat> match(fsFormat) {
(uint8 | uint16 | uint32) count; Format8: uint8;
Format16: uint16;
Format32: uint32;
}
StringTable<FSFormat fsFormat> {
FSPrimitive<fsFormat> count;
String[count] strings; String[count] strings;
} }
```
### Body Body<FSFormat fsFormat> {
primary Value<fsFormat> object;
```cpp
struct Body {
Value object;
} }
// read as uint8 ValueType enum<uint8> {
enum ValueType {
Null, Null,
Boolean, Boolean,
Uint8, Uint8,
@@ -78,96 +59,43 @@ enum ValueType {
Float32, Float32,
Float64, Float64,
String, String,
FastString, FS,
Array, Array,
Object, Object,
} }
struct Value = `Value${ValueType}` String {
uint32 length;
struct ValueNull { primary utf8<length> value;
ValueType.Null type;
} }
struct ValueBoolean { StringValue Value & { (ValueType.String | ValueType.FS) type }
ValueType.Boolean type;
uint8 value;
}
struct ValueUint8 { Value<FSFormat fsFormat> {
ValueType.Uint8 type; ValueType type;
uint8 value; match<type> {
} Boolean: { uint8 value; }
Uint8: { uint8 value; }
struct ValueUint16 { Uint16: { uint16 value; }
ValueType.Uint16 type; Uint32: { uint32 value; }
uint16 value; Uint64: { uint64 value; }
} Int8: { int8 value; }
Int16: { int16 value; }
struct ValueUint32 { Int32: { int32 value; }
ValueType.Uint32 type; Int64: { int64 value; }
uint32 value; Float32: { float32 value; }
} Float64: { float64 value; }
String: { String value; }
struct ValueUint64 { FS: { FSPrimitive<fsFormat> index; }
ValueType.Uint64 type; Array: {
uint64 value; uint32 count;
} Value[count] values;
}
struct ValueInt8 { Object: {
ValueType.Int8 type; uint32 count;
int8 value; StringValue[count] keys;
} Value[count] values;
}
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;
} }
``` ```
## 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.
+48
View File
@@ -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);
}
+1
View File
@@ -27,4 +27,5 @@ export const POI = {
'UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml', 'UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml',
provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml', provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml',
defaultCamoIcon: 'Gfx/UI/Hangar/IconCamouflage.webp', defaultCamoIcon: 'Gfx/UI/Hangar/IconCamouflage.webp',
boosterIcons: 'Gfx/Shared/boosters',
} as const; } as const;
@@ -6,7 +6,7 @@ import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { ConsumablesProvisionsCommon } from './definitions'; import { ConsumablesCommon, ProvisionsCommon } from './definitions';
interface Mappings { interface Mappings {
Header: { version: number }; Header: { version: number };
@@ -35,10 +35,10 @@ export async function consumableProvisionIcons(production: boolean) {
`${DATA}/${POI.equipmentItemImageMappings}.dvpl`, `${DATA}/${POI.equipmentItemImageMappings}.dvpl`,
); );
const consumablesCommon = await readXMLDVPL<{ const consumablesCommon = await readXMLDVPL<{
root: ConsumablesProvisionsCommon; root: ConsumablesCommon;
}>(`${DATA}/${POI.consumablesCommon}.dvpl`); }>(`${DATA}/${POI.consumablesCommon}.dvpl`);
const provisionsCommon = await readXMLDVPL<{ const provisionsCommon = await readXMLDVPL<{
root: ConsumablesProvisionsCommon; root: ProvisionsCommon;
}>(`${DATA}/${POI.provisionsCommon}.dvpl`); }>(`${DATA}/${POI.provisionsCommon}.dvpl`);
const styleSheets = Object.values(equipmentItemImageMappings.StyleSheets); const styleSheets = Object.values(equipmentItemImageMappings.StyleSheets);
+3 -3
View File
@@ -1,5 +1,6 @@
import { config } from 'dotenv'; import { config } from 'dotenv';
import { argv } from 'process'; import { argv } from 'process';
import { boosterIcons } from './boosterIcons';
import { camouflageIcons } from './camouflageIcons'; import { camouflageIcons } from './camouflageIcons';
import { circleFlags } from './circleFlags'; import { circleFlags } from './circleFlags';
import { consumableProvisionIcons } from './consumableProvisionIcons'; import { consumableProvisionIcons } from './consumableProvisionIcons';
@@ -35,10 +36,9 @@ const methods = [
equipmentIcons, equipmentIcons,
consumableProvisionIcons, consumableProvisionIcons,
camouflageIcons, camouflageIcons,
boosterIcons,
]; ];
for (const method of methods) { for (const method of methods) {
if (allTargets || targets?.includes(method.name)) { if (allTargets || targets?.includes(method.name)) method(production);
await method(production);
}
} }
@@ -23,6 +23,9 @@ export function Consumables() {
protagonist.gun, protagonist.gun,
), ),
); );
const cooldownBooster = useTankopediaTemporary(
(state) => state.cooldownBooster,
);
const hasConsumableDeliverySystem = useEquipment(118); const hasConsumableDeliverySystem = useEquipment(118);
const hasHighEndConsumables = useEquipment(101); const hasHighEndConsumables = useEquipment(101);
@@ -42,7 +45,9 @@ export function Consumables() {
: undefined : undefined
} }
cooldown={ cooldown={
consumable.cooldown * (hasConsumableDeliverySystem ? 0.85 : 1) consumable.cooldown *
(hasConsumableDeliverySystem ? 0.85 : 1) *
(1 - cooldownBooster * 0.1)
} }
key={consumable.id} key={consumable.id}
first={index === 0} first={index === 0}
@@ -1,5 +1,6 @@
import { Flex, Heading } from '@radix-ui/themes'; 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 { import {
mutateTankopediaTemporary, mutateTankopediaTemporary,
useTankopediaTemporary, useTankopediaTemporary,
@@ -8,14 +9,23 @@ import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Miscellaneous() { export function Miscellaneous() {
const camouflage = useTankopediaTemporary((state) => state.camouflage); const camouflage = useTankopediaTemporary((state) => state.camouflage);
const cooldownBooster = useTankopediaTemporary(
(state) => state.cooldownBooster,
);
return ( return (
<ConfigurationChildWrapper> <ConfigurationChildWrapper>
<Heading size="4">Miscellaneous</Heading> <Heading size="4">Miscellaneous</Heading>
<Flex gap="2"> <Flex gap="2">
<CamouflageButton <GenericTankComponentButton
icon={asset('icons/camo.webp')}
selected={camouflage} selected={camouflage}
iconStyles={{
top: '50%',
left: '50%',
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
}}
first first
last last
onClick={() => { onClick={() => {
@@ -24,6 +34,31 @@ export function Miscellaneous() {
}); });
}} }}
/> />
<GenericTankComponentButton
icon={asset('icons/boosters/equipment.webp')}
selected={cooldownBooster > 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;
});
}}
/>
</Flex> </Flex>
</ConfigurationChildWrapper> </ConfigurationChildWrapper>
); );
+1 -1
View File
@@ -119,9 +119,9 @@ export default function Page({ params }: { params: { id: string } }) {
<Flex direction="column" gap="4"> <Flex direction="column" gap="4">
<Modules /> <Modules />
<Crew />
<Equipments /> <Equipments />
<Consumables /> <Consumables />
<Crew />
<Provisions /> <Provisions />
<Miscellaneous /> <Miscellaneous />
</Flex> </Flex>
@@ -1,17 +0,0 @@
import { asset } from '../../core/blitzkrieg/asset';
import { GenericTankComponentButton } from './GenericTankComponentButton';
import { TankComponentButtonProps } from './TankComponentButton';
export function CamouflageButton(props: TankComponentButtonProps) {
return (
<GenericTankComponentButton
icon={asset('icons/camo.webp')}
iconStyles={{
top: '50%',
left: '50%',
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
}}
{...props}
/>
);
}
@@ -28,6 +28,7 @@ export function GenericTankComponentButton({
draggable={false} draggable={false}
src={icon} src={icon}
style={{ style={{
objectFit: 'contain',
width: 32, width: 32,
height: 32, height: 32,
position: 'absolute', position: 'absolute',
@@ -10,6 +10,7 @@ export interface TankComponentButtonProps
children?: ReactNode; children?: ReactNode;
discriminator?: ReactNode; discriminator?: ReactNode;
disabled?: boolean; disabled?: boolean;
banner?: string;
} }
export function TankComponentButton({ export function TankComponentButton({
@@ -19,6 +20,7 @@ export function TankComponentButton({
rowChild, rowChild,
discriminator, discriminator,
children, children,
banner,
...props ...props
}: TankComponentButtonProps) { }: TankComponentButtonProps) {
return ( return (
@@ -36,9 +38,24 @@ export function TankComponentButton({
borderBottomLeftRadius: first ? undefined : 0, borderBottomLeftRadius: first ? undefined : 0,
borderBottomRightRadius: last ? undefined : 0, borderBottomRightRadius: last ? undefined : 0,
margin: rowChild ? -0.5 : 'unset', margin: rowChild ? -0.5 : 'unset',
overflow: 'hidden',
}} }}
{...props} {...props}
> >
{banner !== undefined && (
<div
style={{
position: 'absolute',
width: '200%',
height: 12,
backgroundColor: banner,
top: '50%',
left: '-50%',
transform: 'translateY(-50%) rotate(-22.5deg)',
}}
/>
)}
{discriminator !== undefined && ( {discriminator !== undefined && (
<Text <Text
size="1" size="1"
@@ -25,5 +25,3 @@ 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);
+2
View File
@@ -69,6 +69,7 @@ interface TankopediaTemporary {
consumables: number[]; consumables: number[];
provisions: number[]; provisions: number[];
camouflage: boolean; camouflage: boolean;
cooldownBooster: number;
shot?: Shot; shot?: Shot;
} }
@@ -178,6 +179,7 @@ export const useTankopediaTemporary = create<TankopediaTemporary>()(
consumables: [], consumables: [],
provisions: [], provisions: [],
camouflage: false, camouflage: false,
cooldownBooster: 0,
})), })),
); );