Implement EntityLiving -> EntityLiving damage & block break sfx / effects

This commit is contained in:
Holly Stubbs 2023-12-24 17:47:20 +00:00
parent 4afb4a0633
commit 328ddca458
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
13 changed files with 129 additions and 27 deletions

View file

@ -23,6 +23,11 @@ import { ItemStack } from "./inventories/ItemStack";
import { PacketWindowItems } from "./packets/WindowItems"; import { PacketWindowItems } from "./packets/WindowItems";
import { Block } from "./blocks/Block"; import { Block } from "./blocks/Block";
import { EntityItem } from "./entities/EntityItem"; import { EntityItem } from "./entities/EntityItem";
import AABB from "./AABB";
import { PacketSoundEffect } from "./packets/SoundEffect";
import { SoundEffects } from "./enums/SoundEffects";
import { PacketUseEntity } from "./packets/UseEntity";
import { EntityLiving } from "./entities/EntityLiving";
export class MPClient { export class MPClient {
private readonly mcServer:MinecraftServer; private readonly mcServer:MinecraftServer;
@ -72,6 +77,7 @@ export class MPClient {
switch (packetId) { switch (packetId) {
case Packet.Chat: this.handleChat(new PacketChat().readData(reader)); break; case Packet.Chat: this.handleChat(new PacketChat().readData(reader)); break;
case Packet.UseEntity: this.handleUseEntity(new PacketUseEntity().readData(reader)); break;
case Packet.Respawn: this.handlePacketRespawn(new PacketRespawn().readData(reader)); break; case Packet.Respawn: this.handlePacketRespawn(new PacketRespawn().readData(reader)); break;
case Packet.Player: this.handlePacketPlayer(new PacketPlayer().readData(reader)); break; case Packet.Player: this.handlePacketPlayer(new PacketPlayer().readData(reader)); break;
case Packet.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break; case Packet.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break;
@ -92,6 +98,16 @@ export class MPClient {
} }
} }
private handleUseEntity(packet:PacketUseEntity) {
const attacker = this.entity.world.entites.get(packet.userId);
const target = this.entity.world.entites.get(packet.targetId);
if (attacker && target && target instanceof EntityLiving) {
if (packet.leftClick) {
target.damageFrom(2, attacker);
}
}
}
private handleChat(packet:PacketChat) { private handleChat(packet:PacketChat) {
const message = packet.message.split(" "); const message = packet.message.split(" ");
if (message[0].startsWith("/")) { if (message[0].startsWith("/")) {
@ -180,11 +196,16 @@ export class MPClient {
this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0); 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.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
//this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData()); //this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
const itemId = Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId); const blockBehaviour = Block.blockBehaviours[brokenBlockId];
const itemId = blockBehaviour.droppedItem(brokenBlockId);
if (itemId !== -1) { if (itemId !== -1) {
const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemId, 1, metadata)); const itemCount = blockBehaviour.droppedCount(brokenBlockId);
itemEntity.position.set(x + 0.5, y + 0.5, z + 0.5); for (let i = 0; i < ((itemCount - 1) >> 6) + 1; i++) {
this.entity.world.addEntity(itemEntity); const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemId, Math.min(itemCount - 64 * i, 64), metadata));
itemEntity.position.set(x + 0.5, y + 0.5, z + 0.5);
this.entity.world.addEntity(itemEntity);
this.entity.sendToNearby(new PacketSoundEffect(SoundEffects.BLOCK_BREAK, x, y, z, brokenBlockId).writeData());
}
} }
} }
@ -220,6 +241,10 @@ export class MPClient {
this.diggingAt.set(packet.x, packet.y, packet.z); this.diggingAt.set(packet.x, packet.y, packet.z);
this.mapCoordsFromFace(this.diggingAt, packet.face); this.mapCoordsFromFace(this.diggingAt, packet.face);
if (this.entity.entityAABB.intersects(AABB.getAABB(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, this.diggingAt.x + 1, this.diggingAt.y + 1, this.diggingAt.z + 1))) {
return;
}
const itemStack = this.getHeldItemStack(); const itemStack = this.getHeldItemStack();
if (itemStack == null || itemStack.size == 0) { if (itemStack == null || itemStack.size == 0) {
return; return;

View file

@ -1,6 +1,7 @@
import AABB from "../AABB"; import AABB from "../AABB";
import { World } from "../World"; import { World } from "../World";
import { BlockBehaviour } from "./BlockBehaviour"; import { BlockBehaviour } from "./BlockBehaviour";
import { BlockBehaviourClay } from "./BlockBehaviourClay";
import { BlockBehaviourFlower } from "./BlockBehaviourFlower"; import { BlockBehaviourFlower } from "./BlockBehaviourFlower";
import { BlockBehaviourGrass } from "./BlockBehaviourGrass"; import { BlockBehaviourGrass } from "./BlockBehaviourGrass";
import { BlockBehaviourStone } from "./BlockBehaviourStone"; import { BlockBehaviourStone } from "./BlockBehaviourStone";
@ -15,6 +16,8 @@ abstract class Behaviour {
public static tallGrass = new BlockBehaviourTallGrass(); public static tallGrass = new BlockBehaviourTallGrass();
public static flower = new BlockBehaviourFlower(); public static flower = new BlockBehaviourFlower();
public static clay = new BlockBehaviourClay();
} }
export class Block { export class Block {
@ -98,6 +101,10 @@ export class Block {
this.behaviour.droppedItem(blockId); this.behaviour.droppedItem(blockId);
} }
public droppedCount(blockId:number) {
this.behaviour.droppedCount(blockId);
}
public getHardness() { public getHardness() {
return this.hardness; return this.hardness;
} }
@ -149,7 +156,7 @@ export class Block {
static readonly flowerDandelion = new Block(37).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Dandelion"); static readonly flowerDandelion = new Block(37).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Dandelion");
static readonly flowerRose = new Block(38).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Rose"); static readonly flowerRose = new Block(38).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Rose");
static readonly clay = new Block(82).setHardness(0.6).setBlockName("Clay"); static readonly clay = new Block(82).setHardness(0.6).setBehaviour(Behaviour.clay).setBlockName("Clay");
static readonly netherrack = new Block(87).setHardness(0.4).setBlockName("Netherrack"); static readonly netherrack = new Block(87).setHardness(0.4).setBlockName("Netherrack");
} }

