Block getting

This commit is contained in:
Holly Stubbs 2023-11-02 08:31:43 +00:00
parent d8d1eabcf4
commit f1c4dcda75
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
20 changed files with 406 additions and 162 deletions

View file

@ -7,35 +7,25 @@ export class NibbleArray {
} else if (size instanceof Uint8Array) { } else if (size instanceof Uint8Array) {
this.array = new Uint8Array(size); this.array = new Uint8Array(size);
} else { } else {
this.array = new Uint8Array(Math.round(size / 2)); this.array = new Uint8Array(size >> 1);
} }
} }
// We can determine which side of the byte to read
// from if the halved index has a remainder.
private isLowOrHighNibble(index:number) {
return index % 1 !== 0;
}
public get(index:number) { public get(index:number) {
index = index / 2; const arrayIndex = index >> 1;
if ((index & 1) === 0) {
const arrayIndex = index | 0; return this.array[arrayIndex] & 0xf;
if (this.isLowOrHighNibble(index)) {
return this.array[arrayIndex] >> 4;
} else { } else {
return this.array[arrayIndex] & 0x0f; return this.array[arrayIndex] >> 4 & 0xf;
} }
} }
public set(index:number, value:number) { public set(index:number, value:number) {
index = index / 2; const arrayIndex = index >> 1;
if ((index & 1) === 0) {
const arrayIndex = index | 0; this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value & 0xf;
if (this.isLowOrHighNibble(index)) {
this.array[arrayIndex] = value << 4 | this.array[arrayIndex] & 0xf;
} else { } else {
this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value; this.array[arrayIndex] = this.array[arrayIndex] & 0xf | (value & 0xf) << 4;
} }
} }

View file

