diff --git a/src/main/java/fr/themode/minestom/Viewable.java b/src/main/java/fr/themode/minestom/Viewable.java new file mode 100644 index 000000000..c1e32f7ae --- /dev/null +++ b/src/main/java/fr/themode/minestom/Viewable.java @@ -0,0 +1,19 @@ +package fr.themode.minestom; + +import fr.themode.minestom.entity.Player; + +import java.util.Set; + +public interface Viewable { + + void addViewer(Player player); + + void removeViewer(Player player); + + Set getViewers(); + + default boolean isViewer(Player player) { + return getViewers().contains(player); + } + +} diff --git a/src/main/java/fr/themode/minestom/bossbar/BarColor.java b/src/main/java/fr/themode/minestom/bossbar/BarColor.java new file mode 100644 index 000000000..786a9e337 --- /dev/null +++ b/src/main/java/fr/themode/minestom/bossbar/BarColor.java @@ -0,0 +1,13 @@ +package fr.themode.minestom.bossbar; + +public enum BarColor { + + PINK, + BLUE, + RED, + GREEN, + YELLOW, + PURPLE, + WHITE; + +} diff --git a/src/main/java/fr/themode/minestom/bossbar/BarDivision.java b/src/main/java/fr/themode/minestom/bossbar/BarDivision.java new file mode 100644 index 000000000..34fa8c3bd --- /dev/null +++ b/src/main/java/fr/themode/minestom/bossbar/BarDivision.java @@ -0,0 +1,11 @@ +package fr.themode.minestom.bossbar; + +public enum BarDivision { + + SOLID, + SEGMENT_6, + SEGMENT_10, + SEGMENT_12, + SEGMENT_20; + +} diff --git a/src/main/java/fr/themode/minestom/bossbar/BossBar.java b/src/main/java/fr/themode/minestom/bossbar/BossBar.java new file mode 100644 index 000000000..41f6f3644 --- /dev/null +++ b/src/main/java/fr/themode/minestom/bossbar/BossBar.java @@ -0,0 +1,138 @@ +package fr.themode.minestom.bossbar; + +import fr.themode.minestom.Viewable; +import fr.themode.minestom.chat.Chat; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.server.play.BossBarPacket; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; + +public class BossBar implements Viewable { + + private UUID uuid = UUID.randomUUID(); + private Set viewers = new CopyOnWriteArraySet<>(); + + private String title; + private float progress; + private BarColor color; + private BarDivision division; + private byte flags; + + public BossBar(String title, BarColor color, BarDivision division) { + this.title = Chat.rawText(title); + this.color = color; + this.division = division; + } + + @Override + public void addViewer(Player player) { + this.viewers.add(player); + player.refreshAddBossbar(this); + addToPlayer(player); + } + + @Override + public void removeViewer(Player player) { + this.viewers.remove(player); + player.refreshRemoveBossbar(this); + removeToPlayer(player); + } + + @Override + public Set getViewers() { + return Collections.unmodifiableSet(viewers); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = Chat.rawText(title); + } + + public float getProgress() { + return progress; + } + + public void setProgress(float progress) { + this.progress = progress; + updateProgress(); + } + + public BarColor getColor() { + return color; + } + + public void setColor(BarColor color) { + this.color = color; + updateStyle(); + } + + public BarDivision getDivision() { + return division; + } + + public void setDivision(BarDivision division) { + this.division = division; + updateStyle(); + } + + public void remove() { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.REMOVE; + sendPacket(bossBarPacket); + // TODO remove bar from player class + } + + private void addToPlayer(Player player) { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.ADD; + bossBarPacket.title = title; + bossBarPacket.health = progress; + bossBarPacket.color = color; + bossBarPacket.division = division; + bossBarPacket.flags = flags; + player.getPlayerConnection().sendPacket(bossBarPacket); + } + + private void removeToPlayer(Player player) { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.REMOVE; + player.getPlayerConnection().sendPacket(bossBarPacket); + } + + private void updateTitle() { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.UPDATE_TITLE; + bossBarPacket.title = title; + sendPacket(bossBarPacket); + } + + private void updateProgress() { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.UPDATE_HEALTH; + bossBarPacket.health = progress; + sendPacket(bossBarPacket); + } + + private void updateStyle() { + BossBarPacket bossBarPacket = new BossBarPacket(); + bossBarPacket.uuid = uuid; + bossBarPacket.action = BossBarPacket.Action.UPDATE_STYLE; + bossBarPacket.color = color; + sendPacket(bossBarPacket); + } + + private void sendPacket(BossBarPacket bossBarPacket) { + getViewers().forEach(player -> player.getPlayerConnection().sendPacket(bossBarPacket)); + } +} diff --git a/src/main/java/fr/themode/minestom/chat/Chat.java b/src/main/java/fr/themode/minestom/chat/Chat.java new file mode 100644 index 000000000..9c622ed06 --- /dev/null +++ b/src/main/java/fr/themode/minestom/chat/Chat.java @@ -0,0 +1,8 @@ +package fr.themode.minestom.chat; + +public class Chat { + + public static String rawText(String text) { + return "{\"text\": \"" + text + "\"}"; + } +} diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java index 7bc3af2b7..d7c2b11d6 100644 --- a/src/main/java/fr/themode/minestom/entity/Entity.java +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -1,36 +1,81 @@ package fr.themode.minestom.entity; +import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Instance; +import fr.themode.minestom.utils.Utils; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; -public class Entity { +public abstract class Entity { private static AtomicInteger lastEntityId = new AtomicInteger(); + // Metadata + protected static final byte METADATA_BYTE = 0; + protected static final byte METADATA_VARINT = 1; + protected static final byte METADATA_FLOAT = 2; + protected static final byte METADATA_STRING = 3; + protected static final byte METADATA_CHAT = 4; + protected static final byte METADATA_OPTCHAT = 5; + protected static final byte METADATA_SLOT = 6; + protected static final byte METADATA_BOOLEAN = 7; + protected Instance instance; protected double lastX, lastY, lastZ; protected double x, y, z; + protected float yaw, pitch; private int id; + // Metadata + protected boolean onFire; protected UUID uuid; private boolean isActive; // False if entity has only been instanced without being added somewhere + protected boolean crouched; private boolean shouldRemove; + protected boolean UNUSED_METADATA; + protected boolean sprinting; + protected boolean swimming; + protected boolean invisible; + protected boolean glowing; + protected boolean usingElytra; + protected int air = 300; + protected String customName = ""; + protected boolean customNameVisible; + protected boolean silent; + protected boolean noGravity; + protected Pose pose = Pose.STANDING; + private int entityType; + private long lastUpdate; - public Entity() { + public Entity(int entityType) { this.id = generateId(); + this.entityType = entityType; this.uuid = UUID.randomUUID(); } + private static int generateId() { return lastEntityId.incrementAndGet(); } + public abstract void update(); + + public void tick() { + if (shouldUpdate()) { + update(); + this.lastUpdate = System.currentTimeMillis(); + } + } + public int getEntityId() { return id; } + public int getEntityType() { + return entityType; + } + public UUID getUuid() { return uuid; } @@ -56,6 +101,10 @@ public class Entity { instance.addEntity(this); } + public float getDistance(Entity entity) { + return (float) Math.sqrt(Math.pow(entity.getX() - getX(), 2) + Math.pow(entity.getY() - getY(), 2) + Math.pow(entity.getZ() - getZ(), 2)); + } + public void refreshPosition(double x, double y, double z) { this.lastX = this.x; this.lastY = this.y; @@ -91,10 +140,101 @@ public class Entity { return z; } + public float getYaw() { + return yaw; + } + + public float getPitch() { + return pitch; + } + public void remove() { this.shouldRemove = true; } + public Buffer getMetadataBuffer() { + Buffer buffer = Buffer.create(); + fillMetadataIndex(buffer, 0); + /*Utils.writeVarInt(buffer, 0); + Utils.writeString(buffer, customName); + buffer.putBoolean(silent); + buffer.putBoolean(noGravity); + Utils.writeVarInt(buffer, pose.ordinal());*/ + + // Chicken test + /*buffer.putByte((byte) 0); // Hand states + buffer.putFloat(10f); // Health + Utils.writeVarInt(buffer, 0); // Potion effect color + buffer.putBoolean(false); // Potion effect ambient + Utils.writeVarInt(buffer, 0); // Arrow count in entity + buffer.putByte((byte) 0); // (Insentient) + buffer.putBoolean(false); // baby (Ageable)*/ + return buffer; + } + + private void fillMetadataIndex(Buffer buffer, int index) { + switch (index) { + case 0: + fillStateMetadata(buffer); + break; + case 1: + fillAirTickMetaData(buffer); + break; + case 2: + fillCustomNameMetaData(buffer); + break; + } + } + + private void fillStateMetadata(Buffer buffer) { + buffer.putByte((byte) 0); + buffer.putByte(METADATA_BYTE); + byte index0 = 0; + if (onFire) + index0 += 1; + if (crouched) + index0 += 2; + if (UNUSED_METADATA) + index0 += 4; + if (sprinting) + index0 += 8; + if (swimming) + index0 += 16; + if (invisible) + index0 += 32; + if (glowing) + index0 += 64; + if (usingElytra) + index0 += 128; + buffer.putByte(index0); + } + + private void fillAirTickMetaData(Buffer buffer) { + buffer.putByte((byte) 1); + buffer.putByte(METADATA_VARINT); + Utils.writeVarInt(buffer, air); + } + + private void fillCustomNameMetaData(Buffer buffer) { + buffer.putByte((byte) 2); + buffer.putByte(METADATA_CHAT); + Utils.writeString(buffer, customName); + } + + private boolean shouldUpdate() { + return (float) (System.currentTimeMillis() - lastUpdate) >= 50f * 0.9f; // Margin of error + } + + public enum Pose { + STANDING, + FALL_FLYING, + SLEEPING, + SWIMMING, + SPIN_ATTACK, + SNEAKING, + DYING; + } + protected boolean shouldRemove() { return shouldRemove; } diff --git a/src/main/java/fr/themode/minestom/entity/EntityCreature.java b/src/main/java/fr/themode/minestom/entity/EntityCreature.java index e84357084..22ed47e94 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityCreature.java +++ b/src/main/java/fr/themode/minestom/entity/EntityCreature.java @@ -1,25 +1,20 @@ package fr.themode.minestom.entity; +import fr.themode.minestom.Viewable; import fr.themode.minestom.net.packet.server.ServerPacket; -import fr.themode.minestom.net.packet.server.play.EntityPacket; -import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket; -import fr.themode.minestom.net.packet.server.play.EntityTeleportPacket; -import fr.themode.minestom.net.packet.server.play.SpawnMobPacket; +import fr.themode.minestom.net.packet.server.play.*; import fr.themode.minestom.net.player.PlayerConnection; import java.util.Collections; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; -public abstract class EntityCreature extends LivingEntity { +public abstract class EntityCreature extends LivingEntity implements Viewable { - private Set viewers = Collections.synchronizedSet(new HashSet<>()); - - private int entityType; + private Set viewers = new CopyOnWriteArraySet<>(); public EntityCreature(int entityType) { - super(); - this.entityType = entityType; + super(entityType); } public void move(double x, double y, double z) { @@ -56,6 +51,7 @@ public abstract class EntityCreature extends LivingEntity { sendPacketToViewers(entityTeleportPacket); } + @Override public void addViewer(Player player) { this.viewers.add(player); PlayerConnection playerConnection = player.getPlayerConnection(); @@ -72,10 +68,15 @@ public abstract class EntityCreature extends LivingEntity { spawnMobPacket.yaw = getYaw(); spawnMobPacket.pitch = getPitch(); spawnMobPacket.headPitch = 0; + EntityMetaDataPacket entityMetaDataPacket = new EntityMetaDataPacket(); + entityMetaDataPacket.entityId = getEntityId(); + entityMetaDataPacket.data = getMetadataBuffer(); playerConnection.sendPacket(entityPacket); playerConnection.sendPacket(spawnMobPacket); + playerConnection.sendPacket(entityMetaDataPacket); } + @Override public void removeViewer(Player player) { synchronized (viewers) { if (!viewers.contains(player)) @@ -85,15 +86,12 @@ public abstract class EntityCreature extends LivingEntity { } } - protected void sendPacketToViewers(ServerPacket packet) { - getViewers().forEach(player -> player.getPlayerConnection().sendPacket(packet)); - } - + @Override public Set getViewers() { return Collections.unmodifiableSet(viewers); } - public int getEntityType() { - return entityType; + protected void sendPacketToViewers(ServerPacket packet) { + getViewers().forEach(player -> player.getPlayerConnection().sendPacket(packet)); } } diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java index 922dafabc..47051b70f 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityManager.java +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -1,9 +1,11 @@ package fr.themode.minestom.entity; import fr.themode.minestom.Main; +import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.InstanceManager; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -11,43 +13,98 @@ public class EntityManager { private static InstanceManager instanceManager = Main.getInstanceManager(); + private ExecutorService objectsPool = Executors.newFixedThreadPool(2); private ExecutorService creaturesPool = Executors.newFixedThreadPool(2); private ExecutorService playersPool = Executors.newFixedThreadPool(2); public void update() { for (Instance instance : instanceManager.getInstances()) { - // TODO loop chunks and entities on it instead of individual (to have more non-blocking operation) - - // Creatures - for (EntityCreature creature : instance.getCreatures()) { - creaturesPool.submit(() -> { - boolean shouldRemove = creature.shouldRemove(); - if (!shouldRemove) { - creature.update(); - } - - if (creature.shouldRemove()) { - instance.removeEntity(creature); - } - }); - } - - // Players - for (Player player : instance.getPlayers()) { - playersPool.submit(() -> { - boolean shouldRemove = player.shouldRemove(); - if (!shouldRemove) { - player.update(); - } - - if (player.shouldRemove()) { - instance.removeEntity(player); - } - }); - } - + testTick2(instance); } } + private void testTick2(Instance instance) { + for (Chunk chunk : instance.getChunks()) { + Set objects = chunk.getObjectEntities(); + Set creatures = chunk.getCreatures(); + Set players = chunk.getPlayers(); + + if (!objects.isEmpty()) { + objectsPool.submit(() -> { + for (ObjectEntity objectEntity : objects) { + boolean shouldRemove = objectEntity.shouldRemove(); + if (!shouldRemove) { + objectEntity.tick(); + } + + if (objectEntity.shouldRemove()) { + instance.removeEntity(objectEntity); + } + } + }); + } + + if (!creatures.isEmpty()) { + creaturesPool.submit(() -> { + for (EntityCreature creature : creatures) { + boolean shouldRemove = creature.shouldRemove(); + if (!shouldRemove) { + creature.tick(); + } + + if (creature.shouldRemove()) { + instance.removeEntity(creature); + } + } + }); + } + + if (!players.isEmpty()) { + playersPool.submit(() -> { + for (Player player : players) { + boolean shouldRemove = player.shouldRemove(); + if (!shouldRemove) { + player.tick(); + } + + if (player.shouldRemove()) { + instance.removeEntity(player); + } + } + }); + } + } + } + + private void testTick(Instance instance) { + // Creatures + for (EntityCreature creature : instance.getCreatures()) { + creaturesPool.submit(() -> { + boolean shouldRemove = creature.shouldRemove(); + if (!shouldRemove) { + creature.tick(); + } + + if (creature.shouldRemove()) { + instance.removeEntity(creature); + } + }); + } + + // Players + for (Player player : instance.getPlayers()) { + playersPool.submit(() -> { + boolean shouldRemove = player.shouldRemove(); + if (!shouldRemove) { + player.tick(); + } + + if (player.shouldRemove()) { + instance.removeEntity(player); + } + }); + } + } + } diff --git a/src/main/java/fr/themode/minestom/entity/ItemEntity.java b/src/main/java/fr/themode/minestom/entity/ItemEntity.java new file mode 100644 index 000000000..b22a9b60a --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/ItemEntity.java @@ -0,0 +1,47 @@ +package fr.themode.minestom.entity; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.item.ItemStack; +import fr.themode.minestom.utils.Utils; + +public class ItemEntity extends ObjectEntity { + + private ItemStack itemStack; + private boolean pickable = true; + + public ItemEntity(ItemStack itemStack) { + super(34); + this.itemStack = itemStack; + } + + @Override + public void update() { + + } + + @Override + public Buffer getMetadataBuffer() { + Buffer buffer = super.getMetadataBuffer(); + buffer.putByte((byte) 7); + buffer.putByte(METADATA_SLOT); + Utils.writeItemStack(buffer, itemStack); + return buffer; + } + + @Override + public int getData() { + return 1; + } + + public ItemStack getItemStack() { + return itemStack; + } + + public boolean isPickable() { + return pickable; + } + + public void setPickable(boolean pickable) { + this.pickable = pickable; + } +} diff --git a/src/main/java/fr/themode/minestom/entity/LivingEntity.java b/src/main/java/fr/themode/minestom/entity/LivingEntity.java index 189a6dbbf..9cfd27d32 100644 --- a/src/main/java/fr/themode/minestom/entity/LivingEntity.java +++ b/src/main/java/fr/themode/minestom/entity/LivingEntity.java @@ -2,25 +2,14 @@ package fr.themode.minestom.entity; public abstract class LivingEntity extends Entity { - protected float yaw, pitch; protected boolean onGround; - public LivingEntity() { - super(); + public LivingEntity(int entityType) { + super(entityType); } - public abstract void update(); - public boolean chunkTest(double x, double z) { return getInstance().getChunk((int) Math.floor(x / 16), (int) Math.floor(z / 16)) == null; } - public float getYaw() { - return yaw; - } - - public float getPitch() { - return pitch; - } - } diff --git a/src/main/java/fr/themode/minestom/entity/ObjectEntity.java b/src/main/java/fr/themode/minestom/entity/ObjectEntity.java new file mode 100644 index 000000000..1318c4c34 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/ObjectEntity.java @@ -0,0 +1,53 @@ +package fr.themode.minestom.entity; + +import fr.themode.minestom.Viewable; +import fr.themode.minestom.net.packet.server.play.EntityMetaDataPacket; +import fr.themode.minestom.net.packet.server.play.SpawnObjectPacket; +import fr.themode.minestom.net.player.PlayerConnection; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public abstract class ObjectEntity extends Entity implements Viewable { + + private Set viewers = new CopyOnWriteArraySet<>(); + + public ObjectEntity(int entityType) { + super(entityType); + } + + public abstract int getData(); + + @Override + public void addViewer(Player player) { + this.viewers.add(player); + PlayerConnection playerConnection = player.getPlayerConnection(); + + SpawnObjectPacket spawnObjectPacket = new SpawnObjectPacket(); + spawnObjectPacket.entityId = getEntityId(); + spawnObjectPacket.uuid = getUuid(); + spawnObjectPacket.type = getEntityType(); + spawnObjectPacket.x = getX(); + spawnObjectPacket.y = getY(); + spawnObjectPacket.z = getZ(); + spawnObjectPacket.yaw = getYaw(); + spawnObjectPacket.pitch = getPitch(); + spawnObjectPacket.data = getData(); + EntityMetaDataPacket entityMetaDataPacket = new EntityMetaDataPacket(); + entityMetaDataPacket.entityId = getEntityId(); + entityMetaDataPacket.data = getMetadataBuffer(); + playerConnection.sendPacket(spawnObjectPacket); + playerConnection.sendPacket(entityMetaDataPacket); + } + + @Override + public void removeViewer(Player player) { + this.viewers.remove(player); + } + + @Override + public Set getViewers() { + return Collections.unmodifiableSet(viewers); + } +} diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index eec97514e..56f931e77 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -1,21 +1,24 @@ package fr.themode.minestom.entity; import fr.themode.minestom.Main; +import fr.themode.minestom.bossbar.BossBar; +import fr.themode.minestom.chat.Chat; import fr.themode.minestom.instance.CustomBlock; import fr.themode.minestom.inventory.Inventory; import fr.themode.minestom.inventory.PlayerInventory; import fr.themode.minestom.item.ItemStack; import fr.themode.minestom.net.packet.server.play.*; import fr.themode.minestom.net.player.PlayerConnection; +import fr.themode.minestom.utils.GroupedCollections; import fr.themode.minestom.utils.Position; +import java.util.Collections; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; public class Player extends LivingEntity { - private boolean isSneaking; - private boolean isSprinting; - private long lastKeepAlive; private String username; @@ -30,8 +33,11 @@ public class Player extends LivingEntity { private Position targetBlockPosition; private long targetBlockTime; + private Set bossBars = new CopyOnWriteArraySet<>(); + // TODO set proper UUID public Player(UUID uuid, String username, PlayerConnection playerConnection) { + super(93); // TODO correct ? this.uuid = uuid; this.username = username; this.playerConnection = playerConnection; @@ -51,11 +57,25 @@ public class Player extends LivingEntity { sendBlockBreakAnimation(targetBlockPosition, stage);// TODO send to all near players if (stage > 9) { instance.setBlock(targetBlockPosition.getX(), targetBlockPosition.getY(), targetBlockPosition.getZ(), (short) 0); - testParticle(targetBlockPosition.getX() + 0.5f, targetBlockPosition.getY(), targetBlockPosition.getZ() + 0.5f); + testParticle(targetBlockPosition.getX() + 0.5f, targetBlockPosition.getY(), targetBlockPosition.getZ() + 0.5f, targetCustomBlock.getType()); resetTargetBlock(); } } + // Item pickup + if (instance != null) { + GroupedCollections objectEntities = instance.getObjectEntities(); + for (ObjectEntity objectEntity : objectEntities) { + if (objectEntity instanceof ItemEntity) { + float distance = getDistance(objectEntity); + if (distance <= 1) { // FIXME set correct value + getInventory().addItemStack(((ItemEntity) objectEntity).getItemStack()); + objectEntity.remove(); + } + } + } + } + // Multiplayer sync EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket(); @@ -81,7 +101,7 @@ public class Player extends LivingEntity { playerConnection.sendPacket(breakAnimationPacket); } - private void testParticle(float x, float y, float z) { + private void testParticle(float x, float y, float z, int blockId) { ParticlePacket particlePacket = new ParticlePacket(); particlePacket.particleId = 3; // Block particle particlePacket.longDistance = false; @@ -93,11 +113,12 @@ public class Player extends LivingEntity { particlePacket.offsetZ = 0.55f; particlePacket.particleData = 0.25f; particlePacket.particleCount = 100; + particlePacket.blockId = blockId; playerConnection.sendPacket(particlePacket); } public void sendMessage(String message) { - ChatMessagePacket chatMessagePacket = new ChatMessagePacket("{\"text\": \"" + message + "\"}", ChatMessagePacket.Position.CHAT); + ChatMessagePacket chatMessagePacket = new ChatMessagePacket(Chat.rawText(message), ChatMessagePacket.Position.CHAT); playerConnection.sendPacket(chatMessagePacket); } @@ -162,6 +183,10 @@ public class Player extends LivingEntity { return targetCustomBlock; } + public Set getBossBars() { + return Collections.unmodifiableSet(bossBars); + } + public void openInventory(Inventory inventory) { if (inventory == null) throw new IllegalArgumentException("Inventory cannot be null, use Player#closeInventory() to close current"); @@ -207,11 +232,11 @@ public class Player extends LivingEntity { } public void refreshSneaking(boolean sneaking) { - isSneaking = sneaking; + sneaking = sneaking; } public void refreshSprinting(boolean sprinting) { - isSprinting = sprinting; + sprinting = sprinting; } public void refreshKeepAlive(long lastKeepAlive) { @@ -238,6 +263,14 @@ public class Player extends LivingEntity { this.targetBlockTime = 0; } + public void refreshAddBossbar(BossBar bossBar) { + this.bossBars.add(bossBar); + } + + public void refreshRemoveBossbar(BossBar bossBar) { + this.bossBars.remove(bossBar); + } + public long getLastKeepAlive() { return lastKeepAlive; } diff --git a/src/main/java/fr/themode/minestom/instance/Biome.java b/src/main/java/fr/themode/minestom/instance/Biome.java index 6a0191659..5a92c2e88 100644 --- a/src/main/java/fr/themode/minestom/instance/Biome.java +++ b/src/main/java/fr/themode/minestom/instance/Biome.java @@ -5,7 +5,79 @@ import java.util.Arrays; public enum Biome { OCEAN(0), + DEEP_OCEAN(24), + FROZEN_OCEAN(10), + DEEP_FROZEN_OCEAN(50), + COLD_OCEAN(46), + DEEP_COLD_OCEAN(49), + LUKEWARM_OCEAN(45), + DEEP_LUKEWARM_OCEAN(48), + WARM_OCEAN(44), + DEEP_WARM_OCEAN(47), + RIVER(7), + FROZEN_RIVER(11), + BEACH(16), + STONE_SHORE(25), + SNOWY_BEACH(26), + FOREST(4), + WOODED_HILLS(18), + FLOWER_FOREST(132), + BIRCH_FOREST(27), + BIRCH_FOREST_HILLS(28), + TALL_BIRCH_FOREST(155), + TALL_BIRCH_HILLS(156), + DARK_FOREST(29), + DARK_FOREST_HILLS(157), + JUNGLE(21), + JUNGLE_HILLS(22), + MODIFIED_JUNGLE(149), + JUNGLE_EDGE(23), + MODIFIED_JUNGLE_EDGE(151), + BAMBOO_JUNGLE(168), + BAMBOO_JUNGLE_HILLS(169), + TAIGA(5), + TAIGA_HILLS(19), + TAIGA_MOUNTAINS(133), + SNOWY_TAIGA(30), + SNOWY_TAIGA_HILLS(31), + SNOWY_TAIGA_MOUNTAINS(158), + GIANT_TREE_TAIGA(32), + GIANT_TREE_TAIGA_HILLS(33), + GIANT_SPRUCE_TAIGA(160), + GIANT_SPRUCE_TAIGA_HILLS(161), + MUSHROOM_FIELDS(14), + MUSHROOM_FIELDS_SHORE(15), + SWAMP(6), + SWAMP_HILLS(134), + SAVANA(35), + SAVANA_PLATEAU(36), + SHATTERED_SAVANA(163), + SHATTERED_SAVANA_PLATEAU(164), PLAINS(1), + SUNFLOWER_PLAINS(129), + DESERT(2), + DESERT_HILLS(17), + DESERT_LAKES(130), + SNOWY_TUNDRA(12), + SNOWY_MOUNTAINS(13), + ICE_SPIKES(140), + MOUNTAINS(3), + WOODED_MOUTAINS(34), + GRAVELLY_MOUNTAINS(131), + MODIFIED_GRAVELLY_MOUNTAINS(162), + MOUNTAIN_EDGE(20), + BADLANDS(37), + BADLANDS_PLATEAU(39), + MODIFIED_BADLANDS_PLATEAU(167), + WOODED_BADLANDS_PLATEAU(38), + MODIFIED_WOODED_BADLANDS_PLATEAU(166), + ERODED_BADLANDS(165), + NETHER(8), + THE_END(9), + SMALL_END_ISLANDS(40), + END_MIDLANDS(41), + END_HIGHLANDS(42), + END_BARRENS(43), VOID(127); private int id; diff --git a/src/main/java/fr/themode/minestom/instance/BlockBatch.java b/src/main/java/fr/themode/minestom/instance/BlockBatch.java index cf777fa44..fd9f6ee18 100644 --- a/src/main/java/fr/themode/minestom/instance/BlockBatch.java +++ b/src/main/java/fr/themode/minestom/instance/BlockBatch.java @@ -7,7 +7,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class BlockBatch { +public class BlockBatch implements BlockModifier { private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(2); @@ -19,12 +19,9 @@ public class BlockBatch { this.instance = instance; } + @Override 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 = this.instance.getChunk(chunkX, chunkZ); - if (chunk == null) - chunk = this.instance.createChunk(Biome.VOID, chunkX, chunkZ); + Chunk chunk = this.instance.getChunkAt(x, z); List blockData = this.data.getOrDefault(chunk, new ArrayList<>()); BlockData data = new BlockData(); @@ -38,6 +35,22 @@ public class BlockBatch { this.data.put(chunk, blockData); } + @Override + public void setBlock(int x, int y, int z, String blockId) { + Chunk chunk = this.instance.getChunkAt(x, z); + List blockData = this.data.getOrDefault(chunk, new ArrayList<>()); + + BlockData data = new BlockData(); + data.x = x % 16; + data.y = y; + data.z = z % 16; + data.blockIdentifier = blockId; + + blockData.add(data); + + this.data.put(chunk, blockData); + } + public void flush() { for (Map.Entry> entry : data.entrySet()) { Chunk chunk = entry.getKey(); @@ -57,9 +70,14 @@ public class BlockBatch { private int x, y, z; private short blockId; + private String blockIdentifier; public void apply(Chunk chunk) { - chunk.setBlock((byte) x, (byte) y, (byte) z, blockId); + if (blockIdentifier == null) { + chunk.setBlock((byte) x, (byte) y, (byte) z, blockId); + } else { + chunk.setBlock((byte) x, (byte) y, (byte) z, blockIdentifier); + } } } diff --git a/src/main/java/fr/themode/minestom/instance/BlockManager.java b/src/main/java/fr/themode/minestom/instance/BlockManager.java index acf460a0c..632ef4143 100644 --- a/src/main/java/fr/themode/minestom/instance/BlockManager.java +++ b/src/main/java/fr/themode/minestom/instance/BlockManager.java @@ -6,13 +6,13 @@ import java.util.function.Supplier; public class BlockManager { - private Map blocksInternalId = new HashMap<>(); + private Map blocksInternalId = new HashMap<>(); private Map blocksId = new HashMap<>(); public void registerBlock(Supplier blocks) { CustomBlock customBlock = blocks.get(); String identifier = customBlock.getIdentifier(); - int id = customBlock.getId(); + short id = customBlock.getId(); this.blocksInternalId.put(id, customBlock); this.blocksId.put(identifier, customBlock); } @@ -21,7 +21,7 @@ public class BlockManager { return blocksId.get(identifier); } - public CustomBlock getBlock(int id) { + public CustomBlock getBlock(short id) { return blocksInternalId.get(id); } diff --git a/src/main/java/fr/themode/minestom/instance/BlockModifier.java b/src/main/java/fr/themode/minestom/instance/BlockModifier.java new file mode 100644 index 000000000..1c51c4bdc --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/BlockModifier.java @@ -0,0 +1,8 @@ +package fr.themode.minestom.instance; + +public interface BlockModifier { + + void setBlock(int x, int y, int z, short blockId); + + void setBlock(int x, int y, int z, String blockId); +} diff --git a/src/main/java/fr/themode/minestom/instance/Chunk.java b/src/main/java/fr/themode/minestom/instance/Chunk.java index 5066ec1f9..25a995955 100644 --- a/src/main/java/fr/themode/minestom/instance/Chunk.java +++ b/src/main/java/fr/themode/minestom/instance/Chunk.java @@ -3,6 +3,7 @@ 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.ObjectEntity; import fr.themode.minestom.entity.Player; import java.util.Collections; @@ -13,12 +14,13 @@ public class Chunk { private static final int CHUNK_SIZE = 16 * 256 * 16; + protected Set objectEntities = new CopyOnWriteArraySet<>(); protected Set creatures = new CopyOnWriteArraySet<>(); protected Set players = new CopyOnWriteArraySet<>(); private int chunkX, chunkZ; private Biome biome; private short[] blocksId = new short[CHUNK_SIZE]; - private int[] customBlocks = new int[CHUNK_SIZE]; + private short[] customBlocks = new short[CHUNK_SIZE]; public Chunk(Biome biome, int chunkX, int chunkZ) { this.biome = biome; @@ -29,9 +31,7 @@ public class Chunk { 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] = 0; - } + this.customBlocks[index] = 0; } protected void setBlock(byte x, byte y, byte z, String blockId) { @@ -49,7 +49,7 @@ public class Chunk { } public CustomBlock getCustomBlock(byte x, byte y, byte z) { - int id = this.customBlocks[getIndex(x, y, z)]; + short id = this.customBlocks[getIndex(x, y, z)]; return id != 0 ? Main.getBlockManager().getBlock(id) : null; } @@ -66,6 +66,12 @@ public class Chunk { return; this.creatures.add((EntityCreature) entity); } + } else if (entity instanceof ObjectEntity) { + synchronized (objectEntities) { + if (this.objectEntities.contains(entity)) + return; + this.objectEntities.add((ObjectEntity) entity); + } } } @@ -78,6 +84,10 @@ public class Chunk { synchronized (creatures) { this.creatures.remove(entity); } + } else if (entity instanceof ObjectEntity) { + synchronized (objectEntities) { + this.objectEntities.remove(entity); + } } } @@ -97,6 +107,10 @@ public class Chunk { return chunkZ; } + public Set getObjectEntities() { + return Collections.unmodifiableSet(objectEntities); + } + public Set getCreatures() { return Collections.unmodifiableSet(creatures); } diff --git a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java new file mode 100644 index 000000000..c504fe09e --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java @@ -0,0 +1,71 @@ +package fr.themode.minestom.instance; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ChunkBatch implements BlockModifier { + + private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(3); + + private Instance instance; + private Chunk chunk; + + private List dataList = new ArrayList<>(); + + public ChunkBatch(Instance instance, Chunk chunk) { + this.instance = instance; + this.chunk = chunk; + } + + @Override + public void setBlock(int x, int y, int z, short blockId) { + BlockData data = new BlockData(); + data.x = (byte) x; + data.y = (byte) y; + data.z = (byte) z; + data.blockId = blockId; + + this.dataList.add(data); + } + + @Override + public void setBlock(int x, int y, int z, String blockId) { + BlockData data = new BlockData(); + data.x = (byte) x; + data.y = (byte) y; + data.z = (byte) z; + data.blockIdentifier = blockId; + + this.dataList.add(data); + } + + public void flush() { + synchronized (chunk) { + batchesPool.submit(() -> { + for (BlockData data : dataList) { + data.apply(chunk); + } + instance.sendChunkUpdate(chunk); // TODO partial chunk data + }); + } + } + + private class BlockData { + + private byte x, y, z; + private short blockId; + private String blockIdentifier; + + public void apply(Chunk chunk) { + if (blockIdentifier == null) { + chunk.setBlock(x, y, z, blockId); + } else { + chunk.setBlock(x, y, z, blockIdentifier); + } + } + + } + +} diff --git a/src/main/java/fr/themode/minestom/instance/ChunkGenerator.java b/src/main/java/fr/themode/minestom/instance/ChunkGenerator.java new file mode 100644 index 000000000..212e5fee0 --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/ChunkGenerator.java @@ -0,0 +1,9 @@ +package fr.themode.minestom.instance; + +public abstract class ChunkGenerator { + + public abstract void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ); + + public abstract Biome getBiome(int chunkX, int chunkZ); + +} diff --git a/src/main/java/fr/themode/minestom/instance/CustomBlock.java b/src/main/java/fr/themode/minestom/instance/CustomBlock.java index eaf2f7a94..a2eef14c3 100644 --- a/src/main/java/fr/themode/minestom/instance/CustomBlock.java +++ b/src/main/java/fr/themode/minestom/instance/CustomBlock.java @@ -4,14 +4,18 @@ import fr.themode.minestom.entity.Player; import java.util.concurrent.atomic.AtomicInteger; +/** + * TODO + * option to set the global as "global breaking" meaning that multiple players mining the same block will break it faster (cumulation) + */ public abstract class CustomBlock { private static final AtomicInteger idCounter = new AtomicInteger(); - private int id; + private short id; public CustomBlock() { - this.id = idCounter.incrementAndGet(); + this.id = (short) idCounter.incrementAndGet(); } public abstract short getType(); @@ -23,7 +27,7 @@ public abstract class CustomBlock { */ public abstract int getBreakDelay(Player player); - public int getId() { + public short getId() { return id; } } diff --git a/src/main/java/fr/themode/minestom/instance/Instance.java b/src/main/java/fr/themode/minestom/instance/Instance.java index 4fae0e7dd..e3ef5b06b 100644 --- a/src/main/java/fr/themode/minestom/instance/Instance.java +++ b/src/main/java/fr/themode/minestom/instance/Instance.java @@ -1,30 +1,37 @@ package fr.themode.minestom.instance; +import fr.themode.minestom.Viewable; import fr.themode.minestom.entity.Entity; import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.entity.ObjectEntity; import fr.themode.minestom.entity.Player; import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; +import fr.themode.minestom.net.packet.server.play.DestroyEntitiesPacket; import fr.themode.minestom.utils.GroupedCollections; +import java.util.Collection; import java.util.Collections; -import java.util.Set; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; -public class Instance { +public class Instance implements BlockModifier { private UUID uniqueId; + private GroupedCollections objectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>()); private GroupedCollections creatures = new GroupedCollections<>(new CopyOnWriteArrayList()); private GroupedCollections players = new GroupedCollections<>(new CopyOnWriteArrayList()); - private Set chunksSet = new CopyOnWriteArraySet<>(); // TODO change for a map with position as key and chunk as value + private ChunkGenerator chunkGenerator; + private Map chunks = new ConcurrentHashMap<>(); public Instance(UUID uniqueId) { this.uniqueId = uniqueId; } + @Override public synchronized void setBlock(int x, int y, int z, short blockId) { Chunk chunk = getChunkAt(x, z); synchronized (chunk) { @@ -33,6 +40,7 @@ public class Instance { } } + @Override public synchronized void setBlock(int x, int y, int z, String blockId) { Chunk chunk = getChunkAt(x, z); synchronized (chunk) { @@ -41,6 +49,11 @@ public class Instance { } } + public Chunk loadChunk(int chunkX, int chunkZ) { + Chunk chunk = getChunk(chunkX, chunkZ); + return chunk == null ? createChunk(chunkX, chunkZ) : chunk; // TODO load from file + } + 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)); @@ -56,11 +69,7 @@ public class Instance { } public Chunk getChunk(int chunkX, int chunkZ) { - for (Chunk chunk : getChunks()) { - if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) - return chunk; - } - return createChunk(Biome.VOID, chunkX, chunkZ); // TODO generation API + return chunks.getOrDefault(getChunkKey(chunkX, chunkZ), null); } public Chunk getChunkAt(double x, double z) { @@ -69,8 +78,12 @@ public class Instance { return getChunk(chunkX, chunkZ); } - public Set getChunks() { - return Collections.unmodifiableSet(chunksSet); + public void setChunkGenerator(ChunkGenerator chunkGenerator) { + this.chunkGenerator = chunkGenerator; + } + + public Collection getChunks() { + return Collections.unmodifiableCollection(chunks.values()); } public void addEntity(Entity entity) { @@ -79,9 +92,10 @@ public class Instance { lastInstance.removeEntity(entity); } - if (entity instanceof EntityCreature) { + if (entity instanceof Viewable) { // TODO based on distance with players - getPlayers().forEach(p -> ((EntityCreature) entity).addViewer(p)); + Viewable viewable = (Viewable) entity; + getPlayers().forEach(p -> ((Viewable) entity).addViewer(p)); } else if (entity instanceof Player) { Player player = (Player) entity; sendChunks(player); @@ -97,15 +111,23 @@ public class Instance { if (entityInstance == null || entityInstance != this) return; - if (entity instanceof EntityCreature) { - EntityCreature creature = (EntityCreature) entity; - creature.getViewers().forEach(p -> creature.removeViewer(p)); + if (entity instanceof Viewable) { + DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(); + destroyEntitiesPacket.entityIds = new int[]{entity.getEntityId()}; + + Viewable viewable = (Viewable) entity; + viewable.getViewers().forEach(p -> p.getPlayerConnection().sendPacket(destroyEntitiesPacket)); // TODO destroy batch + viewable.getViewers().forEach(p -> viewable.removeViewer(p)); } Chunk chunk = getChunkAt(entity.getX(), entity.getZ()); chunk.removeEntity(entity); } + public GroupedCollections getObjectEntities() { + return objectEntities; + } + public GroupedCollections getCreatures() { return creatures; } @@ -118,14 +140,25 @@ public class Instance { return uniqueId; } - protected Chunk createChunk(Biome biome, int chunkX, int chunkZ) { + protected Chunk createChunk(int chunkX, int chunkZ) { + Biome biome = chunkGenerator != null ? chunkGenerator.getBiome(chunkX, chunkZ) : Biome.VOID; Chunk chunk = new Chunk(biome, chunkX, chunkZ); + this.objectEntities.addCollection(chunk.objectEntities); this.creatures.addCollection(chunk.creatures); this.players.addCollection(chunk.players); - this.chunksSet.add(chunk); + this.chunks.put(getChunkKey(chunkX, chunkZ), chunk); + if (chunkGenerator != null) { + ChunkBatch chunkBatch = createChunkBatch(chunk); + chunkGenerator.generateChunkData(chunkBatch, chunkX, chunkZ); + chunkBatch.flush(); + } return chunk; } + protected ChunkBatch createChunkBatch(Chunk chunk) { + return new ChunkBatch(this, chunk); + } + protected void sendChunkUpdate(Chunk chunk) { ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); chunkDataPacket.fullChunk = false; // TODO partial chunk data @@ -141,4 +174,8 @@ public class Instance { player.getPlayerConnection().sendPacket(chunkDataPacket); } } + + private long getChunkKey(int chunkX, int chunkZ) { + return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); + } } diff --git a/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java b/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java new file mode 100644 index 000000000..4a9efe460 --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java @@ -0,0 +1,29 @@ +package fr.themode.minestom.instance.demo; + +import fr.themode.minestom.instance.Biome; +import fr.themode.minestom.instance.ChunkBatch; +import fr.themode.minestom.instance.ChunkGenerator; + +import java.util.Random; + +public class ChunkGeneratorDemo extends ChunkGenerator { + + private final Random random = new Random(); + + @Override + public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) { + for (byte x = 0; x < 16; x++) + for (byte z = 0; z < 16; z++) { + if (random.nextInt(2) == 1) { + batch.setBlock(x, (byte) 4, z, (short) 10); + } else { + batch.setBlock(x, (byte) 4, z, "custom_block"); + } + } + } + + @Override + public Biome getBiome(int chunkX, int chunkZ) { + return Biome.PLAINS; + } +} diff --git a/src/main/java/fr/themode/minestom/instance/demo/StoneBlock.java b/src/main/java/fr/themode/minestom/instance/demo/StoneBlock.java index 3a047ad52..010d0d36c 100644 --- a/src/main/java/fr/themode/minestom/instance/demo/StoneBlock.java +++ b/src/main/java/fr/themode/minestom/instance/demo/StoneBlock.java @@ -7,12 +7,12 @@ public class StoneBlock extends CustomBlock { @Override public short getType() { - return 1; + return 117; } @Override public String getIdentifier() { - return "stone_block"; + return "custom_block"; } @Override diff --git a/src/main/java/fr/themode/minestom/inventory/Inventory.java b/src/main/java/fr/themode/minestom/inventory/Inventory.java index d57a5c56c..6742aee18 100644 --- a/src/main/java/fr/themode/minestom/inventory/Inventory.java +++ b/src/main/java/fr/themode/minestom/inventory/Inventory.java @@ -1,5 +1,6 @@ package fr.themode.minestom.inventory; +import fr.themode.minestom.Viewable; import fr.themode.minestom.entity.Player; import fr.themode.minestom.item.ItemStack; import fr.themode.minestom.net.packet.server.play.SetSlotPacket; @@ -12,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; -public class Inventory implements InventoryModifier, InventoryClickHandler { +public class Inventory implements InventoryModifier, InventoryClickHandler, Viewable { private static AtomicInteger lastInventoryId = new AtomicInteger(); @@ -81,16 +82,19 @@ public class Inventory implements InventoryModifier, InventoryClickHandler { getViewers().forEach(p -> p.getPlayerConnection().sendPacket(windowItemsPacket)); } + @Override public Set getViewers() { return Collections.unmodifiableSet(viewers); } + @Override public void addViewer(Player player) { this.viewers.add(player); WindowItemsPacket windowItemsPacket = getWindowItemsPacket(); player.getPlayerConnection().sendPacket(windowItemsPacket); } + @Override public void removeViewer(Player player) { this.viewers.remove(player); } diff --git a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java index 45c2377b9..8fd0ffd41 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java @@ -2,10 +2,15 @@ package fr.themode.minestom.net.packet.client.login; import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.Main; +import fr.themode.minestom.bossbar.BarColor; +import fr.themode.minestom.bossbar.BarDivision; +import fr.themode.minestom.bossbar.BossBar; import fr.themode.minestom.entity.GameMode; +import fr.themode.minestom.entity.ItemEntity; import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.demo.ChickenCreature; import fr.themode.minestom.instance.Instance; +import fr.themode.minestom.instance.demo.ChunkGeneratorDemo; import fr.themode.minestom.inventory.Inventory; import fr.themode.minestom.inventory.InventoryType; import fr.themode.minestom.inventory.PlayerInventory; @@ -34,11 +39,17 @@ public class LoginStartPacket implements ClientPreplayPacket { private static Instance instance; static { + ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo(); instance = Main.getInstanceManager().createInstance(); - for (int x = -64; x < 64; x++) - for (int z = -64; z < 64; z++) { - instance.setBlock(x, 4, z, (short) 10); + instance.setChunkGenerator(chunkGeneratorDemo); + int loopStart = -4; + int loopEnd = 4; + long time = System.currentTimeMillis(); + for (int x = loopStart; x < loopEnd; x++) + for (int z = loopStart; z < loopEnd; z++) { + instance.loadChunk(x, z); } + System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms"); } @Override @@ -106,7 +117,6 @@ public class LoginStartPacket implements ClientPreplayPacket { for (int x = 0; x < 4; x++) for (int z = 0; z < 4; z++) { - // TODO test entity ChickenCreature chickenCreature = new ChickenCreature(); chickenCreature.refreshPosition(0 + (double) x * 1, 5, 0 + (double) z * 1); chickenCreature.setInstance(instance); @@ -151,6 +161,18 @@ public class LoginStartPacket implements ClientPreplayPacket { player.openInventory(inv); inv.setItemStack(1, new ItemStack(1, (byte) 2)); inv.updateItems(); + + BossBar bossBar = new BossBar("Le titre", BarColor.BLUE, BarDivision.SEGMENT_12); + bossBar.setProgress(0.75f); + bossBar.addViewer(player); + + for (int x = 0; x < 4; x++) + for (int z = 0; z < 4; z++) { + ItemEntity itemEntity = new ItemEntity(new ItemStack(1, (byte) 32)); + itemEntity.refreshPosition(x, 5, z); + itemEntity.setInstance(instance); + //itemEntity.remove(); + } } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerBlockPlacementPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerBlockPlacementPacket.java index 597b45b2a..9a51923a2 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerBlockPlacementPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerBlockPlacementPacket.java @@ -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, "stone_block"); + instance.setBlock(position.getX() + offsetX, position.getY() + offsetY, position.getZ() + offsetZ, "custom_block"); player.getInventory().refreshSlot(player.getHeldSlot()); // TODO consume block in hand for survival players /*Random random = new Random(); diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/BossBarPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/BossBarPacket.java new file mode 100644 index 000000000..48a1e5d8f --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/BossBarPacket.java @@ -0,0 +1,69 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.bossbar.BarColor; +import fr.themode.minestom.bossbar.BarDivision; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +import java.util.UUID; + +public class BossBarPacket implements ServerPacket { + + public UUID uuid; + public Action action; + + public String title; + public float health; + public BarColor color; + public BarDivision division; + public byte flags; + + + @Override + public void write(Buffer buffer) { + Utils.writeUuid(buffer, uuid); + Utils.writeVarInt(buffer, action.ordinal()); + + switch (action) { + case ADD: + Utils.writeString(buffer, title); + buffer.putFloat(health); + Utils.writeVarInt(buffer, color.ordinal()); + Utils.writeVarInt(buffer, division.ordinal()); + buffer.putByte(flags); + break; + case REMOVE: + + break; + case UPDATE_HEALTH: + buffer.putFloat(health); + break; + case UPDATE_TITLE: + Utils.writeString(buffer, title); + break; + case UPDATE_STYLE: + Utils.writeVarInt(buffer, color.ordinal()); + Utils.writeVarInt(buffer, division.ordinal()); + break; + case UPDATE_FLAGS: + buffer.putByte(flags); + break; + } + } + + @Override + public int getId() { + return 0x0C; + } + + public enum Action { + ADD, + REMOVE, + UPDATE_HEALTH, + UPDATE_TITLE, + UPDATE_STYLE, + UPDATE_FLAGS; + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/DisplayScoreboardPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/DisplayScoreboardPacket.java new file mode 100644 index 000000000..fdb0cb2e1 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/DisplayScoreboardPacket.java @@ -0,0 +1,22 @@ +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 DisplayScoreboardPacket implements ServerPacket { + + public byte position; + public String scoreName; + + @Override + public void write(Buffer buffer) { + buffer.putByte(position); + Utils.writeString(buffer, scoreName); + } + + @Override + public int getId() { + return 0x42; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityMetaDataPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityMetaDataPacket.java index 691625614..64238495c 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityMetaDataPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityMetaDataPacket.java @@ -7,10 +7,13 @@ import fr.themode.minestom.utils.Utils; public class EntityMetaDataPacket implements ServerPacket { public int entityId; + public Buffer data; @Override public void write(Buffer buffer) { Utils.writeVarInt(buffer, entityId); + buffer.putBuffer(data); + buffer.putByte((byte) 0xFF); } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ParticlePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ParticlePacket.java index 719dba1a0..1c0c87fcf 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/ParticlePacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ParticlePacket.java @@ -12,7 +12,8 @@ public class ParticlePacket implements ServerPacket { public float offsetX, offsetY, offsetZ; public float particleData; public int particleCount; - // TODO data + + public int blockId; @Override public void write(Buffer buffer) { @@ -26,7 +27,8 @@ public class ParticlePacket implements ServerPacket { buffer.putFloat(offsetZ); buffer.putFloat(particleData); buffer.putInt(particleCount); - Utils.writeVarInt(buffer, 1); + if (particleId == 3) + Utils.writeVarInt(buffer, blockId); } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ScoreboardObjectivePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ScoreboardObjectivePacket.java new file mode 100644 index 000000000..f9558212d --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ScoreboardObjectivePacket.java @@ -0,0 +1,28 @@ +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 ScoreboardObjectivePacket implements ServerPacket { + + public String objectiveName; + public byte mode; + public String objectiveValue; + public int type; + + @Override + public void write(Buffer buffer) { + Utils.writeString(buffer, objectiveName); + buffer.putByte(mode); + if (mode == 0 || mode == 2) { + Utils.writeString(buffer, objectiveValue); + Utils.writeVarInt(buffer, type); + } + } + + @Override + public int getId() { + return 0x49; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java index 8aa791650..12cc8fd6b 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java @@ -20,8 +20,7 @@ public class SpawnMobPacket implements ServerPacket { @Override public void write(Buffer buffer) { Utils.writeVarInt(buffer, entityId); - buffer.putLong(entityUuid.getMostSignificantBits()); - buffer.putLong(entityUuid.getLeastSignificantBits()); + Utils.writeUuid(buffer, entityUuid); Utils.writeVarInt(buffer, entityType); buffer.putDouble(x); buffer.putDouble(y); diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnObjectPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnObjectPacket.java new file mode 100644 index 000000000..2b78db362 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnObjectPacket.java @@ -0,0 +1,35 @@ +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; + +import java.util.UUID; + +public class SpawnObjectPacket implements ServerPacket { + + public int entityId; + public UUID uuid; + public int type; + public double x, y, z; + public float yaw, pitch; + public int data; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + Utils.writeUuid(buffer, uuid); + Utils.writeVarInt(buffer, type); + buffer.putDouble(x); + buffer.putDouble(y); + buffer.putDouble(z); + buffer.putFloat(yaw); + buffer.putFloat(pitch); + buffer.putInt(data); + } + + @Override + public int getId() { + return 0x00; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/TeamsPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/TeamsPacket.java new file mode 100644 index 000000000..08247d06b --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/TeamsPacket.java @@ -0,0 +1,72 @@ +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 TeamsPacket implements ServerPacket { + + public String teamName; + public Action action; + + public String teamDisplayName; + public byte friendlyFlags; + public String nameTagVisibility; + public String collisionRule; + public int teamColor; + public String teamPrefix; + public String teamSuffix; + public int entityCount; + public String[] entities; + + @Override + public void write(Buffer buffer) { + Utils.writeString(buffer, teamName); + buffer.putByte((byte) action.ordinal()); + + switch (action) { + case CREATE_TEAM: + case UPDATE_TEAM_INFO: + Utils.writeString(buffer, teamDisplayName); + buffer.putByte(friendlyFlags); + Utils.writeString(buffer, nameTagVisibility); + Utils.writeString(buffer, collisionRule); + Utils.writeVarInt(buffer, teamColor); + Utils.writeString(buffer, teamPrefix); + Utils.writeString(buffer, teamSuffix); + break; + case REMOVE_TEAM: + + break; + case ADD_PLAYERS_TEAM: + case REMOVE_PLAYERS_TEAM: + Utils.writeVarInt(buffer, entities.length); + for (String entity : entities) { + Utils.writeString(buffer, entity); + } + break; + } + + if (action == Action.CREATE_TEAM) { + Utils.writeVarInt(buffer, entities.length); + for (String entity : entities) { + Utils.writeString(buffer, entity); + } + } + + } + + @Override + public int getId() { + return 0x4B; + } + + public enum Action { + CREATE_TEAM, + REMOVE_TEAM, + UPDATE_TEAM_INFO, + ADD_PLAYERS_TEAM, + REMOVE_PLAYERS_TEAM; + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/UpdateScorePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/UpdateScorePacket.java new file mode 100644 index 000000000..78e782561 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/UpdateScorePacket.java @@ -0,0 +1,28 @@ +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 UpdateScorePacket implements ServerPacket { + + public String entityName; + public byte action; + public String objectiveName; + public int value; + + @Override + public void write(Buffer buffer) { + Utils.writeString(buffer, entityName); + buffer.putByte(action); + Utils.writeString(buffer, objectiveName); + if (action != 1) { + Utils.writeVarInt(buffer, value); + } + } + + @Override + public int getId() { + return 0x4C; + } +} diff --git a/src/main/java/fr/themode/minestom/utils/Utils.java b/src/main/java/fr/themode/minestom/utils/Utils.java index 220c2964a..17c0a6456 100644 --- a/src/main/java/fr/themode/minestom/utils/Utils.java +++ b/src/main/java/fr/themode/minestom/utils/Utils.java @@ -5,6 +5,7 @@ import fr.themode.minestom.item.ItemStack; import java.io.UnsupportedEncodingException; import java.util.Arrays; +import java.util.UUID; import java.util.stream.Collectors; public class Utils { @@ -136,6 +137,11 @@ public class Utils { return new Position(x, y, z); } + public static void writeUuid(Buffer buffer, UUID uuid) { + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + } + public static void writeItemStack(Buffer buffer, ItemStack itemStack) { if (itemStack == null) { buffer.putBoolean(false);