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) {
this.array = new Uint8Array(size);
} 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) {
index = index / 2;
const arrayIndex = index | 0;
if (this.isLowOrHighNibble(index)) {
return this.array[arrayIndex] >> 4;
const arrayIndex = index >> 1;
if ((index & 1) === 0) {
return this.array[arrayIndex] & 0xf;
} else {
return this.array[arrayIndex] & 0x0f;
return this.array[arrayIndex] >> 4 & 0xf;
}
}
public set(index:number, value:number) {
index = index / 2;
const arrayIndex = index | 0;
if (this.isLowOrHighNibble(index)) {
this.array[arrayIndex] = value << 4 | this.array[arrayIndex] & 0xf;
const arrayIndex = index >> 1;
if ((index & 1) === 0) {
this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value & 0xf;
} 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 { PacketHoldingChange } from "./packets/HoldingChange";
import { PacketDisconnectKick } from "./packets/DisconnectKick";
import { ItemStack } from "./inventories/ItemStack";
import { PacketWindowItems } from "./packets/WindowItems";
import { Block } from "./blocks/Block";
export class MPClient {
private readonly mcServer:MinecraftServer;
@ -86,24 +89,24 @@ export class MPClient {
if (message[0].startsWith("/")) {
packet.message = "";
if (message[0] === "/tp") {
const x = this.entity.x = parseFloat(message[1]);
const y = this.entity.y = parseFloat(message[2]);
const z = this.entity.z = parseFloat(message[3]);
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]);
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]}`);
} else if (message[0] === "/csay") {
this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`);
} else if (message[0] === "/top") {
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);
this.send(new PacketPlayerPosition(this.entity.x, topBlock + 3, topBlock + 3.62, this.entity.z, false).writeData());
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());
} else if (message[0] === "/tpx") {
const dimension = parseInt(message[1]);
if (this.mcServer.worlds.has(dimension)) {
packet.message = "\u00a76Switching dimensions...";
this.switchDimension(dimension);
} 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) {
this.entity.x = packet.x;
this.entity.y = packet.y;
this.entity.z = packet.z;
this.entity.position.set(packet.x, packet.y, packet.z);
}
private handlePacketPlayerLook(packet:PacketPlayerLook) {
this.entity.yaw = packet.yaw;
this.entity.pitch = packet.pitch;
this.entity.rotation.set(packet.yaw, packet.pitch);
}
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
this.entity.x = packet.x;
this.entity.y = packet.y;
this.entity.z = packet.z;
this.entity.yaw = packet.yaw;
this.entity.pitch = packet.pitch;
this.entity.position.set(packet.x, packet.y, packet.z);
this.entity.rotation.set(packet.yaw, packet.pitch);
}
private handlePacketPlayerDigging(packet:PacketPlayerDigging) {
@ -155,21 +152,38 @@ export class MPClient {
if (packet.status === 0) {
// Started digging
} 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);
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) {
this.diggingAt.set(packet.x, packet.y, packet.z);
this.mapCoordsFromFace(this.diggingAt, packet.face);
const itemStack = this.inventory.getSlotItemStack(this.holdingIndex);
if (itemStack != null && itemStack.size > 0 && this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) === 0) {
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);
}
} else {
// TODO: Handle item usage
}
}
private handlePacketHoldingChange(packet:PacketHoldingChange) {
@ -209,9 +223,7 @@ export class MPClient {
this.send(new PacketRespawn(dimension).writeData());
//this.send(new PacketSpawnPosition(8, 64, 8).writeData());
this.entity.x = 8;
this.entity.y = 70;
this.entity.z = 8;
this.entity.position.set(8, 60, 8);
this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData());
this.entity.forceUpdatePlayerChunks();

View file

@ -225,10 +225,10 @@ export class MinecraftServer {
socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, dimension).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 => {
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);
}
});
@ -237,7 +237,6 @@ export class MinecraftServer {
const playerInventory = clientEntity.inventory;
socket.write(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData());
console.log(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData());
} else {
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 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") {
this.x = x;
this.y = y;
@ -12,14 +12,26 @@ export class Vec3 {
this.x = x;
this.y = x;
this.z = x;
} else if (x instanceof Vec3) {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else {
this.x = this.y = this.z = 0;
}
}
public set(x:number, y:number, z:number) {
public set(x?:Vec3 | number, y?:number, z?:number) {
if (x instanceof Vec3) {
this.x = x.x;
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 { BlockBehaviour } from "./BlockBehaviour";
import { BlockBehaviourFlower } from "./BlockBehaviourFlower";
import { BlockBehaviourGrass } from "./BlockBehaviourGrass";
import { IBlockBehaviour } from "./IBlockBehaviour";
abstract class Behaviour {
public static base = new BlockBehaviour();
public static grass = new BlockBehaviourGrass();
public static flower = new BlockBehaviourFlower();
}
export class Block {
public readonly blockId:number;
public static readonly blocks:Array<Block> = new Array<Block>();
public static readonly lightPassage:Array<number> = new Array<number>();
public static readonly blockBehaviours:Array<IBlockBehaviour> = new Array<IBlockBehaviour>();
@ -17,6 +23,7 @@ export class Block {
Block.blocks[blockId] = this;
Block.lightPassage[blockId] = 0;
Block.blockNames[blockId] = "";
Block.blockBehaviours[blockId] = Behaviour.base;
this.blockId = blockId;
}
@ -60,14 +67,16 @@ export class Block {
}
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
if (this.behaviour !== undefined) {
this.behaviour.neighborBlockChange(world, x, y, z, blockId);
}
public droppedItem(blockId:number) {
this.behaviour.droppedItem(blockId);
}
// Define statics here
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 bedrock = new Block(7).setBlockName("Bedrock");

View file

@ -3,4 +3,5 @@ import { IBlockBehaviour } from "./IBlockBehaviour";
export class BlockBehaviour implements IBlockBehaviour {
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";
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 { MetadataEntry, MetadataWriter } from "../MetadataWriter";
import { Rotation } from "../Rotation";
import { Vec3 } from "../Vec3";
import { World } from "../World";
import { MetadataFieldType } from "../enums/MetadataFieldType";
import { PacketEntityLook } from "../packets/EntityLook";
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
import { PacketEntityMetadata } from "../packets/EntityMetadata";
import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove";
import { PacketEntityTeleport } from "../packets/EntityTeleport";
import { IEntity } from "./IEntity";
export class Entity implements IEntity {
@ -11,14 +17,22 @@ export class Entity implements IEntity {
public entityId:number;
public world:World;
public x:number;
public y:number;
public z:number;
public lastX:number;
public lastY:number;
public lastZ:number;
public position:Vec3;
public lastPosition:Vec3;
public absPosition:Vec3;
public lastAbsPosition:Vec3;
public rotation:Rotation;
public lastRotation:Rotation;
public absRotation:Rotation;
public lastAbsRotation:Rotation;
public velocity:Vec3;
public health:number;
public wasHurt:boolean;
public isDead:boolean;
public fire:number;
@ -36,12 +50,26 @@ export class Entity implements IEntity {
this.fire = 0;
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.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.wasHurt = false;
this.isDead = false;
}
sendToNearby(buffer:Buffer) {
@ -61,10 +89,7 @@ export class Entity implements IEntity {
// 1 = On Fire
// 2 = Player crouched
// 4 = Player on mount?
//metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, 1));
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());
@ -74,9 +99,9 @@ export class Entity implements IEntity {
}
distanceTo(entity:IEntity) {
const dX = entity.x - this.x,
dY = entity.y - this.y,
dZ = entity.z - this.z;
const dX = entity.position.x - this.position.x,
dY = entity.position.y - this.position.y,
dZ = entity.position.z - this.position.z;
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) {
this.health -= damage;
}
this.wasHurt = true;
}
updateEntityChunk() {
const bitX = this.x >> 4;
const bitZ = this.z >> 4;
if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.queuedChunkUpdate) {
const bitX = this.position.x >> 4;
const bitZ = this.position.z >> 4;
if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.queuedChunkUpdate) {
if (this.world.chunkExists(bitX, bitZ)) {
this.chunk = this.world.getChunk(bitX, bitZ);
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() {
this.updateMetadata();
this.updateEntityChunk();
@ -116,8 +181,17 @@ export class Entity implements IEntity {
this.fire--;
}
this.lastX = this.x;
this.lastY = this.y;
this.lastZ = this.z;
if (!this.isDead && this.health <= 0) {
this.isDead = true;
}
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 itemStack:ItemStack;
public constructor(world:World, x:number, y:number, z:number, itemStack:ItemStack) {
public constructor(world:World, itemStack:ItemStack) {
super(world);
this.x = x;
this.y = y;
this.z = z;
this.itemStack = itemStack;

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import { ItemStack } from "./ItemStack";
import IInventory from "./IInventory";
export class Inventory implements IInventory {
private itemStacks:Array<ItemStack | null>;
public itemStacks:Array<ItemStack | null>;
private size:number;
private name:string;
@ -18,6 +18,20 @@ export class Inventory implements IInventory {
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() {
return this.name;
}

View file

@ -3,13 +3,15 @@ import { Item } from "../items/Item";
export class ItemStack {
public readonly itemID:number;
public readonly isBlock:boolean;
public readonly maxSize:number;
public size:number;
public damage:number;
public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) {
if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId;
this.size = 1;
this.size = 0;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId;
@ -21,7 +23,7 @@ export class ItemStack {
this.damage = damage;
} else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID;
this.size = 1;
this.size = 0;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID;
@ -35,9 +37,43 @@ export class ItemStack {
this.itemID = blockOrItemOrItemID;
this.size = size;
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 {
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) {

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 {
public static items:Array<Item> = new Array<Item>();
public maxStackSize:number;
public shiftedItemID:number;
public name:string;
public constructor(itemID:number) {
this.shiftedItemID = 256 + itemID;
this.maxStackSize = 64;
this.name = "UNNAMED";
Item.items[itemID] = this;
}
public static getByShiftedItemId(shiftedItemID:number) {
return Item.items[shiftedItemID - 256];
}
public setMaxStackSize(stackSize:number) {
@ -12,5 +22,19 @@ export class Item {
return this;
}
// Define statics here
public setName(name:string) {
this.name = name;
return this;
}
public getName() {
return this.name;
}
// 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");
}