@ -19,6 +19,9 @@ import { PacketPlayerBlockPlacement } from "./packets/PlayerBlockPlacement";
import { Inventory } from "./inventories/Inventory"; import { Inventory } from "./inventories/Inventory";
import { PacketHoldingChange } from "./packets/HoldingChange"; import { PacketHoldingChange } from "./packets/HoldingChange";
import { PacketDisconnectKick } from "./packets/DisconnectKick"; import { PacketDisconnectKick } from "./packets/DisconnectKick";
import { ItemStack } from "./inventories/ItemStack";
import { PacketWindowItems } from "./packets/WindowItems";
import { Block } from "./blocks/Block";
export class MPClient { export class MPClient {
private readonly mcServer:MinecraftServer; private readonly mcServer:MinecraftServer;
@ -86,24 +89,24 @@ export class MPClient {
if (message[0].startsWith("/")) { if (message[0].startsWith("/")) {
packet.message = ""; packet.message = "";
if (message[0] === "/tp") { if (message[0] === "/tp") {
const x = this.entity.x = parseFloat(message[1]); const x = this.entity.position.x = parseFloat(message[1]);
const y = this.entity.y = parseFloat(message[2]); const y = this.entity.position.y = parseFloat(message[2]);
const z = this.entity.z = parseFloat(message[3]); const z = this.entity.position.z = parseFloat(message[3]);
this.send(new PacketPlayerPositionLook(x, y, y + 0.62, z, 0, 0, false).writeData()); this.send(new PacketPlayerPositionLook(x, y, y + 0.62, z, 0, 0, false).writeData());
Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`); Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`);
} else if (message[0] === "/csay") { } else if (message[0] === "/csay") {
this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`); this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`);
} else if (message[0] === "/top") { } else if (message[0] === "/top") {
packet.message = `Woosh!`; packet.message = `Woosh!`;
const topBlock = this.entity.world.getChunk(this.entity.x >> 4, this.entity.z >> 4).getTopBlockY(this.entity.x & 0xf, this.entity.z & 0xf); const topBlock = this.entity.chunk.getTopBlockY(this.entity.position.x & 0xf, this.entity.position.z & 0xf);
this.send(new PacketPlayerPosition(this.entity.x, topBlock + 3, topBlock + 3.62, this.entity.z, false).writeData()); this.send(new PacketPlayerPosition(this.entity.position.x, topBlock + 3, topBlock + 3.62, this.entity.position.z, false).writeData());
} else if (message[0] === "/tpx") { } else if (message[0] === "/tpx") {
const dimension = parseInt(message[1]); const dimension = parseInt(message[1]);
if (this.mcServer.worlds.has(dimension)) { if (this.mcServer.worlds.has(dimension)) {
packet.message = "\u00a76Switching dimensions..."; packet.message = "\u00a76Switching dimensions...";
this.switchDimension(dimension); this.switchDimension(dimension);
} else { } else {
packet.message = `\u00a7cNo dimension by id "${dimension} exists!"`; packet.message = `\u00a7cNo dimension by id "${dimension}" exists!`;
} }
} }
@ -124,22 +127,16 @@ export class MPClient {
} }
private handlePacketPlayerPosition(packet:PacketPlayerPosition) { private handlePacketPlayerPosition(packet:PacketPlayerPosition) {
this.entity.x = packet.x; this.entity.position.set(packet.x, packet.y, packet.z);
this.entity.y = packet.y;
this.entity.z = packet.z;
} }
private handlePacketPlayerLook(packet:PacketPlayerLook) { private handlePacketPlayerLook(packet:PacketPlayerLook) {
this.entity.yaw = packet.yaw; this.entity.rotation.set(packet.yaw, packet.pitch);
this.entity.pitch = packet.pitch;
} }
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) { private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
this.entity.x = packet.x; this.entity.position.set(packet.x, packet.y, packet.z);
this.entity.y = packet.y; this.entity.rotation.set(packet.yaw, packet.pitch);
this.entity.z = packet.z;
this.entity.yaw = packet.yaw;
this.entity.pitch = packet.pitch;
} }
private handlePacketPlayerDigging(packet:PacketPlayerDigging) { private handlePacketPlayerDigging(packet:PacketPlayerDigging) {
@ -155,20 +152,37 @@ export class MPClient {
if (packet.status === 0) { if (packet.status === 0) {
// Started digging // Started digging
} else if (packet.status === 2) { } else if (packet.status === 2) {
if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) != 0) { let brokenBlockId:number;
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) {
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); this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0);
console.log("Metadata: ", metadata);
this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
} }
} }
} }
public getHeldItemStack() {
return this.inventory.getSlotItemStack(this.holdingIndex);
}
private handlePacketBlockPlacement(packet:PacketPlayerBlockPlacement) { private handlePacketBlockPlacement(packet:PacketPlayerBlockPlacement) {
this.diggingAt.set(packet.x, packet.y, packet.z); this.diggingAt.set(packet.x, packet.y, packet.z);
this.mapCoordsFromFace(this.diggingAt, packet.face); this.mapCoordsFromFace(this.diggingAt, packet.face);
const itemStack = this.inventory.getSlotItemStack(this.holdingIndex); const itemStack = this.getHeldItemStack();
if (itemStack != null && itemStack.size > 0 && this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) === 0) { if (itemStack == null || itemStack.size == 0) {
itemStack.size--; return;
this.entity.world.setBlockAndMetadataWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, itemStack.itemID, itemStack.damage); }
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);
}
} else {
// TODO: Handle item usage
} }
} }
@ -209,9 +223,7 @@ export class MPClient {
this.send(new PacketRespawn(dimension).writeData()); this.send(new PacketRespawn(dimension).writeData());
//this.send(new PacketSpawnPosition(8, 64, 8).writeData()); //this.send(new PacketSpawnPosition(8, 64, 8).writeData());
this.entity.x = 8; this.entity.position.set(8, 60, 8);
this.entity.y = 70;
this.entity.z = 8;
this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData()); this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData());
this.entity.forceUpdatePlayerChunks(); this.entity.forceUpdatePlayerChunks();

View file

@ -225,10 +225,10 @@ export class MinecraftServer {
socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, dimension).writeData()); socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, dimension).writeData());
socket.write(new PacketSpawnPosition(8, 64, 8).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(); const thisPlayerSpawn = new PacketNamedEntitySpawn(clientEntity.entityId, clientEntity.username, clientEntity.absPosition.x, clientEntity.absPosition.y, clientEntity.absPosition.z, clientEntity.absRotation.yaw, clientEntity.absRotation.pitch, clientEntity.mpClient?.getHeldItemStack()?.itemID).writeData();
world.players.forEach(player => { world.players.forEach(player => {
if (player.entityId !== clientEntity.entityId && clientEntity.distanceTo(player) < World.ENTITY_MAX_SEND_DISTANCE) { 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()); socket.write(new PacketNamedEntitySpawn(player.entityId, player.username, player.absPosition.x, player.absPosition.y, player.absPosition.z, player.absRotation.yaw, player.absRotation.pitch, player.mpClient?.getHeldItemStack()?.itemID).writeData());
player.mpClient?.send(thisPlayerSpawn); player.mpClient?.send(thisPlayerSpawn);
} }
}); });
@ -237,7 +237,6 @@ export class MinecraftServer {
const playerInventory = clientEntity.inventory; const playerInventory = clientEntity.inventory;
socket.write(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData()); socket.write(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData());
console.log(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData());
} else { } else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData()); socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());
} }

