2023-08-15 10:00:12 +01:00
|
|
|
import { Console } from "hsconsole";
|
2023-11-13 00:00:33 +00:00
|
|
|
import { Endian, IReader, createWriter } from "bufferstuff";
|
2023-05-02 10:24:48 +01:00
|
|
|
import { MinecraftServer } from "./MinecraftServer";
|
2023-04-10 21:52:30 +01:00
|
|
|
import { Packet } from "./enums/Packet";
|
2023-05-02 10:24:48 +01:00
|
|
|
import { PacketAnimation } from "./packets/Animation";
|
|
|
|
import { PacketChat } from "./packets/Chat"
|
|
|
|
import { PacketEntityAction } from "./packets/EntityAction";
|
2023-04-09 04:19:10 +01:00
|
|
|
import { PacketPlayer } from "./packets/Player";
|
|
|
|
import { PacketPlayerPosition } from "./packets/PlayerPosition";
|
|
|
|
import { PacketPlayerLook } from "./packets/PlayerLook";
|
|
|
|
import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook";
|
2023-05-02 10:24:48 +01:00
|
|
|
import { PacketPlayerDigging } from "./packets/PlayerDigging";
|
2023-04-09 04:19:10 +01:00
|
|
|
import { Player } from "./entities/Player";
|
2023-05-02 10:24:48 +01:00
|
|
|
import { Socket } from "net";
|
2023-11-08 15:45:25 +00:00
|
|
|
import Vec3 from "./Vec3";
|
2023-09-04 23:42:38 +01:00
|
|
|
import { PacketRespawn } from "./packets/Respawn";
|
|
|
|
import { PacketSpawnPosition } from "./packets/SpawnPosition";
|
2023-10-29 05:08:26 +00:00
|
|
|
import { PacketPlayerBlockPlacement } from "./packets/PlayerBlockPlacement";
|
|
|
|
import { Inventory } from "./inventories/Inventory";
|
|
|
|
import { PacketHoldingChange } from "./packets/HoldingChange";
|
|
|
|
import { PacketDisconnectKick } from "./packets/DisconnectKick";
|
2023-11-02 08:31:43 +00:00
|
|
|
import { ItemStack } from "./inventories/ItemStack";
|
|
|
|
import { PacketWindowItems } from "./packets/WindowItems";
|
|
|
|
import { Block } from "./blocks/Block";
|
2023-11-09 16:30:40 +00:00
|
|
|
import { EntityItem } from "./entities/EntityItem";
|
2023-04-08 20:52:47 +01:00
|
|
|
|
|
|
|
export class MPClient {
|
2023-04-10 14:42:14 +01:00
|
|
|
private readonly mcServer:MinecraftServer;
|
2023-04-08 20:52:47 +01:00
|
|
|
private readonly socket:Socket;
|
2023-09-04 23:42:38 +01:00
|
|
|
public entity:Player;
|
2023-10-29 05:08:26 +00:00
|
|
|
private inventory:Inventory;
|
2023-11-05 00:55:23 +00:00
|
|
|
private dimension:number;
|
2023-04-08 20:52:47 +01:00
|
|
|
|
2023-10-29 05:08:26 +00:00
|
|
|
private holdingIndex:number = 36; // First hotbar slot.
|
2023-04-10 21:52:30 +01:00
|
|
|
private diggingAt:Vec3;
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) {
|
|
|
|
this.mcServer = mcServer;
|
2023-04-08 20:52:47 +01:00
|
|
|
this.socket = socket;
|
|
|
|
this.entity = entity;
|
2023-10-29 05:08:26 +00:00
|
|
|
this.inventory = entity.inventory;
|
2023-11-05 00:55:23 +00:00
|
|
|
this.dimension = 0;
|
2023-04-10 21:52:30 +01:00
|
|
|
|
|
|
|
this.diggingAt = new Vec3();
|
2023-04-08 20:52:47 +01:00
|
|
|
}
|
|
|
|
|
2023-10-29 05:08:26 +00:00
|
|
|
private mapCoordsFromFace(pos:Vec3, face:number) {
|
2023-04-10 14:42:14 +01:00
|
|
|
switch (face) {
|
|
|
|
case 0:
|
|
|
|
pos.y--;
|
|
|
|
return pos;
|
|
|
|
case 1:
|
|
|
|
pos.y++;
|
|
|
|
return pos;
|
|
|
|
case 2:
|
|
|
|
pos.z--;
|
|
|
|
return pos;
|
|
|
|
case 3:
|
|
|
|
pos.z++;
|
|
|
|
return pos;
|
|
|
|
case 4:
|
|
|
|
pos.x--;
|
|
|
|
return pos;
|
|
|
|
case 5:
|
|
|
|
pos.x++;
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 10:24:48 +01:00
|
|
|
public handlePacket(reader:IReader) {
|
2023-04-09 04:19:10 +01:00
|
|
|
const packetId = reader.readUByte();
|
|
|
|
|
|
|
|
switch (packetId) {
|
2023-04-10 21:52:30 +01:00
|
|
|
case Packet.Chat: this.handleChat(new PacketChat().readData(reader)); break;
|
2023-11-13 00:00:33 +00:00
|
|
|
case Packet.Respawn: this.handlePacketRespawn(new PacketRespawn().readData(reader)); break;
|
2023-04-10 21:52:30 +01:00
|
|
|
case Packet.Player: this.handlePacketPlayer(new PacketPlayer().readData(reader)); break;
|
|
|
|
case Packet.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break;
|
|
|
|
case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break;
|
|
|
|
case Packet.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break;
|
|
|
|
case Packet.PlayerDigging: this.handlePacketPlayerDigging(new PacketPlayerDigging().readData(reader)); break;
|
2023-10-29 05:08:26 +00:00
|
|
|
case Packet.PlayerBlockPlacement: this.handlePacketBlockPlacement(new PacketPlayerBlockPlacement().readData(reader)); break;
|
|
|
|
case Packet.HoldingChange: this.handlePacketHoldingChange(new PacketHoldingChange().readData(reader)); break;
|
2023-04-10 21:52:30 +01:00
|
|
|
//case Packets.UseBed: break;
|
|
|
|
case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break;
|
|
|
|
case Packet.EntityAction: this.handlePacketEntityAction(new PacketEntityAction().readData(reader)); break;
|
2023-08-20 01:18:05 +01:00
|
|
|
case Packet.DisconnectKick: this.handleDisconnectKick(); break;
|
2023-11-07 01:50:35 +00:00
|
|
|
default: return Console.printWarn(`UNIMPLEMENTED PACKET: ${Packet[packetId]}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reader.readOffset < reader.length - 1) {
|
|
|
|
this.handlePacket(reader);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
private handleChat(packet:PacketChat) {
|
2023-04-09 04:19:10 +01:00
|
|
|
const message = packet.message.split(" ");
|
2023-04-10 14:42:14 +01:00
|
|
|
if (message[0].startsWith("/")) {
|
|
|
|
packet.message = "";
|
|
|
|
if (message[0] === "/tp") {
|
2023-11-02 08:31:43 +00:00
|
|
|
const x = this.entity.position.x = parseFloat(message[1]);
|
|
|
|
const y = this.entity.position.y = parseFloat(message[2]);
|
|
|
|
const z = this.entity.position.z = parseFloat(message[3]);
|
2023-09-04 23:42:38 +01:00
|
|
|
this.send(new PacketPlayerPositionLook(x, y, y + 0.62, z, 0, 0, false).writeData());
|
2023-04-10 14:42:14 +01:00
|
|
|
Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`);
|
|
|
|
} else if (message[0] === "/csay") {
|
2023-08-20 01:18:05 +01:00
|
|
|
this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`);
|
2023-04-17 02:05:11 +01:00
|
|
|
} else if (message[0] === "/top") {
|
|
|
|
packet.message = `Woosh!`;
|
2023-11-02 08:31:43 +00:00
|
|
|
const topBlock = this.entity.chunk.getTopBlockY(this.entity.position.x & 0xf, this.entity.position.z & 0xf);
|
|
|
|
this.send(new PacketPlayerPosition(this.entity.position.x, topBlock + 3, topBlock + 3.62, this.entity.position.z, false).writeData());
|
2023-09-04 23:42:38 +01:00
|
|
|
} else if (message[0] === "/tpx") {
|
|
|
|
const dimension = parseInt(message[1]);
|
|
|
|
if (this.mcServer.worlds.has(dimension)) {
|
|
|
|
packet.message = "\u00a76Switching dimensions...";
|
|
|
|
this.switchDimension(dimension);
|
|
|
|
} else {
|
2023-11-02 08:31:43 +00:00
|
|
|
packet.message = `\u00a7cNo dimension by id "${dimension}" exists!`;
|
2023-09-04 23:42:38 +01:00
|
|
|
}
|
2023-04-10 14:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (packet.message !== "") {
|
|
|
|
this.send(packet.writeData());
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
2023-04-10 14:42:14 +01:00
|
|
|
|
|
|
|
packet.message = `<${this.entity.username}> ${packet.message}`;
|
2023-08-15 10:00:12 +01:00
|
|
|
Console.printInfo(`[CHAT] ${packet.message}`);
|
2023-04-10 14:42:14 +01:00
|
|
|
this.mcServer.sendToAllClients(packet.writeData());
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
2023-11-13 00:00:33 +00:00
|
|
|
private handlePacketRespawn(packet:PacketRespawn) {
|
|
|
|
if (!this.entity.isDead && packet.dimension === this.entity.world.dimension) {
|
2023-11-05 00:55:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2023-11-13 00:00:33 +00:00
|
|
|
|
|
|
|
const world = this.mcServer.worlds.get(this.entity.world.dimension);
|
|
|
|
if (world == undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.entity.world.removeEntity(this.entity);
|
|
|
|
const oldPlayerEntity = this.entity;
|
|
|
|
|
|
|
|
this.entity = new Player(this.mcServer, world, oldPlayerEntity.username);
|
|
|
|
this.entity.position.set(8, 70, 8);
|
|
|
|
world.addEntity(this.entity);
|
|
|
|
|
|
|
|
this.send(new PacketRespawn(world.dimension).writeData());
|
|
|
|
//this.send(new PacketSpawnPosition(8, 64, 8).writeData());
|
|
|
|
this.entity.position.set(this.entity.position.x, this.entity.position.y, this.entity.position.z);
|
|
|
|
this.send(new PacketPlayerPositionLook(this.entity.position.x, this.entity.position.y, this.entity.position.y + 0.62, this.entity.position.z, 0, 0, false).writeData());
|
|
|
|
|
|
|
|
this.entity.forceUpdatePlayerChunks();
|
2023-11-05 00:55:23 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
private handlePacketPlayer(packet:PacketPlayer) {
|
2023-04-09 04:47:23 +01:00
|
|
|
this.entity.onGround = packet.onGround;
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
private handlePacketPlayerPosition(packet:PacketPlayerPosition) {
|
2023-11-05 00:55:23 +00:00
|
|
|
this.entity.onGround = packet.onGround;
|
2023-11-02 08:31:43 +00:00
|
|
|
this.entity.position.set(packet.x, packet.y, packet.z);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
private handlePacketPlayerLook(packet:PacketPlayerLook) {
|
2023-11-05 00:55:23 +00:00
|
|
|
this.entity.onGround = packet.onGround;
|
2023-11-02 08:31:43 +00:00
|
|
|
this.entity.rotation.set(packet.yaw, packet.pitch);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
2023-04-10 14:42:14 +01:00
|
|
|
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
|
2023-11-05 00:55:23 +00:00
|
|
|
this.entity.onGround = packet.onGround;
|
2023-11-02 08:31:43 +00:00
|
|
|
this.entity.position.set(packet.x, packet.y, packet.z);
|
|
|
|
this.entity.rotation.set(packet.yaw, packet.pitch);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
2023-11-07 01:50:51 +00:00
|
|
|
private breakBlock(brokenBlockId:number, x:number, y:number, z:number) {
|
|
|
|
const metadata = this.entity.world.getBlockMetadata(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
|
|
|
|
this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0);
|
2023-12-06 00:04:57 +00:00
|
|
|
//this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
|
|
|
|
//this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
|
|
|
|
const itemId = Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId);
|
2023-11-11 00:14:27 +00:00
|
|
|
if (itemId !== -1) {
|
|
|
|
const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemId, 1, metadata));
|
|
|
|
itemEntity.position.set(x + 0.5, y + 0.5, z + 0.5);
|
|
|
|
this.entity.world.addEntity(itemEntity);
|
2023-12-06 00:04:57 +00:00
|
|
|
}
|
2023-11-07 01:50:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Cap how far away a player is able to break blocks
|
2023-04-10 21:52:30 +01:00
|
|
|
private handlePacketPlayerDigging(packet:PacketPlayerDigging) {
|
2023-11-05 00:55:23 +00:00
|
|
|
|
2023-04-10 21:52:30 +01:00
|
|
|
// Special drop item case
|
|
|
|
if (packet.status === 4) {
|
|
|
|
// TODO: Handle dropping items
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.diggingAt.set(packet.x, packet.y, packet.z);
|
|
|
|
|
2023-11-07 01:50:51 +00:00
|
|
|
let brokenBlockId:number;
|
2023-04-10 21:52:30 +01:00
|
|
|
if (packet.status === 0) {
|
|
|
|
// Started digging
|
2023-11-07 01:50:51 +00:00
|
|
|
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0 && Block.blocks[brokenBlockId].blockStrength() >= 1) {
|
|
|
|
this.breakBlock(brokenBlockId, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
|
|
|
|
}
|
2023-04-10 21:52:30 +01:00
|
|
|
} else if (packet.status === 2) {
|
2023-11-02 08:31:43 +00:00
|
|
|
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) {
|
2023-11-07 01:50:51 +00:00
|
|
|
this.breakBlock(brokenBlockId, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
|
2023-04-10 21:52:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-02 08:31:43 +00:00
|
|
|
public getHeldItemStack() {
|
|
|
|
return this.inventory.getSlotItemStack(this.holdingIndex);
|
|
|
|
}
|
|
|
|
|
2023-10-29 05:08:26 +00:00
|
|
|
private handlePacketBlockPlacement(packet:PacketPlayerBlockPlacement) {
|
|
|
|
this.diggingAt.set(packet.x, packet.y, packet.z);
|
|
|
|
this.mapCoordsFromFace(this.diggingAt, packet.face);
|
|
|
|
|
2023-11-02 08:31:43 +00:00
|
|
|
const itemStack = this.getHeldItemStack();
|
|
|
|
if (itemStack == null || itemStack.size == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (itemStack.isBlock) {
|
|
|
|
if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) === 0) {
|
|
|
|
itemStack.size--;
|
|
|
|
this.entity.world.setBlockAndMetadataWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, itemStack.itemID, itemStack.damage);
|
2023-11-07 01:50:51 +00:00
|
|
|
this.inventory.dropEmptyItemStacks();
|
2023-11-02 08:31:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// TODO: Handle item usage
|
2023-10-29 05:08:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private handlePacketHoldingChange(packet:PacketHoldingChange) {
|
|
|
|
if (packet.slotId < 0 || packet.slotId > 8) {
|
|
|
|
this.send(new PacketDisconnectKick("Out of Bounds Holding Index!").writeData());
|
|
|
|
this.socket.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.holdingIndex = 36 + packet.slotId;
|
|
|
|
}
|
|
|
|
|
2023-04-10 21:52:30 +01:00
|
|
|
// Animation start
|
|
|
|
private handlePacketAnimation(packet:PacketAnimation) {
|
|
|
|
// Forward this packet to all nearby clients
|
|
|
|
this.entity.world.sendToNearbyClients(this.entity, packet.writeData());
|
|
|
|
}
|
2023-04-10 14:42:14 +01:00
|
|
|
|
2023-04-10 21:52:30 +01:00
|
|
|
private handlePacketEntityAction(packet:PacketEntityAction) {
|
|
|
|
// Forward this packet to all nearby clients
|
|
|
|
switch (packet.action) {
|
|
|
|
case 1: this.entity.crouching = true; break;
|
|
|
|
case 2: this.entity.crouching = false; break;
|
|
|
|
case 3: break; // TODO: Leave Bed
|
|
|
|
}
|
2023-04-10 14:42:14 +01:00
|
|
|
}
|
|
|
|
|
2023-09-04 23:42:38 +01:00
|
|
|
private switchDimension(dimension:number) {
|
|
|
|
const world = this.mcServer.worlds.get(dimension);
|
|
|
|
if (world == undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.entity.world.removeEntity(this.entity);
|
|
|
|
this.entity.world = world;
|
|
|
|
world.addEntity(this.entity);
|
|
|
|
|
|
|
|
this.send(new PacketRespawn(dimension).writeData());
|
|
|
|
//this.send(new PacketSpawnPosition(8, 64, 8).writeData());
|
2023-11-02 08:31:43 +00:00
|
|
|
this.entity.position.set(8, 60, 8);
|
2023-09-04 23:42:38 +01:00
|
|
|
this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData());
|
|
|
|
|
|
|
|
this.entity.forceUpdatePlayerChunks();
|
|
|
|
}
|
|
|
|
|
2023-08-20 01:18:05 +01:00
|
|
|
private handleDisconnectKick() {
|
|
|
|
this.socket.end();
|
|
|
|
}
|
|
|
|
|
2023-05-02 10:24:48 +01:00
|
|
|
public send(buffer:Buffer) {
|
|
|
|
this.socket.write(buffer);
|
2023-04-08 20:52:47 +01:00
|
|
|
}
|
|
|
|
}
|