Offroaders123 / NBTify

A library to read and write NBT files on the web!
http://npm.im/nbtify
MIT License
42 stars 5 forks source link

Minecraft Data Structure Type Definitions #6

Closed Offroaders123 closed 1 year ago

Offroaders123 commented 2 years ago

Not sure if it will be in this project or not yet (or it could be a separate repo), but I want to write type definitions for the Minecraft file data structures. One example could be the Bedrock level.dat file. It may look something like this:

import { NBTData, ByteTag, IntTag, StringTag } from "nbtify";

export interface BedrockLevel extends NBTData {
  name: "";
  endian: "little";
  compression: "none";
  data: {
    BiomeOverride: StringTag;
    CenterMapsToOrigin: ByteTag;
    ConfirmPlatformLockedContent: ByteTag;
    Difficulty: IntTag;
    FlatWorldLayers: StringTag;
    ForceGameType: ByteTag;
    GameType: IntTag;
    Generator: IntTag;
    // More entries...
  };
}
Offroaders123 commented 1 year ago

This is still a goal, but it will not be for this project specifically, so I'm closing the idea for it here.

Initially I planned on making a central repo for managing the logic for reading from all different Minecraft world formats, but maybe I will instead make 3 separate packages for each version; one for Bedrock, one for Java, and one for Legacy Console Edition. That sounds like possibly a nicer way to organize things, rather than trying to shoehorn all of the different versions' handling into a single codebase. That doesn't sound very nice.

The first idea consisted of working with world data using JavaScript objects to over-arch the platform differences, so you wouldn't have to worry about the underlying implementation itself, only the data that it holds. So, for Bedrock and Java, you could do Region.read(x: number, y: number, { platform: "bedrock" | "java" | "legacy-console"; }) or something like that, and it would get the Chunk entries inside of that specific Region offset, even though each platform may not specifically use that kind of data storage (Bedrock Edition). This kind of abstraction could be used to work with other parts of the data, which allows you to use simple function calls like that to do larger amounts of work with simple commands, like Chunk.from(BedrockChunk,{ platform: "java"; }) or things like that, which simply maps the JavaScript object-representation of that NBT/game data into the shape that the other platform can use, without having to manually do anything with the NBT format yourself.

Say if I take the multi-repo route, then each versions' NBT TypeScript definitions will have the type definitions unique to that version. So, the Bedrock level.dat file types would be defined in the Bedrock library, for example.

I think I will look into defining the logic for each program, separately in different packages/repos, then a central API can leverage those libraries together, providing a single, consistent API to work with the raw world data from a top-down view, or something like that.

Maybe instead of doing separate repos, I can simply use separate module folders, and it can still be under a singular package, I really like the sound of that too.

One more example of the top-down view would be the type definitions for each building block. Say, to differ a Bedrock chunk from a Java chunk, while still using the same object building block to manage each of them:

// This is a bit of an ugly API, but it allows the typing setup that I may try using for working with the world data.

export type Platform = "bedrock" | "java" | "legacy-console";

export interface BedrockChunkData {
  x: number;
  y: number;
  version: string;
}

export interface JavaChunkData {
  xPos: number;
  yPos: number;
  Version: number;
}

export interface LegacyConsoleChunkData {
  X: number;
  Y: number;
  Version: number;
}

export interface ChunkDataPlatformMap {
  "bedrock": BedrockChunkData;
  "java": JavaChunkData;
  "legacy-console": LegacyConsoleChunkData;
}

// Inspired by the typings for `ParentNode.querySelector()`
export declare class Chunk<K extends keyof ChunkDataPlatformMap> {
  platform: K;
  data: ChunkDataPlatformMap[K];

  // I think using further `interface` declarations here could help make the typings a bit nicer to work with.
  // Seeing `Chunk<"bedrock">` as a type isn't very neat, I'd rather have an interface of `BedrockChunk` or something like that.
  static read<K extends keyof ChunkDataPlatformMap>(data: Uint8Array, platform: K): Promise<Chunk<K>>;
}

const bedrockChunk = await Chunk.read(new Uint8Array(),"bedrock");
bedrockChunk.data.version;

const javaChunk = await Chunk.read(new Uint8Array(),"java");
javaChunk.data.xPos;

const legacyConsoleChunk = await Chunk.read(new Uint8Array(),"legacy-console");
legacyConsoleChunk.data.X;
Offroaders123 commented 1 year ago

This has been superseded by Region-Types!