19
server/Rotation.ts Normal file
View file

@ -0,0 +1,19 @@
import { Vec2 } from "./Vec2";
export class Rotation extends Vec2 {
public get yaw() {
return this.x;
}
public set yaw(value:number) {
this.x = value;
}
public get pitch() {
return this.y;
}
public set pitch(value:number) {
this.y = value;
}
}

31
server/Vec2.ts Normal file
View file

@ -0,0 +1,31 @@
export class Vec2 {
public x:number;
public y:number;
public constructor(x?:Vec2 | number, y?:number) {
if (typeof(x) === "number" && typeof(y) === "number") {
this.x = x;
this.y = y;
} else if (typeof(x) === "number" && typeof(y) !== "number") {
this.x = x;
this.y = x;
} else if (x instanceof Vec2) {
this.x = x.x;
this.y = x.y;
} else {
this.x = this.y = 0;
}
}
public set(x?:Vec2 | number, y?:number) {
if (x instanceof Vec2) {
this.x = x.x;
this.y = x.y;
} else if (typeof(x) === "number" && typeof(y) === "number") {
this.x = x;
this.y = y;
} else {
this.x = this.y = 0;
}
}
}

View file

@ -3,7 +3,7 @@ export class Vec3 {
public y:number; public y:number;
public z:number; public z:number;
public constructor(x?:number, y?:number, z?:number) { public constructor(x?:Vec3 | number, y?:number, z?:number) {
if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") { if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.x = x; this.x = x;
this.y = y; this.y = y;
@ -12,14 +12,26 @@ export class Vec3 {
this.x = x; this.x = x;
this.y = x; this.y = x;
this.z = x; this.z = x;
} else if (x instanceof Vec3) {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else { } else {
this.x = this.y = this.z = 0; this.x = this.y = this.z = 0;
} }
} }
public set(x:number, y:number, z:number) { public set(x?:Vec3 | number, y?:number, z?:number) {
this.x = x; if (x instanceof Vec3) {
this.y = y; this.x = x.x;
this.z = z; this.y = x.y;
this.z = x.z;
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.x = x;
this.y = y;
this.z = z;
} else {
this.x = this.y = this.z = 0;
}
} }
} }

View file

@ -1,13 +1,19 @@
import { World } from "../World"; import { World } from "../World";
import { BlockBehaviour } from "./BlockBehaviour";
import { BlockBehaviourFlower } from "./BlockBehaviourFlower"; import { BlockBehaviourFlower } from "./BlockBehaviourFlower";
import { BlockBehaviourGrass } from "./BlockBehaviourGrass";
import { IBlockBehaviour } from "./IBlockBehaviour"; import { IBlockBehaviour } from "./IBlockBehaviour";
abstract class Behaviour { abstract class Behaviour {
public static base = new BlockBehaviour();
public static grass = new BlockBehaviourGrass();
public static flower = new BlockBehaviourFlower(); public static flower = new BlockBehaviourFlower();
} }
export class Block { export class Block {
public readonly blockId:number; public readonly blockId:number;
public static readonly blocks:Array<Block> = new Array<Block>(); public static readonly blocks:Array<Block> = new Array<Block>();
public static readonly lightPassage:Array<number> = new Array<number>(); public static readonly lightPassage:Array<number> = new Array<number>();
public static readonly blockBehaviours:Array<IBlockBehaviour> = new Array<IBlockBehaviour>(); public static readonly blockBehaviours:Array<IBlockBehaviour> = new Array<IBlockBehaviour>();
@ -17,6 +23,7 @@ export class Block {
Block.blocks[blockId] = this; Block.blocks[blockId] = this;
Block.lightPassage[blockId] = 0; Block.lightPassage[blockId] = 0;
Block.blockNames[blockId] = ""; Block.blockNames[blockId] = "";
Block.blockBehaviours[blockId] = Behaviour.base;
this.blockId = blockId; this.blockId = blockId;
} }
@ -60,14 +67,16 @@ export class Block {
} }
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) { public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
if (this.behaviour !== undefined) { this.behaviour.neighborBlockChange(world, x, y, z, blockId);
this.behaviour.neighborBlockChange(world, x, y, z, blockId); }
}
public droppedItem(blockId:number) {
this.behaviour.droppedItem(blockId);
} }
// Define statics here // Define statics here
static readonly stone = new Block(1).setBlockName("Stone"); static readonly stone = new Block(1).setBlockName("Stone");
static readonly grass = new Block(2).setBlockName("Grass"); static readonly grass = new Block(2).setBehaviour(Behaviour.grass).setBlockName("Grass");
static readonly dirt = new Block(3).setBlockName("Dirt"); static readonly dirt = new Block(3).setBlockName("Dirt");
static readonly bedrock = new Block(7).setBlockName("Bedrock"); static readonly bedrock = new Block(7).setBlockName("Bedrock");

