custom block with custom hardness

This commit is contained in:
TheMode 2019-08-18 23:52:11 +02:00
parent 57def5aaac
commit 0d40be1552
14 changed files with 253 additions and 75 deletions

View File

@ -7,7 +7,9 @@ import fr.adamaq01.ozao.net.server.ServerHandler;
import fr.adamaq01.ozao.net.server.backend.tcp.TCPServer;
import fr.themode.minestom.entity.EntityManager;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.BlockManager;
import fr.themode.minestom.instance.InstanceManager;
import fr.themode.minestom.instance.demo.StoneBlock;
import fr.themode.minestom.net.ConnectionManager;
import fr.themode.minestom.net.PacketProcessor;
import fr.themode.minestom.net.packet.server.play.DestroyEntitiesPacket;
@ -26,6 +28,7 @@ public class Main {
// In-Game Manager
private static InstanceManager instanceManager;
private static BlockManager blockManager;
private static EntityManager entityManager;
public static void main(String[] args) {
@ -33,8 +36,11 @@ public class Main {
packetProcessor = new PacketProcessor();
instanceManager = new InstanceManager();
blockManager = new BlockManager();
entityManager = new EntityManager();
blockManager.registerBlock("stone", StoneBlock::new);
server = new TCPServer(new MinecraftProtocol()).addHandler(new ServerHandler() {
@Override
public void onConnect(Server server, Connection connection) {
@ -123,6 +129,10 @@ public class Main {
return instanceManager;
}
public static BlockManager getBlockManager() {
return blockManager;
}
public static EntityManager getEntityManager() {
return entityManager;
}

View File

@ -1,6 +1,7 @@
package fr.themode.minestom.entity;
import fr.themode.minestom.Main;
import fr.themode.minestom.instance.CustomBlock;
import fr.themode.minestom.inventory.Inventory;
import fr.themode.minestom.inventory.PlayerInventory;
import fr.themode.minestom.item.ItemStack;
@ -25,6 +26,7 @@ public class Player extends LivingEntity {
private short heldSlot;
private Inventory openInventory;
private CustomBlock targetCustomBlock;
private Position targetBlockPosition;
private long targetBlockTime;
@ -41,20 +43,17 @@ public class Player extends LivingEntity {
public void update() {
// Target block stage
if (instance != null && targetBlockPosition != null) {
int timeBreak = 750; // In ms
if (instance != null && targetCustomBlock != null) {
int timeBreak = targetCustomBlock.getBreakDelay(this);
int animationCount = 10;
long since = System.currentTimeMillis() - targetBlockTime;
byte stage = (byte) (since / (timeBreak / animationCount));
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
breakAnimationPacket.entityId = getEntityId() + 1;
breakAnimationPacket.blockPosition = targetBlockPosition;
breakAnimationPacket.destroyStage = stage;
sendBlockBreakAnimation(targetBlockPosition, stage);// TODO send to all near players
if (stage > 9) {
instance.setBlock(targetBlockPosition.getX(), targetBlockPosition.getY(), targetBlockPosition.getZ(), (short) 0);
refreshTargetBlock(null);
testParticle(targetBlockPosition.getX() + 0.5f, targetBlockPosition.getY(), targetBlockPosition.getZ() + 0.5f);
resetTargetBlock();
}
playerConnection.sendPacket(breakAnimationPacket); // TODO send to all online players
}
@ -74,6 +73,29 @@ public class Player extends LivingEntity {
playerConnection.sendPacket(new UpdateViewPositionPacket(Math.floorDiv((int) x, 16), Math.floorDiv((int) z, 16)));
}
public void sendBlockBreakAnimation(Position blockPosition, byte destroyStage) {
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
breakAnimationPacket.entityId = getEntityId() + 1;
breakAnimationPacket.blockPosition = blockPosition;
breakAnimationPacket.destroyStage = destroyStage;
playerConnection.sendPacket(breakAnimationPacket);
}
private void testParticle(float x, float y, float z) {
ParticlePacket particlePacket = new ParticlePacket();
particlePacket.particleId = 3; // Block particle
particlePacket.longDistance = false;
particlePacket.x = x;
particlePacket.y = y;
particlePacket.z = z;
particlePacket.offsetX = 0.55f;
particlePacket.offsetY = 0.75f;
particlePacket.offsetZ = 0.55f;
particlePacket.particleData = 0.25f;
particlePacket.particleCount = 100;
playerConnection.sendPacket(particlePacket);
}
public void sendMessage(String message) {
ChatMessagePacket chatMessagePacket = new ChatMessagePacket("{\"text\": \"" + message + "\"}", ChatMessagePacket.Position.CHAT);
playerConnection.sendPacket(chatMessagePacket);
@ -136,6 +158,10 @@ public class Player extends LivingEntity {
return openInventory;
}
public CustomBlock getCustomBlockTarget() {
return targetCustomBlock;
}
public void openInventory(Inventory inventory) {
if (inventory == null)
throw new IllegalArgumentException("Inventory cannot be null, use Player#closeInventory() to close current");
@ -200,11 +226,18 @@ public class Player extends LivingEntity {
this.openInventory = openInventory;
}
public void refreshTargetBlock(Position targetBlockPosition) {
public void refreshTargetBlock(CustomBlock targetCustomBlock, Position targetBlockPosition) {
this.targetCustomBlock = targetCustomBlock;
this.targetBlockPosition = targetBlockPosition;
this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis();
}
public void resetTargetBlock() {
this.targetCustomBlock = null;
this.targetBlockPosition = null;
this.targetBlockTime = 0;
}
public long getLastKeepAlive() {
return lastKeepAlive;
}

View File

@ -1,23 +0,0 @@
package fr.themode.minestom.instance;
public class Block {
private short type;
public Block(short type) {
this.type = type;
}
public short getType() {
return type;
}
public void setType(short type) {
this.type = type;
}
@Override
public String toString() {
return String.format("CustomBlock{type=%s}", type);
}
}

View File

@ -59,7 +59,7 @@ public class BlockBatch {
private short blockId;
public void apply(Chunk chunk) {
chunk.setBlock(x, y, z, blockId);
chunk.setBlock((byte) x, (byte) y, (byte) z, blockId);
}
}

View File

@ -0,0 +1,19 @@
package fr.themode.minestom.instance;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class BlockManager {
private Map<String, CustomBlock> blocks = new HashMap<>();
public void registerBlock(String id, Supplier<CustomBlock> blocks) {
this.blocks.put(id, blocks.get());
}
public CustomBlock getBlock(String id) {
return this.blocks.get(id);
}
}

View File

@ -1,21 +1,24 @@
package fr.themode.minestom.instance;
import fr.themode.minestom.Main;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.Player;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class Chunk {
private static final int CHUNK_SIZE = 16 * 256 * 16;
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();
protected Set<Player> players = new CopyOnWriteArraySet<>();
private int chunkX, chunkZ;
private Biome biome;
private HashMap<Short, Short> blocks = new HashMap<>(); // Index/BlockID
private short[] blocksId = new short[CHUNK_SIZE];
private String[] customBlocks = new String[CHUNK_SIZE];
public Chunk(Biome biome, int chunkX, int chunkZ) {
this.biome = biome;
@ -23,18 +26,27 @@ public class Chunk {
this.chunkZ = chunkZ;
}
protected void setBlock(int x, int y, int z, short blockId) {
short index = (short) (x & 0x000F);
index |= (y << 4) & 0x0FF0;
index |= (z << 12) & 0xF000;
this.blocks.put(index, blockId);
protected void setBlock(byte x, byte y, byte z, short blockId) {
int index = getIndex(x, y, z);
this.blocksId[index] = blockId;
if (blockId == 0) {
this.customBlocks[index] = null;
}
}
public short getBlockId(int x, int y, int z) {
short index = (short) (x & 0x000F);
index |= (y << 4) & 0x0FF0;
index |= (z << 12) & 0xF000;
return this.blocks.getOrDefault(index, (short) 0);
protected void setBlock(byte x, byte y, byte z, String blockId) {
int index = getIndex(x, y, z);
CustomBlock customBlock = Main.getBlockManager().getBlock(blockId);
this.blocksId[index] = customBlock.getType();
this.customBlocks[index] = blockId;
}
public short getBlockId(byte x, byte y, byte z) {
return this.blocksId[getIndex(x, y, z)];
}
public String getCustomBlockId(byte x, byte y, byte z) {
return this.customBlocks[getIndex(x, y, z)];
}
public void addEntity(Entity entity) {
@ -65,8 +77,8 @@ public class Chunk {
}
}
public HashMap<Short, Short> getBlocks() {
return blocks;
public short[] getBlocksId() {
return blocksId;
}
public Biome getBiome() {
@ -88,4 +100,11 @@ public class Chunk {
public Set<Player> getPlayers() {
return Collections.unmodifiableSet(players);
}
private int getIndex(byte x, byte y, byte z) {
short index = (short) (x & 0x000F);
index |= (y << 4) & 0x0FF0;
index |= (z << 12) & 0xF000;
return index & 0xffff;
}
}

View File

@ -0,0 +1,21 @@
package fr.themode.minestom.instance;
import fr.themode.minestom.entity.Player;
public abstract class CustomBlock {
private short type;
public CustomBlock(short type) {
this.type = type;
}
/*
Time in ms
*/
public abstract int getBreakDelay(Player player);
public short getType() {
return type;
}
}

View File

@ -25,20 +25,32 @@ public class Instance {
this.uniqueId = uniqueId;
}
// TODO BlockBatch with pool
public synchronized void setBlock(int x, int y, int z, short blockId) {
final int chunkX = Math.floorDiv(x, 16);
final int chunkZ = Math.floorDiv(z, 16);
Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk == null) {
chunk = createChunk(Biome.VOID, chunkX, chunkZ);
}
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock(x % 16, y, z % 16, blockId);
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
sendChunkUpdate(chunk);
}
}
public synchronized void setBlock(int x, int y, int z, String blockId) {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
sendChunkUpdate(chunk);
}
}
public short getBlockId(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
}
public String getCustomBlockId(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getCustomBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
}
public BlockBatch createBlockBatch() {
return new BlockBatch(this);
}
@ -48,7 +60,7 @@ public class Instance {
if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ)
return chunk;
}
return null;
return createChunk(Biome.VOID, chunkX, chunkZ); // TODO generation API
}
public Chunk getChunkAt(double x, double z) {

View File

@ -0,0 +1,16 @@
package fr.themode.minestom.instance.demo;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.CustomBlock;
public class StoneBlock extends CustomBlock {
public StoneBlock() {
super((short) 1);
}
@Override
public int getBreakDelay(Player player) {
return 750;
}
}

View File

@ -15,7 +15,10 @@ import fr.themode.minestom.net.ConnectionState;
import fr.themode.minestom.net.packet.client.ClientPreplayPacket;
import fr.themode.minestom.net.packet.server.login.JoinGamePacket;
import fr.themode.minestom.net.packet.server.login.LoginSuccessPacket;
import fr.themode.minestom.net.packet.server.play.*;
import fr.themode.minestom.net.packet.server.play.PlayerInfoPacket;
import fr.themode.minestom.net.packet.server.play.PlayerPositionAndLookPacket;
import fr.themode.minestom.net.packet.server.play.SpawnPlayerPacket;
import fr.themode.minestom.net.packet.server.play.SpawnPositionPacket;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.Utils;
import fr.themode.minestom.world.Dimension;
@ -148,15 +151,6 @@ public class LoginStartPacket implements ClientPreplayPacket {
player.openInventory(inv);
inv.setItemStack(1, new ItemStack(1, (byte) 2));
inv.updateItems();
EntityEffectPacket entityEffectPacket = new EntityEffectPacket();
entityEffectPacket.entityId = player.getEntityId();
entityEffectPacket.effectId = 4;
entityEffectPacket.amplifier = -1;
entityEffectPacket.duration = 3600;
entityEffectPacket.flags = 0;
connection.sendPacket(entityEffectPacket);
}
@Override

View File

@ -25,7 +25,7 @@ public class ClientPlayerBlockPlacementPacket implements ClientPlayPacket {
int offsetY = blockFace == ClientPlayerDiggingPacket.BlockFace.BOTTOM ? -1 : blockFace == ClientPlayerDiggingPacket.BlockFace.TOP ? 1 : 0;
int offsetZ = blockFace == ClientPlayerDiggingPacket.BlockFace.NORTH ? -1 : blockFace == ClientPlayerDiggingPacket.BlockFace.SOUTH ? 1 : 0;
instance.setBlock(position.getX() + offsetX, position.getY() + offsetY, position.getZ() + offsetZ, (short) 1);
instance.setBlock(position.getX() + offsetX, position.getY() + offsetY, position.getZ() + offsetZ, "stone");
player.getInventory().refreshSlot(player.getHeldSlot());
// TODO consume block in hand for survival players
/*Random random = new Random();

View File

@ -1,10 +1,14 @@
package fr.themode.minestom.net.packet.client.play;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.Main;
import fr.themode.minestom.entity.GameMode;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.CustomBlock;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.net.packet.client.ClientPlayPacket;
import fr.themode.minestom.net.packet.server.play.EntityEffectPacket;
import fr.themode.minestom.net.packet.server.play.RemoveEntityEffectPacket;
import fr.themode.minestom.utils.Position;
import fr.themode.minestom.utils.Utils;
@ -24,19 +28,56 @@ public class ClientPlayerDiggingPacket implements ClientPlayPacket {
instance.setBlock(position.getX(), position.getY(), position.getZ(), (short) 0);
}
} else if (player.getGameMode() == GameMode.SURVIVAL) {
player.refreshTargetBlock(position);
// TODO survival mining
Instance instance = player.getInstance();
if (instance != null) {
String customBlockId = instance.getCustomBlockId(position.getX(), position.getY(), position.getZ());
if (customBlockId != null) {
CustomBlock customBlock = Main.getBlockManager().getBlock(customBlockId);
player.refreshTargetBlock(customBlock, position);
addEffect(player);
} else {
player.resetTargetBlock();
removeEffect(player);
}
}
}
break;
case CANCELLED_DIGGING:
player.refreshTargetBlock(null);
player.sendBlockBreakAnimation(position, (byte) -1);
player.resetTargetBlock();
removeEffect(player);
break;
case FINISHED_DIGGING:
player.refreshTargetBlock(null);
if (player.getCustomBlockTarget() != null) {
player.resetTargetBlock();
removeEffect(player);
} else {
Instance instance = player.getInstance();
if (instance != null) {
instance.setBlock(position.getX(), position.getY(), position.getZ(), (short) 0);
}
}
break;
}
}
private void addEffect(Player player) {
EntityEffectPacket entityEffectPacket = new EntityEffectPacket();
entityEffectPacket.entityId = player.getEntityId();
entityEffectPacket.effectId = 4;
entityEffectPacket.amplifier = -1;
entityEffectPacket.duration = 0;
entityEffectPacket.flags = 0;
player.getPlayerConnection().sendPacket(entityEffectPacket);
}
private void removeEffect(Player player) {
RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket();
removeEntityEffectPacket.entityId = player.getEntityId();
removeEntityEffectPacket.effectId = 4;
player.getPlayerConnection().sendPacket(removeEntityEffectPacket);
}
@Override
public void read(Buffer buffer) {
this.status = Status.values()[Utils.readVarInt(buffer)];

View File

@ -74,11 +74,11 @@ public class ChunkDataPacket implements ServerPacket {
private Short[] getSection(Chunk chunk, int section) {
Short[] blocks = new Short[16 * 16 * 16];
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (byte y = 0; y < 16; y++) {
for (byte x = 0; x < 16; x++) {
for (byte z = 0; z < 16; z++) {
int index = (((y * 16) + x) * 16) + z;
blocks[index] = chunk.getBlockId(x, y + 16 * section, z);
blocks[index] = chunk.getBlockId(x, (byte) (y + 16 * section), z);
}
}
}

View File

@ -0,0 +1,36 @@
package fr.themode.minestom.net.packet.server.play;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.utils.Utils;
public class ParticlePacket implements ServerPacket {
public int particleId;
public boolean longDistance;
public float x, y, z;
public float offsetX, offsetY, offsetZ;
public float particleData;
public int particleCount;
// TODO data
@Override
public void write(Buffer buffer) {
buffer.putInt(particleId);
buffer.putBoolean(longDistance);
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);
buffer.putFloat(offsetX);
buffer.putFloat(offsetY);
buffer.putFloat(offsetZ);
buffer.putFloat(particleData);
buffer.putInt(particleCount);
Utils.writeVarInt(buffer, 1);
}
@Override
public int getId() {
return 0x23;
}
}