Implement fall damage & fix damage in general

This commit is contained in:
Holly Stubbs 2023-11-05 00:55:23 +00:00
parent a3c865bd9f
commit 522916ecaa
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
13 changed files with 148 additions and 22 deletions

View file

@ -28,6 +28,7 @@ export class MPClient {
private readonly socket:Socket; private readonly socket:Socket;
public entity:Player; public entity:Player;
private inventory:Inventory; private inventory:Inventory;
private dimension:number;
private holdingIndex:number = 36; // First hotbar slot. private holdingIndex:number = 36; // First hotbar slot.
private diggingAt:Vec3; private diggingAt:Vec3;
@ -37,6 +38,7 @@ export class MPClient {
this.socket = socket; this.socket = socket;
this.entity = entity; this.entity = entity;
this.inventory = entity.inventory; this.inventory = entity.inventory;
this.dimension = 0;
this.diggingAt = new Vec3(); this.diggingAt = new Vec3();
} }
@ -69,6 +71,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.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;
case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break; case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break;
@ -122,24 +125,35 @@ export class MPClient {
this.mcServer.sendToAllClients(packet.writeData()); this.mcServer.sendToAllClients(packet.writeData());
} }
private handlePacketRespawn(packet:PacketRespawn) {
if (this.entity.health > 0) {
return;
}
}
private handlePacketPlayer(packet:PacketPlayer) { private handlePacketPlayer(packet:PacketPlayer) {
this.entity.onGround = packet.onGround; this.entity.onGround = packet.onGround;
} }
private handlePacketPlayerPosition(packet:PacketPlayerPosition) { private handlePacketPlayerPosition(packet:PacketPlayerPosition) {
this.entity.onGround = packet.onGround;
this.entity.position.set(packet.x, packet.y, packet.z); this.entity.position.set(packet.x, packet.y, packet.z);
} }
private handlePacketPlayerLook(packet:PacketPlayerLook) { private handlePacketPlayerLook(packet:PacketPlayerLook) {
this.entity.onGround = packet.onGround;
this.entity.rotation.set(packet.yaw, packet.pitch); this.entity.rotation.set(packet.yaw, packet.pitch);
} }
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) { private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
this.entity.onGround = packet.onGround;
this.entity.position.set(packet.x, packet.y, packet.z); this.entity.position.set(packet.x, packet.y, packet.z);
this.entity.rotation.set(packet.yaw, packet.pitch); this.entity.rotation.set(packet.yaw, packet.pitch);
} }
private handlePacketPlayerDigging(packet:PacketPlayerDigging) { private handlePacketPlayerDigging(packet:PacketPlayerDigging) {
console.log(packet.status);
// Special drop item case // Special drop item case
if (packet.status === 4) { if (packet.status === 4) {
// TODO: Handle dropping items // TODO: Handle dropping items
@ -156,7 +170,6 @@ export class MPClient {
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) { if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) {
const metadata = this.entity.world.getBlockMetadata(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z); const metadata = this.entity.world.getBlockMetadata(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
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);
console.log("Metadata: ", metadata);
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());
} }

View file

@ -2,12 +2,15 @@ import { World } from "../World";
import { BlockBehaviour } from "./BlockBehaviour"; import { BlockBehaviour } from "./BlockBehaviour";
import { BlockBehaviourFlower } from "./BlockBehaviourFlower"; import { BlockBehaviourFlower } from "./BlockBehaviourFlower";
import { BlockBehaviourGrass } from "./BlockBehaviourGrass"; import { BlockBehaviourGrass } from "./BlockBehaviourGrass";
import { BlockBehaviourStone } from "./BlockBehaviourStone";
import { IBlockBehaviour } from "./IBlockBehaviour"; import { IBlockBehaviour } from "./IBlockBehaviour";
abstract class Behaviour { abstract class Behaviour {
public static base = new BlockBehaviour(); public static base = new BlockBehaviour();
public static stone = new BlockBehaviourStone();
public static grass = new BlockBehaviourGrass(); public static grass = new BlockBehaviourGrass();
public static flower = new BlockBehaviourFlower(); public static flower = new BlockBehaviourFlower();
} }
@ -75,9 +78,10 @@ export class Block {
} }
// Define statics here // Define statics here
static readonly stone = new Block(1).setBlockName("Stone"); static readonly stone = new Block(1).setBehaviour(Behaviour.stone).setBlockName("Stone");
static readonly grass = new Block(2).setBehaviour(Behaviour.grass).setBlockName("Grass"); static readonly grass = new Block(2).setBehaviour(Behaviour.grass).setBlockName("Grass");
static readonly dirt = new Block(3).setBlockName("Dirt"); static readonly dirt = new Block(3).setBlockName("Dirt");
static readonly cobblestone = new Block(4).setBlockName("Cobblestone");
static readonly bedrock = new Block(7).setBlockName("Bedrock"); static readonly bedrock = new Block(7).setBlockName("Bedrock");

View file

@ -0,0 +1,8 @@
import { Block } from "./Block";
import { BlockBehaviour } from "./BlockBehaviour";
export class BlockBehaviourStone extends BlockBehaviour {
public droppedItem(blockId:number) {
return Block.cobblestone.blockId;
}
}

View file

@ -22,6 +22,7 @@ export class Entity implements IEntity {
public lastPosition:Vec3; public lastPosition:Vec3;
public absPosition:Vec3; public absPosition:Vec3;
public lastAbsPosition:Vec3; public lastAbsPosition:Vec3;
public motion:Vec3;
public rotation:Rotation; public rotation:Rotation;
public lastRotation:Rotation; public lastRotation:Rotation;
@ -35,6 +36,9 @@ export class Entity implements IEntity {
public isDead:boolean; public isDead:boolean;
public fire:number; public fire:number;
public fallDistance:number;
public onGround:boolean;
public chunk:Chunk; public chunk:Chunk;
@ -47,7 +51,8 @@ export class Entity implements IEntity {
public constructor(world:World) { public constructor(world:World) {
this.entityId = Entity.nextEntityId++; this.entityId = Entity.nextEntityId++;
this.fire = 0; this.fire = this.fallDistance = 0;
this.onGround = false;
this.world = world; this.world = world;
@ -55,6 +60,7 @@ export class Entity implements IEntity {
this.lastPosition = new Vec3(); this.lastPosition = new Vec3();
this.absPosition = new Vec3(); this.absPosition = new Vec3();
this.lastAbsPosition = new Vec3(); this.lastAbsPosition = new Vec3();
this.motion = new Vec3();
this.rotation = new Rotation(); this.rotation = new Rotation();
this.lastRotation = new Rotation(); this.lastRotation = new Rotation();
@ -77,7 +83,7 @@ export class Entity implements IEntity {
} }
sendToAllNearby(buffer:Buffer) { sendToAllNearby(buffer:Buffer) {
this.world.sendToNearbyClients(this, buffer); this.world.sendToNearbyAllNearbyClients(this, buffer);
} }
updateMetadata() { updateMetadata() {
@ -169,9 +175,26 @@ export class Entity implements IEntity {
} }
} }
fall(distance:number) {
// TODO: Entity falling mount transfer
}
updateFalling(distance:number) {
if (this.onGround) {
if (this.fallDistance > 0)
{
this.fall(this.fallDistance);
this.fallDistance = 0;
}
} else if (distance < 0) {
this.fallDistance -= distance;
}
}
onTick() { onTick() {
this.updateMetadata(); this.updateMetadata();
this.updateEntityChunk(); this.updateEntityChunk();
this.updateFalling(this.motion.y);
if (this.fire > 0) { if (this.fire > 0) {
if (this.fire % 20 === 0) { if (this.fire % 20 === 0) {

View file

@ -2,6 +2,7 @@ import { Rotation } from "../Rotation";
import { Vec3 } from "../Vec3"; 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 { PacketAnimation } from "../packets/Animation"; import { PacketAnimation } from "../packets/Animation";
import { PacketEntityLook } from "../packets/EntityLook"; import { PacketEntityLook } from "../packets/EntityLook";
import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove"; import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove";
@ -12,17 +13,18 @@ import { Entity } from "./Entity";
import { IEntity } from "./IEntity"; import { IEntity } from "./IEntity";
export class EntityLiving extends Entity { export class EntityLiving extends Entity {
public onGround:boolean;
public fallDistance:number; public fallDistance:number;
public timeInWater:number; public timeInWater:number;
public headHeight:number; public headHeight:number;
public lastHealth:number;
public constructor(world:World) { public constructor(world:World) {
super(world); super(world);
this.fallDistance = this.timeInWater = 0; this.fallDistance = this.timeInWater = 0;
this.onGround = true;
this.headHeight = 1.62; this.headHeight = 1.62;
this.lastHealth = this.health;
} }
damageFrom(damage:number, entity?:IEntity) { damageFrom(damage:number, entity?:IEntity) {
@ -31,14 +33,25 @@ export class EntityLiving extends Entity {
} }
super.damageFrom(damage, entity); super.damageFrom(damage, entity);
// Send Damage Animation packet // Send Damage Animation packet or death packet
this.sendToAllNearby(new PacketEntityStatus(this.entityId, 2).writeData()); if (this.health === 0) {
this.sendToAllNearby(new PacketEntityStatus(this.entityId, EntityStatus.Dead).writeData());
} else {
this.sendToAllNearby(new PacketEntityStatus(this.entityId, EntityStatus.Hurt).writeData());
}
} }
isInWater() { isInWater() {
return this.world.getChunkBlockId(this.chunk, this.position.x, this.position.y + this.headHeight, this.position.z) === Block.waterStill.blockId; return this.world.getChunkBlockId(this.chunk, this.position.x, this.position.y + this.headHeight, this.position.z) === Block.waterStill.blockId;
} }
fall(distance:number) {
const adjustedFallDistance = Math.ceil(distance - 3);
if (adjustedFallDistance > 0) {
this.damageFrom(adjustedFallDistance);
}
}
onTick() { onTick() {
super.onTick(); super.onTick();

View file

@ -12,7 +12,7 @@ import { Block } from "../blocks/Block";
import PlayerInventory from "../inventories/PlayerInventory"; import PlayerInventory from "../inventories/PlayerInventory";
import { Item } from "../items/Item"; import { Item } from "../items/Item";
const CHUNK_LOAD_RANGE = 5; const CHUNK_LOAD_RANGE = 15;
export class Player extends EntityLiving { export class Player extends EntityLiving {
public username:string; public username:string;
@ -23,8 +23,6 @@ export class Player extends EntityLiving {
public mpClient?:MPClient; public mpClient?:MPClient;
public inventory:PlayerInventory; public inventory:PlayerInventory;
private lastHealth:number;
public constructor(server:MinecraftServer, world:World, username:string) { public constructor(server:MinecraftServer, world:World, username:string) {
super(world); super(world);
this.server = server; this.server = server;
@ -42,8 +40,6 @@ export class Player extends EntityLiving {
this.username = username; this.username = username;
this.position.set(8, 64, 8); this.position.set(8, 64, 8);
this.lastHealth = this.health;
} }
public forceUpdatePlayerChunks() { public forceUpdatePlayerChunks() {
@ -97,11 +93,14 @@ export class Player extends EntityLiving {
public onTick() { public onTick() {
this.updatePlayerChunks(); this.updatePlayerChunks();
// Calculate player motion
this.motion.set(this.position.x - this.lastPosition.x, this.position.y - this.lastPosition.y, this.position.z - this.lastPosition.z);
super.onTick();
if (this.health != this.lastHealth) { if (this.health != this.lastHealth) {
this.lastHealth = this.health; this.lastHealth = this.health;
this.mpClient?.send(new PacketUpdateHealth(this.health).writeData()); this.mpClient?.send(new PacketUpdateHealth(this.health).writeData());
} }
super.onTick();
} }
} }

View file

@ -0,0 +1,8 @@
export enum EntityStatus {
Unknown0,
Unknown1,
Hurt,
Dead,
Unknown4,
Unknown5
}

7
server/enums/MaxUses.ts Normal file
View file

@ -0,0 +1,7 @@
export enum MaxUses {
GOLD = 32,
WOOD = 59,
STONE = 131,
IRON = 250,
DIAMOND = 1561
}

View file

@ -1,13 +1,18 @@
import { Block } from "../blocks/Block"; import { Block } from "../blocks/Block";
import { IEntity } from "../entities/IEntity";
import { Player } from "../entities/Player";
import { Item } from "../items/Item"; import { Item } from "../items/Item";
export class ItemStack { export class ItemStack {
public readonly itemID:number; public readonly itemID:number;
public readonly isBlock:boolean; public readonly isBlock:boolean;
public readonly maxSize:number;
public size:number; public size:number;
public damage:number; public damage:number;
private readonly maxSize:number;
private readonly maxDamage:number;
private readonly canBeDamaged:boolean;
public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) { public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) {
if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) { if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId; this.itemID = blockOrItemOrItemID.blockId;
@ -50,7 +55,9 @@ export class ItemStack {
} }
this.isBlock = this.itemID < 256; this.isBlock = this.itemID < 256;
this.maxSize = !this.isBlock ? Item.getByShiftedItemId(this.itemID).maxStackSize : 64; this.maxSize = this.isBlock ? 64 : Item.getByShiftedItemId(this.itemID).maxStackSize;
this.maxDamage = this.isBlock ? 0 : Item.getByShiftedItemId(this.itemID).maxDamage;
this.canBeDamaged = this.maxDamage > 0;
} }
public insert(itemStack:ItemStack) { public insert(itemStack:ItemStack) {
@ -69,7 +76,22 @@ export class ItemStack {
this.size += remainingSpace; this.size += remainingSpace;
itemStack.size -= remainingSpace; itemStack.size -= remainingSpace;
} }
} }
public damageItem(damageAmount:number, entity:IEntity) {
if (!this.canBeDamaged) {
return;
}
this.damage += damageAmount;
if (this.damage > this.maxDamage) {
this.size--;
if (this.size < 0) {
this.size = 0;
}
this.damage = 0;
}
}
public get spaceAvaliable() { public get spaceAvaliable() {
// Stack size check for Item(s) and Block(s). // Stack size check for Item(s) and Block(s).

View file

@ -1,3 +1,6 @@
import { EntityLiving } from "../entities/EntityLiving";
import { ItemStack } from "../inventories/ItemStack";
export interface IItemBehaviour { export interface IItemBehaviour {
} }

View file

@ -1,12 +1,16 @@
import { MaxUses } from "../enums/MaxUses";
export class Item { export class Item {
public static items:Array<Item> = new Array<Item>(); public static items:Array<Item> = new Array<Item>();
public maxStackSize:number; public maxStackSize:number;
public maxDamage:number;
public shiftedItemID:number; public shiftedItemID:number;
public name:string; public name:string;
public constructor(itemID:number) { public constructor(itemID:number) {
this.shiftedItemID = 256 + itemID; this.shiftedItemID = 256 + itemID;
this.maxDamage = 0;
this.maxStackSize = 64; this.maxStackSize = 64;
this.name = "UNNAMED"; this.name = "UNNAMED";
@ -22,6 +26,16 @@ export class Item {
return this; return this;
} }
public getMaxDamage() {
return this.maxDamage;
}
public setMaxDamage(value:number) {
this.maxDamage = value;
return this;
}
public setName(name:string) { public setName(name:string) {
this.name = name; this.name = name;
@ -33,8 +47,8 @@ export class Item {
} }
// Define statics here // Define statics here
static ironShovel = new Item(0).setName("Iron Shovel"); static ironShovel = new Item(0).setMaxDamage(MaxUses.IRON).setName("Iron Shovel");
static ironPickaxe = new Item(1).setName("Iron Pickaxe"); static ironPickaxe = new Item(1).setMaxDamage(MaxUses.IRON).setName("Iron Pickaxe");
static ironAxe = new Item(2).setName("Iron Axe"); static ironAxe = new Item(2).setMaxDamage(MaxUses.IRON).setName("Iron Axe");
static ironSword = new Item(11).setName("Iron Sword"); static ironSword = new Item(11).setMaxDamage(MaxUses.IRON).setName("Iron Sword");
} }

View file

@ -0,0 +1,7 @@
import { EntityLiving } from "../entities/EntityLiving";
import { ItemStack } from "../inventories/ItemStack";
import { IItemBehaviour } from "./IItemBehaviour";
export default class ItemBehaviour implements IItemBehaviour {
}

View file

@ -0,0 +1,5 @@
import ItemBehaviour from "./ItemBehaviour";
export default class ItemBehaviourTool extends ItemBehaviour {
}