View file

@ -3,4 +3,5 @@ import { IBlockBehaviour } from "./IBlockBehaviour";
export class BlockBehaviour implements IBlockBehaviour { export class BlockBehaviour implements IBlockBehaviour {
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {} public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {}
public droppedItem(blockId:number) { return blockId; }
} }

View file

@ -0,0 +1,8 @@
import { Block } from "./Block";
import { BlockBehaviour } from "./BlockBehaviour";
export class BlockBehaviourGrass extends BlockBehaviour {
public droppedItem(blockId:number) {
return Block.dirt.blockId;
}
}

View file

@ -1,5 +1,6 @@
import { World } from "../World"; import { World } from "../World";
export interface IBlockBehaviour { export interface IBlockBehaviour {
neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void,
droppedItem: (blockId:number) => number
} }

View file

@ -1,8 +1,14 @@
import { Chunk } from "../Chunk"; import { Chunk } from "../Chunk";
import { MetadataEntry, MetadataWriter } from "../MetadataWriter"; import { MetadataEntry, MetadataWriter } from "../MetadataWriter";
import { Rotation } from "../Rotation";
import { Vec3 } from "../Vec3";
import { World } from "../World"; import { World } from "../World";
import { MetadataFieldType } from "../enums/MetadataFieldType"; import { MetadataFieldType } from "../enums/MetadataFieldType";
import { PacketEntityLook } from "../packets/EntityLook";
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
import { PacketEntityMetadata } from "../packets/EntityMetadata"; import { PacketEntityMetadata } from "../packets/EntityMetadata";
import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove";
import { PacketEntityTeleport } from "../packets/EntityTeleport";
import { IEntity } from "./IEntity"; import { IEntity } from "./IEntity";
export class Entity implements IEntity { export class Entity implements IEntity {
@ -11,14 +17,22 @@ export class Entity implements IEntity {
public entityId:number; public entityId:number;
public world:World; public world:World;
public x:number;
public y:number; public position:Vec3;
public z:number; public lastPosition:Vec3;
public lastX:number; public absPosition:Vec3;
public lastY:number; public lastAbsPosition:Vec3;
public lastZ:number;
public rotation:Rotation;
public lastRotation:Rotation;
public absRotation:Rotation;
public lastAbsRotation:Rotation;
public velocity:Vec3;
public health:number; public health:number;
public wasHurt:boolean;
public isDead:boolean;
public fire:number; public fire:number;
@ -36,12 +50,26 @@ export class Entity implements IEntity {
this.fire = 0; this.fire = 0;
this.world = world; this.world = world;
this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0;
this.position = new Vec3();
this.lastPosition = new Vec3();
this.absPosition = new Vec3();
this.lastAbsPosition = new Vec3();
this.rotation = new Rotation();
this.lastRotation = new Rotation();
this.absRotation = new Rotation();
this.lastAbsRotation = new Rotation();
this.velocity = new Vec3();
this.crouching = this.lastCrouchState = this.lastFireState = this.queuedChunkUpdate = false; this.crouching = this.lastCrouchState = this.lastFireState = this.queuedChunkUpdate = false;
this.chunk = world.getChunk(this.x >> 4, this.z >> 4); this.chunk = world.getChunk(this.position.x >> 4, this.position.z >> 4);
this.health = 20; this.health = 20;
this.wasHurt = false;
this.isDead = false;
} }
sendToNearby(buffer:Buffer) { sendToNearby(buffer:Buffer) {
@ -61,10 +89,7 @@ export class Entity implements IEntity {
// 1 = On Fire // 1 = On Fire
// 2 = Player crouched // 2 = Player crouched
// 4 = Player on mount? // 4 = Player on mount?
//metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, 1)); metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2));
if (crouchStateChanged) {
metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2));
}
this.sendToNearby(new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData()); this.sendToNearby(new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData());
@ -74,9 +99,9 @@ export class Entity implements IEntity {
} }
distanceTo(entity:IEntity) { distanceTo(entity:IEntity) {
const dX = entity.x - this.x, const dX = entity.position.x - this.position.x,
dY = entity.y - this.y, dY = entity.position.y - this.position.y,
dZ = entity.z - this.z; dZ = entity.position.z - this.position.z;
return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2)); return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2));
} }
@ -89,12 +114,14 @@ export class Entity implements IEntity {
if (entity === undefined) { if (entity === undefined) {
this.health -= damage; this.health -= damage;
} }
this.wasHurt = true;
} }
updateEntityChunk() { updateEntityChunk() {
const bitX = this.x >> 4; const bitX = this.position.x >> 4;
const bitZ = this.z >> 4; const bitZ = this.position.z >> 4;
if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.queuedChunkUpdate) { if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.queuedChunkUpdate) {
if (this.world.chunkExists(bitX, bitZ)) { if (this.world.chunkExists(bitX, bitZ)) {
this.chunk = this.world.getChunk(bitX, bitZ); this.chunk = this.world.getChunk(bitX, bitZ);
this.queuedChunkUpdate = false; this.queuedChunkUpdate = false;
@ -104,6 +131,44 @@ export class Entity implements IEntity {
} }
} }
private constrainRot(rot:number) {
return Math.min(Math.max(rot, -128), 127);
}
private sendPositionUpdate() {
this.absPosition.set(Math.floor(this.position.x * 32), Math.floor(this.position.y * 32), Math.floor(this.position.z * 32));
// This is suuuuuper jank
this.absRotation.set(
this.constrainRot(Math.floor(((this.rotation.yaw - 180 >= 0 ? this.rotation.yaw - 180 : (this.rotation.yaw - 180) % 360 + 360) % 360 / 360) * 256) - 128), // Yaw
this.constrainRot(Math.floor((this.rotation.pitch % 360 * 256) / 360)) // Pitch
);
const diffX = this.absPosition.x - this.lastAbsPosition.x;
const diffY = this.absPosition.y - this.lastAbsPosition.y;
const diffZ = this.absPosition.z - this.lastAbsPosition.z;
const diffYaw = this.absRotation.yaw - this.lastAbsRotation.yaw;
const diffPitch = this.absRotation.pitch - this.lastAbsRotation.pitch;
const doRelativeMove = Math.abs(diffX) >= 4 || Math.abs(diffY) >= 4 || Math.abs(diffZ) >= 4;
const doLook = Math.abs(diffYaw) >= 4 || Math.abs(diffPitch) >= 4;
if (Math.abs(diffX) > 128 || Math.abs(diffY) > 128 || Math.abs(diffZ) > 128) {
this.world.sendToNearbyClients(this, new PacketEntityTeleport(this.entityId, this.absPosition.x, this.absPosition.y, this.absPosition.z, this.absRotation.yaw, this.absRotation.pitch).writeData());
} else if (doRelativeMove && doLook) {
this.world.sendToNearbyClients(this, new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absRotation.yaw, this.absRotation.pitch).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.absRotation.yaw, this.absRotation.pitch).writeData());
}
if (doRelativeMove) {
this.lastAbsPosition.set(this.absPosition);
}
if (doLook) {
this.lastAbsRotation.set(this.absRotation);
}
}
onTick() { onTick() {
this.updateMetadata(); this.updateMetadata();
this.updateEntityChunk(); this.updateEntityChunk();
@ -116,8 +181,17 @@ export class Entity implements IEntity {
this.fire--; this.fire--;
} }
this.lastX = this.x; if (!this.isDead && this.health <= 0) {
this.lastY = this.y; this.isDead = true;
this.lastZ = this.z;
}
if (this.wasHurt) {
this.wasHurt = false;
}
this.sendPositionUpdate();
this.lastPosition.set(this.position);
} }
} }

