2023-11-09 21:59:45 +00:00
|
|
|
import { IReader, IWriter } from "bufferstuff";
|
2024-10-26 14:24:38 +01:00
|
|
|
import Block from "../blocks/Block";
|
|
|
|
import Chunk from "../Chunk";
|
|
|
|
import Entity from "./Entity";
|
|
|
|
import EntityItem from "./EntityItem";
|
|
|
|
import EntityLiving from "./EntityLiving";
|
|
|
|
import Item from "../items/Item";
|
|
|
|
import ItemStack from "../inventories/ItemStack";
|
|
|
|
import MinecraftServer from "../MinecraftServer";
|
|
|
|
import MPClient from "../MPClient";
|
|
|
|
import PacketCollectItem from "../packets/CollectItem";
|
|
|
|
import PacketEntityEquipment from "../packets/EntityEquipment";
|
|
|
|
import PacketMapChunk from "../packets/MapChunk";
|
|
|
|
import PacketPreChunk from "../packets/PreChunk";
|
|
|
|
import PacketUpdateHealth from "../packets/UpdateHealth";
|
|
|
|
import PlayerInventory from "../inventories/PlayerInventory";
|
|
|
|
import World from "../World";
|
2023-04-08 20:52:47 +01:00
|
|
|
|
2023-11-05 00:55:23 +00:00
|
|
|
const CHUNK_LOAD_RANGE = 15;
|
2023-09-04 23:42:38 +01:00
|
|
|
|
2024-10-26 14:24:38 +01:00
|
|
|
export default class Player extends EntityLiving {
|
2023-04-08 20:52:47 +01:00
|
|
|
public username:string;
|
|
|
|
private server:MinecraftServer;
|
2023-04-09 04:19:10 +01:00
|
|
|
private firstUpdate:boolean;
|
|
|
|
public loadedChunks:Array<number>;
|
|
|
|
public justUnloaded:Array<number>;
|
|
|
|
public mpClient?:MPClient;
|
2023-11-02 08:31:43 +00:00
|
|
|
public inventory:PlayerInventory;
|
2023-04-08 20:52:47 +01:00
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
public trackedEquipment:Array<ItemStack | null>;
|
|
|
|
|
2023-04-08 20:52:47 +01:00
|
|
|
public constructor(server:MinecraftServer, world:World, username:string) {
|
2023-11-09 16:30:40 +00:00
|
|
|
super(world, true);
|
2023-04-08 20:52:47 +01:00
|
|
|
this.server = server;
|
2023-04-09 04:19:10 +01:00
|
|
|
this.firstUpdate = true;
|
|
|
|
this.loadedChunks = new Array<number>();
|
|
|
|
this.justUnloaded = new Array<number>();
|
2023-04-08 20:52:47 +01:00
|
|
|
|
2023-12-18 01:23:52 +00:00
|
|
|
this.inventory = new PlayerInventory(this);
|
2023-10-29 05:08:26 +00:00
|
|
|
|
2023-11-02 08:31:43 +00:00
|
|
|
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));
|
2024-11-29 15:00:48 +00:00
|
|
|
this.inventory.setSlotItemStack(41, new ItemStack(Block.craftingTable, 1));
|
2024-11-25 22:28:33 +00:00
|
|
|
this.inventory.setSlotItemStack(42, new ItemStack(Block.chest, 32));
|
2023-11-02 08:31:43 +00:00
|
|
|
this.inventory.setSlotItemStack(43, new ItemStack(Block.dirt, 32));
|
2023-10-29 05:08:26 +00:00
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
this.trackedEquipment = new Array<ItemStack | null>();
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
this.trackedEquipment.push(null);
|
|
|
|
}
|
|
|
|
|
2023-04-08 20:52:47 +01:00
|
|
|
this.username = username;
|
2023-11-02 08:31:43 +00:00
|
|
|
this.position.set(8, 64, 8);
|
2023-04-08 20:52:47 +01:00
|
|
|
}
|
2023-04-09 04:19:10 +01:00
|
|
|
|
2023-11-09 21:59:45 +00:00
|
|
|
public fromSave(reader:IReader) {
|
|
|
|
super.fromSave(reader);
|
|
|
|
|
|
|
|
this.inventory.fromSave(reader);
|
|
|
|
}
|
|
|
|
|
|
|
|
public toSave(writer:IWriter) {
|
|
|
|
super.toSave(writer);
|
|
|
|
|
|
|
|
this.inventory.toSave(writer);
|
|
|
|
}
|
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
// Forces a player chunk update *next tick*
|
2023-09-04 23:42:38 +01:00
|
|
|
public forceUpdatePlayerChunks() {
|
|
|
|
this.firstUpdate = true;
|
|
|
|
}
|
|
|
|
|
2023-12-18 01:23:52 +00:00
|
|
|
public itemPickup(entity:Entity, stackSize:number) {
|
|
|
|
if (!this.isDead) {
|
|
|
|
if (entity instanceof EntityItem) {
|
|
|
|
this.sendToAllNearby(new PacketCollectItem(entity.entityId, this.entityId).writeData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-18 21:49:23 +00:00
|
|
|
dropAllItems() {
|
|
|
|
for (const itemStack of this.inventory.itemStacks) {
|
|
|
|
if (itemStack) {
|
|
|
|
const item = new EntityItem(this.world, itemStack);
|
|
|
|
item.position.set(this.position);
|
|
|
|
this.world.addEntity(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-11 07:47:56 +01:00
|
|
|
private async updatePlayerChunks() {
|
2023-11-02 08:31:43 +00:00
|
|
|
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) {
|
2023-04-09 04:19:10 +01:00
|
|
|
if (this.firstUpdate) {
|
|
|
|
this.firstUpdate = false;
|
2023-04-13 23:52:13 +01:00
|
|
|
// TODO: Make this based on the player's initial coords
|
2023-04-09 04:19:10 +01:00
|
|
|
this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData());
|
2023-04-11 07:47:56 +01:00
|
|
|
const chunk = await this.world.getChunkSafe(0, 0);
|
|
|
|
const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData());
|
|
|
|
this.mpClient?.send(chunkData);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load or keep any chunks we need
|
|
|
|
const currentLoads = [];
|
2023-09-04 23:42:38 +01:00
|
|
|
for (let x = bitX - CHUNK_LOAD_RANGE; x < bitX + CHUNK_LOAD_RANGE; x++) {
|
|
|
|
for (let z = bitZ - CHUNK_LOAD_RANGE; z < bitZ + CHUNK_LOAD_RANGE; z++) {
|
2023-04-09 04:19:10 +01:00
|
|
|
const coordPair = Chunk.CreateCoordPair(x, z);
|
|
|
|
if (!this.loadedChunks.includes(coordPair)) {
|
2023-04-11 07:47:56 +01:00
|
|
|
const chunk = await this.world.getChunkSafe(x, z);
|
2023-04-09 04:19:10 +01:00
|
|
|
this.mpClient?.send(new PacketPreChunk(x, z, true).writeData());
|
|
|
|
this.loadedChunks.push(coordPair);
|
|
|
|
chunk.playersInChunk.set(this.entityId, this);
|
2023-04-11 07:47:56 +01:00
|
|
|
const chunkData = await (new PacketMapChunk(x, 0, z, 15, 127, 15, chunk).writeData());
|
|
|
|
this.mpClient?.send(chunkData);
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
|
|
|
currentLoads.push(coordPair);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark any unaccounted chunks for unload
|
2023-04-09 04:47:23 +01:00
|
|
|
for (const coordPair of this.loadedChunks) {
|
2023-05-02 08:50:49 +01:00
|
|
|
if (!currentLoads.includes(coordPair) && this.world.chunkExists(coordPair)) {
|
2023-04-09 04:19:10 +01:00
|
|
|
this.justUnloaded.push(coordPair);
|
|
|
|
const chunkToUnload = this.world.getChunkByCoordPair(coordPair);
|
|
|
|
this.mpClient?.send(new PacketPreChunk(chunkToUnload.x, chunkToUnload.z, false).writeData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overwrite loaded chunks
|
|
|
|
this.loadedChunks = currentLoads;
|
|
|
|
}
|
2023-04-11 07:47:56 +01:00
|
|
|
}
|
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
private getEquipmentForVirtualSlot(slot:number) {
|
|
|
|
if (slot === 0) {
|
|
|
|
return this.mpClient?.getHeldItemStack() ?? null;
|
|
|
|
} else {
|
|
|
|
this.inventory.getSlotItemStack(4 + slot); // 5 - 8
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private sendEquipment(equipmentId:number, itemStack:ItemStack | null) {
|
|
|
|
this.sendToNearby(new PacketEntityEquipment(this.entityId, equipmentId, itemStack == null ? -1 : itemStack.itemID, itemStack == null ? 0 : itemStack.damage).writeData());
|
|
|
|
}
|
|
|
|
|
|
|
|
private sendEquipmentPlayer(mpClient:MPClient, equipmentId:number, itemStack:ItemStack | null) {
|
|
|
|
mpClient.send(new PacketEntityEquipment(this.entityId, equipmentId, itemStack == null ? -1 : itemStack.itemID, itemStack == null ? 0 : itemStack.damage).writeData());
|
|
|
|
}
|
|
|
|
|
|
|
|
// For login.
|
|
|
|
public sendPlayerEquipment(playerToSendTo:Player) {
|
|
|
|
const mpClient = playerToSendTo.mpClient;
|
|
|
|
if (mpClient == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let slotId = 0; slotId < 5; slotId++) {
|
|
|
|
const itemStack = this.getEquipmentForVirtualSlot(slotId);
|
|
|
|
const trackedEquipment = this.trackedEquipment[slotId];
|
|
|
|
|
|
|
|
if ((itemStack == null || trackedEquipment == null) || !itemStack.compare(trackedEquipment)) {
|
|
|
|
this.trackedEquipment[slotId] = itemStack;
|
|
|
|
this.sendEquipmentPlayer(mpClient, slotId, itemStack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-11 07:47:56 +01:00
|
|
|
public onTick() {
|
|
|
|
this.updatePlayerChunks();
|
2023-04-09 04:19:10 +01:00
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
// Calculate player motion since we don't have it serverside.
|
2023-11-05 00:55:23 +00:00
|
|
|
this.motion.set(this.position.x - this.lastPosition.x, this.position.y - this.lastPosition.y, this.position.z - this.lastPosition.z);
|
2023-11-09 16:30:40 +00:00
|
|
|
if (!this.motion.isZero) {
|
|
|
|
this.entityAABB.move(this.position);
|
|
|
|
}
|
2023-11-05 00:55:23 +00:00
|
|
|
|
|
|
|
super.onTick();
|
|
|
|
|
2023-11-05 10:58:35 +00:00
|
|
|
for (let slotId = 0; slotId < 5; slotId++) {
|
|
|
|
const itemStack = this.getEquipmentForVirtualSlot(slotId);
|
|
|
|
const trackedEquipment = this.trackedEquipment[slotId];
|
|
|
|
|
|
|
|
if ((itemStack == null || trackedEquipment == null) || !itemStack.compare(trackedEquipment)) {
|
|
|
|
this.trackedEquipment[slotId] = itemStack;
|
|
|
|
this.sendEquipment(slotId, itemStack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 23:52:13 +01:00
|
|
|
if (this.health != this.lastHealth) {
|
2023-12-18 21:49:23 +00:00
|
|
|
if (this.health <= 0 && this.isDead) {
|
|
|
|
this.dropAllItems();
|
|
|
|
}
|
|
|
|
|
2023-04-13 23:52:13 +01:00
|
|
|
this.lastHealth = this.health;
|
|
|
|
this.mpClient?.send(new PacketUpdateHealth(this.health).writeData());
|
|
|
|
}
|
2023-04-09 04:19:10 +01:00
|
|
|
}
|
2023-04-08 20:52:47 +01:00
|
|
|
}
|