View file

@ -5,5 +5,6 @@ import { IBlockBehaviour } from "./IBlockBehaviour";
export class BlockBehaviour implements IBlockBehaviour { export class BlockBehaviour implements IBlockBehaviour {
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {} public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {}
public droppedItem(blockId:number) { return blockId; } public droppedItem(blockId:number) { return blockId; }
public droppedCount(blockId:number) { return 1; }
public getBoundingBox(x:number, y:number, z:number) { return AABB.getAABB(0 + x, 0 + y, 0 + z, 1 + x, 1 + y, 1 + z); } public getBoundingBox(x:number, y:number, z:number) { return AABB.getAABB(0 + x, 0 + y, 0 + z, 1 + x, 1 + y, 1 + z); }
} }

View file

@ -0,0 +1,12 @@
import { Item } from "../items/Item";
import { BlockBehaviour } from "./BlockBehaviour";
export class BlockBehaviourClay extends BlockBehaviour {
public droppedItem(blockId:number) {
return Item.clay.shiftedItemID;
}
public droppedCount(blockId:number) {
return 4;
}
}

View file

@ -4,5 +4,6 @@ import { World } from "../World";
export interface IBlockBehaviour { 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, droppedItem: (blockId:number) => number,
droppedCount: (blockId:number) => number,
getBoundingBox: (x:number, y:number, z:number) => AABB, getBoundingBox: (x:number, y:number, z:number) => AABB,
} }

View file

