players!!!!

This commit is contained in:
Holly Stubbs 2023-04-10 14:42:14 +01:00
parent 36d294a810
commit f93bb2605d
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
17 changed files with 487 additions and 18 deletions

View file

@ -7,17 +7,45 @@ import { PacketPlayerLook } from "./packets/PlayerLook";
import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook";
import { Player } from "./entities/Player";
import { PacketChat } from "./packets/Chat";
import { MinecraftServer } from "./MinecraftServer";
import { Vec3 } from "./Vec3";
import { Console } from "../console";
export class MPClient {
private readonly mcServer:MinecraftServer;
private readonly socket:Socket;
public readonly entity:Player;
public constructor(socket:Socket, entity:Player) {
public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) {
this.mcServer = mcServer;
this.socket = socket;
this.entity = entity;
}
handlePacket(reader:Reader) {
private mapCoordsToFace(pos:Vec3, face:number) {
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;
}
}
public handlePacket(reader:Reader) {
const packetId = reader.readUByte();
switch (packetId) {
@ -26,32 +54,51 @@ export class MPClient {
case Packets.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break;
case Packets.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break;
case Packets.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break;
case Packets.PlayerDigging: this.handlePacketPlayerDigging(); break;
}
}
handleChat(packet:PacketChat) {
private handleChat(packet:PacketChat) {
const message = packet.message.split(" ");
if (message[0].startsWith("/")) {
packet.message = "";
if (message[0] === "/tp") {
this.send(new PacketPlayerPositionLook(parseFloat(message[1]), parseFloat(message[2]), parseFloat(message[2]) + 0.62, parseFloat(message[3]), 0, 0, false).writeData());
}
Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`);
} else if (message[0] === "/csay") {
const consoleMessage = `[CONSOLE] ${message.slice(1, message.length).join(" ")}`;
Console.printChat(consoleMessage);
this.mcServer.sendToAllClients(new PacketChat(consoleMessage).writeData());
}
handlePacketPlayer(packet:PacketPlayer) {
if (packet.message !== "") {
this.send(packet.writeData());
}
return;
}
packet.message = `<${this.entity.username}> ${packet.message}`;
Console.printChat(packet.message);
this.mcServer.sendToAllClients(packet.writeData());
}
private handlePacketPlayer(packet:PacketPlayer) {
this.entity.onGround = packet.onGround;
}
handlePacketPlayerPosition(packet:PacketPlayerPosition) {
private handlePacketPlayerPosition(packet:PacketPlayerPosition) {
this.entity.x = packet.x;
this.entity.y = packet.y;
this.entity.z = packet.z;
}
handlePacketPlayerLook(packet:PacketPlayerLook) {
private handlePacketPlayerLook(packet:PacketPlayerLook) {
this.entity.yaw = packet.yaw;
this.entity.pitch = packet.pitch;
}
handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
this.entity.x = packet.x;
this.entity.y = packet.y;
this.entity.z = packet.z;
@ -59,7 +106,11 @@ export class MPClient {
this.entity.pitch = packet.pitch;
}
send(buffer:Buffer|Writer) {
private handlePacketPlayerDigging() {
}
public send(buffer:Buffer|Writer) {
if (buffer instanceof Writer) {
this.socket.write(buffer.toBuffer());
} else {

View file

@ -1,6 +1,6 @@
import { Config } from "../config";
import { Console } from "../console";
import { Server, Socket } from "net";
import { Server, Socket, SocketAddress } from "net";
import { FunkyArray } from "../funkyArray";
import { World } from "./World";
import { Reader } from "../bufferStuff";
@ -14,6 +14,7 @@ import { Player } from "./entities/Player";
import { PacketSpawnPosition } from "./packets/SpawnPosition";
import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook";
import { PacketChat } from "./packets/Chat";
import { PacketNamedEntitySpawn } from "./packets/NamedEntitySpawn";
export class MinecraftServer {
private static readonly PROTOCOL_VERSION = 14;
@ -112,7 +113,7 @@ export class MinecraftServer {
const clientEntity = new Player(this, world, loginPacket.username);
world.addEntity(clientEntity);
const client = new MPClient(socket, clientEntity);
const client = new MPClient(this, socket, clientEntity);
setMPClient(client);
clientEntity.mpClient = client;
this.clients.set(loginPacket.username, client);
@ -122,6 +123,14 @@ export class MinecraftServer {
socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, 0).writeData());
socket.write(new PacketSpawnPosition(8, 64, 8).writeData());
const thisPlayerSpawn = new PacketNamedEntitySpawn(clientEntity.entityId, clientEntity.username, clientEntity.absX, clientEntity.absY, clientEntity.absZ, clientEntity.absYaw, clientEntity.absPitch, 0).writeData();
world.players.forEach(player => {
if (player.entityId !== clientEntity.entityId && clientEntity.distanceTo(player) < World.ENTITY_MAX_SEND_DISTANCE) {
socket.write(new PacketNamedEntitySpawn(player.entityId, player.username, player.absX, player.absY, player.absZ, player.absYaw, player.absPitch, 0).writeData());
player.mpClient?.send(thisPlayerSpawn);
}
});
socket.write(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData());
} else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());

19
server/Vec3.ts Normal file
View file

@ -0,0 +1,19 @@
export class Vec3 {
public x:number;
public y:number;
public z:number;
public constructor(x?:number, y?:number, z?:number) {
if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.x = x;
this.y = y;
this.z = z;
} else if (typeof(x) === "number" && typeof(y) !== "number" && typeof(z) !== "number") {
this.x = x;
this.y = x;
this.z = x;
} else {
this.x = this.y = this.z = 0;
}
}
}

View file

@ -7,19 +7,26 @@ import { HillyGenerator } from "./generators/Hilly";
import { IGenerator } from "./generators/IGenerator";
export class World {
public static ENTITY_MAX_SEND_DISTANCE = 50;
public chunks:FunkyArray<number, Chunk>;
public entites:FunkyArray<number, IEntity>;
public players:FunkyArray<number, Player>;
public generator:IGenerator;
public constructor(seed:number) {
this.chunks = new FunkyArray<number, Chunk>();
this.entites = new FunkyArray<number, IEntity>();
this.players = new FunkyArray<number, Player>();
this.generator = new HillyGenerator(seed);
}
public addEntity(entity:IEntity) {
this.entites.set(entity.entityId, entity);
if (entity instanceof Player) {
this.players.set(entity.entityId, entity);
}
}
// TODO: getChunkByCoordPair failed in here during removeEntity, figure out why.
@ -33,6 +40,7 @@ export class World {
this.unloadChunk(coordPair);
}
}
this.players.remove(entity.entityId);
}
this.entites.remove(entity.entityId);
@ -53,6 +61,14 @@ export class World {
return existingChunk;
}
public sendToNearbyClients(sentFrom:IEntity, buffer:Buffer) {
this.players.forEach(player => {
if (sentFrom.entityId !== player.entityId && Math.abs(sentFrom.distanceTo(player)) < World.ENTITY_MAX_SEND_DISTANCE) {
player.mpClient?.send(buffer);
}
});
}
public getChunkByCoordPair(coordPair:number) {
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
@ -69,6 +85,8 @@ export class World {
public tick() {
this.entites.forEach(entity => {
entity.onTick();
if (entity instanceof Player) {
if (entity.justUnloaded.length > 0) {
for (const coordPair of entity.justUnloaded) {
@ -82,8 +100,6 @@ export class World {
entity.justUnloaded = new Array<number>();
}
}
entity.onTick();
})
}
}

View file

@ -21,6 +21,14 @@ export class Entity implements IEntity {
this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0;
}
distanceTo(entity:IEntity) {
const dX = entity.x - this.x,
dY = entity.y - this.y,
dZ = entity.z - this.z;
return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2));
}
onTick() {
this.lastX = this.x;
this.lastY = this.y;

View file

@ -1,20 +1,79 @@
import { World } from "../World";
import { PacketEntityLook } from "../packets/EntityLook";
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove";
import { PacketEntityTeleport } from "../packets/EntityTeleport";
import { Entity } from "./Entity";
export class EntityLiving extends Entity {
public yaw:number;
public lastYaw:number;
public pitch:number;
public lastPitch:number;
public onGround:boolean;
public absX:number;
public absY:number;
public absZ:number;
public absYaw:number;
public absPitch:number;
public lastAbsX:number;
public lastAbsY:number;
public lastAbsZ:number;
public lastAbsYaw:number;
public lastAbsPitch:number;
public constructor(world:World) {
super(world);
this.yaw = 0;
this.pitch = 0;
this.yaw = this.lastYaw = this.pitch = this.lastPitch = this.absX = this.absY = this.absZ = this.absYaw = this.absPitch = this.lastAbsX = this.lastAbsY = this.lastAbsZ = this.lastAbsYaw = this.lastAbsPitch = 0;
this.onGround = false;
}
private constrainRot(rot:number) {
return Math.min(Math.max(rot, -128), 127);
}
private sendPositionUpdate() {
this.absX = Math.floor(this.x * 32);
this.absY = Math.floor(this.y * 32);
this.absZ = Math.floor(this.z * 32);
// This is suuuuuper jank
this.absYaw = this.constrainRot(Math.floor(((this.yaw - 180 >= 0 ? this.yaw - 180 : (this.yaw - 180) % 360 + 360) % 360 / 360) * 256) - 128);
this.absPitch = this.constrainRot(Math.floor((this.pitch % 360 * 256) / 360));
const diffX = this.absX - this.lastAbsX;
const diffY = this.absY - this.lastAbsY;
const diffZ = this.absZ - this.lastAbsZ;
const diffYaw = this.absYaw - this.lastAbsYaw;
const diffPitch = this.absPitch - this.lastAbsPitch;
const doRelativeMove = Math.abs(diffX) >= 5 || Math.abs(diffY) >= 5 || Math.abs(diffZ) >= 5;
const doLook = Math.abs(diffYaw) >= 5 || Math.abs(diffPitch) >= 5;
if (Math.abs(diffX) > 128 || Math.abs(diffY) > 128 || Math.abs(diffZ) > 128) {
this.world.sendToNearbyClients(this, new PacketEntityTeleport(this.entityId, this.absX, this.absY, this.absZ, this.absYaw, this.absPitch).writeData());
} else if (doRelativeMove && doLook) {
this.world.sendToNearbyClients(this, new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absYaw, this.absPitch).writeData());
} else if (doRelativeMove) {
this.world.sendToNearbyClients(this, new PacketEntityRelativeMove(this.entityId, diffX, diffY, diffZ).writeData());
} else if (doLook) {
this.world.sendToNearbyClients(this, new PacketEntityLook(this.entityId, this.absYaw, this.absPitch).writeData());
}
if (doRelativeMove) {
this.lastAbsX = this.absX;
this.lastAbsY = this.absY;
this.lastAbsZ = this.absZ;
}
if (doLook) {
this.lastAbsYaw = this.absYaw;
this.lastAbsPitch = this.absPitch;
}
}
onTick() {
super.onTick();
this.sendPositionUpdate();
this.lastYaw = this.yaw;
this.lastPitch = this.lastPitch;
}
}

View file

@ -6,5 +6,6 @@ export interface IEntity {
lastX:number,
lastY:number,
lastZ:number,
distanceTo:(entity:IEntity) => number,
onTick:() => void
}

View file

@ -33,6 +33,7 @@ export class Player extends EntityLiving {
if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) {
if (this.firstUpdate) {
this.firstUpdate = false;
// TODO: Make this based on the player's coords
this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData());
const chunk = this.world.getChunk(0, 0);
(async () => {

View file

@ -13,9 +13,22 @@ export enum Packets {
PlayerPosition = 0x0B,
PlayerLook = 0x0C,
PlayerPositionLook = 0x0D,
PlayerDigging = 0x0E,
PlayerBlockPlacement = 0x0F,
HoldingChange = 0x10,
UseBed = 0x11,
Animation = 0x12,
EntityAction = 0x13,
NamedEntitySpawn = 0x14,
PreChunk = 0x32,
MapChunk = 0x33,
DisconnectKick = 255
Entity = 0x1E,
EntityRelativeMove = 0x1F,
EntityLook = 0x20,
EntityLookRelativeMove = 0x21,
EntityTeleport = 0x22,
DisconnectKick = 0xff
}

View file

@ -34,7 +34,6 @@ export class HillyGenerator implements IGenerator {
// This is good enough (and fast enough) for what is needed here.
private mulberry32(a:number) {
return function() {
// TODO: Determine if "a" is needed
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
@ -87,6 +86,14 @@ export class HillyGenerator implements IGenerator {
chunk.setBlock(Block.wood.blockId, x, tY, z);
tY++;
}
chunk.setBlock(Block.leaves.blockId, x - 1, tY - 2, z);
chunk.setBlock(Block.leaves.blockId, x + 1, tY - 2, z);
chunk.setBlock(Block.leaves.blockId, x, tY - 2, z - 1);
chunk.setBlock(Block.leaves.blockId, x, tY - 2, z + 1);
chunk.setBlock(Block.leaves.blockId, x - 2, tY - 2, z);
chunk.setBlock(Block.leaves.blockId, x + 2, tY - 2, z);
chunk.setBlock(Block.leaves.blockId, x, tY - 2, z - 2);
chunk.setBlock(Block.leaves.blockId, x, tY - 2, z + 2);
}
}
}

26
server/packets/Entity.ts Normal file
View file

@ -0,0 +1,26 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntity implements IPacket {
public packetId = Packets.Entity;
public entityId:number;
public constructor(entityId?:number) {
if (typeof(entityId) == "number") {
this.entityId = entityId;
} else {
this.entityId = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
return this;
}
public writeData() {
return new Writer(5).writeUByte(this.packetId).writeInt(this.entityId).toBuffer();
}
}

View file

@ -0,0 +1,34 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntityLook implements IPacket {
public packetId = Packets.EntityLook;
public entityId:number;
public yaw:number;
public pitch:number;
public constructor(entityId?:number, yaw?:number, pitch?:number) {
if (typeof(entityId) == "number" && typeof(yaw) === "number" && typeof(pitch) === "number") {
this.entityId = entityId;
this.yaw = yaw;
this.pitch = pitch;
} else {
this.entityId = Number.MIN_VALUE;
this.yaw = Number.MIN_VALUE;
this.pitch = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.yaw = reader.readByte();
this.pitch = reader.readByte();
return this;
}
public writeData() {
return new Writer(7).writeUByte(this.packetId).writeInt(this.entityId).writeByte(this.yaw).writeByte(this.pitch).toBuffer();
}
}

View file

@ -0,0 +1,46 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntityLookRelativeMove implements IPacket {
public packetId = Packets.EntityLookRelativeMove;
public entityId:number;
public dX:number;
public dY:number;
public dZ:number;
public yaw:number;
public pitch:number;
public constructor(entityId?:number, dX?:number, dY?:number, dZ?:number, yaw?:number, pitch?:number) {
if (typeof(entityId) == "number" && typeof(dX) === "number" && typeof(dY) === "number" && typeof(dZ) === "number" && typeof(yaw) === "number" && typeof(pitch) === "number") {
this.entityId = entityId;
this.dX = dX;
this.dY = dY;
this.dZ = dZ;
this.yaw = yaw;
this.pitch = pitch;
} else {
this.entityId = Number.MIN_VALUE;
this.dX = Number.MIN_VALUE;
this.dY = Number.MIN_VALUE;
this.dZ = Number.MIN_VALUE;
this.yaw = Number.MIN_VALUE;
this.pitch = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.dX = reader.readByte();
this.dY = reader.readByte();
this.dZ = reader.readByte();
this.yaw = reader.readByte();
this.pitch = reader.readByte();
return this;
}
public writeData() {
return new Writer(10).writeUByte(this.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,38 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntityRelativeMove implements IPacket {
public packetId = Packets.EntityRelativeMove;
public entityId:number;
public dX:number;
public dY:number;
public dZ:number;
public constructor(entityId?:number, dX?:number, dY?:number, dZ?:number) {
if (typeof(entityId) == "number" && typeof(dX) === "number" && typeof(dY) === "number" && typeof(dZ) === "number") {
this.entityId = entityId;
this.dX = dX;
this.dY = dY;
this.dZ = dZ;
} else {
this.entityId = Number.MIN_VALUE;
this.dX = Number.MIN_VALUE;
this.dY = Number.MIN_VALUE;
this.dZ = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.dX = reader.readByte();
this.dY = reader.readByte();
this.dZ = reader.readByte();
return this;
}
public writeData() {
return new Writer(8).writeUByte(this.packetId).writeInt(this.entityId).writeByte(this.dX).writeByte(this.dY).writeByte(this.dZ).toBuffer();
}
}

View file

@ -0,0 +1,46 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntityTeleport implements IPacket {
public packetId = Packets.EntityTeleport;
public entityId:number;
public x:number;
public y:number;
public z:number;
public yaw:number;
public pitch:number;
public constructor(entityId?:number, x?:number, y?:number, z?:number, yaw?:number, pitch?:number) {
if (typeof(entityId) == "number" && typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(yaw) === "number" && typeof(pitch) === "number") {
this.entityId = entityId;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
} else {
this.entityId = Number.MIN_VALUE;
this.x = Number.MIN_VALUE;
this.y = Number.MIN_VALUE;
this.z = Number.MIN_VALUE;
this.yaw = Number.MIN_VALUE;
this.pitch = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.x = reader.readInt();
this.y = reader.readInt();
this.z = reader.readInt();
this.yaw = reader.readByte();
this.pitch = reader.readByte();
return this;
}
public writeData() {
return new Writer(19).writeUByte(this.packetId).writeInt(this.entityId).writeInt(this.x).writeInt(this.y).writeInt(this.z).writeByte(this.yaw).writeByte(this.pitch).toBuffer();
}
}

View file

@ -0,0 +1,53 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketNamedEntitySpawn implements IPacket {
public packetId = Packets.NamedEntitySpawn;
public entityId:number;
public playerName:string;
public x:number;
public y:number;
public z:number;
public yaw:number;
public pitch:number;
public currentItem:number;
public constructor(entityId?:number, playerName?:string, x?:number, y?:number, z?:number, yaw?:number, pitch?:number, currentItem?:number) {
if (typeof(entityId) === "number" && typeof(playerName) === "string" && typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(yaw) === "number" && typeof(pitch) === "number" && typeof(currentItem) === "number") {
this.entityId = entityId;
this.playerName = playerName;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.currentItem = currentItem;
} else {
this.entityId = Number.MIN_VALUE;
this.playerName = "";
this.x = Number.MIN_VALUE;
this.y = Number.MIN_VALUE;
this.z = Number.MIN_VALUE;
this.yaw = Number.MIN_VALUE;
this.pitch = Number.MIN_VALUE;
this.currentItem = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.playerName = reader.readString();
this.x = reader.readInt();
this.y = reader.readInt();
this.z = reader.readInt();
this.yaw = reader.readByte();
this.pitch = reader.readByte();
return this;
}
public writeData() {
return new Writer(23 + this.playerName.length * 2).writeUByte(this.packetId).writeInt(this.entityId).writeString(this.playerName).writeInt(this.x).writeInt(this.y).writeInt(this.z).writeByte(this.yaw).writeByte(this.pitch).writeShort(this.currentItem).toBuffer();
}
}

View file

@ -0,0 +1,42 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPlayerDigging implements IPacket {
public packetId = Packets.PlayerDigging;
public status:number;
public x:number;
public y:number;
public z:number;
public face:number;
public constructor(status?:number, x?:number, y?:number, z?:number, face?:number) {
if (typeof(status) == "number" && typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(face) === "number") {
this.status = status;
this.x = x;
this.y = y;
this.z = z;
this.face = face;
} else {
this.status = Number.MIN_VALUE;
this.x = Number.MIN_VALUE;
this.y = Number.MIN_VALUE;
this.z = Number.MIN_VALUE;
this.face = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.status = reader.readByte();
this.x = reader.readInt();
this.y = reader.readByte();
this.z = reader.readInt();
this.face = reader.readByte();
return this;
}
public writeData() {
return new Writer(12).writeUByte(this.packetId).writeByte(this.status).writeInt(this.x).writeByte(this.y).writeInt(this.z).writeByte(this.face).toBuffer();
}
}