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",
"FASTNAME",
"FOURCC",
"fsformat",
"fullstats",
"fuzzysort",
"gamemode",
+51 -123
View File
@@ -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<header.fsFormat> body;
}
```
### Header
- Version is always `1`
```cpp
struct Header {
Header {
uint16 version;
FastStringFormat fastStringFormat;
StringTable stringTable;
FSFormat fsFormat;
StringTable<fsFormat> stringTable;
}
// read as uint8
enum FastStringFormat {
FSFormat enum<uint8> {
Format8,
Format16,
Format32,
}
struct StringTable {
(uint8 | uint16 | uint32) count;
FSPrimitive<FSFormat fsFormat> match(fsFormat) {
Format8: uint8;
Format16: uint16;
Format32: uint32;
}
StringTable<FSFormat fsFormat> {
FSPrimitive<fsFormat> count;
String[count] strings;
}
```
### Body
```cpp
struct Body {
Value object;
Body<FSFormat fsFormat> {
primary Value<fsFormat> object;
}
// read as uint8
enum ValueType {
ValueType enum<uint8> {
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<length> 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<FSFormat fsFormat> {
ValueType type;
match<type> {
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<fsFormat> 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.
+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',
provisionsCommon: 'XML/item_defs/vehicles/common/provisions/common.xml',
defaultCamoIcon: 'Gfx/UI/Hangar/IconCamouflage.webp',
boosterIcons: 'Gfx/Shared/boosters',
} as const;
@@ -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);
+3 -3
View File
@@ -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);
}
@@ -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}
@@ -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 (
<ConfigurationChildWrapper>
<Heading size="4">Miscellaneous</Heading>
<Flex gap="2">
<CamouflageButton
<GenericTankComponentButton
icon={asset('icons/camo.webp')}
selected={camouflage}
iconStyles={{
top: '50%',
left: '50%',
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
}}
first
last
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>
</ConfigurationChildWrapper>
);
+1 -1
View File
@@ -119,9 +119,9 @@ export default function Page({ params }: { params: { id: string } }) {
<Flex direction="column" gap="4">
<Modules />
<Crew />
<Equipments />
<Consumables />
<Crew />
<Provisions />
<Miscellaneous />
</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}
src={icon}
style={{
objectFit: 'contain',
width: 32,
height: 32,
position: 'absolute',
@@ -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 && (
<div
style={{
position: 'absolute',
width: '200%',
height: 12,
backgroundColor: banner,
top: '50%',
left: '-50%',
transform: 'translateY(-50%) rotate(-22.5deg)',
}}
/>
)}
{discriminator !== undefined && (
<Text
size="1"
@@ -25,5 +25,3 @@ export interface EquipmentDefinitions {
export const equipmentDefinitions = fetchCdonLz4<EquipmentDefinitions>(
asset('definitions/equipment.cdon.lz4'),
);
equipmentDefinitions.then(console.log);
+2
View File
@@ -69,6 +69,7 @@ interface TankopediaTemporary {
consumables: number[];
provisions: number[];
camouflage: boolean;
cooldownBooster: number;
shot?: Shot;
}
@@ -178,6 +179,7 @@ export const useTankopediaTemporary = create<TankopediaTemporary>()(
consumables: [],
provisions: [],
camouflage: false,
cooldownBooster: 0,
})),
);