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

View File

@ -1,6 +1,7 @@
package fr.themode.minestom.entity; package fr.themode.minestom.entity;
import fr.themode.minestom.Main; import fr.themode.minestom.Main;
import fr.themode.minestom.instance.CustomBlock;
import fr.themode.minestom.inventory.Inventory; import fr.themode.minestom.inventory.Inventory;
import fr.themode.minestom.inventory.PlayerInventory; import fr.themode.minestom.inventory.PlayerInventory;
import fr.themode.minestom.item.ItemStack; import fr.themode.minestom.item.ItemStack;
@ -25,6 +26,7 @@ public class Player extends LivingEntity {
private short heldSlot; private short heldSlot;
private Inventory openInventory; private Inventory openInventory;
private CustomBlock targetCustomBlock;
private Position targetBlockPosition; private Position targetBlockPosition;
private long targetBlockTime; private long targetBlockTime;
@ -41,20 +43,17 @@ public class Player extends LivingEntity {
public void update() { public void update() {
// Target block stage // Target block stage
if (instance != null && targetBlockPosition != null) { if (instance != null && targetCustomBlock != null) {
int timeBreak = 750; // In ms int timeBreak = targetCustomBlock.getBreakDelay(this);
int animationCount = 10; int animationCount = 10;
long since = System.currentTimeMillis() - targetBlockTime; long since = System.currentTimeMillis() - targetBlockTime;
byte stage = (byte) (since / (timeBreak / animationCount)); byte stage = (byte) (since / (timeBreak / animationCount));
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket(); sendBlockBreakAnimation(targetBlockPosition, stage);// TODO send to all near players
breakAnimationPacket.entityId = getEntityId() + 1;
breakAnimationPacket.blockPosition = targetBlockPosition;
breakAnimationPacket.destroyStage = stage;
if (stage > 9) { if (stage > 9) {
instance.setBlock(targetBlockPosition.getX(), targetBlockPosition.getY(), targetBlockPosition.getZ(), (short) 0); 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))); 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) { public void sendMessage(String message) {
ChatMessagePacket chatMessagePacket = new ChatMessagePacket("{\"text\": \"" + message + "\"}", ChatMessagePacket.Position.CHAT); ChatMessagePacket chatMessagePacket = new ChatMessagePacket("{\"text\": \"" + message + "\"}", ChatMessagePacket.Position.CHAT);
playerConnection.sendPacket(chatMessagePacket); playerConnection.sendPacket(chatMessagePacket);
@ -136,6 +158,10 @@ public class Player extends LivingEntity {
return openInventory; return openInventory;
} }
public CustomBlock getCustomBlockTarget() {
return targetCustomBlock;
}
public void openInventory(Inventory inventory) { public void openInventory(Inventory inventory) {
if (inventory == null) if (inventory == null)
throw new IllegalArgumentException("Inventory cannot be null, use Player#closeInventory() to close current"); 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; this.openInventory = openInventory;
} }
public void refreshTargetBlock(Position targetBlockPosition) { public void refreshTargetBlock(CustomBlock targetCustomBlock, Position targetBlockPosition) {
this.targetCustomBlock = targetCustomBlock;
this.targetBlockPosition = targetBlockPosition; this.targetBlockPosition = targetBlockPosition;
this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis(); this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis();
} }
public void resetTargetBlock() {
this.targetCustomBlock = null;
this.targetBlockPosition = null;
this.targetBlockTime = 0;
}
public long getLastKeepAlive() { public long getLastKeepAlive() {
return lastKeepAlive; 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; private short blockId;
public void apply(Chunk chunk) { 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; package fr.themode.minestom.instance;
import fr.themode.minestom.Main;
import fr.themode.minestom.entity.Entity; import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature; import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.Player;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
public class Chunk { public class Chunk {
private static final int CHUNK_SIZE = 16 * 256 * 16;
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>(); protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();
protected Set<Player> players = new CopyOnWriteArraySet<>(); protected Set<Player> players = new CopyOnWriteArraySet<>();
private int chunkX, chunkZ; private int chunkX, chunkZ;
private Biome biome; 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) { public Chunk(Biome biome, int chunkX, int chunkZ) {
this.biome = biome; this.biome = biome;
@ -23,18 +26,27 @@ public class Chunk {
this.chunkZ = chunkZ; this.chunkZ = chunkZ;
} }
protected void setBlock(int x, int y, int z, short blockId) { protected void setBlock(byte x, byte y, byte z, short blockId) {
short index = (short) (x & 0x000F); int index = getIndex(x, y, z);
index |= (y << 4) & 0x0FF0; this.blocksId[index] = blockId;
index |= (z << 12) & 0xF000; if (blockId == 0) {
this.blocks.put(index, blockId); this.customBlocks[index] = null;
}
} }
public short getBlockId(int x, int y, int z) { protected void setBlock(byte x, byte y, byte z, String blockId) {
short index = (short) (x & 0x000F); int index = getIndex(x, y, z);
index |= (y << 4) & 0x0FF0; CustomBlock customBlock = Main.getBlockManager().getBlock(blockId);
index |= (z << 12) & 0xF000; this.blocksId[index] = customBlock.getType();
return this.blocks.getOrDefault(index, (short) 0); 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) { public void addEntity(Entity entity) {
@ -65,8 +77,8 @@ public class Chunk {
} }
} }
public HashMap<Short, Short> getBlocks() { public short[] getBlocksId() {
return blocks; return blocksId;
} }
public Biome getBiome() { public Biome getBiome() {
@ -88,4 +100,11 @@ public class Chunk {
public Set<Player> getPlayers() { public Set<Player> getPlayers() {
return Collections.unmodifiableSet(players); 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; this.uniqueId = uniqueId;
} }
// TODO BlockBatch with pool
public synchronized void setBlock(int x, int y, int z, short blockId) { public synchronized void setBlock(int x, int y, int z, short blockId) {
final int chunkX = Math.floorDiv(x, 16); Chunk chunk = getChunkAt(x, z);
final int chunkZ = Math.floorDiv(z, 16);
Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk == null) {
chunk = createChunk(Biome.VOID, chunkX, chunkZ);
}
synchronized (chunk) { synchronized (chunk) {
chunk.setBlock(x % 16, y, z % 16, blockId); chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
sendChunkUpdate(chunk); 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() { public BlockBatch createBlockBatch() {
return new BlockBatch(this); return new BlockBatch(this);
} }
@ -48,7 +60,7 @@ public class Instance {
if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ)
return chunk; return chunk;
} }
return null; return createChunk(Biome.VOID, chunkX, chunkZ); // TODO generation API
} }
public Chunk getChunkAt(double x, double z) { 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.client.ClientPreplayPacket;
import fr.themode.minestom.net.packet.server.login.JoinGamePacket; 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.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.net.player.PlayerConnection;
import fr.themode.minestom.utils.Utils; import fr.themode.minestom.utils.Utils;
import fr.themode.minestom.world.Dimension; import fr.themode.minestom.world.Dimension;
@ -148,15 +151,6 @@ public class LoginStartPacket implements ClientPreplayPacket {
player.openInventory(inv); player.openInventory(inv);
inv.setItemStack(1, new ItemStack(1, (byte) 2)); inv.setItemStack(1, new ItemStack(1, (byte) 2));
inv.updateItems(); 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 @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 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; 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()); player.getInventory().refreshSlot(player.getHeldSlot());
// TODO consume block in hand for survival players // TODO consume block in hand for survival players
/*Random random = new Random(); /*Random random = new Random();

View File

@ -1,10 +1,14 @@
package fr.themode.minestom.net.packet.client.play; package fr.themode.minestom.net.packet.client.play;
import fr.adamaq01.ozao.net.Buffer; import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.Main;
import fr.themode.minestom.entity.GameMode; import fr.themode.minestom.entity.GameMode;
import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.CustomBlock;
import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.net.packet.client.ClientPlayPacket; 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.Position;
import fr.themode.minestom.utils.Utils; 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); instance.setBlock(position.getX(), position.getY(), position.getZ(), (short) 0);
} }
} else if (player.getGameMode() == GameMode.SURVIVAL) { } else if (player.getGameMode() == GameMode.SURVIVAL) {
player.refreshTargetBlock(position); Instance instance = player.getInstance();
// TODO survival mining 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; break;
case CANCELLED_DIGGING: case CANCELLED_DIGGING:
player.refreshTargetBlock(null); player.sendBlockBreakAnimation(position, (byte) -1);
player.resetTargetBlock();
removeEffect(player);
break; break;
case FINISHED_DIGGING: 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; 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 @Override
public void read(Buffer buffer) { public void read(Buffer buffer) {
this.status = Status.values()[Utils.readVarInt(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) { private Short[] getSection(Chunk chunk, int section) {
Short[] blocks = new Short[16 * 16 * 16]; Short[] blocks = new Short[16 * 16 * 16];
for (int y = 0; y < 16; y++) { for (byte y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) { for (byte x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) { for (byte z = 0; z < 16; z++) {
int index = (((y * 16) + x) * 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;
}
}