@ -114,10 +114,10 @@ export class Entity implements IEntity {
.writeByte(this.health); .writeByte(this.health);
} }
collidesWithPlayer(aabb:AABB) { async collidesWithPlayer(aabb:AABB) {
let collidedWith:Player | undefined; let collidedWith:Player | undefined;
this.world.players.forEach(player => { await this.world.players.forEach(player => {
if (this.entityAABB.intersects(player.entityAABB)) { if (this.entityAABB.intersects(player.entityAABB) && collidedWith == undefined) {
collidedWith = player; collidedWith = player;
} }
}); });
@ -166,6 +166,8 @@ export class Entity implements IEntity {
if (entity === undefined) { if (entity === undefined) {
this.health -= damage; this.health -= damage;
} else {
this.health -= damage;
} }
this.wasHurt = true; this.wasHurt = true;
@ -249,18 +251,21 @@ export class Entity implements IEntity {
moveEntity(motionX:number, motionY:number, motionZ:number) { moveEntity(motionX:number, motionY:number, motionZ:number) {
this.positionBeforeMove.set(this.position); 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.position.add(motionX, motionY, motionZ);
this.entityAABB.move(this.position); this.entityAABB.move(this.position);
const blockId = this.chunk.getBlockId(Math.floor(this.position.x) & 0xf, Math.floor(this.position.y), Math.floor(this.position.z) & 0xf);
const blockUnderEntity = blockId > 0 ? Block.blocks[blockId] : null;
if (blockUnderEntity !== null) { if (blockUnderEntity !== null) {
const blockBoundingBox = blockUnderEntity.getBoundingBox(Math.floor(this.positionBeforeMove.x), Math.floor(this.positionBeforeMove.y), Math.floor(this.positionBeforeMove.z)); const blockBoundingBox = blockUnderEntity.getBoundingBox(Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z));
// TODO: Handle X and Z collisions. // TODO: Handle X and Z collisions.
if (this.entityAABB.intersects(blockBoundingBox)) { if (this.entityAABB.intersects(blockBoundingBox)) {
const inersectionY = this.entityAABB.intersectionY(blockBoundingBox); const intersectionY = this.entityAABB.intersectionY(blockBoundingBox);
this.position.add(0, inersectionY, 0); console.log(intersectionY);
this.position.add(0, intersectionY, 0);
this.motion.y = 0; this.motion.y = 0;
this.onGround = true; this.onGround = true;
} }

View file

@ -24,13 +24,14 @@ export class EntityItem extends Entity {
this.health = 5; this.health = 5;
} }
onTick() { async onTick() {
super.onTick(); super.onTick();
if (this.pickupDelay > 0) { if (this.pickupDelay > 0) {
this.pickupDelay--; this.pickupDelay--;
} else { } else {
let playerCollided; let playerCollided = await this.collidesWithPlayer(this.entityAABB);
if (playerCollided = this.collidesWithPlayer(this.entityAABB)) { if (playerCollided !== undefined) {
console.log(playerCollided.username);
playerCollided.inventory.addItemStack(this.itemStack); playerCollided.inventory.addItemStack(this.itemStack);
playerCollided.itemPickup(this, this.itemStack.size); playerCollided.itemPickup(this, this.itemStack.size);
if (this.itemStack.size <= 0) { if (this.itemStack.size <= 0) {

View file

@ -1,15 +1,8 @@
import { IReader, IWriter } from "bufferstuff"; import { IReader, IWriter } from "bufferstuff";
import { Rotation } from "../Rotation";
import Vec3 from "../Vec3";
import { World } from "../World"; import { World } from "../World";
import { Block } from "../blocks/Block"; import { Block } from "../blocks/Block";
import { EntityStatus } from "../enums/EntityStatus"; import { EntityStatus } from "../enums/EntityStatus";
import { PacketAnimation } from "../packets/Animation";
import { PacketEntityLook } from "../packets/EntityLook";
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove";
import { PacketEntityStatus } from "../packets/EntityStatus"; import { PacketEntityStatus } from "../packets/EntityStatus";
import { PacketEntityTeleport } from "../packets/EntityTeleport";
import { Entity } from "./Entity"; import { Entity } from "./Entity";
import { IEntity } from "./IEntity"; import { IEntity } from "./IEntity";

View file

@ -35,6 +35,8 @@ export enum Packet {
MultiBlockChange = 0x34, MultiBlockChange = 0x34,
BlockChange = 0x035, BlockChange = 0x035,
SoundEffect = 0x3D,
Entity = 0x1E, Entity = 0x1E,
EntityRelativeMove = 0x1F, EntityRelativeMove = 0x1F,
EntityLook = 0x20, EntityLook = 0x20,

View file

@ -0,0 +1,10 @@
export enum SoundEffects {
CLICK2 = 1000,
CLICK1 = 1001,
BOW_FIRE = 1002,
DOOR_TOGGLE = 1003,
EXTINGUISH = 1004,
RECORD_PLAY = 1005,
SMOKE = 2000,
BLOCK_BREAK = 2001
}

View file

@ -51,4 +51,6 @@ export class Item {
static ironPickaxe = new Item(1).setMaxDamage(MaxUses.IRON).setName("Iron Pickaxe"); static ironPickaxe = new Item(1).setMaxDamage(MaxUses.IRON).setName("Iron Pickaxe");
static ironAxe = new Item(2).setMaxDamage(MaxUses.IRON).setName("Iron Axe"); static ironAxe = new Item(2).setMaxDamage(MaxUses.IRON).setName("Iron Axe");
static ironSword = new Item(11).setMaxDamage(MaxUses.IRON).setName("Iron Sword"); static ironSword = new Item(11).setMaxDamage(MaxUses.IRON).setName("Iron Sword");
static clay = new Item(81).setName("Clay");
} }

View file

@ -0,0 +1,43 @@
import { createWriter, IReader, Endian } from "bufferstuff";
import { IPacket } from "./IPacket";
import { Packet } from "../enums/Packet";
import { SoundEffects } from "../enums/SoundEffects";
export class PacketSoundEffect implements IPacket {
public packetId = Packet.SoundEffect;
public effectId:SoundEffects;
public x:number;
public y:number;
public z:number;
public soundData:number;
public constructor(effectId:number, x:number, y:number, z:number, soundData:number) {
if (typeof(effectId) === "number" && typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(soundData) === "number") {
this.effectId = effectId;
this.x = x;
this.y = y;
this.z = z;
this.soundData = soundData;
} else {
this.effectId = Number.MIN_VALUE;
this.x = Number.MIN_VALUE;
this.y = Number.MIN_VALUE;
this.z = Number.MIN_VALUE;
this.soundData = Number.MIN_VALUE;
}
}
public readData(reader:IReader) {
this.effectId = reader.readInt();
this.x = reader.readInt();
this.y = reader.readByte();
this.z = reader.readInt();
this.soundData = reader.readInt();
return this;
}
public writeData() {
return createWriter(Endian.BE, 18).writeUByte(this.packetId).writeInt(this.effectId).writeInt(this.x).writeByte(this.y).writeInt(this.z).writeInt(this.soundData).toBuffer();
}
}

View file

@ -8,10 +8,10 @@ export class PacketUseEntity implements IPacket {
public targetId:number; public targetId:number;
public leftClick:boolean; public leftClick:boolean;
public constructor(userId:number, targetId:number, leftClick:boolean) { public constructor(userId?:number, targetId?:number, leftClick?:boolean) {
this.userId = userId; this.userId = userId ?? Number.MIN_VALUE;
this.targetId = targetId; this.targetId = targetId ?? Number.MIN_VALUE;
this.leftClick = leftClick; this.leftClick = leftClick ?? false;
} }
public readData(reader:IReader) { public readData(reader:IReader) {