implement packet compression, implement player controller, implement join server authentication, add cobblestone, implement chunk provider, implement block position, implement session, implement movement, chunk, chat and block update packets, version 1.1.5

This commit is contained in:
LabyStudio
2022-06-19 14:25:40 +02:00
parent 2e42d482ed
commit 7266a89f5a
67 changed files with 11831 additions and 2761 deletions
+33 -13
View File
@@ -5,7 +5,6 @@ import WorldRenderer from "./render/WorldRenderer.js";
import ScreenRenderer from "./render/gui/ScreenRenderer.js";
import ItemRenderer from "./render/gui/ItemRenderer.js";
import IngameOverlay from "./gui/overlay/IngameOverlay.js";
import PlayerEntity from "./entity/PlayerEntity.js";
import SoundManager from "./sound/SoundManager.js";
import Block from "./world/block/Block.js";
import BoundingBox from "../util/BoundingBox.js";
@@ -22,12 +21,14 @@ import GuiContainerCreative from "./gui/screens/container/GuiContainerCreative.j
import GameProfile from "../util/GameProfile.js";
import UUID from "../util/UUID.js";
import FocusStateType from "../util/FocusStateType.js";
import Session from "../util/Session.js";
import PlayerControllerMultiplayer from "./network/controller/PlayerControllerMultiplayer.js";
export default class Minecraft {
static VERSION = "1.1.4"
static VERSION = "1.1.5"
static URL_GITHUB = "https://github.com/labystudio/js-minecraft";
static PROTOCOL_VERSION = 758;
static PROTOCOL_VERSION = 47; //758;
// TODO Add to settings
static PROXY = {
@@ -45,12 +46,13 @@ export default class Minecraft {
this.loadingScreen = null;
this.world = null;
this.player = null;
this.playerController = null;
this.fps = 0;
this.maxFps = 0;
let username = "Player" + Math.floor(Math.random() * 100);
this.profile = new GameProfile(username, UUID.randomUUID());
let profile = new GameProfile(username, UUID.randomUUID());
this.session = new Session(profile, "");
// Tick timer
this.timer = new Timer(20);
@@ -111,7 +113,14 @@ export default class Minecraft {
this.worldRenderer.reset();
this.itemRenderer.reset();
this.world.chunks.clear();
// Disconnect from server
if (this.playerController instanceof PlayerControllerMultiplayer) {
let networkHandler = this.playerController.getNetworkHandler();
networkHandler.getNetworkManager().close();
}
this.playerController = null;
this.world.getChunkProvider().getChunks().clear();
this.world = null;
this.player = null;
this.loadingScreen = null;
@@ -127,12 +136,11 @@ export default class Minecraft {
this.worldRenderer.scene.add(this.world.group);
// Create player
this.player = new PlayerEntity(this, this.world);
this.player.username = this.profile.username;
this.player = this.playerController.createPlayer(this.world);
this.player.username = this.session.getProfile().getUsername();
this.world.addEntity(this.player);
// Load spawn chunks and respawn player
this.world.findSpawn();
this.world.loadSpawnChunks();
this.player.respawn();
}
@@ -192,7 +200,7 @@ export default class Minecraft {
onRender(partialTicks) {
if (this.isInGame()) {
// Player rotation
if (!this.isPaused()) {
if (this.hasInGameFocus()) {
let deltaX = this.window.pullMouseMotionX();
let deltaY = this.window.pullMouseMotionY();
this.player.turn(deltaX, deltaY);
@@ -283,8 +291,8 @@ export default class Minecraft {
let cameraChunkZ = Math.floor(this.player.z) >> 4;
let renderDistance = this.settings.viewDistance;
let requiredChunks = Math.pow(renderDistance * 2 - 1, 2);
let loadedChunks = this.world.chunks.size;
let requiredChunks = this.isSingleplayer() ? Math.pow(renderDistance * 2 - 1, 2) : 1;
let loadedChunks = this.world.getChunkProvider().getChunks().size;
// Load chunks and count
setTimeout(() => {
@@ -448,7 +456,19 @@ export default class Minecraft {
}
isPaused() {
return !this.hasInGameFocus() && this.loadingScreen === null;
return !this.hasInGameFocus() && this.loadingScreen === null && this.isSingleplayer();
}
setSession(session) {
this.session = session;
}
getSession() {
return this.session;
}
isSingleplayer() {
return this.isInGame() && !(this.playerController instanceof PlayerControllerMultiplayer);
}
stop() {
+8 -2
View File
@@ -8,8 +8,7 @@ export default class Entity {
this.minecraft = minecraft;
this.world = world;
this.random = new Random();
this.renderer = this.minecraft.worldRenderer.entityRenderManager.createEntityRendererByEntity(this);
this.renderer = null;
this.x = 0;
this.y = 0;
@@ -48,6 +47,13 @@ export default class Entity {
this.setPosition(this.x, this.y, this.z);
}
initRenderer() {
this.renderer = this.minecraft.worldRenderer.entityRenderManager.createEntityRendererByEntity(this);
if (this.renderer === null) {
throw new Error("No entity renderer for entity " + this.constructor.name + " found!");
}
}
setPosition(x, y, z) {
// Update position
this.x = x;
@@ -0,0 +1,69 @@
import PlayerEntity from "./PlayerEntity.js";
import ClientPlayerMovementPacket from "../network/packet/play/client/ClientPlayerMovementPacket.js";
import ClientPlayerRotationPacket from "../network/packet/play/client/ClientPlayerRotationPacket.js";
import ClientPlayerPositionPacket from "../network/packet/play/client/ClientPlayerPositionPacket.js";
import ClientPlayerPositionRotationPacket from "../network/packet/play/client/ClientPlayerPositionRotationPacket.js";
export default class PlayerEntityMultiplayer extends PlayerEntity {
constructor(minecraft, world, networkHandler) {
super(minecraft, world);
this.networkHandler = networkHandler;
this.positionUpdateTicks = 0;
this.lastReportedX = 0;
this.lastReportedY = 0;
this.lastReportedZ = 0;
this.lastReportedYaw = 0;
this.lastReportedPitch = 0;
}
onUpdate() {
super.onUpdate();
this.onUpdateWalkingPlayer();
}
onUpdateWalkingPlayer() {
let movementX = this.x - this.lastReportedX;
let movementY = this.y - this.lastReportedY;
let movementZ = this.z - this.lastReportedZ;
let movementYaw = this.rotationYaw - this.lastReportedYaw;
let movementPitch = this.rotationPitch - this.lastReportedPitch;
let reportPosition = movementX * movementX + movementY * movementY + movementZ * movementZ > 9.0E-4 || this.positionUpdateTicks >= 20;
let reportRotation = movementYaw !== 0.0 || movementPitch !== 0.0;
if (reportPosition && reportRotation) {
this.networkHandler.sendPacket(new ClientPlayerPositionRotationPacket(this.onGround, this.x, this.y, this.z, this.rotationYaw, this.rotationPitch));
} else if (reportPosition) {
this.networkHandler.sendPacket(new ClientPlayerPositionPacket(this.onGround, this.x, this.y, this.z));
} else if (reportRotation) {
this.networkHandler.sendPacket(new ClientPlayerRotationPacket(this.onGround, this.rotationYaw, this.rotationPitch));
} else {
this.networkHandler.sendPacket(new ClientPlayerMovementPacket(this.onGround));
}
this.positionUpdateTicks++;
if (reportPosition) {
this.lastReportedX = this.x;
this.lastReportedY = this.y;
this.lastReportedZ = this.z;
this.positionUpdateTicks = 0;
}
if (reportRotation) {
this.lastReportedYaw = this.rotationYaw;
this.lastReportedPitch = this.rotationPitch;
}
}
getNetworkHandler() {
return this.networkHandler;
}
}
@@ -129,7 +129,7 @@ export default class IngameOverlay extends Gui {
let visibleChunks = 0;
let loadedChunks = 0;
for (let [index, chunk] of world.chunks) {
for (let [index, chunk] of world.getChunkProvider().getChunks()) {
for (let y in chunk.sections) {
let chunkSection = chunk.sections[y];
if (chunkSection.group.visible) {
@@ -47,7 +47,7 @@ export default class GuiChat extends GuiScreen {
if (message.startsWith("/")) {
this.minecraft.commandHandler.handleMessage(message.substring(1));
} else {
this.minecraft.addMessageToChat("<" + this.minecraft.player.username + "> " + message);
this.minecraft.playerController.sendChatMessage(message);
}
return;
}
@@ -34,7 +34,7 @@ export default class GuiConnecting extends GuiScreen {
// Send Minecraft protocol handshake
this.networkManager.sendPacket(new HandshakePacket(Minecraft.PROTOCOL_VERSION, ProtocolState.LOGIN));
this.networkManager.sendPacket(new LoginStartPacket(this.minecraft.profile.username));
this.networkManager.sendPacket(new LoginStartPacket(this.minecraft.getSession().getProfile().getUsername()));
}
init() {
@@ -66,7 +66,7 @@ export default class GuiConnecting extends GuiScreen {
onClose() {
super.onClose();
if (this.networkManager !== null) {
if (this.networkManager !== null && this.networkManager.getState() !== ProtocolState.PLAY) {
this.networkManager.close();
}
}
@@ -4,6 +4,8 @@ import World from "../../world/World.js";
import GuiTextField from "../widgets/GuiTextField.js";
import Random from "../../../util/Random.js";
import Long from "../../../../../../../libraries/long.js";
import ChunkProviderGenerate from "../../world/provider/ChunkProviderGenerate.js";
import PlayerController from "../../network/controller/PlayerController.js";
export default class GuiCreateWorld extends GuiScreen {
@@ -33,7 +35,14 @@ export default class GuiCreateWorld extends GuiScreen {
}
seed = Long.fromNumber(h);
}
this.minecraft.loadWorld(new World(this.minecraft, seed));
// Load world
let world = new World(this.minecraft);
world.setChunkProvider(new ChunkProviderGenerate(world, seed));
world.getChunkProvider().findSpawn();
this.minecraft.playerController = new PlayerController(this.minecraft);
this.minecraft.loadWorld(world);
}));
this.buttonList.push(new GuiButton("Cancel", this.width / 2 + 5, y + 110, 150, 20, () => {
this.minecraft.displayScreen(this.previousScreen);
@@ -1,10 +1,13 @@
import ByteBuf from "./util/ByteBuf.js";
import PacketRegistry from "./PacketRegistry.js";
import ProtocolState from "./ProtocolState.js";
import {aesjs} from "../../../../../../libraries/aes.js";
import {require} from "../../../../Start.js";
export default class NetworkManager {
static DEBUG = false;
static MAX_COMPRESSION = 2097152;
constructor(minecraft) {
this.minecraft = minecraft;
this.socket = null;
@@ -14,10 +17,10 @@ export default class NetworkManager {
this.registry = new PacketRegistry();
this.protocolState = ProtocolState.HANDSHAKE;
this.readBuffer = new ByteBuf();
this.expectedLength = -1;
this.queue = [];
this.pako = require("pako");
this.compressionThreshold = 0;
}
setNetworkHandler(networkHandler) {
@@ -41,11 +44,10 @@ export default class NetworkManager {
this.connected = true;
// Send proxy handshake
let object = {
"address": this.address,
this.sendProxyPacket(0, {
"host": this.address,
"port": this.port,
};
this.socket.send(JSON.stringify(object));
});
// Handle connect event
this.networkHandler.onConnect();
@@ -54,6 +56,14 @@ export default class NetworkManager {
this.flushPacketQueue();
}
sendProxyPacket(id, payload) {
let object = {
"id": id,
"payload": payload
};
this.socket.send(JSON.stringify(object));
}
sendPacket(packet) {
if (this.connected) {
this._sendPacketImmediately(packet);
@@ -67,81 +77,138 @@ export default class NetworkManager {
let packetState = this.registry.getPacketState(packet);
if (packetState !== this.protocolState) {
if (packetState === null) {
console.error("[Network] Tried to send unknown packet: " + packet);
console.error("[Network] Tried to send unknown packet: " + packet.constructor.name);
return;
}
console.log("[Network] Switching protocol state from " + ProtocolState.getName(this.protocolState) + " to " + ProtocolState.getName(packetState));
this.protocolState = packetState;
this.setState(packetState);
}
// Write packet to buffer
// Packet Codec
let buffer = new ByteBuf();
buffer.writeByte(this.registry.getClientBoundPacketId(this.protocolState, packet));
packet.write(buffer);
buffer.setPosition(0);
// Write chunk header
let array = buffer.getArray();
// Packet Compression
if (this.compressionThreshold !== 0) {
let length = buffer.length();
if (length > this.compressionThreshold) {
let compressed = this.pako.deflate(buffer.getArray(), {
chunkSize: 8192
});
buffer = new ByteBuf();
buffer.writeVarInt(length);
buffer.write(compressed);
} else {
let copy = buffer.getArray();
buffer = new ByteBuf();
buffer.writeVarInt(0);
buffer.write(copy);
}
buffer.setPosition(0);
}
// Packet Sizer
let wrapper = new ByteBuf();
wrapper.writeVarInt(array.length);
wrapper.write(array);
let chunk = wrapper.getArray().buffer;
wrapper.writeVarInt(buffer.length());
wrapper.write(buffer.getArray());
// Encrypt chunk
// Packet Compression
if (this.isEncrypted) {
chunk = this.encryption.encrypt(new Uint8Array(chunk));
wrapper = new ByteBuf(this.encryption.encrypt(wrapper.getArray()));
}
// Send chunk
this.socket.send(chunk);
this.socket.send(wrapper.getArray());
console.log("[Network] [OUT] " + packet.constructor.name);
if (NetworkManager.DEBUG) {
console.log("[Network] [OUT] " + packet.constructor.name);
}
}
_onMessage(event) {
let chunk = new Int8Array(event.data);
try {
let data = new Uint8Array(event.data);
// Decrypt chunk
if (this.isEncrypted) {
chunk = this.encryption.decrypt(new Uint8Array(event.data));
}
// Packet Compression
if (this.isEncrypted) {
data = this.decryption.decrypt(data);
}
// Read packet header
if (this.expectedLength === -1) {
let buf = new ByteBuf(chunk);
this.expectedLength = buf.readVarInt(); // Read packet length
this.readBuffer.setPosition(0);
chunk = chunk.slice(buf.getPosition());
}
// Packet Sizer
let bufferIn = new ByteBuf(new Int8Array(data));
while (bufferIn.readableBytes() > 0) {
let three = [0, 0, 0];
for (let i = 0; i < three.length; i++) {
three[i] = bufferIn.readByte();
if (three[i] >= 0) {
let length = new ByteBuf(three).readVarInt();
if (length === 0) {
throw new Error("Empty Packet!");
}
// Fill packet content
this.readBuffer.write(chunk);
// Handle packet
if (this.readBuffer.getPosition() >= this.expectedLength) {
this.expectedLength = -1;
this.readBuffer.setPosition(0);
this.onPacketBufferReceived(this.readBuffer);
if (bufferIn.readableBytes() < length) {
break;
} else {
this.handlePacket(new ByteBuf(bufferIn.getSlicedArray(length)));
bufferIn.skipBytes(length);
}
break;
}
}
}
} catch (e) {
console.error(e);
}
}
onPacketBufferReceived(buffer) {
handlePacket(buffer) {
// Packet Compression
if (this.compressionThreshold !== 0) {
let uncompressedLength = buffer.readVarInt();
if (uncompressedLength !== 0) {
if (uncompressedLength < this.compressionThreshold) {
throw new Error("Badly compressed packet - size of " + uncompressedLength + " is below server threshold of " + this.compressionThreshold);
}
if (uncompressedLength > NetworkManager.MAX_COMPRESSION) {
throw new Error("Badly compressed packet - size of " + uncompressedLength + " is larger than protocol maximum of " + NetworkManager.MAX_COMPRESSION);
}
// Decompress
buffer = new ByteBuf(this.pako.inflate(new Uint8Array(buffer.getSlicedArray()), {
chunkSize: 8192
}));
if (buffer.length() !== uncompressedLength) {
throw new Error("Badly compressed packet - decompressed size of " + buffer.length() + " is not equal to original size of " + uncompressedLength);
}
}
}
// Packet Codec
let packetId = buffer.readByte(); // Read packet id
let clazz = this.registry.getServerBoundById(this.protocolState, packetId);
if (clazz === null) {
console.log("[Network] Unknown packet id: " + packetId);
if (NetworkManager.DEBUG) {
console.log("[Network] [IN] Unknown packet id: " + packetId + " (0x" + packetId.toString(16) + ")");
}
return;
} else {
if (NetworkManager.DEBUG) {
console.log("[Network] [IN] " + clazz.name);
}
}
let packet = new clazz;
console.log("[Network] [IN] " + packet.constructor.name);
packet.read(buffer, buffer.length);
packet.handle(this.networkHandler);
}
_onError(event) {
console.error("[Network] Error: " + event.data);
}
_onClose(event) {
@@ -164,6 +231,26 @@ export default class NetworkManager {
enableEncryption(secretKey) {
this.isEncrypted = true;
this.encryption = new aesjs.ModeOfOperation.cfb(secretKey, secretKey, 1);
this.decryption = new (require("aesjs").ModeOfOperation).cfb(secretKey, secretKey, 1);
this.encryption = new (require("aesjs").ModeOfOperation).cfb(secretKey, secretKey, 1);
}
setState(packetState) {
console.log("[Network] Switching protocol state from " + this.protocolState.getName() + " to " + packetState.getName());
this.protocolState = packetState;
}
getState() {
return this.protocolState;
}
setCompressionThreshold(threshold) {
console.log("[Network] Set compression threshold to " + threshold);
if (threshold >= 0) {
this.compressionThreshold = threshold;
} else {
this.compressionThreshold = 0;
}
}
}
@@ -6,6 +6,20 @@ import StatusResponsePacket from "./packet/status/server/StatusResponsePacket.js
import EncryptionRequestPacket from "./packet/login/server/EncryptionRequestPacket.js";
import EncryptionResponsePacket from "./packet/login/client/EncryptionResponsePacket.js";
import LoginDisconnectPacket from "./packet/login/server/LoginDisconnectPacket.js";
import LoginSuccessPacket from "./packet/login/server/LoginSuccessPacket.js";
import EnableCompressionPacket from "./packet/login/server/EnableCompressionPacket.js";
import ServerKeepAlivePacket from "./packet/play/server/ServerKeepAlivePacket.js";
import ServerJoinGamePacket from "./packet/play/server/ServerJoinGamePacket.js";
import ClientKeepAlivePacket from "./packet/play/client/ClientKeepAlivePacket.js";
import ClientChatPacket from "./packet/play/client/ClientChatPacket.js";
import ClientPlayerMovementPacket from "./packet/play/client/ClientPlayerMovementPacket.js";
import ClientPlayerRotationPacket from "./packet/play/client/ClientPlayerRotationPacket.js";
import ClientPlayerPositionPacket from "./packet/play/client/ClientPlayerPositionPacket.js";
import ClientPlayerPositionRotationPacket from "./packet/play/client/ClientPlayerPositionRotationPacket.js";
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";
export default class PacketRegistry {
@@ -23,9 +37,26 @@ export default class PacketRegistry {
// Register login
this.registerServer(ProtocolState.LOGIN, 0x00, LoginDisconnectPacket);
this.registerServer(ProtocolState.LOGIN, 0x01, EncryptionRequestPacket);
this.registerServer(ProtocolState.LOGIN, 0x02, LoginSuccessPacket);
this.registerServer(ProtocolState.LOGIN, 0x03, EnableCompressionPacket);
this.registerClient(ProtocolState.LOGIN, 0x00, LoginStartPacket);
this.registerClient(ProtocolState.LOGIN, 0x01, EncryptionResponsePacket);
// Register play
this.registerServer(ProtocolState.PLAY, 0x00, ServerKeepAlivePacket);
this.registerServer(ProtocolState.PLAY, 0x01, ServerJoinGamePacket);
this.registerServer(ProtocolState.PLAY, 0x02, ServerChatPacket);
this.registerServer(ProtocolState.PLAY, 0x21, ServerChunkDataPacket);
this.registerServer(ProtocolState.PLAY, 0x23, ServerBlockChangePacket);
this.registerServer(ProtocolState.PLAY, 0x26, ServerMultiChunkDataPacket);
this.registerClient(ProtocolState.PLAY, 0x00, ClientKeepAlivePacket);
this.registerClient(ProtocolState.PLAY, 0x01, ClientChatPacket);
this.registerClient(ProtocolState.PLAY, 0x03, ClientPlayerMovementPacket);
this.registerClient(ProtocolState.PLAY, 0x04, ClientPlayerPositionPacket);
this.registerClient(ProtocolState.PLAY, 0x05, ClientPlayerRotationPacket);
this.registerClient(ProtocolState.PLAY, 0x06, ClientPlayerPositionRotationPacket);
}
registerClient(state, id, packet) {
@@ -37,29 +68,29 @@ export default class PacketRegistry {
}
_register(registry, state, id, packet) {
if (typeof registry[state] === "undefined") {
registry[state] = [];
if (typeof registry[state.getId()] === "undefined") {
registry[state.getId()] = [];
}
registry[state][id] = packet;
registry[state.getId()][id] = packet;
}
getServerBoundById(state, id) {
if (typeof this.packetsServer[state][id] === "undefined") {
if (typeof this.packetsServer[state.getId()][id] === "undefined") {
return null;
}
return this.packetsServer[state][id];
return this.packetsServer[state.getId()][id];
}
getClientBoundById(state, id) {
if (typeof this.packetsClient[state][id] === "undefined") {
if (typeof this.packetsClient[state.getId()][id] === "undefined") {
return null;
}
return this.packetsClient[state][id];
return this.packetsClient[state.getId()][id];
}
getClientBoundPacketId(state, packet) {
for (let id in this.packetsClient[state]) {
if (this.packetsClient[state][id] === packet.constructor) {
for (let id in this.packetsClient[state.getId()]) {
if (this.packetsClient[state.getId()][id] === packet.constructor) {
return id;
}
}
@@ -68,7 +99,7 @@ export default class PacketRegistry {
getServerBoundPacketId(state, packet) {
for (let id in this.packetsServer[state]) {
if (this.packetsServer[state][id] === packet.constructor) {
if (this.packetsServer[state.getId()][id] === packet.constructor) {
return id;
}
}
@@ -79,14 +110,14 @@ export default class PacketRegistry {
for (const [state, value] of Object.entries(this.packetsClient)) {
for (let id in value) {
if (value[id] === packet.constructor) {
return parseInt(state);
return ProtocolState.fromId(parseInt(state));
}
}
}
for (const [state, value] of Object.entries(this.packetsServer)) {
for (let id in value) {
if (value[id] === packet.constructor) {
return parseInt(state);
return ProtocolState.fromId(parseInt(state));
}
}
}
@@ -1,11 +1,19 @@
export default class ProtocolState {
static HANDSHAKE = -1;
static PLAY = 0;
static STATUS = 1;
static LOGIN = 2;
static HANDSHAKE = new ProtocolState(-1);
static PLAY = new ProtocolState(0);
static STATUS = new ProtocolState(1);
static LOGIN = new ProtocolState(2);
static getName(state) {
switch (state) {
constructor(id) {
this.id = id;
}
getId() {
return this.id;
}
getName() {
switch (this) {
case ProtocolState.HANDSHAKE:
return "HANDSHAKE";
case ProtocolState.LOGIN:
@@ -18,4 +26,22 @@ export default class ProtocolState {
return "UNKNOWN";
}
}
static fromId(id) {
for (let state of this.values()) {
if (state.getId() === id) {
return state;
}
}
}
static values() {
return [
ProtocolState.HANDSHAKE,
ProtocolState.LOGIN,
ProtocolState.PLAY,
ProtocolState.STATUS
];
}
}
@@ -0,0 +1,16 @@
import PlayerEntity from "../../entity/PlayerEntity.js";
export default class PlayerController {
constructor(minecraft) {
this.minecraft = minecraft;
}
createPlayer(world) {
return new PlayerEntity(this.minecraft, world);
}
sendChatMessage(message) {
this.minecraft.addMessageToChat("<" + this.minecraft.player.username + "> " + message);
}
}
@@ -0,0 +1,24 @@
import PlayerController from "./PlayerController.js";
import PlayerEntityMultiplayer from "../../entity/PlayerEntityMultiplayer.js";
import ClientChatPacket from "../packet/play/client/ClientChatPacket.js";
export default class PlayerControllerMultiplayer extends PlayerController {
constructor(minecraft, networkHandler) {
super(minecraft);
this.networkHandler = networkHandler;
}
createPlayer(world) {
return new PlayerEntityMultiplayer(this.minecraft, world, this.networkHandler);
}
sendChatMessage(message) {
this.networkHandler.sendPacket(new ClientChatPacket(message));
}
getNetworkHandler() {
return this.networkHandler;
}
}
@@ -1,7 +1,10 @@
import PacketHandler from "../PacketHandler.js";
import PacketHandler from "./PacketHandler.js";
import EncryptionResponsePacket from "../packet/login/client/EncryptionResponsePacket.js";
import CryptManager from "../util/CryptManager.js";
import GuiDisconnected from "../../gui/screens/GuiDisconnected.js";
import Authentication from "../util/Authentication.js";
import NetworkPlayHandler from "./NetworkPlayHandler.js";
import ProtocolState from "../ProtocolState.js";
export default class NetworkLoginHandler extends PacketHandler {
@@ -9,10 +12,19 @@ export default class NetworkLoginHandler extends PacketHandler {
super();
this.networkManager = networkManager;
this.authentication = new Authentication(networkManager);
}
handleEncryptionRequest(packet) {
// Create an AES key for the packet encryption
let secretKey = CryptManager.createNewSharedKey();
// Send join server request to Mojang
let session = this.networkManager.minecraft.getSession();
let serverId = this.authentication.createServerHash(packet.serverId, secretKey, packet.publicKey);
this.authentication.joinServer(session.getProfile(), session.getAccessToken(), serverId);
// Send encryption response
this.networkManager.sendPacket(new EncryptionResponsePacket(secretKey, packet.publicKey, packet.verifyToken));
// Enable encryption
@@ -24,8 +36,19 @@ export default class NetworkLoginHandler extends PacketHandler {
this.networkManager.minecraft.displayScreen(new GuiDisconnected(packet.message));
}
onDisconnect() {
handleLoginSuccess(packet) {
this.networkManager.setState(ProtocolState.PLAY);
this.networkManager.setNetworkHandler(new NetworkPlayHandler(this.networkManager, packet.profile));
}
handleEnableCompression(packet) {
this.networkManager.setCompressionThreshold(packet.getCompressionThreshold());
}
onDisconnect() {
if (this.networkManager.minecraft.isInGame()) {
this.networkManager.minecraft.displayScreen(new GuiDisconnected("Disconnected from server"));
}
}
}
@@ -0,0 +1,76 @@
import PacketHandler from "./PacketHandler.js";
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";
export default class NetworkPlayHandler extends PacketHandler {
constructor(networkManager, profile) {
super();
this.minecraft = networkManager.minecraft;
this.networkManager = networkManager;
this.profile = profile;
}
handleKeepAlive(packet) {
this.networkManager.sendPacket(new ClientKeepAlivePacket(packet.getId()));
}
handleJoinGame(packet) {
this.minecraft.playerController = new PlayerControllerMultiplayer(this.minecraft, this);
let world = new WorldClient(this.minecraft);
this.minecraft.loadWorld(world);
}
handleServerChat(packet) {
this.minecraft.ingameOverlay.chatOverlay.addMessage(packet.getMessage());
}
handleChunkData(packet) {
let provider = this.minecraft.world.getChunkProvider();
if (packet.isFullChunk()) {
if (packet.getDataSize() === 0) {
provider.unloadChunk(packet.getX(), packet.getZ());
return;
}
provider.loadChunk(packet.getX(), packet.getZ());
}
let chunk = this.minecraft.world.getChunkAt(packet.getX(), packet.getZ());
chunk.fillChunk(packet.getData(), packet.getDataSize(), packet.isFullChunk());
}
handleMultiChunkData(packet) {
for (let chunkData of packet.getChunkData()) {
this.handleChunkData(chunkData);
}
}
handleBlockChange(packet) {
let position = packet.getBlockPosition();
let blockState = packet.getBlockState();
let typeId = blockState >> 4;
this.minecraft.world.setBlockAt(position.getX(), position.getY(), position.getZ(), typeId);
}
onDisconnect() {
if (this.minecraft.isInGame()) {
this.minecraft.displayScreen(new GuiDisconnected("Disconnected from server"));
}
}
getNetworkManager() {
return this.networkManager;
}
sendPacket(packet) {
this.networkManager.sendPacket(packet);
}
}
@@ -4,14 +4,6 @@ export default class PacketHandler {
}
handleStatusResponse(packet) {
}
handleEncryptionRequest(packet) {
}
onDisconnect() {
}
@@ -13,7 +13,7 @@ export default class HandshakePacket extends Packet {
buffer.writeVarInt(this.version); // Protocol version
buffer.writeString("localhost"); // Server address
buffer.writeShort(25565); // Server port
buffer.writeVarInt(this.nextState); // Next state
buffer.writeVarInt(this.nextState.getId()); // Next state
}
read(buffer) {
@@ -0,0 +1,26 @@
import Packet from "../../../Packet.js";
export default class EnableCompressionPacket extends Packet {
constructor() {
super();
this.compressionThreshold = 0;
}
write(buffer) {
}
read(buffer) {
this.compressionThreshold = buffer.readVarInt();
}
handle(handler) {
handler.handleEnableCompression(this);
}
getCompressionThreshold() {
return this.compressionThreshold;
}
}
@@ -14,7 +14,7 @@ export default class LoginDisconnectPacket extends Packet {
}
read(buffer) {
this.message = format(JSON.parse(buffer.readString()));
this.message = format(JSON.parse(buffer.readString(32767)));
}
handle(handler) {
@@ -0,0 +1,27 @@
import Packet from "../../../Packet.js";
import UUID from "../../../../../util/UUID.js";
import GameProfile from "../../../../../util/GameProfile.js";
export default class LoginSuccessPacket extends Packet {
constructor() {
super();
this.profile = null;
}
write(buffer) {
}
read(buffer) {
let uuid = UUID.fromString(buffer.readString());
let username = buffer.readString();
this.profile = new GameProfile(uuid, username);
}
handle(handler) {
handler.handleLoginSuccess(this);
}
}
@@ -0,0 +1,14 @@
import Packet from "../../../Packet.js";
export default class ClientChatPacket extends Packet {
constructor(message) {
super();
this.message = message;
}
write(buffer) {
buffer.writeString(this.message);
}
}
@@ -0,0 +1,18 @@
import Packet from "../../../Packet.js";
export default class ClientKeepAlivePacket extends Packet {
constructor(id = 0) {
super();
this.id = id;
}
write(buffer) {
buffer.writeVarInt(this.id);
}
getId() {
return this.id;
}
}
@@ -0,0 +1,35 @@
import Packet from "../../../Packet.js";
export default class ClientPlayerMovementPacket extends Packet {
constructor(onGround) {
super();
this.position = false;
this.rotation = false;
this.onGround = onGround;
this.x = 0;
this.y = 0;
this.z = 0;
this.yaw = 0;
this.pitch = 0;
}
write(buffer) {
if (this.position) {
buffer.writeDouble(this.x);
buffer.writeDouble(this.y);
buffer.writeDouble(this.z);
}
if (this.rotation) {
buffer.writeFloat(this.yaw);
buffer.writeFloat(this.pitch);
}
buffer.writeBoolean(this.onGround);
}
}
@@ -0,0 +1,14 @@
import ClientPlayerMovementPacket from "./ClientPlayerMovementPacket.js";
export default class ClientPlayerPositionPacket extends ClientPlayerMovementPacket {
constructor(onGround, x, y, z) {
super(onGround);
this.position = true;
this.x = x;
this.y = y;
this.z = z;
}
}
@@ -0,0 +1,18 @@
import ClientPlayerMovementPacket from "./ClientPlayerMovementPacket.js";
export default class ClientPlayerPositionRotationPacket extends ClientPlayerMovementPacket {
constructor(onGround, x, y, z, yaw, pitch) {
super(onGround);
this.position = true;
this.rotation = true;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
}
@@ -0,0 +1,13 @@
import ClientPlayerMovementPacket from "./ClientPlayerMovementPacket.js";
export default class ClientPlayerRotationPacket extends ClientPlayerMovementPacket {
constructor(onGround, yaw, pitch) {
super(onGround);
this.rotation = true;
this.yaw = yaw;
this.pitch = pitch;
}
}
@@ -0,0 +1,27 @@
import Packet from "../../../Packet.js";
export default class ServerBlockChangePacket extends Packet {
constructor() {
super();
this.blockPosition = null;
}
read(buffer) {
this.blockPosition = buffer.readBlockPosition();
this.blockState = buffer.readVarInt();
}
handle(handler) {
handler.handleBlockChange(this);
}
getBlockPosition() {
return this.blockPosition;
}
getBlockState() {
return this.blockState;
}
}
@@ -0,0 +1,31 @@
import Packet from "../../../Packet.js";
import {format} from "../../../../../../../../../libraries/chat.js";
export default class ServerChatPacket extends Packet {
constructor() {
super();
this.message = "";
this.type = 0;
}
read(buffer) {
this.message = format(JSON.parse(buffer.readString(32767)), {
useAnsiCodes: true
});
this.type = buffer.readByte();
}
handle(handler) {
handler.handleServerChat(this);
}
getMessage() {
return this.message;
}
getType() {
return this.type;
}
}
@@ -0,0 +1,46 @@
import Packet from "../../../Packet.js";
export default class ServerChunkDataPacket extends Packet {
constructor() {
super();
this.x = 0;
this.z = 0;
this.fullChunk = false;
this.dataSize = 0;
this.data = [];
}
read(buffer) {
this.x = buffer.readInt();
this.z = buffer.readInt();
this.fullChunk = buffer.readBoolean();
this.dataSize = buffer.readShort();
this.data = buffer.readByteArray();
}
handle(handler) {
handler.handleChunkData(this);
}
getX() {
return this.x;
}
getZ() {
return this.z;
}
isFullChunk() {
return this.fullChunk;
}
getDataSize() {
return this.dataSize;
}
getData() {
return this.data;
}
}
@@ -0,0 +1,37 @@
import Packet from "../../../Packet.js";
export default class ServerJoinGamePacket extends Packet {
constructor() {
super();
this.entityId = 0;
this.hardcoreMode = false;
this.gameType = 0;
this.dimension = 0;
this.difficulty = 0;
this.maxPlayers = 0;
this.worldType = "";
this.reducedDebugInfo = false;
}
write(buffer) {
}
read(buffer) {
this.entityId = buffer.readVarInt();
let bits = buffer.readByte();
this.hardcoreMode = (bits & 8) === 8;
this.gameType = bits & -9;
this.dimension = buffer.readByte();
this.difficulty = buffer.readByte();
this.maxPlayers = buffer.readByte();
this.worldType = buffer.readString();
this.reducedDebugInfo = buffer.readBoolean();
}
handle(handler) {
handler.handleJoinGame(this);
}
}
@@ -0,0 +1,22 @@
import Packet from "../../../Packet.js";
export default class ServerKeepAlivePacket extends Packet {
constructor() {
super();
this.id = 0;
}
read(buffer) {
this.id = buffer.readVarInt();
}
handle(handler) {
handler.handleKeepAlive(this);
}
getId() {
return this.id;
}
}
@@ -0,0 +1,38 @@
import Packet from "../../../Packet.js";
import ServerChunkDataPacket from "./ServerChunkDataPacket.js";
export default class ServerMultiChunkDataPacket extends Packet {
constructor() {
super();
this.overworld = false;
this.chunkData = [];
}
read(buffer) {
this.overworld = buffer.readBoolean();
let amount = buffer.readVarInt();
for (let i = 0; i < amount; i++) {
let x = buffer.readInt();
let y = buffer.readInt();
let dataSize = buffer.readShort() & 65535;
let data = buffer.readByteArray();
this.chunkData.push(new ServerChunkDataPacket(x, y, dataSize, data));
}
}
handle(handler) {
handler.handleMultiChunkData(this);
}
getChunkData() {
return this.chunkData;
}
isOverworld() {
return this.overworld;
}
}
@@ -1,4 +1,4 @@
import PacketHandler from "../PacketHandler.js";
import PacketHandler from "../handler/PacketHandler.js";
import GuiDisconnected from "../../gui/screens/GuiDisconnected.js";
export default class NetworkStatusHandler extends PacketHandler {
@@ -0,0 +1,50 @@
import ByteBuf from "./ByteBuf.js";
import {require} from "../../../../../Start.js";
export default class Authentication {
constructor(networkManager) {
this.networkManager = networkManager;
}
joinServer(profile, accessToken, serverId) {
this.networkManager.sendProxyPacket(1, {
"accessToken": accessToken,
"selectedProfile": profile.getCompactUUID(),
"serverId": serverId
})
}
createServerHash(serverId, secretKey, publicKey) {
// Create hash
let bytes = require("sha1").create()
.update(new TextEncoder().encode(serverId))
.update(secretKey)
.update(new Uint8Array(publicKey))
.digest()
// Convert to hex string
let buffer = new ByteBuf(new Int8Array(bytes));
let sign = '';
// Handle negative hashes
if (buffer.readByte() < 0) {
let carry = true
for (let pos = buffer.length() - 1; pos >= 0; --pos) {
let value = buffer.readByte(pos);
let newValue = ~value & 0xff
if (carry) {
carry = newValue === 0xff
buffer.writeByte(carry ? 0 : newValue + 1, pos);
} else {
buffer.writeByte(newValue, pos)
}
}
sign = '-';
}
// Convert to hex string
return sign + buffer.toString().replace(/^0+/g, '');
}
}
@@ -1,3 +1,6 @@
import Long from "../../../../../../../libraries/long.js";
import BlockPosition from "../../../util/BlockPosition.js";
export default class ByteBuf {
static SEGMENT_BITS = 0x7F;
@@ -24,7 +27,12 @@ export default class ByteBuf {
return this.array;
}
readByte() {
getSlicedArray(length = this.array.length - this.pos) {
return this.array.slice(this.pos, this.pos + length);
}
readByte(pos = this.pos) {
this.pos = pos;
return this.array[this.pos++];
}
@@ -40,32 +48,42 @@ export default class ByteBuf {
}
readLong() {
return this.array[this.pos++] << 56
| this.array[this.pos++] << 48
| this.array[this.pos++] << 40
| this.array[this.pos++] << 32
| this.array[this.pos++] << 24
| this.array[this.pos++] << 16
| this.array[this.pos++] << 8
| this.array[this.pos++];
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++]));
}
readFloat() {
return this.readInt() / (1 << 24);
return new Float32Array(new Uint32Array([this.readInt(), 0, 0, 0, 0, 0, 0, 0]).buffer)[0];
}
readDouble() {
return this.readLong() / (1 << 53);
let lng = this.readLong();
return new Float64Array(new Uint32Array([lng.low >>> 0, lng.high >>> 0, 0, 0, 0, 0, 0, 0]).buffer)[0];
}
readString() {
readBoolean() {
return this.readByte() !== 0;
}
readString(maxLength) {
let len = this.readVarInt();
if (len > maxLength * 4) {
throw new Error("Trying to read string longer than max length (" + len + " > " + (maxLength * 4) + ")");
}
let array = new Uint8Array(len);
this.read(array, len);
return new TextDecoder().decode(array);
}
writeByte(value) {
writeByte(value, pos = this.pos) {
this.pos = pos;
this.extendIfNeeded(1);
this.array[this.pos++] = value;
}
@@ -86,22 +104,24 @@ export default class ByteBuf {
writeLong(value) {
this.extendIfNeeded(8);
this.array[this.pos++] = value >> 56;
this.array[this.pos++] = value >> 48;
this.array[this.pos++] = value >> 40;
this.array[this.pos++] = value >> 32;
this.array[this.pos++] = value >> 24;
this.array[this.pos++] = value >> 16;
this.array[this.pos++] = value >> 8;
this.array[this.pos++] = value;
this.array[this.pos++] = value.shiftRightUnsigned(56).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(48).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(40).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(32).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(24).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(16).toInt() & 0xFF;
this.array[this.pos++] = value.shiftRightUnsigned(8).toInt() & 0xFF;
this.array[this.pos++] = value.toInt() & 0xFF;
}
writeFloat(value) {
this.writeInt(value * (1 << 24));
let buffer = new Uint32Array(new Float32Array([value, 0, 0, 0, 0, 0, 0, 0]).buffer);
this.writeInt(buffer[0]);
}
writeDouble(value) {
this.writeLong(value * (1 << 53));
let buffer = new Uint32Array(new Float64Array([value, 0, 0, 0, 0, 0, 0, 0]).buffer);
this.writeLong(Long.fromBits(buffer[0], buffer[1]));
}
writeString(value) {
@@ -110,6 +130,10 @@ export default class ByteBuf {
this.write(array);
}
writeBoolean(value) {
this.writeByte(value ? 1 : 0);
}
writeVarInt(value) {
while (true) {
if ((value & ~ByteBuf.SEGMENT_BITS) === 0) {
@@ -131,6 +155,10 @@ export default class ByteBuf {
}
}
skipBytes(length) {
this.pos += length;
}
extendIfNeeded(bytes) {
if (this.pos + bytes > this.array.length) {
let newArray = new Uint8Array(this.array.length + bytes);
@@ -169,4 +197,22 @@ export default class ByteBuf {
this.writeVarInt(array.length);
this.write(array);
}
writeBlockPosition(blockPosition) {
this.writeLong(blockPosition.toLong());
}
readBlockPosition() {
return BlockPosition.fromLong(this.readLong());
}
readableBytes() {
return this.array.length - this.pos;
}
toString() {
return Array.from(this.array, byte => {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
}
@@ -1,7 +1,6 @@
import Random from "../../../util/Random.js";
import ByteBuf from "./ByteBuf.js";
import {bigIntToBytes, bytesToBigInt, modPow} from "../../../../../../../libraries/modpow.js";
import {parseAsn1} from "../../../../../../../libraries/asn1.js";
import {require} from "../../../../../Start.js";
export default class CryptManager {
@@ -13,11 +12,12 @@ export default class CryptManager {
static encryptRSA(publicKey, data) {
// Parse asn1 public key
let asn1 = parseAsn1(new Uint8Array(publicKey))
let asn1 = require("ASN1").parse(new Uint8Array(publicKey))
let bigintModArith = require("bigint-mod-arith");
// Extract n an e of the public key
let n = bytesToBigInt(asn1.children[1].children[0].children[0].value);
let e = bytesToBigInt(asn1.children[1].children[0].children[1].value);
// Extract n and e of the public key
let n = bigintModArith.bytesToBigInt(asn1.children[1].children[0].children[0].value);
let e = bigintModArith.bytesToBigInt(asn1.children[1].children[0].children[1].value);
// Check length of public key
let length = (n.toString(2).length + 7) >> 3;
@@ -44,13 +44,13 @@ export default class CryptManager {
let reversed = buffer.getArray().reverse();
// Convert to bigint
let bigInt = bytesToBigInt(reversed);
let bigInt = bigintModArith.bytesToBigInt(reversed);
// Encrypt
bigInt = modPow(bigInt, e, n);
bigInt = bigintModArith.modPow(bigInt, e, n);
// Convert to bytes
return bigIntToBytes(bigInt);
return bigintModArith.bigIntToBytes(bigInt);
}
}
@@ -558,7 +558,7 @@ export default class WorldRenderer {
let renderDistance = this.minecraft.settings.viewDistance;
// Update chunks
for (let [index, chunk] of world.chunks) {
for (let [index, chunk] of world.getChunkProvider().getChunks()) {
let distanceX = Math.abs(cameraChunkX - chunk.x);
let distanceZ = Math.abs(cameraChunkZ - chunk.z);
@@ -599,7 +599,7 @@ export default class WorldRenderer {
// TODO Implement chunk unloading
//let index = chunk.x + (chunk.z << 16);
//world.chunks.delete(index);
//world.getChunkProvider().getChunks().delete(index);
//world.group.remove(chunk.group);
}
}
@@ -637,7 +637,7 @@ export default class WorldRenderer {
rebuildAll() {
let world = this.minecraft.world;
for (let [index, chunk] of world.chunks) {
for (let [index, chunk] of world.getChunkProvider().getChunks()) {
chunk.setModifiedAllSections();
}
}
@@ -1,5 +1,6 @@
import PlayerRenderer from "./entity/PlayerRenderer.js";
import PlayerEntity from "../../entity/PlayerEntity.js";
import PlayerEntityMultiplayer from "../../entity/PlayerEntityMultiplayer.js";
export default class EntityRenderManager {
@@ -8,6 +9,7 @@ export default class EntityRenderManager {
this.renderers = [];
this.push(PlayerEntity, PlayerRenderer);
this.push(PlayerEntityMultiplayer, PlayerRenderer);
}
push(entityType, entityRenderer) {
@@ -18,6 +20,6 @@ export default class EntityRenderManager {
if (!(entity.constructor.name in this.renderers)) {
return null;
}
return new this.renderers[entity.constructor.name].prototype.constructor(this.worldRenderer);
return new this.renderers[entity.constructor.name]["prototype"]["constructor"](this.worldRenderer);
}
}
+30 -2
View File
@@ -300,6 +300,34 @@ export default class Chunk {
return true;
}
fillChunk(data, size, fullChunk) {
let i = 0;
for (let layer = 0; layer < Chunk.SECTION_AMOUNT; layer++) {
if ((size & 1 << layer) !== 0) {
let section = this.getSection(layer);
for (let k = 0; k < ChunkSection.SIZE * ChunkSection.SIZE * ChunkSection.SIZE; k++) {
let x = k & 15;
let z = (k >> 4) & 15;
let y = (k >> 8) & 15;
let value = (((data[i] & 0xFF) | (data[i + 1] & 0xFF) << 8));
let typeId = value >> 4;
let meta = value & 0xF; // TODO handle meta of block
// TODO support more blocks
if (typeId !== 0 && Block.getById(typeId) === null) {
typeId = 1;
}
section.setBlockAt(x, y, z, typeId);
i += 2;
}
}
}
}
getBlockID(x, y, z) {
return this.getBlockAt(x, y, z);
}
@@ -317,7 +345,7 @@ export default class Chunk {
}
rebuild(renderer) {
for (let y = 0; y < this.sections.length; y++) {
for (let y = 0; y < Chunk.SECTION_AMOUNT; y++) {
this.sections[y].rebuild(renderer);
}
}
@@ -331,7 +359,7 @@ export default class Chunk {
}
setModifiedAllSections() {
for (let y = 0; y < this.sections.length; y++) {
for (let y = 0; y < Chunk.SECTION_AMOUNT; y++) {
this.sections[y].isModified = true;
}
}
@@ -66,7 +66,7 @@ export default class ChunkSection {
let absoluteZ = this.z * ChunkSection.SIZE + z;
let block = Block.getById(typeId);
if (block.isTranslucent() !== isTranslucentRenderPhase) {
if (block === null || block.isTranslucent() !== isTranslucentRenderPhase) {
continue;
}
+17 -75
View File
@@ -1,5 +1,4 @@
import ChunkSection from "./ChunkSection.js";
import WorldGenerator from "./generator/WorldGenerator.js";
import MathHelper from "../../util/MathHelper.js";
import BoundingBox from "../../util/BoundingBox.js";
import EnumSkyBlock from "../../util/EnumSkyBlock.js";
@@ -9,13 +8,12 @@ import Vector3 from "../../util/Vector3.js";
import Vector4 from "../../util/Vector4.js";
import MetadataChunkBlock from "../../util/MetadataChunkBlock.js";
import * as THREE from "../../../../../../libraries/three.module.js";
import Random from "../../util/Random.js";
export default class World {
static TOTAL_HEIGHT = ChunkSection.SIZE * 8 - 1; // ChunkSection.SIZE * 16 - 1;
constructor(minecraft, seed) {
constructor(minecraft) {
this.minecraft = minecraft;
this.entities = [];
@@ -23,14 +21,12 @@ export default class World {
this.group = new THREE.Object3D();
this.group.matrixAutoUpdate = false;
this.chunks = new Map();
this.lightUpdateQueue = [];
this.chunkProvider = null;
this.time = 0;
this.spawn = new Vector3(0, 0, 0);
this.setSeed(seed);
// Update lights async
let scope = this;
setInterval(function () {
@@ -42,14 +38,8 @@ export default class World {
}, 0);
}
setSeed(seed) {
this.seed = seed;
this.generator = new WorldGenerator(this, seed);
this.random = new Random(seed);
}
getSeed() {
return this.seed;
setChunkProvider(chunkProvider) {
this.chunkProvider = chunkProvider;
}
onTick() {
@@ -67,44 +57,7 @@ export default class World {
}
getChunkAt(x, z) {
let index = x + (z << 16);
let chunk = this.chunks.get(index);
if (typeof chunk === 'undefined') {
// Generate new chunk
chunk = this.generator.newChunk(this, x, z);
// Register and mark as loaded
chunk.loaded = true;
this.chunks.set(index, chunk);
// Populate the chunk
if (!chunk.isTerrainPopulated && this.chunkExists(x + 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x + 1, z)) {
this.populate(x, z);
}
if (this.chunkExists(x - 1, z) && !this.getChunkAt(x - 1, z).isTerrainPopulated && this.chunkExists(x - 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x - 1, z)) {
this.populate(x - 1, z);
}
if (this.chunkExists(x, z - 1) && !this.getChunkAt(x, z - 1).isTerrainPopulated && this.chunkExists(x + 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x + 1, z)) {
this.populate(x, z - 1);
}
if (this.chunkExists(x - 1, z - 1) && !this.getChunkAt(x - 1, z - 1).isTerrainPopulated && this.chunkExists(x - 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x - 1, z)) {
this.populate(x - 1, z - 1);
}
// Register in three.js
this.group.add(chunk.group);
}
return chunk;
}
populate(x, z) {
let chunk = this.getChunkAt(x, z);
if (!chunk.isTerrainPopulated) {
chunk.isTerrainPopulated = true;
// Populate chunk
this.generator.populateChunk(chunk.x, chunk.z);
}
return this.chunkProvider.getChunkAt(x, z);
}
getChunkAtBlock(x, y, z) {
@@ -207,9 +160,7 @@ export default class World {
}
chunkExists(chunkX, chunkZ) {
let index = chunkX + (chunkZ << 16);
let chunk = this.chunks.get(index);
return typeof chunk !== 'undefined';
return this.chunkProvider !== null && this.chunkProvider.chunkExists(chunkX, chunkZ);
}
neighborLightPropagationChanged(sourceType, x, y, z, level) {
@@ -302,7 +253,12 @@ export default class World {
isSolidBlockAt(x, y, z) {
let typeId = this.getBlockAt(x, y, z);
return typeId !== 0 && Block.getById(typeId).isSolid();
if (typeId === 0) {
return false;
}
let block = Block.getById(typeId);
return block !== null && block.isSolid();
}
isTranslucentBlockAt(x, y, z) {
@@ -604,6 +560,7 @@ export default class World {
addEntity(entity) {
this.entities.push(entity);
entity.initRenderer();
this.group.add(entity.renderer.group);
}
@@ -631,25 +588,6 @@ export default class World {
this.spawn = new Vector3(x, y + 8, z);
}
findSpawn() {
if (this.spawn.y <= 0) {
this.spawn.y = 64;
}
while (this.getBlockAboveSeaLevel(this.spawn.x, this.spawn.z) === 0) {
this.spawn.x += this.random.nextInt(8) - this.random.nextInt(8);
this.spawn.z += this.random.nextInt(8) - this.random.nextInt(8);
}
}
getBlockAboveSeaLevel(x, z) {
let y = this.generator.seaLevel;
while (this.getBlockAt(x, y + 1, z) !== 0) {
y++;
}
return this.getBlockAt(x, y, z);
}
loadSpawnChunks() {
let viewDistance = this.minecraft.settings.viewDistance;
for (let x = -viewDistance; x <= viewDistance; x++) {
@@ -660,4 +598,8 @@ export default class World {
this.spawn.y = this.getHeightAt(this.spawn.x, this.spawn.z) + 8;
}
getChunkProvider() {
return this.chunkProvider;
}
}
@@ -0,0 +1,12 @@
import World from "./World.js";
import ChunkProviderClient from "./provider/ChunkProviderClient.js";
export default class WorldClient extends World {
constructor(minecraft) {
super(minecraft);
// Set chunk provider to remote chunk loader
this.setChunkProvider(new ChunkProviderClient(this))
}
}
@@ -49,7 +49,12 @@ export default class Block {
shouldRenderFace(world, x, y, z, face) {
let typeId = world.getBlockAtFace(x, y, z, face);
return typeId === 0 || Block.getById(typeId).isTranslucent();
if (typeId === 0) {
return true;
}
let block = Block.getById(typeId);
return block === null || block.isTranslucent();
}
getColor(world, x, y, z, face) {
@@ -208,7 +213,8 @@ export default class Block {
}
static getById(typeId) {
return Block.blocks.get(typeId);
let block = Block.blocks.get(typeId);
return typeof block === "undefined" ? null : block;
}
}
@@ -13,6 +13,7 @@ import BlockBedrock from "./type/BlockBedrock.js";
import BlockGlass from "./type/BlockGlass.js";
import SoundGlass from "./sound/SoundGlass.js";
import BlockGravel from "./type/BlockGravel.js";
import BlockCobblestone from "./type/BlockCobblestone.js";
export class BlockRegistry {
@@ -30,6 +31,7 @@ export class BlockRegistry {
BlockRegistry.STONE = new BlockStone(1, 0);
BlockRegistry.GRASS = new BlockGrass(2, 1);
BlockRegistry.DIRT = new BlockDirt(3, 2);
BlockRegistry.COBBLE_STONE = new BlockCobblestone(4, 14);
BlockRegistry.WOOD = new BlockWood(5, 10);
BlockRegistry.BEDROCK = new BlockBedrock(7, 11);
BlockRegistry.GRAVEL = new BlockGravel(13, 13);
@@ -0,0 +1,9 @@
import Block from "../Block.js";
export default class BlockCobblestone extends Block {
constructor(id, textureSlotId) {
super(id, textureSlotId);
}
}
@@ -7,6 +7,8 @@ export default class Generator {
this.world = world;
this.seed = seed;
this.random = new Random(seed);
this.seaLevel = 64;
}
generateInChunk(chunkX, chunkZ, primer) {
@@ -35,4 +37,12 @@ export default class Generator {
let {seedX, seedZ} = this.generateSeedOffset();
this.setSeedOffset(chunkX, chunkZ, seedX, seedZ);
}
getSeed() {
return this.seed;
}
getSeaLevel() {
return this.seaLevel;
}
}
@@ -13,8 +13,6 @@ export default class WorldGenerator extends Generator {
constructor(world, seed) {
super(world, seed);
this.seaLevel = 64;
this.caveGenerator = new CaveGenerator(world, seed);
this.terrainGenerator4 = new NoiseGeneratorOctaves(this.random, 16);
@@ -0,0 +1,61 @@
import Chunk from "../Chunk.js";
export default class ChunkProvider {
constructor(world) {
this.world = world;
this.chunks = new Map();
}
chunkExists(x, z) {
let index = x + (z << 16);
let chunk = this.chunks.get(index);
return typeof chunk !== 'undefined';
}
getChunkAt(x, z) {
let index = x + (z << 16);
let chunk = this.chunks.get(index);
if (typeof chunk === 'undefined') {
chunk = this.loadChunk(x, z);
}
return chunk;
}
generateChunk(x, z) {
let chunk = new Chunk(this.world, x, z);
chunk.generateSkylightMap();
chunk.generateBlockLightMap();
return chunk;
}
populateChunk(chunk) {
}
loadChunk(x, z) {
let index = x + (z << 16);
let chunk = this.generateChunk(x, z)
// Register and mark as loaded
chunk.loaded = true;
this.chunks.set(index, chunk);
this.populateChunk(chunk);
// Register in three.js
this.world.group.add(chunk.group);
return chunk;
}
unloadChunk(x, z) {
let index = x + (z << 16);
this.chunks.delete(index);
}
getChunks() {
return this.chunks;
}
}
@@ -0,0 +1,20 @@
import ChunkProvider from "./ChunkProvider.js";
import Chunk from "../Chunk.js";
export default class ChunkProviderClient extends ChunkProvider {
constructor(world) {
super(world);
this.emptyChunk = new Chunk(world, 0, 0);
this.emptyChunk.generateSkylightMap();
this.emptyChunk.generateBlockLightMap();
}
getChunkAt(x, z) {
let index = x + (z << 16);
let chunk = this.chunks.get(index);
return typeof chunk === 'undefined' ? this.emptyChunk : chunk;
}
}
@@ -0,0 +1,67 @@
import ChunkProvider from "./ChunkProvider.js";
import WorldGenerator from "../generator/WorldGenerator.js";
import Random from "../../../util/Random.js";
export default class ChunkProviderGenerate extends ChunkProvider {
constructor(world, seed) {
super(world);
this.generator = new WorldGenerator(world, seed);
}
generateChunk(x, z) {
return this.generator.newChunk(this.world, x, z);
}
populateChunk(chunk) {
let x = chunk.x;
let z = chunk.z;
// Populate the chunk
if (!chunk.isTerrainPopulated && this.chunkExists(x + 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x + 1, z)) {
this._populateChunkAt(x, z);
}
if (this.chunkExists(x - 1, z) && !this.getChunkAt(x - 1, z).isTerrainPopulated && this.chunkExists(x - 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x - 1, z)) {
this._populateChunkAt(x - 1, z);
}
if (this.chunkExists(x, z - 1) && !this.getChunkAt(x, z - 1).isTerrainPopulated && this.chunkExists(x + 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x + 1, z)) {
this._populateChunkAt(x, z - 1);
}
if (this.chunkExists(x - 1, z - 1) && !this.getChunkAt(x - 1, z - 1).isTerrainPopulated && this.chunkExists(x - 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x - 1, z)) {
this._populateChunkAt(x - 1, z - 1);
}
}
_populateChunkAt(x, z) {
let chunk = this.getChunkAt(x, z);
if (!chunk.isTerrainPopulated) {
chunk.isTerrainPopulated = true;
// Populate chunk
this.generator.populateChunk(chunk.x, chunk.z);
}
}
findSpawn() {
let spawn = this.world.spawn;
if (spawn.y <= 0) {
spawn.y = 64;
}
let random = new Random(this.generator.getSeed());
while (this.getBlockAboveSeaLevel(spawn.x, spawn.z) === 0) {
spawn.x += random.nextInt(8) - random.nextInt(8);
spawn.z += random.nextInt(8) - random.nextInt(8);
}
}
getBlockAboveSeaLevel(x, z) {
let y = this.generator.getSeaLevel();
while (this.world.getBlockAt(x, y + 1, z) !== 0) {
y++;
}
return this.world.getBlockAt(x, y, z);
}
}