protocol-classicube 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Binary.js ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * ClassiCube Protocol - Binary Read/Write Helpers
3
+ *
4
+ * Classic protocol types:
5
+ * Byte - 1 byte unsigned
6
+ * SByte - 1 byte signed
7
+ * Short - 2 bytes signed big-endian
8
+ * Int - 4 bytes signed big-endian
9
+ * String - 64 bytes, padded with spaces (0x20), ASCII
10
+ * ByteArray - 1024 bytes of raw data
11
+ * FByte - Fixed-point: value / 32 for positions
12
+ */
13
+
14
+ class BinaryWriter {
15
+ constructor() {
16
+ this._chunks = [];
17
+ this.length = 0;
18
+ }
19
+
20
+ writeByte(value) {
21
+ const buf = Buffer.alloc(1);
22
+ buf.writeUInt8(value & 0xFF, 0);
23
+ this._chunks.push(buf);
24
+ this.length += 1;
25
+ return this;
26
+ }
27
+
28
+ writeSByte(value) {
29
+ const buf = Buffer.alloc(1);
30
+ buf.writeInt8(value, 0);
31
+ this._chunks.push(buf);
32
+ this.length += 1;
33
+ return this;
34
+ }
35
+
36
+ writeShort(value) {
37
+ const buf = Buffer.alloc(2);
38
+ buf.writeInt16BE(value, 0);
39
+ this._chunks.push(buf);
40
+ this.length += 2;
41
+ return this;
42
+ }
43
+
44
+ writeInt(value) {
45
+ const buf = Buffer.alloc(4);
46
+ buf.writeInt32BE(value, 0);
47
+ this._chunks.push(buf);
48
+ this.length += 4;
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Classic string: always 64 bytes, space-padded
54
+ */
55
+ writeString(value) {
56
+ const buf = Buffer.alloc(64, 0x20); // fill with spaces
57
+ const encoded = Buffer.from(String(value).substring(0, 64), 'ascii');
58
+ encoded.copy(buf, 0);
59
+ this._chunks.push(buf);
60
+ this.length += 64;
61
+ return this;
62
+ }
63
+
64
+ /**
65
+ * ByteArray: always 1024 bytes
66
+ */
67
+ writeByteArray(data) {
68
+ const buf = Buffer.alloc(1024, 0x00);
69
+ if (Buffer.isBuffer(data)) {
70
+ data.copy(buf, 0, 0, Math.min(data.length, 1024));
71
+ }
72
+ this._chunks.push(buf);
73
+ this.length += 1024;
74
+ return this;
75
+ }
76
+
77
+ /**
78
+ * Fixed-point position byte: value * 32
79
+ */
80
+ writeFByte(value) {
81
+ return this.writeSByte(Math.round(value * 32));
82
+ }
83
+
84
+ toBuffer() {
85
+ return Buffer.concat(this._chunks);
86
+ }
87
+ }
88
+
89
+ class BinaryReader {
90
+ constructor(buffer) {
91
+ this._buf = buffer;
92
+ this._offset = 0;
93
+ }
94
+
95
+ get bytesLeft() {
96
+ return this._buf.length - this._offset;
97
+ }
98
+
99
+ readByte() {
100
+ const val = this._buf.readUInt8(this._offset);
101
+ this._offset += 1;
102
+ return val;
103
+ }
104
+
105
+ readSByte() {
106
+ const val = this._buf.readInt8(this._offset);
107
+ this._offset += 1;
108
+ return val;
109
+ }
110
+
111
+ readShort() {
112
+ const val = this._buf.readInt16BE(this._offset);
113
+ this._offset += 2;
114
+ return val;
115
+ }
116
+
117
+ readInt() {
118
+ const val = this._buf.readInt32BE(this._offset);
119
+ this._offset += 4;
120
+ return val;
121
+ }
122
+
123
+ /**
124
+ * Classic string: read 64 bytes, trim trailing spaces
125
+ */
126
+ readString() {
127
+ const buf = this._buf.slice(this._offset, this._offset + 64);
128
+ this._offset += 64;
129
+ return buf.toString('ascii').trimEnd();
130
+ }
131
+
132
+ /**
133
+ * ByteArray: read 1024 bytes
134
+ */
135
+ readByteArray() {
136
+ const buf = this._buf.slice(this._offset, this._offset + 1024);
137
+ this._offset += 1024;
138
+ return Buffer.from(buf);
139
+ }
140
+
141
+ /**
142
+ * Fixed-point position byte: raw / 32
143
+ */
144
+ readFByte() {
145
+ return this.readSByte() / 32;
146
+ }
147
+ }
148
+
149
+ module.exports = { BinaryWriter, BinaryReader };
package/PacketIDs.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ClassiCube Protocol - Packet IDs
3
+ * Based on the Classic Minecraft / ClassiCube protocol spec
4
+ */
5
+
6
+ const PacketIDs = {
7
+ // Server -> Client
8
+ SERVER: {
9
+ SERVER_IDENTIFICATION: 0x00,
10
+ PING: 0x01,
11
+ LEVEL_INITIALIZE: 0x02,
12
+ LEVEL_DATA_CHUNK: 0x03,
13
+ LEVEL_FINALIZE: 0x04,
14
+ SET_BLOCK: 0x06,
15
+ SPAWN_PLAYER: 0x07,
16
+ PLAYER_TELEPORT: 0x08,
17
+ PLAYER_UPDATE: 0x09,
18
+ PLAYER_MOVE: 0x0A,
19
+ PLAYER_ROTATE: 0x0B,
20
+ DESPAWN_PLAYER: 0x0C,
21
+ CHAT_MESSAGE: 0x0D,
22
+ DISCONNECT: 0x0E,
23
+ UPDATE_USER_TYPE: 0x0F,
24
+ },
25
+
26
+ // Client -> Server
27
+ CLIENT: {
28
+ PLAYER_IDENTIFICATION: 0x00,
29
+ SET_BLOCK: 0x05,
30
+ PLAYER_TELEPORT: 0x08,
31
+ CHAT_MESSAGE: 0x0D,
32
+ },
33
+ };
34
+
35
+ module.exports = PacketIDs;
@@ -0,0 +1,308 @@
1
+ /**
2
+ * ClientConnection
3
+ *
4
+ * Wraps a raw TCP socket from the client's perspective.
5
+ * Handles framing, dispatches ALL server packets,
6
+ * and exposes send helpers for client->server packets.
7
+ */
8
+
9
+ const { EventEmitter } = require('events');
10
+ const net = require('net');
11
+ const PacketIDs = require('../PacketIDs');
12
+ const { BinaryWriter } = require('../Binary');
13
+
14
+ // Packet deserializers
15
+ const ServerIdentification = require('../packets/ServerIdentification');
16
+ const Ping = require('../packets/Ping');
17
+ const LevelInitialize = require('../packets/LevelInitialize');
18
+ const LevelDataChunk = require('../packets/LevelDataChunk');
19
+ const LevelFinalize = require('../packets/LevelFinalize');
20
+ const SpawnPlayer = require('../packets/SpawnPlayer');
21
+ const PlayerTeleport = require('../packets/PlayerTeleport');
22
+ const PlayerUpdate = require('../packets/PlayerUpdate');
23
+ const PlayerMove = require('../packets/PlayerMove');
24
+ const PlayerRotate = require('../packets/PlayerRotate');
25
+ const DespawnPlayer = require('../packets/DespawnPlayer');
26
+ const SetBlock = require('../packets/SetBlock');
27
+ const ChatMessage = require('../packets/ChatMessage');
28
+ const Disconnect = require('../packets/Disconnect');
29
+ const UpdateUserType = require('../packets/UpdateUserType');
30
+
31
+ // Fixed packet sizes for server->client packets
32
+ const SERVER_PACKET_SIZES = {
33
+ [PacketIDs.SERVER.SERVER_IDENTIFICATION]: 131,
34
+ [PacketIDs.SERVER.PING]: 1,
35
+ [PacketIDs.SERVER.LEVEL_INITIALIZE]: 1,
36
+ [PacketIDs.SERVER.LEVEL_DATA_CHUNK]: 1028,
37
+ [PacketIDs.SERVER.LEVEL_FINALIZE]: 7,
38
+ [PacketIDs.SERVER.SET_BLOCK]: 8,
39
+ [PacketIDs.SERVER.SPAWN_PLAYER]: 74,
40
+ [PacketIDs.SERVER.PLAYER_TELEPORT]: 10,
41
+ [PacketIDs.SERVER.PLAYER_UPDATE]: 7,
42
+ [PacketIDs.SERVER.PLAYER_MOVE]: 5,
43
+ [PacketIDs.SERVER.PLAYER_ROTATE]: 4,
44
+ [PacketIDs.SERVER.DESPAWN_PLAYER]: 2,
45
+ [PacketIDs.SERVER.CHAT_MESSAGE]: 66,
46
+ [PacketIDs.SERVER.DISCONNECT]: 65,
47
+ [PacketIDs.SERVER.UPDATE_USER_TYPE]: 2,
48
+ };
49
+
50
+ class ClientConnection extends EventEmitter {
51
+ constructor() {
52
+ super();
53
+ this.socket = null;
54
+ this._buffer = Buffer.alloc(0);
55
+
56
+ // Re-ensamble del level
57
+ this._levelChunks = [];
58
+ this._receivingLevel = false;
59
+
60
+ // Estado local del cliente
61
+ this.x = 0;
62
+ this.y = 0;
63
+ this.z = 0;
64
+ this.yaw = 0;
65
+ this.pitch = 0;
66
+ this.op = false;
67
+
68
+ // Entidades de otros jugadores: Map<playerId, { name, x, y, z, yaw, pitch }>
69
+ this.players = new Map();
70
+ }
71
+
72
+ /**
73
+ * Conectar a un servidor ClassiCube.
74
+ * @param {string} host
75
+ * @param {number} port
76
+ * @returns {Promise<void>}
77
+ */
78
+ connect(host, port) {
79
+ return new Promise((resolve, reject) => {
80
+ this.socket = net.createConnection({ host, port }, () => resolve());
81
+ this.socket.on('data', (chunk) => this._onData(chunk));
82
+ this.socket.on('close', () => this.emit('close'));
83
+ this.socket.on('error', (err) => { this.emit('error', err); reject(err); });
84
+ });
85
+ }
86
+
87
+ disconnect() {
88
+ if (this.socket && !this.socket.destroyed) this.socket.destroy();
89
+ }
90
+
91
+ // ──────────────────────────────────────────────
92
+ // Framing
93
+ // ──────────────────────────────────────────────
94
+
95
+ _onData(chunk) {
96
+ this._buffer = Buffer.concat([this._buffer, chunk]);
97
+
98
+ while (this._buffer.length > 0) {
99
+ const packetId = this._buffer[0];
100
+ const size = SERVER_PACKET_SIZES[packetId];
101
+
102
+ if (size === undefined) {
103
+ this.emit('error', new Error(`Unknown server packet ID: 0x${packetId.toString(16)}`));
104
+ this._buffer = Buffer.alloc(0);
105
+ return;
106
+ }
107
+
108
+ if (this._buffer.length < size) break;
109
+
110
+ const raw = this._buffer.slice(0, size);
111
+ this._buffer = this._buffer.slice(size);
112
+ this._dispatch(packetId, raw);
113
+ }
114
+ }
115
+
116
+ _dispatch(packetId, buf) {
117
+ try {
118
+ switch (packetId) {
119
+
120
+ case PacketIDs.SERVER.SERVER_IDENTIFICATION: {
121
+ const pkt = ServerIdentification.deserialize(buf);
122
+ this.op = pkt.op;
123
+ this.emit('serverIdentification', pkt);
124
+ break;
125
+ }
126
+
127
+ case PacketIDs.SERVER.PING:
128
+ this.emit('ping');
129
+ break;
130
+
131
+ case PacketIDs.SERVER.LEVEL_INITIALIZE:
132
+ this._levelChunks = [];
133
+ this._receivingLevel = true;
134
+ this.emit('levelInitialize');
135
+ break;
136
+
137
+ case PacketIDs.SERVER.LEVEL_DATA_CHUNK: {
138
+ const pkt = LevelDataChunk.deserialize(buf);
139
+ if (this._receivingLevel) {
140
+ this._levelChunks.push(pkt.chunkData.slice(0, pkt.chunkLength));
141
+ }
142
+ this.emit('levelDataChunk', pkt);
143
+ break;
144
+ }
145
+
146
+ case PacketIDs.SERVER.LEVEL_FINALIZE: {
147
+ const pkt = LevelFinalize.deserialize(buf);
148
+ const levelData = Buffer.concat(this._levelChunks);
149
+ this._receivingLevel = false;
150
+ this._levelChunks = [];
151
+ this.emit('levelFinalize', { ...pkt, levelData });
152
+ break;
153
+ }
154
+
155
+ case PacketIDs.SERVER.SET_BLOCK:
156
+ this.emit('setBlock', SetBlock.server.deserialize(buf));
157
+ break;
158
+
159
+ case PacketIDs.SERVER.SPAWN_PLAYER: {
160
+ const pkt = SpawnPlayer.deserialize(buf);
161
+ if (pkt.playerId === -1) {
162
+ // Somos nosotros
163
+ this.x = pkt.x; this.y = pkt.y; this.z = pkt.z;
164
+ this.yaw = pkt.yaw; this.pitch = pkt.pitch;
165
+ } else {
166
+ this.players.set(pkt.playerId, {
167
+ name: pkt.name,
168
+ x: pkt.x, y: pkt.y, z: pkt.z,
169
+ yaw: pkt.yaw, pitch: pkt.pitch,
170
+ });
171
+ }
172
+ this.emit('spawnPlayer', pkt);
173
+ break;
174
+ }
175
+
176
+ case PacketIDs.SERVER.PLAYER_TELEPORT: {
177
+ const pkt = PlayerTeleport.deserialize(buf);
178
+ if (pkt.playerId === -1) {
179
+ this.x = pkt.x; this.y = pkt.y; this.z = pkt.z;
180
+ this.yaw = pkt.yaw; this.pitch = pkt.pitch;
181
+ } else {
182
+ const p = this.players.get(pkt.playerId);
183
+ if (p) {
184
+ p.x = pkt.x; p.y = pkt.y; p.z = pkt.z;
185
+ p.yaw = pkt.yaw; p.pitch = pkt.pitch;
186
+ }
187
+ }
188
+ this.emit('playerTeleport', pkt);
189
+ break;
190
+ }
191
+
192
+ case PacketIDs.SERVER.PLAYER_UPDATE: {
193
+ const pkt = PlayerUpdate.deserialize(buf);
194
+ const p = this.players.get(pkt.playerId);
195
+ if (p) {
196
+ p.x += pkt.dx; p.y += pkt.dy; p.z += pkt.dz;
197
+ p.yaw = pkt.yaw; p.pitch = pkt.pitch;
198
+ }
199
+ this.emit('playerUpdate', pkt);
200
+ break;
201
+ }
202
+
203
+ case PacketIDs.SERVER.PLAYER_MOVE: {
204
+ const pkt = PlayerMove.deserialize(buf);
205
+ const p = this.players.get(pkt.playerId);
206
+ if (p) { p.x += pkt.dx; p.y += pkt.dy; p.z += pkt.dz; }
207
+ this.emit('playerMove', pkt);
208
+ break;
209
+ }
210
+
211
+ case PacketIDs.SERVER.PLAYER_ROTATE: {
212
+ const pkt = PlayerRotate.deserialize(buf);
213
+ const p = this.players.get(pkt.playerId);
214
+ if (p) { p.yaw = pkt.yaw; p.pitch = pkt.pitch; }
215
+ this.emit('playerRotate', pkt);
216
+ break;
217
+ }
218
+
219
+ case PacketIDs.SERVER.DESPAWN_PLAYER: {
220
+ const pkt = DespawnPlayer.deserialize(buf);
221
+ this.players.delete(pkt.playerId);
222
+ this.emit('despawnPlayer', pkt);
223
+ break;
224
+ }
225
+
226
+ case PacketIDs.SERVER.CHAT_MESSAGE:
227
+ this.emit('chatMessage', ChatMessage.deserialize(buf));
228
+ break;
229
+
230
+ case PacketIDs.SERVER.DISCONNECT: {
231
+ const pkt = Disconnect.deserialize(buf);
232
+ this.emit('disconnect', pkt);
233
+ this.disconnect();
234
+ break;
235
+ }
236
+
237
+ case PacketIDs.SERVER.UPDATE_USER_TYPE: {
238
+ const pkt = UpdateUserType.deserialize(buf);
239
+ this.op = pkt.op;
240
+ this.emit('updateUserType', pkt);
241
+ break;
242
+ }
243
+
244
+ default:
245
+ this.emit('unknownPacket', { packetId, buf });
246
+ }
247
+ } catch (err) {
248
+ this.emit('error', err);
249
+ }
250
+ }
251
+
252
+ // ──────────────────────────────────────────────
253
+ // Write helper
254
+ // ──────────────────────────────────────────────
255
+
256
+ _write(buf) {
257
+ if (this.socket && !this.socket.destroyed) this.socket.write(buf);
258
+ }
259
+
260
+ // ──────────────────────────────────────────────
261
+ // Send helpers - Client -> Server
262
+ // ──────────────────────────────────────────────
263
+
264
+ /**
265
+ * Login handshake. Primer paquete que debe enviarse.
266
+ * @param {object} opts
267
+ * @param {string} opts.username
268
+ * @param {string} [opts.verificationKey='-']
269
+ */
270
+ sendIdentification({ username, verificationKey = '-' }) {
271
+ const buf = new BinaryWriter()
272
+ .writeByte(PacketIDs.CLIENT.PLAYER_IDENTIFICATION)
273
+ .writeByte(7) // protocol version
274
+ .writeString(username)
275
+ .writeString(verificationKey)
276
+ .writeByte(0x00) // unused
277
+ .toBuffer();
278
+ this._write(buf);
279
+ }
280
+
281
+ /**
282
+ * @param {object} opts
283
+ * @param {number} opts.x
284
+ * @param {number} opts.y
285
+ * @param {number} opts.z
286
+ * @param {number} opts.mode - 0 = destruir, 1 = colocar
287
+ * @param {number} opts.blockType - ID del bloque
288
+ */
289
+ sendSetBlock({ x, y, z, mode, blockType }) {
290
+ this._write(SetBlock.client.serialize({ x, y, z, mode, blockType }));
291
+ }
292
+
293
+ /**
294
+ * Actualiza la posicion del cliente en el servidor.
295
+ */
296
+ sendTeleport({ x, y, z, yaw = 0, pitch = 0 }) {
297
+ // playerId = -1 (self) cuando el cliente envia su propia posicion
298
+ this._write(PlayerTeleport.serialize({ playerId: -1, x, y, z, yaw, pitch }));
299
+ this.x = x; this.y = y; this.z = z;
300
+ this.yaw = yaw; this.pitch = pitch;
301
+ }
302
+
303
+ sendChat(message) {
304
+ this._write(ChatMessage.serialize({ playerId: -1, message }));
305
+ }
306
+ }
307
+
308
+ module.exports = ClientConnection;
package/index.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * classicube-protocol
3
+ *
4
+ * Implementacion completa del protocolo ClassiCube / Classic Minecraft.
5
+ * Protocol version 7. Sin dependencias externas. Node.js 14+.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const PacketIDs = require('./PacketIDs');
11
+ const { BinaryWriter, BinaryReader } = require('./Binary');
12
+ const ServerConnection = require('./server/ServerConnection');
13
+ const ClientConnection = require('./client/ClientConnection');
14
+
15
+ // Packets
16
+ const PlayerIdentification = require('./packets/PlayerIdentification');
17
+ const ServerIdentification = require('./packets/ServerIdentification');
18
+ const Ping = require('./packets/Ping');
19
+ const LevelInitialize = require('./packets/LevelInitialize');
20
+ const LevelDataChunk = require('./packets/LevelDataChunk');
21
+ const LevelFinalize = require('./packets/LevelFinalize');
22
+ const SpawnPlayer = require('./packets/SpawnPlayer');
23
+ const PlayerTeleport = require('./packets/PlayerTeleport');
24
+ const PlayerUpdate = require('./packets/PlayerUpdate');
25
+ const PlayerMove = require('./packets/PlayerMove');
26
+ const PlayerRotate = require('./packets/PlayerRotate');
27
+ const DespawnPlayer = require('./packets/DespawnPlayer');
28
+ const SetBlock = require('./packets/SetBlock');
29
+ const ChatMessage = require('./packets/ChatMessage');
30
+ const Disconnect = require('./packets/Disconnect');
31
+ const UpdateUserType = require('./packets/UpdateUserType');
32
+
33
+ module.exports = {
34
+ // Utilidades core
35
+ PacketIDs,
36
+ BinaryWriter,
37
+ BinaryReader,
38
+
39
+ // Conexiones de alto nivel
40
+ ServerConnection,
41
+ ClientConnection,
42
+
43
+ // Todos los paquetes
44
+ packets: {
45
+ PlayerIdentification,
46
+ ServerIdentification,
47
+ Ping,
48
+ LevelInitialize,
49
+ LevelDataChunk,
50
+ LevelFinalize,
51
+ SpawnPlayer,
52
+ PlayerTeleport,
53
+ PlayerUpdate,
54
+ PlayerMove,
55
+ PlayerRotate,
56
+ DespawnPlayer,
57
+ SetBlock,
58
+ ChatMessage,
59
+ Disconnect,
60
+ UpdateUserType,
61
+ },
62
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "protocol-classicube",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "ClassiCube / Classic Minecraft protocol implementation for Node.js. No dependencies.",
5
+ "main": "index.js",
6
+ "type": "commonjs",
7
+ "keywords": [
8
+ "classicube",
9
+ "minecraft",
10
+ "minecraft-classic",
11
+ "protocol",
12
+ "network",
13
+ "server",
14
+ "client"
15
+ ],
16
+ "author": "Rosendo Torre",
17
+ "license": "MIT",
18
+ "engines": {
19
+ "node": ">=14.0.0"
20
+ },
21
+ "files": [
22
+ "index.js",
23
+ "Binary.js",
24
+ "PacketIDs.js",
25
+ "packets/",
26
+ "client/",
27
+ "server/"
28
+ ]
29
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Packet 0x0D - Chat Message (bidirectional)
3
+ *
4
+ * Server -> Client:
5
+ * Player ID -1 (0xFF) = server message
6
+ * Other IDs = player chat
7
+ *
8
+ * Client -> Server:
9
+ * Player ID is always 0xFF (unused by server but must be sent)
10
+ *
11
+ * Layout:
12
+ * Byte - Packet ID (0x0D)
13
+ * SByte - Player ID
14
+ * String - Message (64 bytes)
15
+ *
16
+ * Total: 66 bytes
17
+ */
18
+
19
+ const { BinaryWriter, BinaryReader } = require('../Binary');
20
+ const PacketIDs = require('../PacketIDs');
21
+
22
+ const SERVER_MESSAGE_ID = -1;
23
+
24
+ /**
25
+ * @param {object} opts
26
+ * @param {number} opts.playerId - -1 for server, player ID otherwise
27
+ * @param {string} opts.message
28
+ */
29
+ function serialize({ playerId = SERVER_MESSAGE_ID, message }) {
30
+ return new BinaryWriter()
31
+ .writeByte(PacketIDs.SERVER.CHAT_MESSAGE)
32
+ .writeSByte(playerId)
33
+ .writeString(message)
34
+ .toBuffer();
35
+ }
36
+
37
+ function deserialize(buf) {
38
+ const r = new BinaryReader(buf);
39
+ const packetId = r.readByte();
40
+ if (packetId !== PacketIDs.SERVER.CHAT_MESSAGE) {
41
+ throw new Error(`Expected ChatMessage (0x0D), got 0x${packetId.toString(16)}`);
42
+ }
43
+ const playerId = r.readSByte();
44
+ const message = r.readString();
45
+ return { packetId, playerId, message };
46
+ }
47
+
48
+ module.exports = { serialize, deserialize, PACKET_SIZE: 66, SERVER_MESSAGE_ID };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Packet 0x0C - Despawn Player (Server -> Client)
3
+ *
4
+ * Elimina la entidad de un jugador del mundo del cliente.
5
+ * Se envia cuando un jugador se desconecta o cambia de mundo.
6
+ *
7
+ * Layout:
8
+ * Byte - Packet ID (0x0C)
9
+ * SByte - Player ID
10
+ *
11
+ * Total: 2 bytes
12
+ */
13
+
14
+ const { BinaryWriter, BinaryReader } = require('../Binary');
15
+ const PacketIDs = require('../PacketIDs');
16
+
17
+ /**
18
+ * @param {object} opts
19
+ * @param {number} opts.playerId
20
+ */
21
+ function serialize({ playerId }) {
22
+ return new BinaryWriter()
23
+ .writeByte(PacketIDs.SERVER.DESPAWN_PLAYER)
24
+ .writeSByte(playerId)
25
+ .toBuffer();
26
+ }
27
+
28
+ function deserialize(buf) {
29
+ const r = new BinaryReader(buf);
30
+ const packetId = r.readByte();
31
+ if (packetId !== PacketIDs.SERVER.DESPAWN_PLAYER) {
32
+ throw new Error(`Expected DespawnPlayer (0x0C), got 0x${packetId.toString(16)}`);
33
+ }
34
+ const playerId = r.readSByte();
35
+ return { packetId, playerId };
36
+ }
37
+
38
+ module.exports = { serialize, deserialize, PACKET_SIZE: 2 };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Packet 0x0E - Disconnect (Server -> Client)
3
+ *
4
+ * Kicks the player with a reason.
5
+ * Layout:
6
+ * Byte - Packet ID (0x0E)
7
+ * String - Disconnect reason (64 bytes)
8
+ *
9
+ * Total: 65 bytes
10
+ */
11
+
12
+ const { BinaryWriter, BinaryReader } = require('../Binary');
13
+ const PacketIDs = require('../PacketIDs');
14
+
15
+ /**
16
+ * @param {object} opts
17
+ * @param {string} opts.reason
18
+ */
19
+ function serialize({ reason }) {
20
+ return new BinaryWriter()
21
+ .writeByte(PacketIDs.SERVER.DISCONNECT)
22
+ .writeString(reason)
23
+ .toBuffer();
24
+ }
25
+
26
+ function deserialize(buf) {
27
+ const r = new BinaryReader(buf);
28
+ const packetId = r.readByte();
29
+ if (packetId !== PacketIDs.SERVER.DISCONNECT) {
30
+ throw new Error(`Expected Disconnect (0x0E), got 0x${packetId.toString(16)}`);
31
+ }
32
+ const reason = r.readString();
33
+ return { packetId, reason };
34
+ }
35
+
36
+ module.exports = { serialize, deserialize, PACKET_SIZE: 65 };