Compare commits

...

2 Commits

17 changed files with 239 additions and 45 deletions

5
src/Entity.ts Normal file
View File

@@ -0,0 +1,5 @@
import {IEntity} from "./IEntity";
export class Entity implements IEntity {
entityID: number;
}

View File

@@ -1,3 +1,3 @@
export interface IEntity {
entityID: Number
entityID: number
}

View File

@@ -2,7 +2,6 @@ import { PacketManager } from "./utils/PacketManager";
import {PlayerManager} from "./utils/PlayerManager";
import {Server} from "node:net"; // TODO: Replace with https://bun.sh/docs/api/tcp
import {PacketDisconnectKick} from "./packet/impl/player/PacketDisconnectKick";
import {Player} from "./Player";
export class MinecraftServer {
public server: Server = new Server();
@@ -10,6 +9,7 @@ export class MinecraftServer {
public static PlayerManagers = new Map<String, PlayerManager>();
// EntityMap = new Map<Number, IEntity>();
public static MAX_PLAYERS = 20;
public async start() {
this.server.listen(25565, async () => {
@@ -24,6 +24,7 @@ export class MinecraftServer {
});
this.server.on('close', this.stop);
this.server.on('drop', () => {})
}
public stop() {

View File

@@ -1,24 +1,60 @@
import {Socket} from "node:net";
import {IEntity} from "./IEntity";
import {PlayerManager} from "./utils/PlayerManager";
import {Entity} from "./Entity";
import {PacketEntityPositionLook} from "./packet/impl/entity/PacketEntityPositionLook";
import {PacketEntityTeleport} from "./packet/impl/entity/PacketEntityTeleport";
export class Player implements IEntity {
public entityID: Number;
export class Player extends Entity {
public username: String;
public socket: Socket;
public playerManager: PlayerManager;
public xPosition: number = 0;
public yPosition: number = 100;
public stance: number = 102;
public prevXPosition: number = this.xPosition;
public yPosition: number = 140;
public prevYPosition: number = this.yPosition;
public zPosition: number = 0;
public prevZPosition: number = this.zPosition;
public stance: number = 102;
public yaw: number = 0;
public pitch: number = 0;
public onGround: boolean = false;
constructor(username: String, socket: Socket, playerManager: PlayerManager) {
super();
this.username = username;
this.socket = socket;
this.playerManager = playerManager;
}
public updatePosition(x?: number, y?: number, stance?: number, z?: number, yaw?: number, pitch?: number, onGround?: boolean) {
if(x) {
this.prevXPosition = this.xPosition;
this.xPosition = x;
}
if(y && stance) {
this.prevYPosition = this.yPosition;
this.yPosition = y;
this.stance = stance;
const sy = stance - y;
if(sy < 0.1 || sy > 1.65) this.playerManager.kickPlayer("Illegal Stance");
}
if(z) {
this.prevZPosition = this.zPosition;
this.zPosition = z;
}
if(yaw) this.yaw = yaw;
if(pitch) this.pitch = pitch;
if(onGround != undefined) this.onGround = onGround;
// console.log(this.toString());
PlayerManager.sendPacketToAll(new PacketEntityTeleport(this.entityID, this.xPosition, this.yPosition, this.zPosition, 0, 0))
}
public toString() {
return `[Player ${this.username}] X=${this.xPosition},Y=${this.yPosition},stance=${this.stance},` +
`Z=${this.zPosition},yaw=${this.yaw},pitch=${this.pitch},onGround=${this.onGround}`;
}
}

View File

@@ -2,13 +2,15 @@ import {PacketEnum} from "../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../Player";
import {Socket} from "node:net";
import {Entity} from "../Entity";
import {IEntity} from "../IEntity";
export class Packet {
constructor(public options: IPacketOption) {
options.name = PacketEnum[options.packetID].toString();
}
readData(reader: IReader, player: Player) {}
readData(reader: IReader, entity: IEntity) {}
writeData() {
return createWriter(Endian.BE).toBuffer();
}

View File

@@ -0,0 +1,29 @@
import {Packet} from "../../Packet";
import {PacketEnum} from "../../../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../../../Player";
import {PlayerManager} from "../../../utils/PlayerManager";
import {Entity} from "../../../Entity";
export class PacketEntityPositionLook extends Packet {
constructor(private entityID: number, private dX: number, private dY: number, private dZ: number, private yaw: number, private pitch: number) {
super({
packetID: PacketEnum.EntityPositionLook
})
}
readData(reader: IReader, entity: Entity) {
}
writeData() {
return createWriter(Endian.BE).writeUByte(this.options.packetID)
.writeInt(this.entityID)
.writeByte(this.dX)
.writeByte(this.dY)
.writeByte(this.dZ)
.writeByte(this.yaw)
.writeByte(this.pitch)
.toBuffer();
}
}

View File

@@ -0,0 +1,29 @@
import {Packet} from "../../Packet";
import {PacketEnum} from "../../../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../../../Player";
import {PlayerManager} from "../../../utils/PlayerManager";
import {Entity} from "../../../Entity";
export class PacketEntityTeleport extends Packet {
constructor(private entityID: number, private dX: number, private dY: number, private dZ: number, private yaw: number, private pitch: number) {
super({
packetID: PacketEnum.EntityTeleport
})
}
readData(reader: IReader, entity: Entity) {
}
writeData() {
return createWriter(Endian.BE).writeUByte(this.options.packetID)
.writeInt(this.entityID)
.writeInt(this.dX)
.writeInt(this.dY)
.writeInt(this.dZ)
.writeByte(this.yaw)
.writeByte(this.pitch)
.toBuffer();
}
}

View File

@@ -9,6 +9,8 @@ import {PacketPreChunk} from "../world/PacketPreChunk";
import {PacketMapChunk} from "../world/PacketMapChunk";
import {PacketPosition} from "../player/PacketPosition";
import {PacketPositionLook} from "../player/PacketPositionLook";
import {MinecraftServer} from "../../../MinecraftServer";
import {ChatColor} from "../../../utils/ChatColor";
export class PacketLogin extends Packet {
constructor() {
@@ -19,15 +21,17 @@ export class PacketLogin extends Packet {
readData(reader: IReader, player: Player) {
const protocol = reader.readInt();
if(protocol > 14) player.playerManager.kickPlayer(`Server is outdated!`);
else if (protocol < 14) player.playerManager.kickPlayer(`Client is outdated!`);
if(protocol > 14) return player.playerManager.kickPlayer(`Server is outdated!`);
else if (protocol < 14) return player.playerManager.kickPlayer(`Client is outdated!`);
if(MinecraftServer.PlayerManagers.size > MinecraftServer.MAX_PLAYERS) return player.playerManager.kickPlayer(`Server is full!`);
const username = reader.readString16();
const seed = reader.readLong();
const dimension = reader.readByte();
player.playerManager.sendPacket(this);
player.playerManager.sendPacket(new PacketPositionLook());
PlayerManager.sendPacketToAll(new PacketChat(`§e<${username}> has joined the game.`));
PlayerManager.sendPacketToAll(new PacketChat(`${ChatColor.YELLOW}<${username}> has joined the game.`));
player.playerManager.sendPacket(new PacketPreChunk());
player.playerManager.sendPacket(new PacketMapChunk());

View File

@@ -3,6 +3,7 @@ import {PacketEnum} from "../../../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../../../Player";
import {PlayerManager} from "../../../utils/PlayerManager";
import {MinecraftServer} from "../../../MinecraftServer";
export class PacketServerList extends Packet {
constructor() {
@@ -12,7 +13,7 @@ export class PacketServerList extends Packet {
}
readData(reader: IReader, player: Player) {
player.playerManager.kickPlayer(`Beta 1.7.3 Server§0§0`);
player.playerManager.kickPlayer(`Beta 1.7.3 Server§${MinecraftServer.PlayerManagers.size}§${MinecraftServer.MAX_PLAYERS}`);
}
writeData() {

View File

@@ -4,6 +4,7 @@ import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../../../Player";
import {PlayerManager} from "../../../utils/PlayerManager";
import {PacketPreChunk} from "../world/PacketPreChunk";
import {PacketPositionLook} from "./PacketPositionLook";
export class PacketChat extends Packet {
constructor(public message: string) {
@@ -13,9 +14,31 @@ export class PacketChat extends Packet {
}
readData(reader: IReader, player: Player) {
this.message = `<${player.username}> ${reader.readString16()}`
const msg = reader.readString16();
this.message = `<${player.username}> ${msg}`
if(msg.startsWith("/")) {
this.handleCommand(player, msg);
return;
}
PlayerManager.sendPacketToAll(this);
}
handleCommand(player: Player, msg: string) {
if(!msg.startsWith("/")) return;
const cmd = msg.replace("/", "").split(" ");
if(cmd.length < 1) {
this.message = "Unknown command!";
return player.playerManager.sendPacket(this)
}
if(cmd[0] == "tp") {
player.xPosition = 0;
player.yPosition = 100;
player.zPosition = 0;
player.playerManager.sendPacket(new PacketPositionLook())
}
}
writeData() {

View File

@@ -0,0 +1,34 @@
import {Packet} from "../../Packet";
import {PacketEnum} from "../../../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {Player} from "../../../Player";
export class PacketLook extends Packet {
constructor() {
super({
packetID: PacketEnum.Look
})
}
readData(reader: IReader, player: Player) {
player.updatePosition(
undefined, // X
undefined, // Y
undefined, // Stance
undefined, // Z
reader.readFloat(), // Yaw
reader.readFloat(), // Pitch
reader.readBool() // onGround
);
}
writeData() {
const player: Player = this.options.player;
return createWriter(Endian.BE).writeUByte(this.options.packetID)
.writeLong(player.yaw)
.writeLong(player.pitch)
.writeBool(player.onGround)
.toBuffer();
}
}

View File

@@ -11,11 +11,15 @@ export class PacketPosition extends Packet {
}
readData(reader: IReader, player: Player) {
player.xPosition = reader.readDouble();
player.yPosition = reader.readDouble();
player.stance = reader.readDouble();
player.zPosition = reader.readDouble();
player.onGround = reader.readBool();
player.updatePosition(
reader.readDouble(), // X
reader.readDouble(), // Y
reader.readDouble(), // Stance
reader.readDouble(), // Z
undefined, // Yaw
undefined, // Pitch
reader.readBool() // onGround
);
}
writeData() {

View File

@@ -1,6 +1,6 @@
import {Packet} from "../../Packet";
import {PacketEnum} from "../../../utils/PacketEnum";
import {createWriter, Endian, IReader} from "bufferstuff";
import {createWriter, Endian, IReader, IWriter} from "bufferstuff";
import {Player} from "../../../Player";
export class PacketPositionLook extends Packet {
@@ -11,13 +11,15 @@ export class PacketPositionLook extends Packet {
}
readData(reader: IReader, player: Player) {
player.xPosition = reader.readDouble();
player.yPosition = reader.readDouble();
player.stance = reader.readDouble();
player.zPosition = reader.readDouble();
player.yaw = reader.readFloat();
player.pitch = reader.readFloat();
player.onGround = reader.readBool();
player.updatePosition(
reader.readDouble(), // X
reader.readDouble(), // Y
reader.readDouble(), // Stance
reader.readDouble(), // Z
reader.readFloat(), // Yaw
reader.readFloat(), // Pitch
reader.readBool() // onGround
);
}
writeData() {
@@ -28,8 +30,8 @@ export class PacketPositionLook extends Packet {
.writeDouble(player.stance)
.writeDouble(player.yPosition)
.writeDouble(player.zPosition)
.writeLong(player.yaw)
.writeLong(player.pitch)
.writeFloat(player.yaw)
.writeFloat(player.pitch)
.writeBool(player.onGround)
.toBuffer();
}

View File

@@ -6,30 +6,32 @@ import {deflate} from "node:zlib";
import * as zlib from "zlib";
export class PacketMapChunk extends Packet {
public world: Buffer;
constructor() {
super({
packetID: PacketEnum.MapChunk
})
const chunk = createWriter(Endian.BE);
for(let x = 0; x < 256; x++) {
for(let i = 0; i < 90; i++) {
chunk.writeByte(1);
}
for(let i = 0; i < 38; i++) {
chunk.writeByte(0);
}
}
this.world = zlib.deflateSync(chunk.toBuffer());
}
readData(reader: IReader, player: Player) {
}
writeData() {
const world = createWriter(Endian.BE);
for(let x = 0; x < 256; x++) {
for(let i = 0; i < 10; i++) {
world.writeByte(1);
}
for(let i = 0; i < 118; i++) {
world.writeByte(0);
}
}
const buf = zlib.deflateSync(world.toBuffer());
return createWriter(Endian.BE).writeUByte(this.options.packetID)
.writeInt(0)
.writeShort(0)
@@ -37,8 +39,8 @@ export class PacketMapChunk extends Packet {
.writeByte(15)
.writeByte(127)
.writeByte(15)
.writeInt(buf.length)
.writeBuffer(buf)
.writeInt(this.world.length)
.writeBuffer(this.world)
.toBuffer();
}
}

18
src/utils/ChatColor.ts Normal file
View File

@@ -0,0 +1,18 @@
export class ChatColor {
public static DARK_RED: string = "§4";
public static RED: string = "§c";
public static GOLD: string = "§6";
public static YELLOW: string = "§e";
public static DARK_GREEN: string = "§2";
public static GREEN: string = "§a";
public static AQUA: string = "§b";
public static DARK_AQUA: string = "§3";
public static DARK_BLUE: string = "§1";
public static BLUE: string = "§9";
public static LIGHT_PURPLE: string = "§d";
public static DARK_PURPLE: string = "§5";
public static WHITE: string = "§f";
public static GRAY: string = "§7";
public static DARK_GRAY: string = "§8";
public static BLACK: string = "§0";
}

View File

@@ -9,6 +9,8 @@ export enum PacketEnum {
Look = 0x0C,
PositionLook = 0x0D,
Animation = 0x12,
EntityPositionLook = 0x21,
EntityTeleport = 0x22,
PreChunk = 0x32,
MapChunk = 0x33,
MultiBlockChange = 0x34,

View File

@@ -8,6 +8,7 @@ import {PacketManager} from "./PacketManager";
import {MinecraftServer} from "../MinecraftServer";
import {PacketEnum} from "./PacketEnum";
import {PacketHandshake} from "../packet/impl/login/PacketHandshake";
import {ChatColor} from "./ChatColor";
export class PlayerManager {
public player: Player;
@@ -30,7 +31,7 @@ export class PlayerManager {
if(packet === undefined) {
if(MinecraftServer.debug) console.log(`Received Unknown packet: ${packetID}. Kicking the player.`);
this.kickPlayer(`Sent unknown packet ${packetID}`);
// this.kickPlayer(`Sent unknown packet ${packetID}`);
return;
}
@@ -42,6 +43,7 @@ export class PlayerManager {
this.socket.on('close', () => this.playerDisconnected());
this.socket.on('timeout', () => this.playerDisconnected());
this.socket.on('error', () => this.playerDisconnected());
}
public sendPacket(packet: Packet) {
@@ -70,7 +72,7 @@ export class PlayerManager {
if(MinecraftServer.debug) console.log(`Player ${this.username} left. Deleting from the map!`);
MinecraftServer.PlayerManagers.delete(this.username);
PlayerManager.sendPacketToAll(new PacketChat(`§e<${this.username}> left the game.`));
PlayerManager.sendPacketToAll(new PacketChat(`${ChatColor.YELLOW}<${this.username}> left the game.`));
}
public static getPlayer(username: String) {