mc-beta-server/server/entities/Entity.ts

295 lines
9.2 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";
import { Player } from "./Player";
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);
}
collidesWithPlayer(aabb:AABB) {
let collidedWith:Player | undefined;
this.world.players.forEach(player => {
if (this.entityAABB.intersects(player.entityAABB)) {
collidedWith = player;
}
});
return collidedWith;
}
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);
}
}