View file

@ -6,11 +6,8 @@ export class EntityItem extends Entity {
public age:number; public age:number;
public itemStack:ItemStack; public itemStack:ItemStack;
public constructor(world:World, x:number, y:number, z:number, itemStack:ItemStack) { public constructor(world:World, itemStack:ItemStack) {
super(world); super(world);
this.x = x;
this.y = y;
this.z = z;
this.itemStack = itemStack; this.itemStack = itemStack;

View file

@ -1,3 +1,5 @@
import { Rotation } from "../Rotation";
import { Vec3 } from "../Vec3";
import { World } from "../World"; import { World } from "../World";
import { Block } from "../blocks/Block"; import { Block } from "../blocks/Block";
import { PacketAnimation } from "../packets/Animation"; import { PacketAnimation } from "../packets/Animation";
@ -10,30 +12,15 @@ import { Entity } from "./Entity";
import { IEntity } from "./IEntity"; import { IEntity } from "./IEntity";
export class EntityLiving extends Entity { export class EntityLiving extends Entity {
public yaw:number;
public lastYaw:number;
public pitch:number;
public lastPitch:number;
public onGround:boolean; public onGround:boolean;
public fallDistance:number; public fallDistance:number;
public timeInWater:number; public timeInWater:number;
public headHeight:number; public headHeight:number;
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) { public constructor(world:World) {
super(world); super(world);
this.fallDistance = 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 = this.timeInWater = 0; this.fallDistance = this.timeInWater = 0;
this.onGround = true; this.onGround = true;
this.headHeight = 1.62; this.headHeight = 1.62;
} }
@ -43,57 +30,17 @@ export class EntityLiving extends Entity {
return; return;
} }
super.damageFrom(damage, entity); super.damageFrom(damage, entity);
// Send Damage Animation packet // Send Damage Animation packet
this.sendToAllNearby(new PacketEntityStatus(this.entityId, 2).writeData()); this.sendToAllNearby(new PacketEntityStatus(this.entityId, 2).writeData());
} }
isInWater() { isInWater() {
return this.world.getChunkBlockId(this.chunk, this.x, this.y + this.headHeight, this.z) === Block.waterStill.blockId; return this.world.getChunkBlockId(this.chunk, this.position.x, this.position.y + this.headHeight, this.position.z) === Block.waterStill.blockId;
}
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) >= 4 || Math.abs(diffY) >= 4 || Math.abs(diffZ) >= 4;
const doLook = Math.abs(diffYaw) >= 4 || Math.abs(diffPitch) >= 4;
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() { onTick() {
super.onTick(); super.onTick();
this.sendPositionUpdate();
if (!this.onGround) { if (!this.onGround) {
this.fallDistance this.fallDistance
@ -111,8 +58,5 @@ export class EntityLiving extends Entity {
} else { } else {
this.timeInWater = Number.MIN_SAFE_INTEGER; this.timeInWater = Number.MIN_SAFE_INTEGER;
} }
this.lastYaw = this.yaw;
this.lastPitch = this.pitch;
} }
} }

