283 lines
8.9 KiB
TypeScript
283 lines
8.9 KiB
TypeScript
import { Endian, IReader, IWriter, createWriter } from "bufferstuff";
|
|
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";
|
|
import { PacketEntityMetadata } from "../packets/EntityMetadata";
|
|
import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove";
|
|
import { PacketEntityTeleport } from "../packets/EntityTeleport";
|
|
import { PacketEntityVelocity } from "../packets/EntityVelocity";
|
|
import { IEntity } from "./IEntity";
|
|
|
|
export class Entity implements IEntity {
|
|
public static nextEntityId:number = 0;
|
|
|
|
public entityId:number;
|
|
|
|
public entitySize:Vec2;
|
|
|
|
public world:World;
|
|
|
|
public position:Vec3;
|
|
public lastPosition:Vec3;
|
|
public absPosition:Vec3;
|
|
public lastAbsPosition:Vec3;
|
|
public motion:Vec3;
|
|
|
|
private positionBeforeMove:Vec3;
|
|
|
|
public rotation:Rotation;
|
|
public lastRotation:Rotation;
|
|
public absRotation:Rotation;
|
|
public lastAbsRotation:Rotation;
|
|
|
|
public health:number;
|
|
public wasHurt:boolean;
|
|
public isDead:boolean;
|
|
|
|
public fire:number;
|
|
public fallDistance:number;
|
|
|
|
public onGround:boolean;
|
|
|
|
public chunk:Chunk;
|
|
|
|
public crouching:boolean;
|
|
private lastCrouchState:boolean;
|
|
private lastFireState:boolean;
|
|
|
|
public entityAABB:AABB;
|
|
|
|
public readonly isPlayer:boolean;
|
|
private queuedChunkUpdate:boolean;
|
|
|
|
public markedForDisposal:boolean = false;
|
|
|
|
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;
|
|
|
|
this.world = world;
|
|
|
|
this.position = new Vec3();
|
|
this.lastPosition = new Vec3();
|
|
this.absPosition = new Vec3();
|
|
this.lastAbsPosition = new Vec3();
|
|
this.motion = new Vec3();
|
|
|
|
this.positionBeforeMove = new Vec3();
|
|
|
|
this.rotation = new Rotation();
|
|
this.lastRotation = new Rotation();
|
|
this.absRotation = new Rotation();
|
|
this.lastAbsRotation = new Rotation();
|
|
|
|
this.crouching = this.lastCrouchState = this.lastFireState = this.queuedChunkUpdate = false;
|
|
|
|
this.chunk = world.getChunk(this.position.x >> 4, this.position.z >> 4);
|
|
|
|
this.health = 20;
|
|
this.wasHurt = false;
|
|
this.isDead = false;
|
|
}
|
|
|
|
public fromSave(reader:IReader) {
|
|
this.position.set(reader.readDouble(), reader.readDouble(), reader.readDouble());
|
|
this.motion.set(reader.readFloat(), reader.readFloat(), reader.readFloat());
|
|
this.rotation.set(reader.readFloat(), reader.readFloat());
|
|
this.fire = reader.readShort();
|
|
this.fallDistance = reader.readFloat();
|
|
this.health = reader.readByte();
|
|
}
|
|
|
|
public toSave(writer:IWriter) {
|
|
writer.writeDouble(this.position.x).writeDouble(this.position.y).writeDouble(this.position.z) // Position
|
|
.writeFloat(this.motion.x).writeFloat(this.motion.y).writeFloat(this.motion.z) // Motion
|
|
.writeFloat(this.rotation.x).writeFloat(this.rotation.y) // Rotation
|
|
.writeShort(this.fire)
|
|
.writeFloat(this.fallDistance)
|
|
.writeByte(this.health);
|
|
}
|
|
|
|
sendToNearby(buffer:Buffer) {
|
|
this.world.sendToNearbyClients(this, buffer);
|
|
}
|
|
|
|
sendToAllNearby(buffer:Buffer) {
|
|
this.world.sendToNearbyAllNearbyClients(this, buffer);
|
|
}
|
|
|
|
updateMetadata() {
|
|
const crouchStateChanged = this.crouching !== this.lastCrouchState;
|
|
const fireStateChanged = this.fire > 0 !== this.lastFireState;
|
|
if (crouchStateChanged || fireStateChanged) {
|
|
const metadata = new MetadataWriter();
|
|
// Flags:
|
|
// 1 = On Fire
|
|
// 2 = Player crouched
|
|
// 4 = Player on mount?
|
|
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.lastCrouchState = this.crouching;
|
|
this.lastFireState = this.fire > 0;
|
|
}
|
|
}
|
|
|
|
distanceTo(entity:IEntity) {
|
|
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));
|
|
}
|
|
|
|
damageFrom(damage:number, entity?:IEntity) {
|
|
if (this.health <= 0) {
|
|
return;
|
|
}
|
|
|
|
if (entity === undefined) {
|
|
this.health -= damage;
|
|
}
|
|
|
|
this.wasHurt = true;
|
|
}
|
|
|
|
updateEntityChunk() {
|
|
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;
|
|
} else {
|
|
this.queuedChunkUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 code *does* work, and it works well. But this is absolutely TERRIBLE!
|
|
// There is definitely a better way to do this.
|
|
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.sendToNearby(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.sendToNearby(new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absRotation.yaw, this.absRotation.pitch).writeData());
|
|
} else if (doRelativeMove) {
|
|
this.sendToNearby(new PacketEntityRelativeMove(this.entityId, diffX, diffY, diffZ).writeData());
|
|
} else if (doLook) {
|
|
this.sendToNearby(new PacketEntityLook(this.entityId, this.absRotation.yaw, this.absRotation.pitch).writeData());
|
|
}
|
|
|
|
if (!this.motion.isZero) {
|
|
this.sendToNearby(new PacketEntityVelocity(this.entityId, this.motion.x, this.motion.y, this.motion.z).writeData());
|
|
}
|
|
|
|
if (doRelativeMove) {
|
|
this.lastAbsPosition.set(this.absPosition);
|
|
}
|
|
if (doLook) {
|
|
this.lastAbsRotation.set(this.absRotation);
|
|
}
|
|
}
|
|
|
|
fall(distance:number) {
|
|
// TODO: Entity falling mount transfer
|
|
}
|
|
|
|
kill() {
|
|
this.health = 0;
|
|
this.markedForDisposal = true;
|
|
}
|
|
|
|
updateFalling(distance:number) {
|
|
if (this.onGround) {
|
|
if (this.fallDistance > 0) {
|
|
this.fall(this.fallDistance);
|
|
this.fallDistance = 0;
|
|
}
|
|
} else if (distance < 0) {
|
|
this.fallDistance -= distance;
|
|
}
|
|
}
|
|
|
|
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));
|
|
// TODO: Handle X and Z collisions.
|
|
if (this.entityAABB.intersects(blockBoundingBox)) {
|
|
const inersectionY = this.entityAABB.intersectionY(blockBoundingBox);
|
|
this.position.add(0, inersectionY, 0);
|
|
this.motion.y = 0;
|
|
this.onGround = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
onTick() {
|
|
this.updateMetadata();
|
|
this.updateEntityChunk();
|
|
this.updateFalling(this.motion.y);
|
|
|
|
if (this.fire > 0) {
|
|
if (this.fire % 20 === 0) {
|
|
this.damageFrom(1);
|
|
}
|
|
|
|
this.fire--;
|
|
}
|
|
|
|
if (!this.isDead && this.health <= 0) {
|
|
this.isDead = true;
|
|
}
|
|
|
|
if (this.wasHurt) {
|
|
this.wasHurt = false;
|
|
}
|
|
|
|
this.sendPositionUpdate();
|
|
|
|
this.lastPosition.set(this.position);
|
|
}
|
|
} |