Implement AABB collision and add EntityItem properly.
The items are fully functional apart from picking them up, they are commented out in the MPClient breakBlock function if you want to play with them.
This commit is contained in:
parent
6033c86247
commit
63333a04aa
14 changed files with 239 additions and 11 deletions
|
@ -7,11 +7,15 @@ export default class AABB {
|
|||
private static readonly aabbPool:FunkyArray<string, AABB> = new FunkyArray<string, AABB>();
|
||||
|
||||
public readonly aabbPoolString:string;
|
||||
public readonly pooled:boolean;
|
||||
|
||||
public initMin:Vec3;
|
||||
public initMax:Vec3;
|
||||
|
||||
public min:Vec3;
|
||||
public max:Vec3;
|
||||
|
||||
public constructor(minXOrMin:Vec3 | number, minYOrMax:Vec3 | number, minZ?:number, maxX?:number, maxY?:number, maxZ?:number) {
|
||||
public constructor(minXOrMin:Vec3 | number, minYOrMax:Vec3 | number, minZ?:number, maxX?:number, maxY?:number, maxZ?:number, pooled:boolean = false) {
|
||||
if (minXOrMin instanceof Vec3 && minYOrMax instanceof Vec3) {
|
||||
this.min = minXOrMin;
|
||||
this.max = minYOrMax;
|
||||
|
@ -22,6 +26,11 @@ export default class AABB {
|
|||
throw new Error("Invalid input parameters: AABB must be supplied with either two Vec3 with the min and max bounds or the raw bounds.");
|
||||
}
|
||||
|
||||
this.initMin = new Vec3(this.min);
|
||||
this.initMax = new Vec3(this.max);
|
||||
|
||||
this.pooled = pooled;
|
||||
|
||||
this.aabbPoolString = AABB.createAABBPoolString(this.min.x, this.min.y, this.min.z, this.max.x, this.max.y, this.max.z);
|
||||
if (!AABB.aabbPool.has(this.aabbPoolString)) {
|
||||
AABB.aabbPool.set(this.aabbPoolString, this);
|
||||
|
@ -36,18 +45,53 @@ export default class AABB {
|
|||
|
||||
public static getAABB(minX:number, minY:number, minZ:number, maxX:number, maxY:number, maxZ:number) {
|
||||
const aabbPoolString = this.createAABBPoolString(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
if (!AABB.aabbPool.has(aabbPoolString)) {
|
||||
return AABB.aabbPool.get(aabbPoolString);
|
||||
if (AABB.aabbPool.has(aabbPoolString)) {
|
||||
const aabb = AABB.aabbPool.get(aabbPoolString);
|
||||
if (aabb === undefined) {
|
||||
throw new Error(`Pooled AABB was ${typeof(aabb)}! This should be impossible.`);
|
||||
}
|
||||
|
||||
return aabb;
|
||||
}
|
||||
|
||||
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
intersects(a:AABB, b:AABB) {
|
||||
public static intersects(a:AABB, b:AABB) {
|
||||
return a.min.x <= b.max.x && a.max.x >= b.min.x && a.min.y <= b.max.y && a.max.y >= b.min.y && a.min.z <= b.max.z && a.max.z >= b.min.z;
|
||||
}
|
||||
|
||||
intersectionAmount(a:AABB, b:AABB) {
|
||||
public intersects(aabb:AABB) {
|
||||
return this.min.x <= aabb.max.x && this.max.x >= aabb.min.x && this.min.y <= aabb.max.y && this.max.y >= aabb.min.y && this.min.z <= aabb.max.z && this.max.z >= aabb.min.z;
|
||||
}
|
||||
|
||||
public static intersectionY(a: AABB, b: AABB) {
|
||||
const minY = Math.max(a.min.y, b.min.y);
|
||||
const maxY = Math.min(a.max.y, b.max.y);
|
||||
|
||||
return minY <= maxY ? maxY - minY : 0;
|
||||
}
|
||||
|
||||
public intersectionY(aabb: AABB) {
|
||||
const minY = Math.max(this.min.y, aabb.min.y);
|
||||
const maxY = Math.min(this.max.y, aabb.max.y);
|
||||
|
||||
return minY <= maxY ? maxY - minY : 0;
|
||||
}
|
||||
|
||||
public move(xOrVec3:Vec3 | number, y?:number, z?:number) {
|
||||
if (this.pooled) {
|
||||
throw new Error(`Attempted to move a pooled AABB. This is not allowed!`);
|
||||
}
|
||||
|
||||
this.min.set(this.initMin);
|
||||
this.max.set(this.initMax);
|
||||
if (xOrVec3 instanceof Vec3) {
|
||||
this.min.add(xOrVec3);
|
||||
this.max.add(xOrVec3);
|
||||
} else if (typeof(xOrVec3) === "number" && typeof(y) === "number" && typeof(z) === "number") {
|
||||
this.min.add(xOrVec3, y, z);
|
||||
this.max.add(xOrVec3, y, z);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import { PacketDisconnectKick } from "./packets/DisconnectKick";
|
|||
import { ItemStack } from "./inventories/ItemStack";
|
||||
import { PacketWindowItems } from "./packets/WindowItems";
|
||||
import { Block } from "./blocks/Block";
|
||||
import { EntityItem } from "./entities/EntityItem";
|
||||
|
||||
export class MPClient {
|
||||
private readonly mcServer:MinecraftServer;
|
||||
|
@ -160,6 +161,9 @@ export class MPClient {
|
|||
this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0);
|
||||
this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
|
||||
this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
|
||||
/*const itemEntity = new EntityItem(this.entity.world, new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
|
||||
itemEntity.position.set(x + 0.5, y + 0.5, z + 0.5);
|
||||
this.entity.world.addEntity(itemEntity);*/
|
||||
}
|
||||
|
||||
// TODO: Cap how far away a player is able to break blocks
|
||||
|
|
|
@ -35,7 +35,27 @@ export default class Vec3 {
|
|||
this.y = y;
|
||||
this.z = z;
|
||||
} else {
|
||||
this.x = this.y = this.z = 0;
|
||||
throw new Error(`Invalid arguments for Vec3.set : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
|
||||
}
|
||||
}
|
||||
|
||||
add(x:Vec3 | number, y?:number, z?:number) {
|
||||
if (x instanceof Vec3) {
|
||||
this.set(this.x + x.x, this.y + x.y, this.z + x.z);
|
||||
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
|
||||
this.set(this.x + x, this.y + y, this.z + z);
|
||||
} else {
|
||||
throw new Error(`Invalid arguments for Vec3.add : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
|
||||
}
|
||||
}
|
||||
|
||||
mult(x:Vec3 | number, y?:number, z?:number) {
|
||||
if (x instanceof Vec3) {
|
||||
this.set(this.x * x.x, this.y * x.y, this.z * x.z);
|
||||
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
|
||||
this.set(this.x * x, this.y * y, this.z * z);
|
||||
} else {
|
||||
throw new Error(`Invalid arguments for Vec3.mult : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import { FunkyArray } from "../funkyArray";
|
|||
import { Chunk } from "./Chunk";
|
||||
import { WorldSaveManager } from "./WorldSaveManager";
|
||||
import { Block } from "./blocks/Block";
|
||||
import { EntityItem } from "./entities/EntityItem";
|
||||
import { IEntity } from "./entities/IEntity";
|
||||
import { Player } from "./entities/Player";
|
||||
//import { FlatGenerator } from "./generators/Flat";
|
||||
|
@ -9,6 +10,7 @@ import { HillyGenerator } from "./generators/Hilly";
|
|||
import { IGenerator } from "./generators/IGenerator";
|
||||
import { PacketBlockChange } from "./packets/BlockChange";
|
||||
import { PacketDestroyEntity } from "./packets/DestroyEntity";
|
||||
import { PacketPickupSpawn } from "./packets/PickupSpawn";
|
||||
import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate";
|
||||
import { IQueuedUpdate } from "./queuedUpdateTypes/IQueuedUpdate";
|
||||
|
||||
|
@ -46,6 +48,9 @@ export class World {
|
|||
this.entites.set(entity.entityId, entity);
|
||||
if (entity instanceof Player) {
|
||||
this.players.set(entity.entityId, entity);
|
||||
} else if (entity instanceof EntityItem) {
|
||||
const packet = new PacketPickupSpawn(entity.entityId, entity.itemStack.itemID, entity.itemStack.size, entity.itemStack.damage, Math.round(entity.position.x * 32), Math.round(entity.position.y * 32), Math.round(entity.position.z * 32), 0, 0, 0).writeData();
|
||||
entity.sendToNearby(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AABB from "../AABB";
|
||||
import { World } from "../World";
|
||||
import { BlockBehaviour } from "./BlockBehaviour";
|
||||
import { BlockBehaviourFlower } from "./BlockBehaviourFlower";
|
||||
|
@ -20,6 +21,7 @@ export class Block {
|
|||
public static readonly blocks:Array<Block> = new Array<Block>();
|
||||
public static readonly lightPassage:Array<number> = new Array<number>();
|
||||
public static readonly hardness:Array<number> = new Array<number>();
|
||||
public static readonly blockAABBs:Array<AABB> = new Array<AABB>();
|
||||
public static readonly blockBehaviours:Array<IBlockBehaviour> = new Array<IBlockBehaviour>();
|
||||
public static readonly blockNames:Array<string> = new Array<string>();
|
||||
|
||||
|
@ -47,6 +49,14 @@ export class Block {
|
|||
Block.hardness[this.blockId] = value;
|
||||
}
|
||||
|
||||
private get blockAABB() {
|
||||
return Block.blockAABBs[this.blockId];
|
||||
}
|
||||
|
||||
private set blockAABB(value:AABB) {
|
||||
Block.blockAABBs[this.blockId] = value;
|
||||
}
|
||||
|
||||
public get blockName() {
|
||||
return Block.blockNames[this.blockId];
|
||||
}
|
||||
|
@ -108,6 +118,10 @@ export class Block {
|
|||
return 1 / this.hardness / 100;
|
||||
}
|
||||
|
||||
public getBoundingBox(x:number, y:number, z:number) {
|
||||
return this.behaviour.getBoundingBox(x, y, z);
|
||||
}
|
||||
|
||||
// Define statics here
|
||||
static readonly stone = new Block(1).setHardness(1.5).setBehaviour(Behaviour.stone).setBlockName("Stone");
|
||||
static readonly grass = new Block(2).setHardness(0.6).setBehaviour(Behaviour.grass).setBlockName("Grass");
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import AABB from "../AABB";
|
||||
import { World } from "../World";
|
||||
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; }
|
||||
public getBoundingBox(x:number, y:number, z:number) { return AABB.getAABB(0 + x, 0 + y, 0 + z, 1 + x, 1 + y, 1 + z); }
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import AABB from "../AABB";
|
||||
import { World } from "../World";
|
||||
import { Block } from "./Block";
|
||||
import { BlockBehaviour } from "./BlockBehaviour";
|
||||
|
@ -9,4 +10,8 @@ export class BlockBehaviourFlower extends BlockBehaviour {
|
|||
world.setBlockWithNotify(x, y, z, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public getBoundingBox() {
|
||||
return AABB.getAABB(0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import AABB from "../AABB";
|
||||
import { World } from "../World";
|
||||
|
||||
export interface IBlockBehaviour {
|
||||
neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void,
|
||||
droppedItem: (blockId:number) => number
|
||||
droppedItem: (blockId:number) => number,
|
||||
getBoundingBox: (x:number, y:number, z:number) => AABB,
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import AABB from "../AABB";
|
||||
import { Chunk } from "../Chunk";
|
||||
import { MetadataEntry, MetadataWriter } from "../MetadataWriter";
|
||||
import { Rotation } from "../Rotation";
|
||||
import { Vec2 } from "../Vec2";
|
||||
import Vec3 from "../Vec3";
|
||||
import { World } from "../World";
|
||||
import { Block } from "../blocks/Block";
|
||||
import { MetadataFieldType } from "../enums/MetadataFieldType";
|
||||
import { PacketEntityLook } from "../packets/EntityLook";
|
||||
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
|
||||
|
@ -28,6 +30,8 @@ export class Entity implements IEntity {
|
|||
public lastAbsPosition:Vec3;
|
||||
public motion:Vec3;
|
||||
|
||||
private positionBeforeMove:Vec3;
|
||||
|
||||
public rotation:Rotation;
|
||||
public lastRotation:Rotation;
|
||||
public absRotation:Rotation;
|
||||
|
@ -50,12 +54,18 @@ export class Entity implements IEntity {
|
|||
private lastCrouchState:boolean;
|
||||
private lastFireState:boolean;
|
||||
|
||||
public entityAABB:AABB;
|
||||
|
||||
private readonly isPlayer:boolean;
|
||||
private queuedChunkUpdate:boolean;
|
||||
|
||||
public constructor(world:World) {
|
||||
public constructor(world:World, isPlayer:boolean = false) {
|
||||
this.entityId = Entity.nextEntityId++;
|
||||
|
||||
this.isPlayer = isPlayer;
|
||||
|
||||
this.entitySize = new Vec2(0.6, 1.8);
|
||||
this.entityAABB = new AABB(-this.entitySize.x / 2, 0, -this.entitySize.x / 2, this.entitySize.x / 2, this.entitySize.y, this.entitySize.x / 2);
|
||||
|
||||
this.fire = this.fallDistance = 0;
|
||||
this.onGround = false;
|
||||
|
@ -68,6 +78,8 @@ export class Entity implements IEntity {
|
|||
this.lastAbsPosition = new Vec3();
|
||||
this.motion = new Vec3();
|
||||
|
||||
this.positionBeforeMove = new Vec3();
|
||||
|
||||
this.rotation = new Rotation();
|
||||
this.lastRotation = new Rotation();
|
||||
this.absRotation = new Rotation();
|
||||
|
@ -201,6 +213,25 @@ export class Entity implements IEntity {
|
|||
}
|
||||
}
|
||||
|
||||
moveEntity(motionX:number, motionY:number, motionZ:number) {
|
||||
this.positionBeforeMove.set(this.position);
|
||||
const blockId = this.chunk.getBlockId(Math.floor(this.positionBeforeMove.x) & 0xf, Math.floor(this.positionBeforeMove.y), Math.floor(this.positionBeforeMove.z) & 0xf);
|
||||
const blockUnderEntity = blockId > 0 ? Block.blocks[blockId] : null;
|
||||
|
||||
this.position.add(motionX, motionY, motionZ);
|
||||
|
||||
this.entityAABB.move(this.position);
|
||||
if (blockUnderEntity !== null) {
|
||||
const blockBoundingBox = blockUnderEntity.getBoundingBox(Math.floor(this.positionBeforeMove.x), Math.floor(this.positionBeforeMove.y), Math.floor(this.positionBeforeMove.z));
|
||||
if (this.entityAABB.intersects(blockBoundingBox)) {
|
||||
const intersection = this.entityAABB.intersectionY(blockBoundingBox);
|
||||
this.position.add(0, intersection, 0);
|
||||
this.motion.y = 0;
|
||||
this.onGround = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTick() {
|
||||
this.updateMetadata();
|
||||
this.updateEntityChunk();
|
||||
|
|
|
@ -6,12 +6,47 @@ export class EntityItem extends Entity {
|
|||
public age:number;
|
||||
public itemStack:ItemStack;
|
||||
|
||||
public pickupDelay:number;
|
||||
|
||||
public constructor(world:World, itemStack:ItemStack) {
|
||||
super(world);
|
||||
|
||||
this.itemStack = itemStack;
|
||||
|
||||
this.entitySize.set(0.2, 0.2);
|
||||
|
||||
this.pickupDelay = 0;
|
||||
|
||||
this.motion.set(Math.random() * 0.2 - 0.1, 0.2, Math.random() * 0.2 - 0.1);
|
||||
|
||||
this.age = 0;
|
||||
this.health = 5;
|
||||
}
|
||||
|
||||
onTick() {
|
||||
super.onTick();
|
||||
if (this.pickupDelay > 0) {
|
||||
this.pickupDelay--;
|
||||
}
|
||||
|
||||
this.motion.add(0, -0.04, 0);
|
||||
this.moveEntity(this.motion.x, this.motion.y, this.motion.z);
|
||||
|
||||
let xyMult = 0.98;
|
||||
if (this.onGround) {
|
||||
xyMult = 0.59;
|
||||
}
|
||||
|
||||
// TODO: Change the x and z based on the slipperiness of a block
|
||||
this.motion.mult(xyMult, 0.98, xyMult);
|
||||
|
||||
if (this.onGround) {
|
||||
this.motion.y *= -0.5;
|
||||
}
|
||||
|
||||
this.age++;
|
||||
if (this.age >= 6000) {
|
||||
// TODO: Kill entity
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,8 +18,8 @@ export class EntityLiving extends Entity {
|
|||
public headHeight:number;
|
||||
public lastHealth:number;
|
||||
|
||||
public constructor(world:World) {
|
||||
super(world);
|
||||
public constructor(world:World, isPlayer:boolean = false) {
|
||||
super(world, isPlayer);
|
||||
|
||||
this.timeInWater = 0;
|
||||
this.headHeight = 1.62;
|
||||
|
|
|
@ -27,7 +27,7 @@ export class Player extends EntityLiving {
|
|||
public trackedEquipment:Array<ItemStack | null>;
|
||||
|
||||
public constructor(server:MinecraftServer, world:World, username:string) {
|
||||
super(world);
|
||||
super(world, true);
|
||||
this.server = server;
|
||||
this.firstUpdate = true;
|
||||
this.loadedChunks = new Array<number>();
|
||||
|
@ -140,6 +140,9 @@ export class Player extends EntityLiving {
|
|||
|
||||
// Calculate player motion since we don't have it serverside.
|
||||
this.motion.set(this.position.x - this.lastPosition.x, this.position.y - this.lastPosition.y, this.position.z - this.lastPosition.z);
|
||||
if (!this.motion.isZero) {
|
||||
this.entityAABB.move(this.position);
|
||||
}
|
||||
|
||||
super.onTick();
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ export enum Packet {
|
|||
Animation = 0x12,
|
||||
EntityAction = 0x13,
|
||||
NamedEntitySpawn = 0x14,
|
||||
PickupSpawn = 0x15,
|
||||
|
||||
EntityVelocity = 0x1C,
|
||||
DestroyEntity = 0x1D,
|
||||
|
|
62
server/packets/PickupSpawn.ts
Normal file
62
server/packets/PickupSpawn.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { createWriter, IReader, Endian } from "bufferstuff";
|
||||
import { IPacket } from "./IPacket";
|
||||
import { Packet } from "../enums/Packet";
|
||||
|
||||
export class PacketPickupSpawn implements IPacket {
|
||||
public packetId = Packet.PickupSpawn;
|
||||
public entityId:number;
|
||||
public item:number;
|
||||
public count:number;
|
||||
public damage:number;
|
||||
public x:number;
|
||||
public y:number;
|
||||
public z:number;
|
||||
public yaw:number;
|
||||
public pitch:number;
|
||||
public roll:number;
|
||||
|
||||
public constructor(entityId?:number, item?:number, count?:number, damage?:number, x?:number, y?:number, z?:number, yaw?:number, pitch?:number, roll?:number) {
|
||||
if (typeof(entityId) === "number" && typeof(item) === "number" && typeof(count) === "number" && typeof(damage) === "number" && typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(yaw) === "number" && typeof(pitch) === "number" && typeof(roll) === "number") {
|
||||
this.entityId = entityId;
|
||||
this.item = item;
|
||||
this.count = count;
|
||||
this.damage = damage;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
this.roll = roll;
|
||||
} else {
|
||||
this.entityId = Number.MIN_VALUE;
|
||||
this.item = Number.MIN_VALUE;
|
||||
this.count = Number.MIN_VALUE;
|
||||
this.damage = 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;
|
||||
this.roll = Number.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
public readData(reader:IReader) {
|
||||
this.entityId = reader.readInt();
|
||||
this.item = reader.readShort();
|
||||
this.count = reader.readByte();
|
||||
this.damage = reader.readShort();
|
||||
this.x = reader.readInt();
|
||||
this.y = reader.readInt();
|
||||
this.z = reader.readInt();
|
||||
this.yaw = reader.readByte();
|
||||
this.pitch = reader.readByte();
|
||||
this.roll = reader.readByte();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public writeData() {
|
||||
return createWriter(Endian.BE, 25).writeUByte(this.packetId).writeInt(this.entityId).writeShort(this.item).writeByte(this.count).writeShort(this.damage).writeInt(this.x).writeInt(this.y).writeInt(this.z).writeByte(this.yaw).writeByte(this.pitch).writeByte(this.roll).toBuffer();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue