diff --git a/src/js/net/minecraft/client/Minecraft.js b/src/js/net/minecraft/client/Minecraft.js index 8bb0617..8a0e951 100644 --- a/src/js/net/minecraft/client/Minecraft.js +++ b/src/js/net/minecraft/client/Minecraft.js @@ -26,7 +26,7 @@ import PlayerControllerMultiplayer from "./network/controller/PlayerControllerMu export default class Minecraft { - static VERSION = "1.1.5" + static VERSION = "1.1.6" static URL_GITHUB = "https://github.com/labystudio/js-minecraft"; static PROTOCOL_VERSION = 47; //758; @@ -116,14 +116,18 @@ export default class Minecraft { // Disconnect from server if (this.playerController instanceof PlayerControllerMultiplayer) { let networkHandler = this.playerController.getNetworkHandler(); - networkHandler.getNetworkManager().close(); + if (networkHandler.getNetworkManager().isConnected()) { + networkHandler.getNetworkManager().close(); + } } this.playerController = null; - this.world.getChunkProvider().getChunks().clear(); - this.world = null; - this.player = null; - this.loadingScreen = null; + if (this.world !== null) { + this.world.getChunkProvider().getChunks().clear(); + this.world = null; + this.player = null; + this.loadingScreen = null; + } this.displayScreen(new GuiMainMenu()); } else { // Display loading screen diff --git a/src/js/net/minecraft/client/entity/Entity.js b/src/js/net/minecraft/client/entity/Entity.js index c9caa2e..7c292dc 100644 --- a/src/js/net/minecraft/client/entity/Entity.js +++ b/src/js/net/minecraft/client/entity/Entity.js @@ -80,6 +80,11 @@ export default class Entity { this.prevZ = this.z; } + setRotation(yaw, pitch) { + this.rotationYaw = yaw; + this.rotationPitch = pitch; + } + onUpdate() { this.onEntityUpdate(); } diff --git a/src/js/net/minecraft/client/gui/screens/GuiChat.js b/src/js/net/minecraft/client/gui/screens/GuiChat.js index f69e8f1..ed44790 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiChat.js +++ b/src/js/net/minecraft/client/gui/screens/GuiChat.js @@ -42,13 +42,7 @@ export default class GuiChat extends GuiScreen { // Add message to sent history this.minecraft.ingameOverlay.chatOverlay.addMessageToSentHistory(message); - - // Handle message - if (message.startsWith("/")) { - this.minecraft.commandHandler.handleMessage(message.substring(1)); - } else { - this.minecraft.playerController.sendChatMessage(message); - } + this.minecraft.playerController.sendChatMessage(message); return; } diff --git a/src/js/net/minecraft/client/network/NetworkManager.js b/src/js/net/minecraft/client/network/NetworkManager.js index d4570c8..b775e4d 100644 --- a/src/js/net/minecraft/client/network/NetworkManager.js +++ b/src/js/net/minecraft/client/network/NetworkManager.js @@ -21,6 +21,8 @@ export default class NetworkManager { this.pako = require("pako"); this.compressionThreshold = 0; + + this.carryBuffer = []; } setNetworkHandler(networkHandler) { @@ -138,9 +140,15 @@ export default class NetworkManager { } // Packet Sizer - let bufferIn = new ByteBuf(new Int8Array(data)); + + let bufferIn = new ByteBuf(new Int8Array([])); + bufferIn.write(this.carryBuffer); + bufferIn.write(data); + bufferIn.setPosition(0); + this.carryBuffer = []; while (bufferIn.readableBytes() > 0) { let three = [0, 0, 0]; + let start = bufferIn.getPosition(); for (let i = 0; i < three.length; i++) { three[i] = bufferIn.readByte(); if (three[i] >= 0) { @@ -150,7 +158,9 @@ export default class NetworkManager { } if (bufferIn.readableBytes() < length) { - break; + bufferIn.setPosition(start); + this.carryBuffer = bufferIn.getSlicedArray(); + return; } else { this.handlePacket(new ByteBuf(bufferIn.getSlicedArray(length))); bufferIn.skipBytes(length); @@ -161,6 +171,7 @@ export default class NetworkManager { } } catch (e) { console.error(e); + console.log(e.stack); } } @@ -213,7 +224,7 @@ export default class NetworkManager { _onClose(event) { if (this.connected) { - this.networkHandler.onDisconnect("Disconnected from server"); + this.networkHandler.onDisconnect(); } this.connected = false; @@ -222,6 +233,11 @@ export default class NetworkManager { close() { this.connected = false; this.socket.close(); + this.networkHandler.onDisconnect(); + } + + isConnected() { + return this.connected; } flushPacketQueue() { diff --git a/src/js/net/minecraft/client/network/PacketRegistry.js b/src/js/net/minecraft/client/network/PacketRegistry.js index ad79794..b9715a5 100644 --- a/src/js/net/minecraft/client/network/PacketRegistry.js +++ b/src/js/net/minecraft/client/network/PacketRegistry.js @@ -20,6 +20,8 @@ import ServerChunkDataPacket from "./packet/play/server/ServerChunkDataPacket.js import ServerMultiChunkDataPacket from "./packet/play/server/ServerMultiChunkDataPacket.js"; import ServerBlockChangePacket from "./packet/play/server/ServerBlockChangePacket.js"; import ServerChatPacket from "./packet/play/server/ServerChatPacket.js"; +import ServerDisconnectPacket from "./packet/play/server/ServerDisconnectPacket.js"; +import ServerPlayerPositionRotationPacket from "./packet/play/server/ServerPlayerPositionRotationPacket.js"; export default class PacketRegistry { @@ -47,9 +49,11 @@ export default class PacketRegistry { this.registerServer(ProtocolState.PLAY, 0x00, ServerKeepAlivePacket); this.registerServer(ProtocolState.PLAY, 0x01, ServerJoinGamePacket); this.registerServer(ProtocolState.PLAY, 0x02, ServerChatPacket); + this.registerServer(ProtocolState.PLAY, 0x08, ServerPlayerPositionRotationPacket); this.registerServer(ProtocolState.PLAY, 0x21, ServerChunkDataPacket); this.registerServer(ProtocolState.PLAY, 0x23, ServerBlockChangePacket); this.registerServer(ProtocolState.PLAY, 0x26, ServerMultiChunkDataPacket); + this.registerServer(ProtocolState.PLAY, 0x40, ServerDisconnectPacket); this.registerClient(ProtocolState.PLAY, 0x00, ClientKeepAlivePacket); this.registerClient(ProtocolState.PLAY, 0x01, ClientChatPacket); diff --git a/src/js/net/minecraft/client/network/controller/PlayerController.js b/src/js/net/minecraft/client/network/controller/PlayerController.js index 4c44797..f36841a 100644 --- a/src/js/net/minecraft/client/network/controller/PlayerController.js +++ b/src/js/net/minecraft/client/network/controller/PlayerController.js @@ -11,6 +11,11 @@ export default class PlayerController { } sendChatMessage(message) { - this.minecraft.addMessageToChat("<" + this.minecraft.player.username + "> " + message); + // Handle message + if (message.startsWith("/")) { + this.minecraft.commandHandler.handleMessage(message.substring(1)); + } else { + this.minecraft.addMessageToChat("<" + this.minecraft.player.username + "> " + message); + } } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/network/handler/NetworkPlayHandler.js b/src/js/net/minecraft/client/network/handler/NetworkPlayHandler.js index b1a4342..751e3f7 100644 --- a/src/js/net/minecraft/client/network/handler/NetworkPlayHandler.js +++ b/src/js/net/minecraft/client/network/handler/NetworkPlayHandler.js @@ -3,6 +3,7 @@ import GuiDisconnected from "../../gui/screens/GuiDisconnected.js"; import WorldClient from "../../world/WorldClient.js"; import ClientKeepAlivePacket from "../packet/play/client/ClientKeepAlivePacket.js"; import PlayerControllerMultiplayer from "../controller/PlayerControllerMultiplayer.js"; +import ClientPlayerPositionRotationPacket from "../packet/play/client/ClientPlayerPositionRotationPacket.js"; export default class NetworkPlayHandler extends PacketHandler { @@ -25,14 +26,57 @@ export default class NetworkPlayHandler extends PacketHandler { } handleServerChat(packet) { - this.minecraft.ingameOverlay.chatOverlay.addMessage(packet.getMessage()); + if (packet.getType() !== 2) { + this.minecraft.ingameOverlay.chatOverlay.addMessage(packet.getMessage()); + } + } + + handleServerPlayerPositionRotation(packet) { + let player = this.minecraft.player; + + let x = packet.getX(); + let y = packet.getY(); + let z = packet.getZ(); + let yaw = packet.getYaw(); + let pitch = packet.getPitch(); + + if (packet.hasFlag(0x01)) { + x += player.x; + } else { + player.motionX = 0; + } + + if (packet.hasFlag(0x02)) { + y += player.y; + } else { + player.motionY = 0; + } + + if (packet.hasFlag(0x04)) { + z += player.z; + } else { + player.motionZ = 0; + } + + if (packet.hasFlag(0x08)) { + yaw += player.rotationYaw; + } + + if (packet.hasFlag(0x10)) { + pitch += player.rotationPitch; + } + + player.setPosition(x, y, z); + player.setRotation(yaw, pitch); + + this.networkManager.sendPacket(new ClientPlayerPositionRotationPacket(player.x, player.y, player.z, player.rotationYaw, player.rotationPitch, player.onGround)); } handleChunkData(packet) { let provider = this.minecraft.world.getChunkProvider(); if (packet.isFullChunk()) { - if (packet.getDataSize() === 0) { + if (packet.getMask() === 0) { provider.unloadChunk(packet.getX(), packet.getZ()); return; } @@ -41,7 +85,7 @@ export default class NetworkPlayHandler extends PacketHandler { } let chunk = this.minecraft.world.getChunkAt(packet.getX(), packet.getZ()); - chunk.fillChunk(packet.getData(), packet.getDataSize(), packet.isFullChunk()); + chunk.fillChunk(packet.getData(), packet.getMask(), packet.isFullChunk()); } handleMultiChunkData(packet) { @@ -59,10 +103,14 @@ export default class NetworkPlayHandler extends PacketHandler { this.minecraft.world.setBlockAt(position.getX(), position.getY(), position.getZ(), typeId); } + handleDisconnect(packet) { + this.minecraft.loadWorld(null); + this.minecraft.displayScreen(new GuiDisconnected(packet.getReason())); + } + onDisconnect() { - if (this.minecraft.isInGame()) { - this.minecraft.displayScreen(new GuiDisconnected("Disconnected from server")); - } + this.minecraft.loadWorld(null); + this.minecraft.displayScreen(new GuiDisconnected("Disconnected from server")); } getNetworkManager() { diff --git a/src/js/net/minecraft/client/network/packet/play/server/ServerChatPacket.js b/src/js/net/minecraft/client/network/packet/play/server/ServerChatPacket.js index f4d6f1b..b93d50f 100644 --- a/src/js/net/minecraft/client/network/packet/play/server/ServerChatPacket.js +++ b/src/js/net/minecraft/client/network/packet/play/server/ServerChatPacket.js @@ -11,9 +11,7 @@ export default class ServerChatPacket extends Packet { } read(buffer) { - this.message = format(JSON.parse(buffer.readString(32767)), { - useAnsiCodes: true - }); + this.message = format(JSON.parse(buffer.readString(32767))); this.type = buffer.readByte(); } diff --git a/src/js/net/minecraft/client/network/packet/play/server/ServerChunkDataPacket.js b/src/js/net/minecraft/client/network/packet/play/server/ServerChunkDataPacket.js index 68b1c29..6038404 100644 --- a/src/js/net/minecraft/client/network/packet/play/server/ServerChunkDataPacket.js +++ b/src/js/net/minecraft/client/network/packet/play/server/ServerChunkDataPacket.js @@ -2,21 +2,21 @@ import Packet from "../../../Packet.js"; export default class ServerChunkDataPacket extends Packet { - constructor() { + constructor(x, y, mask, data) { super(); - this.x = 0; - this.z = 0; - this.fullChunk = false; - this.dataSize = 0; - this.data = []; + this.x = x; + this.z = y; + this.fullChunk = true; + this.mask = mask; + this.data = data; } read(buffer) { this.x = buffer.readInt(); this.z = buffer.readInt(); this.fullChunk = buffer.readBoolean(); - this.dataSize = buffer.readShort(); + this.mask = buffer.readShort(); this.data = buffer.readByteArray(); } @@ -24,6 +24,10 @@ export default class ServerChunkDataPacket extends Packet { handler.handleChunkData(this); } + setData(data) { + this.data = data; + } + getX() { return this.x; } @@ -36,11 +40,19 @@ export default class ServerChunkDataPacket extends Packet { return this.fullChunk; } - getDataSize() { - return this.dataSize; + getMask() { + return this.mask; } getData() { return this.data; } + + static _calculateLength(bits, isOverworld, isFullChunk) { + let x = bits * 2 * 16 * 16 * 16; + let z = bits * 16 * 16 * 16 / 2; + let overworld = isOverworld ? bits * 16 * 16 * 16 / 2 : 0; + let fullChunk = isFullChunk ? 256 : 0; + return x + z + overworld + fullChunk; + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/network/packet/play/server/ServerDisconnectPacket.js b/src/js/net/minecraft/client/network/packet/play/server/ServerDisconnectPacket.js new file mode 100644 index 0000000..ee3ae3c --- /dev/null +++ b/src/js/net/minecraft/client/network/packet/play/server/ServerDisconnectPacket.js @@ -0,0 +1,23 @@ +import Packet from "../../../Packet.js"; +import {format} from "../../../../../../../../../libraries/chat.js"; + +export default class ServerDisconnectPacket extends Packet { + + constructor() { + super(); + + this.reason = ""; + } + + read(buffer) { + this.reason = format(JSON.parse(buffer.readString(32767))); + } + + handle(handler) { + handler.handleDisconnect(this); + } + + getReason() { + return this.reason; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/network/packet/play/server/ServerMultiChunkDataPacket.js b/src/js/net/minecraft/client/network/packet/play/server/ServerMultiChunkDataPacket.js index 26b8b7d..10b4542 100644 --- a/src/js/net/minecraft/client/network/packet/play/server/ServerMultiChunkDataPacket.js +++ b/src/js/net/minecraft/client/network/packet/play/server/ServerMultiChunkDataPacket.js @@ -17,10 +17,14 @@ export default class ServerMultiChunkDataPacket extends Packet { for (let i = 0; i < amount; i++) { let x = buffer.readInt(); let y = buffer.readInt(); - let dataSize = buffer.readShort() & 65535; - let data = buffer.readByteArray(); + let mask = buffer.readShort() & 65535; + let length = ServerChunkDataPacket._calculateLength(ServerMultiChunkDataPacket._bitCount(mask), this.overworld, true); + this.chunkData.push(new ServerChunkDataPacket(x, y, mask, new Uint8Array(length))); + } - this.chunkData.push(new ServerChunkDataPacket(x, y, dataSize, data)); + for (let i = 0; i < amount; i++) { + let data = this.chunkData[i].getData(); + buffer.read(data, data.length); } } @@ -35,4 +39,13 @@ export default class ServerMultiChunkDataPacket extends Packet { isOverworld() { return this.overworld; } + + static _bitCount(bits) { + bits = bits - ((bits >>> 1) & 0x55555555); + bits = (bits & 0x33333333) + ((bits >>> 2) & 0x33333333); + bits = (bits + (bits >>> 4)) & 0x0f0f0f0f; + bits = bits + (bits >>> 8); + bits = bits + (bits >>> 16); + return bits & 0x3f; + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/network/packet/play/server/ServerPlayerPositionRotationPacket.js b/src/js/net/minecraft/client/network/packet/play/server/ServerPlayerPositionRotationPacket.js new file mode 100644 index 0000000..4701aa9 --- /dev/null +++ b/src/js/net/minecraft/client/network/packet/play/server/ServerPlayerPositionRotationPacket.js @@ -0,0 +1,60 @@ +import Packet from "../../../Packet.js"; + +export default class ServerPlayerPositionRotationPacket extends Packet { + + constructor() { + super(); + + this.x = 0; + this.y = 0; + this.z = 0; + + this.yaw = 0; + this.pitch = 0; + + this.flags = 0; + } + + read(buffer) { + this.x = buffer.readDouble(); + this.y = buffer.readDouble(); + this.z = buffer.readDouble(); + + this.yaw = buffer.readFloat(); + this.pitch = buffer.readFloat(); + + this.flags = buffer.readByte(); + } + + handle(handler) { + handler.handleServerPlayerPositionRotation(this); + } + + getX() { + return this.x; + } + + getY() { + return this.y; + } + + getZ() { + return this.z; + } + + getYaw() { + return this.yaw; + } + + getPitch() { + return this.pitch; + } + + getFlags() { + return this.flags; + } + + hasFlag(bit) { + return (this.flags & bit) === bit; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/network/util/ByteBuf.js b/src/js/net/minecraft/client/network/util/ByteBuf.js index 8385b55..19a270a 100644 --- a/src/js/net/minecraft/client/network/util/ByteBuf.js +++ b/src/js/net/minecraft/client/network/util/ByteBuf.js @@ -36,30 +36,35 @@ export default class ByteBuf { return this.array[this.pos++]; } + readUnsignedByte(pos = this.pos) { + return this.readByte(pos) & 0xFF; + } + readShort() { return this.array[this.pos++] << 8 | this.array[this.pos++]; } readInt() { - return this.array[this.pos++] << 24 - | this.array[this.pos++] << 16 - | this.array[this.pos++] << 8 - | this.array[this.pos++]; + return this.readUnsignedByte() << 24 + | this.readUnsignedByte() << 16 + | this.readUnsignedByte() << 8 + | this.readUnsignedByte(); } readLong() { - return Long.fromNumber(this.array[this.pos++]).shiftLeft(56) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(48)) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(40)) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(32)) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(24)) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(16)) - .or(Long.fromNumber(this.array[this.pos++]).shiftLeft(8)) - .or(Long.fromNumber(this.array[this.pos++])); + return Long.fromNumber(this.readUnsignedByte()).shiftLeft(56) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(48)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(40)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(32)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(24)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(16)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(8)) + .or(Long.fromNumber(this.readUnsignedByte()).shiftLeft(0)); } readFloat() { - return new Float32Array(new Uint32Array([this.readInt(), 0, 0, 0, 0, 0, 0, 0]).buffer)[0]; + let num = this.readInt(); + return new Float32Array(new Uint32Array([num, 0, 0, 0, 0, 0, 0, 0]).buffer)[0]; } readDouble() { @@ -161,7 +166,9 @@ export default class ByteBuf { extendIfNeeded(bytes) { if (this.pos + bytes > this.array.length) { - let newArray = new Uint8Array(this.array.length + bytes); + let length = this.array.length + bytes; + + let newArray = this.array instanceof Uint8Array ? new Uint8Array(length) : new Int8Array(length); newArray.set(this.array); this.array = newArray; } diff --git a/src/js/net/minecraft/client/world/Chunk.js b/src/js/net/minecraft/client/world/Chunk.js index 694af82..701e08f 100644 --- a/src/js/net/minecraft/client/world/Chunk.js +++ b/src/js/net/minecraft/client/world/Chunk.js @@ -252,6 +252,10 @@ export default class Chunk { } setBlockAt(x, y, z, typeId, data = 0) { + if (y < 0 || y >= World.TOTAL_HEIGHT) { + return; + } + let section = this.getSection(y >> 4); let yInSection = y & 15; @@ -274,7 +278,7 @@ export default class Chunk { // Update height map let block = Block.getById(typeId); - if (typeId !== 0 && block.isSolid()) { + if (typeId !== 0 && block !== null && block.isSolid()) { if (y >= height) { this.updateHeightMap(x, y + 1, z); } @@ -293,17 +297,17 @@ export default class Chunk { this.notifyNeighbors(x, z); // Handle block abilities - if (typeId !== 0) { + if (typeId !== 0 && block !== null) { block.onBlockAdded(this.world, totalX, y, totalZ); } return true; } - fillChunk(data, size, fullChunk) { + fillChunk(data, mask, fullChunk) { let i = 0; for (let layer = 0; layer < Chunk.SECTION_AMOUNT; layer++) { - if ((size & 1 << layer) !== 0) { + if ((mask & 1 << layer) !== 0) { let section = this.getSection(layer); for (let k = 0; k < ChunkSection.SIZE * ChunkSection.SIZE * ChunkSection.SIZE; k++) { diff --git a/src/js/net/minecraft/util/MetadataChunkBlock.js b/src/js/net/minecraft/util/MetadataChunkBlock.js index 8ecb244..89dfba8 100644 --- a/src/js/net/minecraft/util/MetadataChunkBlock.js +++ b/src/js/net/minecraft/util/MetadataChunkBlock.js @@ -56,7 +56,7 @@ export default class MetadataChunkBlock { level = 15; } } else if (this.type === EnumSkyBlock.BLOCK) { - level = typeId === 0 ? 0 : block.getLightValue(); + level = typeId === 0 || block === null ? 0 : block.getLightValue(); } if (opacity >= 15 && level === 0) {