View file

@ -1,11 +1,10 @@
import { Vec3 } from "../Vec3"
export interface IEntity { export interface IEntity {
entityId:number, entityId:number,
x:number, position:Vec3,
y:number, lastPosition:Vec3,
z:number, velocity:Vec3,
lastX:number,
lastY:number,
lastZ:number,
crouching:boolean, crouching:boolean,
updateMetadata:() => void, updateMetadata:() => void,
distanceTo:(entity:IEntity) => number, distanceTo:(entity:IEntity) => number,

View file

@ -9,6 +9,8 @@ import { PacketUpdateHealth } from "../packets/UpdateHealth";
import { Inventory } from "../inventories/Inventory"; import { Inventory } from "../inventories/Inventory";
import { ItemStack } from "../inventories/ItemStack"; import { ItemStack } from "../inventories/ItemStack";
import { Block } from "../blocks/Block"; import { Block } from "../blocks/Block";
import PlayerInventory from "../inventories/PlayerInventory";
import { Item } from "../items/Item";
const CHUNK_LOAD_RANGE = 5; const CHUNK_LOAD_RANGE = 5;
@ -19,7 +21,7 @@ export class Player extends EntityLiving {
public loadedChunks:Array<number>; public loadedChunks:Array<number>;
public justUnloaded:Array<number>; public justUnloaded:Array<number>;
public mpClient?:MPClient; public mpClient?:MPClient;
public inventory:Inventory; public inventory:PlayerInventory;
private lastHealth:number; private lastHealth:number;
@ -30,16 +32,16 @@ export class Player extends EntityLiving {
this.loadedChunks = new Array<number>(); this.loadedChunks = new Array<number>();
this.justUnloaded = new Array<number>(); this.justUnloaded = new Array<number>();
this.inventory = new Inventory(44, "Player Inventory"); this.inventory = new PlayerInventory();
this.inventory.setSlotItemStack(36, new ItemStack(Block.dirt, 1)); this.inventory.setSlotItemStack(36, new ItemStack(Item.ironSword, 1));
this.inventory.setSlotItemStack(37, new ItemStack(Block.dirt, 2)); this.inventory.setSlotItemStack(37, new ItemStack(Item.ironPickaxe, 1));
this.inventory.setSlotItemStack(38, new ItemStack(Block.dirt, 3)); this.inventory.setSlotItemStack(38, new ItemStack(Item.ironShovel, 1));
this.inventory.setSlotItemStack(39, new ItemStack(Item.ironAxe, 1));
this.inventory.setSlotItemStack(43, new ItemStack(Block.dirt, 32));
this.username = username; this.username = username;
this.x = 8; this.position.set(8, 64, 8);
this.y = 64;
this.z = 8;
this.lastHealth = this.health; this.lastHealth = this.health;
} }
@ -49,9 +51,9 @@ export class Player extends EntityLiving {
} }
private async updatePlayerChunks() { private async updatePlayerChunks() {
const bitX = this.x >> 4; const bitX = this.position.x >> 4;
const bitZ = this.z >> 4; const bitZ = this.position.z >> 4;
if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) { if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.firstUpdate) {
if (this.firstUpdate) { if (this.firstUpdate) {
this.firstUpdate = false; this.firstUpdate = false;
// TODO: Make this based on the player's initial coords // TODO: Make this based on the player's initial coords

View file

@ -3,7 +3,7 @@ import { ItemStack } from "./ItemStack";
import IInventory from "./IInventory"; import IInventory from "./IInventory";
export class Inventory implements IInventory { export class Inventory implements IInventory {
private itemStacks:Array<ItemStack | null>; public itemStacks:Array<ItemStack | null>;
private size:number; private size:number;
private name:string; private name:string;
@ -18,6 +18,20 @@ export class Inventory implements IInventory {
this.name = name; this.name = name;
} }
addItemStack(itemStack:ItemStack) {
// Check bottom inventory row (hotbar) first.
/*let workingItemStack:ItemStack | null;
for (let slotId = 9; slotId <= 35; slotId++) {
if (itemStack.size === 0) {
break;
}
if ((workingItemStack = this.itemStacks[slotId]) != null) {
workingItemStack.insert(itemStack);
}
}*/
}
getInventoryName() { getInventoryName() {
return this.name; return this.name;
} }

View file

@ -3,13 +3,15 @@ import { Item } from "../items/Item";
export class ItemStack { export class ItemStack {
public readonly itemID:number; public readonly itemID:number;
public readonly isBlock:boolean;
public readonly maxSize:number;
public size:number; public size:number;
public damage:number; public damage:number;
public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) { public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) {
if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) { if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId; this.itemID = blockOrItemOrItemID.blockId;
this.size = 1; this.size = 0;
this.damage = 0; this.damage = 0;
} else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) { } else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId; this.itemID = blockOrItemOrItemID.blockId;
@ -21,7 +23,7 @@ export class ItemStack {
this.damage = damage; this.damage = damage;
} else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) { } else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID; this.itemID = blockOrItemOrItemID.shiftedItemID;
this.size = 1; this.size = 0;
this.damage = 0; this.damage = 0;
} else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) { } else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID; this.itemID = blockOrItemOrItemID.shiftedItemID;
@ -35,9 +37,43 @@ export class ItemStack {
this.itemID = blockOrItemOrItemID; this.itemID = blockOrItemOrItemID;
this.size = size; this.size = size;
this.damage = damage; this.damage = damage;
} else if (typeof(blockOrItemOrItemID) === "number" && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID;
this.size = size;
this.damage = 0;
} else if (typeof(blockOrItemOrItemID) === "number" && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID;
this.size = 0;
this.damage = 0;
} else { } else {
throw new Error(`ItemStack created with invalid properties (${typeof(blockOrItemOrItemID)}, ${typeof(size)}, ${typeof(damage)})`); throw new Error(`ItemStack created with invalid properties (${typeof(blockOrItemOrItemID)}, ${typeof(size)}, ${typeof(damage)})`);
} }
this.isBlock = this.itemID < 256;
this.maxSize = !this.isBlock ? Item.getByShiftedItemId(this.itemID).maxStackSize : 64;
}
public insert(itemStack:ItemStack) {
const remainingSpace = this.spaceAvaliable;
if (remainingSpace === 0) {
return;
}
if (remainingSpace >= itemStack.size) {
this.size += itemStack.size;
itemStack.size = 0;
return;
}
if (remainingSpace < itemStack.size) {
this.size += remainingSpace;
itemStack.size -= remainingSpace;
}
}
public get spaceAvaliable() {
// Stack size check for Item(s) and Block(s).
return Math.max(this.maxSize - this.size, 0);
} }
split(amount:number) { split(amount:number) {

View file

@ -0,0 +1,69 @@
import { Inventory } from "./Inventory";
import { ItemStack } from "./ItemStack";
export default class PlayerInventory extends Inventory {
public constructor() {
super(44, "Player Inventory");
}
addItemStack(itemStack:ItemStack) {
const itemStacksOfSameType:Array<ItemStack> = new Array<ItemStack>();
// Check bottom inventory row (hotbar) first.
let workingItemStack:ItemStack | null;
for (let slotId = 36; slotId <= 44; slotId++) {
if ((workingItemStack = this.itemStacks[slotId]) != null) {
itemStacksOfSameType.push(workingItemStack);
}
}
for (let slotId = 9; slotId <= 35; slotId++) {
if ((workingItemStack = this.itemStacks[slotId]) != null) {
itemStacksOfSameType.push(workingItemStack);
}
}
// Insert into existing stacks first.
for (const inventoryItemStack of itemStacksOfSameType) {
// Exit early if we have nothing left
if (itemStack.size === 0) {
return;
}
if (inventoryItemStack.itemID !== itemStack.itemID || inventoryItemStack.damage !== itemStack.damage) {
continue;
}
inventoryItemStack.insert(itemStack);
}
// Exit early if we have nothing left
if (itemStack.size === 0) {
return;
}
for (let slotId = 36; slotId <= 44; slotId++) {
// Exit early if we have nothing left
if (itemStack.size === 0) {
return;
}
if ((workingItemStack = this.itemStacks[slotId]) == null) {
const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage);
stack.insert(itemStack);
}
}
for (let slotId = 9; slotId <= 35; slotId++) {
// Exit early if we have nothing left
if (itemStack.size === 0) {
return;
}
if ((workingItemStack = this.itemStacks[slotId]) == null) {
const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage);
stack.insert(itemStack);
}
}
}
}

