# classic-node-protocol

**Minecraft Classic v7 / ClassiCube protocol library for Node.js**  
Full client & server support · CPE (Classic Protocol Extension) · Auth · Zero dependencies

```
npm install classic-node-protocol
```

> Requires Node.js ≥ 18 · v3.0.0

---

## Table of Contents

1. [Quick Start](#1-quick-start)
2. [Architecture Overview](#2-architecture-overview)
3. [createServer & ClassiCubeServer](#3-createserver--classiccubeserver)
4. [ClientConnection](#4-clientconnection)
5. [createClient & ClassiCubeClient](#5-createclient--classiccubeclient)
6. [PacketDecoder](#6-packetdecoder)
7. [encoder – Low-Level Packet Builders](#7-encoder--low-level-packet-builders)
8. [codec – Generic Encode/Decode Engine](#8-codec--generic-encodedecode-engine)
9. [protocol – Constants](#9-protocol--constants)
10. [level – Map Generation & Transmission](#10-level--map-generation--transmission)
11. [auth – Authentication & Heartbeat](#11-auth--authentication--heartbeat)
12. [cpe – Classic Protocol Extensions](#12-cpe--classic-protocol-extensions)
13. [jugadorUUID – UUID Utilities](#13-jugadoruuid--uuid-utilities)
14. [TypeScript Support](#14-typescript-support)
15. [Protocol Reference](#15-protocol-reference)

---

## 1. Quick Start

### Minimal server

```js
const { createServer, level, BLOCKS } = require('classic-node-protocol');

const srv = createServer({ port: 25565, pingInterval: 2000 });

srv.on('connection', async (client) => {
  // Greet and send a flat map
  client.sendIdentification('My Server', 'Welcome!');

  const blocks = level.buildFlatMap(64, 64, 64);
  await client.sendLevel(blocks, 64, 64, 64);

  client.spawnAt(-1, 'You', 32, 33, 32); // spawn self at center

  client.on('packet', (p) => {
    if (p.name === 'message') {
      srv.broadcastMessage(`<${client.username}> ${p.message}`);
    }
  });
});

srv.on('listening', ({ port }) => console.log(`Listening on :${port}`));
```

### Minimal bot (client)

```js
const { createClient } = require('classic-node-protocol');

const bot = createClient({ host: 'localhost', port: 25565 });

bot.on('connect', () => {
  bot.sendIdentification('MyBot');
});

bot.on('level', ({ blocks, xSize, ySize, zSize }) => {
  console.log(`Map received: ${xSize}×${ySize}×${zSize}`);
  bot.sendMessage('Hello from the bot!');
});

bot.on('packet', (p) => {
  if (p.name === 'message') console.log('[Chat]', p.message);
});
```

---

## 2. Architecture Overview

```
classic-node-protocol
│
├── createServer()  →  ClassiCubeServer
│                         └── emits ClientConnection per player
│
├── createClient()  →  ClassiCubeClient
│                         └── auto-assembles level, emits 'level' event
│
├── PacketDecoder   →  streaming parser (EventEmitter, emits 'packet')
│
├── encoder         →  pure functions → Buffer  (no I/O)
│                         └── thin façade over codec.js
│
├── codec           →  generic encode/decode engine  ← NEW in v3
│                         └── driven by protocol.json
│
├── types           →  primitive type handlers (u8, i16, string, …)  ← NEW in v3
│
├── protocol.json   →  single source of truth for ALL packet schemas  ← NEW in v3
│                         (Classic v7 + full CPE)
│
├── protocol        →  JS constants: packet IDs, sizes, BLOCKS, CPE_EXTENSIONS…
│
├── level           →  map builders + gzip compression pipeline
│
├── auth            →  ClassiCubeAuth (server) + ClassiCubeAccount (bot login)
│
├── cpe             →  all 24+ CPE extension encoders/decoders
│                         └── thin façade over codec.js
│
└── jugadorUUID     →  deterministic UUID generation from username
```

Everything is **CommonJS** (`require()`), zero runtime dependencies, Node ≥ 18.

### v3 internals

In v3 all serialization logic lives in `protocol.json` (packet field schemas) and `lib/codec.js` (generic read/write engine). `encoder.js` and `cpe.js` are now thin wrappers that map positional arguments to `codec.encode()` / `codec.decode()` calls — the public API is fully backward-compatible.

To **add a new packet** you only need to edit `protocol.json`:

```json
// protocol.json → "server" → "toClient"
"myNewPacket": {
  "id": 99,
  "fields": [
    { "name": "entityId", "type": "u8"    },
    { "name": "label",    "type": "string" },
    { "name": "value",    "type": "i32"   }
  ]
}
```

Then use it immediately:

```js
const { codec } = require('classic-node-protocol');

const buf = codec.encode('server', 'toClient', 'myNewPacket', {
  entityId: 3,
  label: 'score',
  value: 1000,
});

const pkt = codec.decode('server', 'toClient', 99, buf);
// → { name: 'myNewPacket', id: 99, entityId: 3, label: 'score', value: 1000 }
```

Available primitive types: `u8`, `i8`, `u16`, `i16`, `i32`, `string` (64-byte padded ASCII), `bytes` (fixed-length raw), `u8array` (fixed-count), `i32array` (fixed-count).

---

## 3. `createServer` & `ClassiCubeServer`

### Factory

```js
const { createServer } = require('classic-node-protocol');

const server = createServer({
  port:         25565,    // if provided with autoListen !== false, starts listening immediately
  host:         '0.0.0.0',
  maxClients:   20,       // 0 = unlimited (default)
  pingInterval: 2000,     // ms between automatic pings to ALL clients (0 = off)
});
```

### Manual instantiation

```js
const { ClassiCubeServer } = require('classic-node-protocol');

const server = new ClassiCubeServer({ maxClients: 10, pingInterval: 5000 });
await server.listen(25565, '0.0.0.0');
```

### `ClassiCubeServer` events

| Event | Callback signature | Description |
|---|---|---|
| `'listening'` | `({ port, host })` | Server is accepting connections |
| `'connection'` | `(client: ClientConnection)` | New TCP connection |
| `'disconnect'` | `(client: ClientConnection)` | A client disconnected cleanly |
| `'clientError'` | `(client, err)` | A client-level socket error |
| `'error'` | `(err)` | Server-level error (e.g. EADDRINUSE) |

### `ClassiCubeServer` properties & methods

```js
server.clients          // Map<number, ClientConnection> — all live connections
server.playerCount      // number — shorthand for server.clients.size

await server.listen(port, host)   // start accepting connections
await server.close()              // stop accepting; existing sockets stay open
await server.shutdown(reason)     // disconnect everyone then close

server.broadcast(buf)                  // send raw Buffer to every client
server.broadcastMessage(msg, senderId) // broadcast chat (senderId default 0xFF = server)
server.broadcastExcept(excludeId, buf) // broadcast to everyone except one client id
server.relayMessage(fromId, message)   // relay a player's chat to everyone else
server.pingAll()                       // send ping packet to all clients
```

### Example: complete multiplayer relay

```js
const { createServer, level } = require('classic-node-protocol');
const { encoder } = require('classic-node-protocol');

const srv   = createServer({ port: 25565, pingInterval: 3000 });
const world = level.buildFlatMap(128, 64, 128);

srv.on('connection', async (client) => {
  // 1. Handshake
  client.sendIdentification('Demo Server', 'Have fun!');

  // 2. Wait for player identification
  await new Promise((resolve) => {
    client.once('packet', (p) => {
      if (p.name === 'identification') {
        client.username = p.username;
        resolve();
      }
    });
  });

  // 3. Send map
  await client.sendLevel(world, 128, 64, 128);

  // 4. Spawn self + all existing players
  client.spawnAt(-1, client.username, 64, 33, 64);
  for (const [id, other] of srv.clients) {
    if (id === client.id) continue;
    client.spawnAt(other.id, other.username, 64, 33, 64);
    other.spawnAt(client.id, client.username, 64, 33, 64);
  }

  // 5. Handle packets
  client.on('packet', (p) => {
    switch (p.name) {
      case 'message':
        srv.broadcastMessage(`<${client.username}> ${p.message}`);
        break;
      case 'setBlock':
        world[level.blockIndex(p.x, p.z, p.y, 128, 128)] = p.mode === 1 ? p.blockType : 0;
        srv.broadcast(encoder.encodeServerSetBlock(p.x, p.y, p.z,
          p.mode === 1 ? p.blockType : 0));
        break;
      case 'position':
        srv.broadcastExcept(client.id,
          encoder.encodePosition(client.id, p.x, p.y, p.z, p.yaw, p.pitch));
        break;
    }
  });
});

srv.on('disconnect', (client) => {
  srv.broadcastMessage(`${client.username} left the game`);
  srv.broadcast(encoder.encodeDespawnPlayer(client.id));
});
```

---

## 4. `ClientConnection`

Every player that connects is represented by a `ClientConnection` instance. You receive it in the `'connection'` event.

### Properties

```js
client.id             // number  — unique session ID (assigned by server)
client.socket         // net.Socket
client.username       // string | null  — set it yourself after identification
client.state          // 'login' | 'level' | 'play'
client.data           // {}  — free-form storage for your app (e.g. health, position)

client.remoteAddress  // string — client IP
client.remotePort     // number
client.isConnected    // boolean

// CPE fields (populated during CPE negotiation)
client.supportsCpe    // boolean — did client send unused=0x42 in identification?
client.cpeReady       // boolean — CPE negotiation fully complete
client.extensions     // Set<string> — extension names both sides agreed on
```

### Sending packets (server → client)

```js
// ── Core ──────────────────────────────────────────────────────────────────────
client.sendIdentification(serverName, motd, userType)  // 0x00
client.sendPing()                                       // 0x01
client.sendLevelInitialize()                            // 0x02  (sets state='level')
client.sendLevelDataChunk(chunkBuf, percentComplete)   // 0x03
client.sendLevelFinalize(xSize, ySize, zSize)          // 0x04  (sets state='play')
client.sendSetBlock(x, y, z, blockType)                // 0x06
client.sendSpawnPlayer(id, name, x, y, z, yaw, pitch) // 0x07  (FShort coords)
client.spawnAt(id, name, bx, by, bz, yaw, pitch)      // 0x07  (block coords, auto-converts)
client.sendPosition(id, x, y, z, yaw, pitch)           // 0x08  teleport (FShort)
client.sendPositionOrientation(id, dx, dy, dz, yaw, pitch) // 0x09 relative+rotation
client.sendPositionUpdate(id, dx, dy, dz)              // 0x0A  relative only
client.sendOrientationUpdate(id, yaw, pitch)           // 0x0B  rotation only
client.sendDespawnPlayer(playerId)                     // 0x0C
client.sendMessage(senderId, message)                  // 0x0D  any sender
client.sendServerMessage(message)                      // 0x0D  sender = 0xFF (server)
client.disconnect(reason)                              // 0x0E + socket.destroy()
client.sendUpdateUserType(userType)                    // 0x0F
client.setOperator(isOp)                               // 0x0F  convenience wrapper

// ── Level helper (all-in-one) ─────────────────────────────────────────────────
await client.sendLevel(blocks, xSize, ySize, zSize, zlibOpts)
// Sends: LevelInitialize → N×LevelDataChunk → LevelFinalize
// zlibOpts: e.g. { level: 9 } for maximum compression

// ── CPE (see section 11) ──────────────────────────────────────────────────────
client.sendCpeHandshake(appName, extensions)
client.sendExtInfo(appName, count)
client.sendExtEntry(extName, version)
client.sendSetClickDistance(distance)
client.sendCustomBlockSupportLevel(level)
client.sendHoldThis(blockToHold, preventChange)
client.sendSetTextHotKey(label, action, keyCode, keyMods)
client.sendExtAddPlayerName(nameId, playerName, listName, groupName, groupRank)
client.sendExtAddEntity2(entityId, inGameName, skinName, x, y, z, yaw, pitch)
client.sendExtRemovePlayerName(nameId)
client.sendEnvSetColor(variable, r, g, b)
client.sendMakeSelection(id, label, x1,y1,z1, x2,y2,z2, r,g,b,a)
client.sendRemoveSelection(selectionId)
client.sendSetBlockPermission(blockType, allowPlace, allowDestroy)
client.sendChangeModel(entityId, modelName)
client.sendSetMapEnvAppearance(textureUrl, sideBlock, edgeBlock, sideLevel)
client.sendEnvSetWeatherType(weatherType)
client.sendHackControl(flying, noClip, speeding, spawnControl, thirdPersonView, jumpHeight)
client.sendDefineBlock(def)
client.sendDefineBlockExt(def)
client.sendRemoveBlockDefinition(blockId)
client.sendBulkBlockUpdate(updates)
client.sendSetTextColor(code, r, g, b, a)
client.sendSetMapEnvUrl(textureUrl)
client.sendSetMapEnvProperty(property, value)
client.sendSetEntityProperty(entityId, propertyType, value)
client.sendTwoWayPing(direction, data)
client.sendSetInventoryOrder(order, blockType)
```

### Receiving packets

```js
client.on('packet', (packet) => {
  // packet.name — string key, e.g. 'identification', 'setBlock', 'position', 'message'
  // packet.id   — raw packet byte ID
  console.log(packet);
});

client.on('end',   ()    => console.log('client disconnected'));
client.on('error', (err) => console.error('client error:', err));
```

### Low-level write

```js
const { encoder } = require('classic-node-protocol');
client.writeBuffer(encoder.encodePing()); // send any raw Buffer
```

---

## 5. `createClient` & `ClassiCubeClient`

Use this to build bots or terminal clients that connect to any ClassiCube-compatible server.

### Factory

```js
const { createClient } = require('classic-node-protocol');

// Auto-connects immediately when host+port are provided:
const bot = createClient({
  host:           'example.com',
  port:           25565,
  connectTimeout: 10000,  // ms before giving up (default 10 s)
  pingInterval:   0,      // ms between client-side pings (0 = off)
});
```

### Manual instantiation + connect

```js
const { ClassiCubeClient } = require('classic-node-protocol');

const client = new ClassiCubeClient({ connectTimeout: 5000 });
await client.connect('localhost', 25565);
client.sendIdentification('MyBot', '-');
```

### `ClassiCubeClient` events

| Event | Callback | Description |
|---|---|---|
| `'connect'` | `()` | TCP handshake complete |
| `'end'` | `()` | Connection closed |
| `'error'` | `(err)` | Socket or decode error |
| `'packet'` | `(packet)` | Any decoded server packet |
| `'level'` | `({ blocks, xSize, ySize, zSize })` | Full map assembled & decompressed |
| `'levelProgress'` | `(percent: number)` | Map download progress 0–100 |
| `'ping'` | `()` | Ping interval tick (if pingInterval > 0) |

### Sending packets (client → server)

```js
// Core protocol
bot.sendIdentification(username, verificationKey, unused)
// verificationKey: use '-' for offline servers, or real MPPass for online mode
// unused: 0x00 for vanilla, 0x42 (CPE_MAGIC) to signal CPE support

bot.sendSetBlock(x, y, z, mode, blockType)
// mode: 0 = destroy, 1 = create

bot.sendPosition(x, y, z, yaw, pitch)       // FShort coordinates
bot.sendPositionBlocks(bx, by, bz, yaw, pitch) // block coordinates (auto-converts)
bot.sendMessage(message)

// CPE
bot.sendCpeIdentification(username, verificationKey, extensions)
bot.sendExtInfo(appName, extensionCount)
bot.sendExtEntry(extName, version)
bot.sendCustomBlockSupportLevel(level)
bot.sendPlayerClicked(button, action, yaw, pitch, targetId, targetX, targetY, targetZ, targetFace)
bot.sendTwoWayPing(direction, data)

bot.disconnect()   // destroy socket
bot.writeBuffer(buf) // raw send
```

### Full bot example (online mode with auth)

```js
const { ClassiCubeClient, ClassiCubeAccount } = require('classic-node-protocol');

async function main() {
  const account = new ClassiCubeAccount();
  await account.login('BotUsername', 'BotPassword');

  const servers = await account.getServers();
  const target  = servers.find(s => s.name.includes('Survival'));

  const bot = new ClassiCubeClient({ connectTimeout: 8000 });
  await bot.connect(target.ip, target.port);

  bot.sendIdentification(account.username, target.mppass);

  bot.on('level', ({ xSize, ySize, zSize }) => {
    console.log(`Joined! Map: ${xSize}×${ySize}×${zSize}`);
    bot.sendMessage('Hello everyone!');
  });

  bot.on('packet', (p) => {
    if (p.name === 'message')    console.log('[Chat]', p.message);
    if (p.name === 'disconnect') console.log('[Kick]', p.reason);
  });
}

main().catch(console.error);
```

---

## 6. `PacketDecoder`

The streaming packet parser used internally by both `ClassiCubeClient` and `ClientConnection`. You can also use it standalone for custom transports.

```js
const { PacketDecoder } = require('classic-node-protocol');

// direction 'client' → you're the server, parsing packets FROM a client
// direction 'server' → you're the client, parsing packets FROM the server
const decoder = new PacketDecoder('server');

decoder.on('packet', (packet) => {
  console.log(packet.name, packet);
});

decoder.on('error', (err) => {
  console.error('Decode error:', err.message);
});

// Feed raw TCP data — handles fragmentation and coalescing automatically
socket.on('data', (data) => decoder.receive(data));
```

### 0x1D disambiguation (ExtAddEntity2 vs ChangeModel)

Both packets share ID `0x1D` but have different sizes (138 vs 66 bytes). The decoder resolves this automatically by matching the buffer length — no manual configuration needed.

---

## 7. `encoder` – Low-Level Packet Builders

Every function returns a `Buffer` ready to write to a socket. No side effects.

```js
const { encoder } = require('classic-node-protocol');
// or
const enc = require('classic-node-protocol/encoder');
```

### Coordinate helpers

```js
encoder.toFShort(blockPos)   // block float → FShort integer (×32)
encoder.fromFShort(fshort)   // FShort → block float (÷32)
// Example: block position 5.5 → FShort 176
```

### String helpers

```js
encoder.writeString(buf, offset, str)  // write 64-byte ASCII string into buf
encoder.readString(buf, offset)        // read 64-byte ASCII string, trim trailing spaces
```

### Server → Client encoders

```js
encoder.encodeServerIdentification(serverName, motd, userType)
// → 131-byte Buffer

encoder.encodePing()
// → 1-byte Buffer

encoder.encodeLevelInitialize()
// → 1-byte Buffer

encoder.encodeLevelDataChunk(chunkData, percentComplete)
// chunkData: Buffer ≤ 1024 bytes of gzipped map data
// → 1028-byte Buffer

encoder.encodeLevelFinalize(xSize, ySize, zSize)
// → 7-byte Buffer

encoder.encodeServerSetBlock(x, y, z, blockType)
// → 8-byte Buffer

encoder.encodeSpawnPlayer(playerId, playerName, x, y, z, yaw, pitch)
// x/y/z: FShort coordinates
// playerId: -1 (0xFF) = spawn as self
// → 74-byte Buffer

encoder.encodePosition(playerId, x, y, z, yaw, pitch)
// Absolute teleport — FShort coordinates
// → 10-byte Buffer

encoder.encodePositionOrientation(playerId, dx, dy, dz, yaw, pitch)
// Relative position (signed byte deltas) + absolute rotation
// → 7-byte Buffer

encoder.encodePositionUpdate(playerId, dx, dy, dz)
// Relative position only
// → 5-byte Buffer

encoder.encodeOrientationUpdate(playerId, yaw, pitch)
// → 4-byte Buffer

encoder.encodeDespawnPlayer(playerId)
// → 2-byte Buffer

encoder.encodeServerMessage(playerId, message)
// playerId: 0xFF = server message
// → 66-byte Buffer

encoder.encodeDisconnect(reason)
// → 65-byte Buffer

encoder.encodeUpdateUserType(userType)
// userType: 0x00 = normal, 0x64 = operator
// → 2-byte Buffer
```

### Client → Server encoders

```js
encoder.encodeClientIdentification(username, verificationKey, unused)
// unused: 0x00 = vanilla, 0x42 = CPE
// → 131-byte Buffer

encoder.encodeClientSetBlock(x, y, z, mode, blockType)
// mode: 0=destroy 1=create
// → 9-byte Buffer

encoder.encodeClientPosition(x, y, z, yaw, pitch)
// Player ID is auto-set to 0xFF (self)
// → 10-byte Buffer

encoder.encodeClientMessage(message)
// → 66-byte Buffer
```

---

## 8. `codec` – Generic Encode/Decode Engine

The codec is the v3 engine that powers all serialization. You can use it directly to work with packets by name, without manual Buffer arithmetic.

```js
const { codec } = require('classic-node-protocol');
```

### `codec.encode(section, direction, name, data)`

```js
// section:   'server' | 'client' | 'cpe'
// direction: 'toClient' | 'toServer' | 'server' | 'client'
// name:      packet name matching protocol.json
// data:      field values object

const buf = codec.encode('server', 'toClient', 'spawnPlayer', {
  playerId:   1,
  playerName: 'Player1',
  x: 320, y: 320, z: 320,
  yaw: 128, pitch: 0,
});
// → 74-byte Buffer, identical to encoder.encodeSpawnPlayer(...)
```

### `codec.decode(section, direction, id, buf)`

```js
const pkt = codec.decode('server', 'toClient', 0x08, buf);
// → { name: 'position', id: 8, playerId: 1, x: 320, y: 320, z: 320, yaw: 128, pitch: 0 }
```

### `codec.decodeByName(section, direction, name, buf)`

```js
const pkt = codec.decodeByName('cpe', 'server', 'hackControl', buf);
// → { name: 'hackControl', id: 32, flying: 1, noClip: 0, ... }
```

### Query helpers

```js
codec.packetSize('server', 'toClient', 0x03)   // → 1028  (levelDataChunk)
codec.packetId('cpe', 'server', 'hackControl') // → 32
codec.sizeTable('server', 'toClient')          // → { 0: 131, 1: 1, 2: 1, ... }
```

### `codec.schema`

Direct access to the parsed `protocol.json` object. Useful for tooling, validation, or generating documentation.

```js
const { codec } = require('classic-node-protocol');
const spawnDef = codec.schema.server.toClient.spawnPlayer;
// → { id: 7, fields: [ { name: 'playerId', type: 'i8' }, ... ] }
```

---

## 9. `protocol` – Constants

```js
const { protocol, BLOCKS, BLOCK_NAMES, BLOCK_MODE, USER_TYPE, CPE_MAGIC,
        CPE_EXTENSIONS, CPE_PACKETS } = require('classic-node-protocol');
// or
const protocol = require('classic-node-protocol/lib/protocol');
```

### Packet ID tables

```js
protocol.CLIENT_PACKETS        // { IDENTIFICATION: 0x00, SET_BLOCK: 0x05, ... }
protocol.SERVER_PACKETS        // { IDENTIFICATION: 0x00, PING: 0x01, ... }
protocol.CLIENT_PACKET_SIZES   // { 0x00: 131, 0x05: 9, 0x08: 10, 0x0D: 66 }
protocol.SERVER_PACKET_SIZES   // { 0x00: 131, 0x01: 1, 0x02: 1, ... }
protocol.CPE_PACKETS           // { EXT_INFO: 0x10, EXT_ENTRY: 0x11, ... }
protocol.CPE_SERVER_PACKET_SIZES
protocol.CPE_CLIENT_PACKET_SIZES
protocol.STRING_LENGTH         // 64
protocol.PROTOCOL_VERSION      // 7 (0x07)
```

### Block type constants

All 50 Classic blocks (IDs 0–49) are exposed as named constants:

```js
BLOCKS.AIR              // 0
BLOCKS.STONE            // 1
BLOCKS.GRASS            // 2
BLOCKS.DIRT             // 3
BLOCKS.COBBLESTONE      // 4
BLOCKS.WOOD_PLANKS      // 5
BLOCKS.SAPLING          // 6
BLOCKS.BEDROCK          // 7
BLOCKS.WATER_FLOWING    // 8
BLOCKS.WATER            // 9
BLOCKS.LAVA_FLOWING     // 10
BLOCKS.LAVA             // 11
BLOCKS.SAND             // 12
BLOCKS.GRAVEL           // 13
BLOCKS.GOLD_ORE         // 14
BLOCKS.IRON_ORE         // 15
BLOCKS.COAL_ORE         // 16
BLOCKS.LOG              // 17
BLOCKS.LEAVES           // 18
BLOCKS.SPONGE           // 19
BLOCKS.GLASS            // 20
BLOCKS.RED_CLOTH        // 21
BLOCKS.ORANGE_CLOTH     // 22
BLOCKS.YELLOW_CLOTH     // 23
BLOCKS.LIME_CLOTH       // 24
BLOCKS.GREEN_CLOTH      // 25
BLOCKS.TEAL_CLOTH       // 26
BLOCKS.AQUA_CLOTH       // 27
BLOCKS.CYAN_CLOTH       // 28
BLOCKS.BLUE_CLOTH       // 29
BLOCKS.INDIGO_CLOTH     // 30
BLOCKS.VIOLET_CLOTH     // 31
BLOCKS.MAGENTA_CLOTH    // 32
BLOCKS.PINK_CLOTH       // 33
BLOCKS.BLACK_CLOTH      // 34
BLOCKS.GRAY_CLOTH       // 35
BLOCKS.WHITE_CLOTH      // 36
BLOCKS.DANDELION        // 37
BLOCKS.ROSE             // 38
BLOCKS.BROWN_MUSHROOM   // 39
BLOCKS.RED_MUSHROOM     // 40
BLOCKS.GOLD_BLOCK       // 41
BLOCKS.IRON_BLOCK       // 42
BLOCKS.DOUBLE_SLAB      // 43
BLOCKS.SLAB             // 44
BLOCKS.BRICK            // 45
BLOCKS.TNT              // 46
BLOCKS.BOOKSHELF        // 47
BLOCKS.MOSSY_COBBLE     // 48
BLOCKS.OBSIDIAN         // 49
```

Reverse lookup (ID → name):

```js
BLOCK_NAMES[2]  // → 'GRASS'
BLOCK_NAMES[49] // → 'OBSIDIAN'
```

### Other constants

```js
BLOCK_MODE.DESTROY  // 0x00 — used in SetBlock
BLOCK_MODE.CREATE   // 0x01

USER_TYPE.NORMAL    // 0x00
USER_TYPE.OP        // 0x64

CPE_MAGIC           // 0x42 — send as `unused` in client identification to signal CPE
```

### CPE extension registry

All 26 known extensions with their canonical version numbers:

```js
CPE_EXTENSIONS = {
  ClickDistance:        1,
  CustomBlocks:         1,
  HeldBlock:            1,
  EmoteFix:             1,
  TextHotKey:           1,
  ExtPlayerList:        2,
  EnvColors:            1,
  SelectionCuboid:      1,
  BlockPermissions:     1,
  ChangeModel:          1,
  EnvMapAppearance:     2,
  EnvWeatherType:       1,
  HackControl:          1,
  MessageTypes:         1,
  PlayerClick:          1,
  LongerMessages:       1,
  FullCP437:            1,
  BlockDefinitions:     1,
  BlockDefinitionsExt:  2,
  BulkBlockUpdate:      1,
  TextColors:           1,
  EnvMapAspect:         1,
  EntityProperty:       1,
  ExtEntityPositions:   1,
  TwoWayPing:           1,
  InventoryOrder:       1,
}
```

---

## 10. `level` – Map Generation & Transmission

```js
const { level } = require('classic-node-protocol');
// or
const level = require('classic-node-protocol/level');
```

### Map coordinate formula

Classic uses **x + z×xSize + y×xSize×zSize** (X fastest, Y slowest = Y is up).

```js
level.blockIndex(x, z, y, xSize, zSize)
// returns the buffer index for block at (x, y, z)

// Example:
const idx = level.blockIndex(10, 5, 20, 64, 64);
world[idx] = BLOCKS.STONE;
```

### Built-in map generators

#### `buildFlatMap` — classic superflat

```js
const blocks = level.buildFlatMap(
  xSize,   // width
  ySize,   // height
  zSize,   // depth
  groundY  // optional: Y of grass surface (default: Math.floor(ySize / 2))
);
// Layout: bedrock at y=0, dirt below groundY-1, grass at groundY-1, air above
```

#### `buildSphereMap` — hollow sphere

```js
const blocks = level.buildSphereMap(
  xSize, ySize, zSize,
  radius,     // optional: outer radius (default: min(dim)/2 - 2)
  shell,      // optional: shell thickness in blocks (default: 2)
  blockType   // optional: block for the shell (default: BLOCKS.STONE)
);
// Good for spaceship / planet demos
```

#### `buildCheckerMap` — checkerboard testing floor

```js
const blocks = level.buildCheckerMap(xSize, ySize, zSize);
// y=0: alternating WHITE_CLOTH and BLACK_CLOTH
// y=ySize-1: solid STONE ceiling
// Everything else: AIR
```

#### `buildMap` — custom generator

```js
const blocks = level.buildMap(
  xSize, ySize, zSize,
  (x, y, z) => {
    // return block type for this position, 0 = air
    if (y === 0) return BLOCKS.BEDROCK;
    if (y < 10)  return BLOCKS.DIRT;
    return BLOCKS.AIR;
  }
);
```

### Level compression pipeline (server side)

```js
// Step 1: compress + prepend 4-byte header
const gzipped = await level.compressLevel(blocks, { level: 6 });

// Step 2: split into ≤1024-byte chunks
const chunks = level.chunkLevel(gzipped);
// chunks = [{ chunk: Buffer, percent: number }, ...]

// Or combine both steps:
const chunks = await level.prepareLevel(blocks, { level: 6 });
```

Sending manually (what `client.sendLevel()` does internally):

```js
client.sendLevelInitialize();
const chunks = await level.prepareLevel(blocks);
for (const { chunk, percent } of chunks) {
  client.sendLevelDataChunk(chunk, percent);
}
client.sendLevelFinalize(xSize, ySize, zSize);
```

### Level reassembly (client side)

`ClassiCubeClient` does this automatically and emits `'level'`. If you need it manually:

```js
const assembler = new level.LevelAssembler();

// on 'levelInitialize':
assembler.reset();

// on each 'levelDataChunk':
assembler.push(p.chunkData, p.chunkLength);
console.log(`Progress: ${assembler.byteLength} bytes buffered`);

// on 'levelFinalize':
const rawBlocks = await assembler.decompress();
// rawBlocks is the flat Buffer of block IDs
```

### Raw zlib helpers

```js
const gzipBuf = await level.gzip(inputBuffer, { level: 9 });
const rawBuf  = await level.gunzip(gzipBuf);
```

---

## 11. `auth` – Authentication & Heartbeat

```js
const {
  ClassiCubeAuth,
  ClassiCubeAccount,
  generateSalt,
  computeMPPass,
  verifyMPPass,
} = require('classic-node-protocol');
```

### Standalone MPPass helpers

These work without any class instance.

```js
// Generate a random 16-char alphanumeric salt
const salt = generateSalt();        // e.g. 'aB3xQzLm9rTpWkYn'
const salt = generateSalt(32);      // longer if you want

// Compute the expected MPPass: MD5(salt + username) as lowercase hex
const expected = computeMPPass(salt, 'PlayerName');

// Verify a player's submission (case-insensitive comparison)
const ok = verifyMPPass(salt, 'PlayerName', playerMppass);
// → true or false
```

### `ClassiCubeAuth` — server-side auth & heartbeat

```js
const auth = new ClassiCubeAuth({
  name:       'My ClassiCube Server',  // required — shown in server list
  port:       25565,                   // required
  maxPlayers: 20,                      // default: 20
  public:     true,                    // default: true — show in public list
  software:   'classic-node-protocol', // optional
  web:        false,                   // set true if accessible via CC web client
  salt:       'customsalt',            // optional — auto-generated if omitted
});

auth.salt  // the salt string (share this with startHeartbeat)
```

**Verify a player on join:**

```js
client.on('packet', (p) => {
  if (p.name === 'identification') {
    if (!auth.verify(p.username, p.verificationKey)) {
      client.disconnect('Invalid login. Please connect via classicube.net');
      return;
    }
    // verified — proceed with login
  }
});
```

**Register on the server list:**

```js
// Start periodic heartbeat (every 45 s) and get the play URL
const { url, error } = await auth.startHeartbeat(() => server.playerCount);

if (error) {
  console.warn('Heartbeat warning:', error);
  // Server still works on LAN. Usually means port not forwarded.
} else {
  console.log('Server URL:', url);
  // → https://www.classicube.net/server/play/abc123hash/
}

// Manual single heartbeat (optional):
const result = await auth.sendHeartbeat(playerCount);

// Stop heartbeats:
auth.stopHeartbeat();

// Get the last known URL:
auth.serverUrl  // string | null
```

**Debug: compute what MPPass you expect from a player:**

```js
const expected = auth.computeExpected('PlayerName');
```

### `ClassiCubeAccount` — bot / client login

```js
const account = new ClassiCubeAccount();

// Log in with a real classicube.net account
const { verified } = await account.login('BotUsername', 'BotPassword');
// verified: false if account email is not confirmed (mppasses will be empty)

account.username  // string — confirmed username from server
account.loggedIn  // boolean
account.verified  // boolean
```

**Browse and join servers:**

```js
// Get all servers in the server list (with your personal mppass for each)
const servers = await account.getServers();
// servers: ClassiCubeServerInfo[]

// Fetch info for one specific server by its hash
const server = await account.getServer('abc123hash');

// Find a server by name substring
const server = await account.findServer('Survival');
```

**`ClassiCubeServerInfo` shape:**

```js
{
  hash:       'abc123hash',
  name:       'My Survival Server',
  ip:         '123.45.67.89',
  port:       25565,
  mppass:     'd3ad8ee0f...',   // use this as verificationKey when connecting
  software:   'MCGalaxy',
  players:    5,
  maxPlayers: 32,
  online:     true,
  playUrl:    'https://www.classicube.net/server/play/abc123hash/'
}
```

**Full bot flow:**

```js
const account = new ClassiCubeAccount();
await account.login('BotUser', 'pass');

const server = await account.findServer('Build World');
const bot    = new ClassiCubeClient();
await bot.connect(server.ip, server.port);

bot.sendIdentification(account.username, server.mppass);
```

---

## 12. `cpe` – Classic Protocol Extensions

```js
const { cpe, CPE_EXTENSIONS, CPE_PACKETS } = require('classic-node-protocol');
```

CPE allows servers to negotiate optional features with clients. The handshake happens right after the client's identification packet (if `unused === 0x42`).

### CPE negotiation flow

**Server side:**

```js
srv.on('connection', (client) => {
  client.on('packet', (p) => {
    if (p.name === 'identification') {
      if (p.unused === 0x42) {
        // Client supports CPE
        client.supportsCpe = true;
        client.sendCpeHandshake('MyServer', ['EnvColors', 'HackControl', 'HeldBlock']);
      }
      // Continue with normal identification response...
      client.sendIdentification('MyServer', 'Welcome');
    }

    if (p.name === 'extEntry') {
      client.extensions.add(p.extName);
    }
  });
});
```

**Client side:**

```js
bot.sendCpeIdentification(
  'BotName',
  '-',
  ['EnvColors', 'HackControl'] // subset of extensions you want to use
  // or omit to advertise all known extensions
);
```

### Low-level CPE encoding

All CPE packets are available as pure functions on the `cpe` module:

```js
// Negotiation (both directions)
cpe.encodeExtInfo(appName, extensionCount)   // → 67-byte Buffer
cpe.encodeExtEntry(extName, version)         // → 69-byte Buffer
cpe.buildExtensionHandshake(appName, extensions) // → Buffer[] (ExtInfo + N×ExtEntry)

// Decoding
cpe.decodeExtInfo(buf)   // → { name: 'extInfo', appName, extensionCount }
cpe.decodeExtEntry(buf)  // → { name: 'extEntry', extName, version }
```

### Extension reference

#### ClickDistance

```js
client.sendSetClickDistance(160)  // 160 units = 5 blocks (default)
client.sendSetClickDistance(32)   // 1 block range (very restrictive)
```

#### CustomBlocks

```js
client.sendCustomBlockSupportLevel(1)  // unlock IDs 50–65
```

#### HeldBlock

```js
client.sendHoldThis(BLOCKS.STONE, 0)  // give player stone, allow switching
client.sendHoldThis(BLOCKS.STONE, 1)  // lock them to stone
client.sendHoldThis(0, 0)             // hide hand
```

#### TextHotKey

```js
client.sendSetTextHotKey(
  'Say Hello',  // label
  '/say Hello', // text to type (use '\n' to auto-send)
  0x48,         // key code (LWJGL — H key)
  0             // modifiers: 0=none, 1=Ctrl, 2=Shift, 4=Alt (combinable)
);
```

#### ExtPlayerList

```js
// Add player to tab list
client.sendExtAddPlayerName(
  nameId,      // unique short ID
  'PlayerName', // in-game username
  '&aPlayerName', // tab-list display name (supports color codes)
  'Admins',    // group name
  0            // group rank (lower = higher in list)
);

// Spawn with custom skin
client.sendExtAddEntity2(entityId, 'PlayerName', 'SkinName', x, y, z, yaw, pitch);

// Remove from tab list
client.sendExtRemovePlayerName(nameId);
```

#### EnvColors

```js
const { ENV_COLOR } = require('classic-node-protocol');

// ENV_COLOR constants:
// SKY=0  CLOUD=1  FOG=2  AMBIENT=3  SUNLIGHT=4

client.sendEnvSetColor(ENV_COLOR.SKY,      135, 206, 235); // sky blue
client.sendEnvSetColor(ENV_COLOR.CLOUD,    255, 255, 255); // white clouds
client.sendEnvSetColor(ENV_COLOR.FOG,      180, 180, 180);
client.sendEnvSetColor(ENV_COLOR.AMBIENT,   40,  40,  40);
client.sendEnvSetColor(ENV_COLOR.SUNLIGHT, 255, 255, 200);
client.sendEnvSetColor(ENV_COLOR.SKY, -1, -1, -1);        // reset to default
```

#### SelectionCuboid

```js
// Highlight a region in the world
client.sendMakeSelection(
  0,          // selectionId (0–255)
  'My Region', // label shown on hover
  0, 0, 0,    // start corner (x1, y1, z1)
  10, 10, 10, // end corner   (x2, y2, z2)
  255, 0, 0,  // color RGB
  128         // alpha (0–255)
);

client.sendRemoveSelection(0); // remove by id
```

#### BlockPermissions

```js
client.sendSetBlockPermission(BLOCKS.BEDROCK, 0, 0); // cannot place or break bedrock
client.sendSetBlockPermission(BLOCKS.GRASS,   1, 1); // can do both
```

#### ChangeModel

```js
client.sendChangeModel(entityId, 'chicken');
client.sendChangeModel(entityId, 'zombie');
client.sendChangeModel(entityId, 'humanoid'); // reset to default player model
```

#### EnvWeatherType

```js
const { WEATHER } = require('classic-node-protocol');
// WEATHER.SUNNY=0  WEATHER.RAINING=1  WEATHER.SNOWING=2

client.sendEnvSetWeatherType(WEATHER.RAINING);
```

#### HackControl

> ⚠️ ClassiCube only processes this packet AFTER the first `LevelDataChunk` has been sent.

```js
client.sendHackControl(
  1,    // flying allowed (1/0)
  1,    // noclip allowed
  1,    // speeding allowed
  1,    // spawn control allowed
  1,    // third-person view allowed
  -1    // jump height in player-space units (-1 = default)
);
```

#### EnvMapAspect

```js
const { MAP_ENV_PROPERTY } = require('classic-node-protocol');

// MAP_ENV_PROPERTY constants:
// SIDE_BLOCK=0  EDGE_BLOCK=1  EDGE_HEIGHT=2  CLOUD_HEIGHT=3
// MAX_FOG=4     CLOUD_SPEED=5 WEATHER_SPEED=6 WEATHER_FADE=7
// EXP_FOG=8     SIDE_OFFSET=9

client.sendSetMapEnvUrl('https://example.com/textures.zip');
client.sendSetMapEnvUrl('');                                  // reset to default

client.sendSetMapEnvProperty(MAP_ENV_PROPERTY.CLOUD_HEIGHT, 128);
client.sendSetMapEnvProperty(MAP_ENV_PROPERTY.EDGE_HEIGHT,  32);
client.sendSetMapEnvProperty(MAP_ENV_PROPERTY.MAX_FOG,      512);
```

#### EntityProperty

```js
const { ENTITY_PROPERTY } = require('classic-node-protocol');
// ENTITY_PROPERTY: ROT_X=0 ROT_Y=1 ROT_Z=2 SCALE_X=3 SCALE_Y=4 SCALE_Z=5

client.sendSetEntityProperty(entityId, ENTITY_PROPERTY.SCALE_X, 64); // 2× scale
client.sendSetEntityProperty(entityId, ENTITY_PROPERTY.ROT_Z,   45); // tilt
```

#### BlockDefinitions

```js
client.sendDefineBlock({
  blockId:        50,
  name:           'Glowstone',
  solidity:       1,   // 0=walk-through 1=solid 2=liquid
  movementSpeed:  128, // 0–255 (128 = normal)
  topTexture:     18,
  sideTexture:    18,
  bottomTexture:  18,
  transmitsLight: 0,
  walkSound:      1,
  fullBright:     1,   // 1 = emits light (glows)
  shape:          0,   // 0–8 (block shape)
  blockDraw:      0,   // 0=opaque 1=transparent 2=translucent 3=gas
  fogDensity:     0,
  fogR: 0, fogG: 0, fogB: 0,
});

client.sendRemoveBlockDefinition(50);
```

#### BulkBlockUpdate

Update up to 256 blocks in a single packet:

```js
const { level, BLOCKS } = require('classic-node-protocol');

client.sendBulkBlockUpdate([
  { index: level.blockIndex(5, 3, 10, 64, 64), blockType: BLOCKS.GOLD_BLOCK },
  { index: level.blockIndex(6, 3, 10, 64, 64), blockType: BLOCKS.GOLD_BLOCK },
  { index: level.blockIndex(7, 3, 10, 64, 64), blockType: BLOCKS.GOLD_BLOCK },
]);
```

#### TextColors

```js
// Redefine the '&c' color code (ASCII 0x63) to hot pink
client.sendSetTextColor(0x63, 255, 20, 147, 255);
```

#### TwoWayPing

```js
// Server sends ping, expects client to echo it back
client.sendTwoWayPing(0, 1234); // direction=0: server→client, data=1234

// Client echoes back (direction=1: client→server)
bot.on('packet', (p) => {
  if (p.name === 'twoWayPing' && p.direction === 0) {
    bot.sendTwoWayPing(1, p.data); // echo
  }
});
```

#### InventoryOrder

```js
client.sendSetInventoryOrder(0, BLOCKS.STONE);    // slot 0 = stone
client.sendSetInventoryOrder(1, BLOCKS.GRASS);    // slot 1 = grass
client.sendSetInventoryOrder(2, BLOCKS.OBSIDIAN); // slot 2 = obsidian
```

---

## 13. `jugadorUUID` – UUID Utilities

Generates deterministic MD5-based (v3-style) UUIDs from usernames, matching ClassiCube's player identification format.

```js
const { jugadorUUID } = require('classic-node-protocol');
```

```js
jugadorUUID.generarUUID('PlayerName')
// → '550e8400-e29b-31d4-a716-446655440000'
// Same username always produces the same UUID

jugadorUUID.validarUUID('550e8400-e29b-31d4-a716-446655440000')
// → true (or false for malformed strings)

jugadorUUID.uuidToHex('550e8400-e29b-31d4-a716-446655440000')
// → '550e8400e29b31d4a716446655440000'

jugadorUUID.hexToUUID('550e8400e29b31d4a716446655440000')
// → '550e8400-e29b-31d4-a716-446655440000'
```

---

## 14. TypeScript Support

Full type definitions are included at `types/index.d.ts`. No `@types/` package needed.

```ts
import {
  createServer,
  createClient,
  ClassiCubeServer,
  ClassiCubeClient,
  ClientConnection,
  PacketDecoder,
  ClassiCubeAuth,
  ClassiCubeAccount,
  ClassiCubeServerInfo,
  BLOCKS,
  BLOCK_NAMES,
  BLOCK_MODE,
  USER_TYPE,
  CPE_MAGIC,
  CPE_EXTENSIONS,
  CPE_PACKETS,
  ENV_COLOR,
  WEATHER,
  MAP_ENV_PROPERTY,
  ENTITY_PROPERTY,
} from 'classic-node-protocol';

const server: ClassiCubeServer = createServer({ port: 25565 });

server.on('connection', async (client: ClientConnection) => {
  const blocks = level.buildFlatMap(64, 64, 64);
  await client.sendLevel(blocks, 64, 64, 64);
});
```

---

## 15. Protocol Reference

### FShort (Fixed-Point Coordinates)

The Classic protocol uses **fixed-point integers** for positions:

```
1 block = 32 units
blockPosition = fshort / 32
fshort = blockPosition × 32
```

```js
const { encoder } = require('classic-node-protocol');

encoder.toFShort(5.5)    // → 176  (5.5 blocks → FShort)
encoder.fromFShort(176)  // → 5.5  (FShort → blocks)
```

When using `sendSpawnPlayer`, `encodePosition`, etc., always pass FShort values unless you're using the convenience methods (`spawnAt`, `sendPositionBlocks`) which convert automatically.

### Packet byte sizes

| Packet | ID | Size | Direction |
|---|---|---|---|
| Identification | 0x00 | 131 | Both |
| Ping | 0x01 | 1 | S→C |
| LevelInitialize | 0x02 | 1 | S→C |
| LevelDataChunk | 0x03 | 1028 | S→C |
| LevelFinalize | 0x04 | 7 | S→C |
| SetBlock (client) | 0x05 | 9 | C→S |
| SetBlock (server) | 0x06 | 8 | S→C |
| SpawnPlayer | 0x07 | 74 | S→C |
| Position | 0x08 | 10 | Both |
| PositionOrientation | 0x09 | 7 | S→C |
| PositionUpdate | 0x0A | 5 | S→C |
| OrientationUpdate | 0x0B | 4 | S→C |
| DespawnPlayer | 0x0C | 2 | S→C |
| Message | 0x0D | 66 | Both |
| Disconnect | 0x0E | 65 | S→C |
| UpdateUserType | 0x0F | 2 | S→C |

### Map memory layout

```
index = x + (z × xSize) + (y × xSize × zSize)

Axes:
  X = East/West   (increases East)
  Y = Up/Down     (increases Up)
  Z = North/South (increases South)

Spawn convention: place players at y = groundY + 1 (above the surface)
```

### Auth error codes

| Code | Meaning | Fatal? |
|---|---|---|
| `token` | CSRF token rejected | Yes |
| `username` | Account not found | Yes |
| `password` | Wrong password | Yes |
| `verification` | Email not verified — mppasses empty | No (warning) |

---

## License

MIT © Rosendo Torres

---