View file

@ -0,0 +1,3 @@
export interface IItemBehaviour {
}

View file

@ -1,10 +1,20 @@
export class Item { export class Item {
public static items:Array<Item> = new Array<Item>();
public maxStackSize:number; public maxStackSize:number;
public shiftedItemID:number; public shiftedItemID:number;
public name:string;
public constructor(itemID:number) { public constructor(itemID:number) {
this.shiftedItemID = 256 + itemID; this.shiftedItemID = 256 + itemID;
this.maxStackSize = 64; this.maxStackSize = 64;
this.name = "UNNAMED";
Item.items[itemID] = this;
}
public static getByShiftedItemId(shiftedItemID:number) {
return Item.items[shiftedItemID - 256];
} }
public setMaxStackSize(stackSize:number) { public setMaxStackSize(stackSize:number) {
@ -12,5 +22,19 @@ export class Item {
return this; return this;
} }
public setName(name:string) {
this.name = name;
return this;
}
public getName() {
return this.name;
}
// Define statics here // Define statics here
static ironShovel = new Item(0).setName("Iron Shovel");
static ironPickaxe = new Item(1).setName("Iron Pickaxe");
static ironAxe = new Item(2).setName("Iron Axe");
static ironSword = new Item(11).setName("Iron Sword");
} }