From e0eb35e81e48c798b62756fca41ba538bafec017 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 25 Aug 2019 20:03:43 +0200 Subject: [PATCH] Auto chunk load & death manager --- src/main/java/fr/themode/minestom/Main.java | 4 + .../java/fr/themode/minestom/data/Data.java | 6 +- .../fr/themode/minestom/entity/Entity.java | 80 +++-- .../minestom/entity/EntityCreature.java | 8 +- .../minestom/entity/EntityManager.java | 32 +- .../themode/minestom/entity/ItemEntity.java | 3 +- .../fr/themode/minestom/entity/Player.java | 275 ++++++++++++++---- .../minestom/entity/demo/ChickenCreature.java | 4 +- .../fr/themode/minestom/event/DeathEvent.java | 7 + .../java/fr/themode/minestom/event/Event.java | 1 + .../themode/minestom/event/InteractEvent.java | 16 - ...kEvent.java => PlayerBlockBreakEvent.java} | 4 +- ...eEvent.java => PlayerBlockPlaceEvent.java} | 4 +- .../minestom/event/PlayerInteractEvent.java | 23 ++ .../minestom/event/PlayerSpawnEvent.java | 5 + .../minestom/event/PlayerSpawnPacket.java | 5 - ...vent.java => PlayerStartDiggingEvent.java} | 4 +- ...ItemEvent.java => PlayerUseItemEvent.java} | 4 +- .../themode/minestom/instance/BlockBatch.java | 2 +- .../fr/themode/minestom/instance/Chunk.java | 24 +- .../themode/minestom/instance/ChunkBatch.java | 3 +- .../themode/minestom/instance/Instance.java | 56 ++-- .../minestom/instance/InstanceContainer.java | 158 +++++----- .../minestom/instance/SharedInstance.java | 30 +- .../listener/BlockPlacementListener.java | 9 +- .../listener/PlayerDiggingListener.java | 11 +- .../minestom/listener/StatusListener.java | 13 +- .../minestom/listener/UseEntityListener.java | 14 +- .../minestom/listener/UseItemListener.java | 6 +- .../packet/client/login/LoginStartPacket.java | 2 +- .../packet/server/play/ChunkDataPacket.java | 6 +- .../packet/server/play/SpawnPlayerPacket.java | 7 +- .../packet/server/play/UnloadChunkPacket.java | 20 ++ .../fr/themode/minestom/utils/ChunkUtils.java | 35 +++ .../fr/themode/minestom/utils/MathUtils.java | 4 + .../fr/themode/minestom/utils/Position.java | 3 +- .../minestom/utils/SerializerUtils.java | 15 +- .../java/fr/themode/minestom/utils/Utils.java | 8 +- 38 files changed, 601 insertions(+), 310 deletions(-) create mode 100644 src/main/java/fr/themode/minestom/event/DeathEvent.java delete mode 100644 src/main/java/fr/themode/minestom/event/InteractEvent.java rename src/main/java/fr/themode/minestom/event/{BlockBreakEvent.java => PlayerBlockBreakEvent.java} (67%) rename src/main/java/fr/themode/minestom/event/{BlockPlaceEvent.java => PlayerBlockPlaceEvent.java} (77%) create mode 100644 src/main/java/fr/themode/minestom/event/PlayerInteractEvent.java create mode 100644 src/main/java/fr/themode/minestom/event/PlayerSpawnEvent.java delete mode 100644 src/main/java/fr/themode/minestom/event/PlayerSpawnPacket.java rename src/main/java/fr/themode/minestom/event/{StartDiggingEvent.java => PlayerStartDiggingEvent.java} (65%) rename src/main/java/fr/themode/minestom/event/{UseItemEvent.java => PlayerUseItemEvent.java} (76%) create mode 100644 src/main/java/fr/themode/minestom/net/packet/server/play/UnloadChunkPacket.java create mode 100644 src/main/java/fr/themode/minestom/utils/ChunkUtils.java diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java index c8784d291..273ae9a4d 100644 --- a/src/main/java/fr/themode/minestom/Main.java +++ b/src/main/java/fr/themode/minestom/Main.java @@ -31,6 +31,10 @@ public class Main { public static final int TICK_MS = 50; public static final int TICK_PER_SECOND = 1000 / TICK_MS; + // Config + public static final int CHUNK_VIEW_DISTANCE = 10; + public static final int ENTITY_VIEW_DISTANCE = 10; // TODO + // Networking private static ConnectionManager connectionManager; private static PacketProcessor packetProcessor; diff --git a/src/main/java/fr/themode/minestom/data/Data.java b/src/main/java/fr/themode/minestom/data/Data.java index 6d2539598..e9393a47a 100644 --- a/src/main/java/fr/themode/minestom/data/Data.java +++ b/src/main/java/fr/themode/minestom/data/Data.java @@ -16,6 +16,10 @@ public class Data { return (T) data.get(key); } - // TODO serialize + public T getOrDefault(String key, T defaultValue) { + return (T) data.getOrDefault(key, defaultValue); + } + + // TODO serialization } diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java index db1b022ab..5ba70b0ea 100644 --- a/src/main/java/fr/themode/minestom/entity/Entity.java +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -38,12 +38,14 @@ public abstract class Entity implements Viewable, DataContainer { protected Instance instance; protected Position position; protected boolean onGround; - protected double lastX, lastY, lastZ; + protected float lastX, lastY, lastZ; protected float lastYaw, lastPitch; private int id; protected Entity vehicle; - private Map, Callback> eventCallbacks = new ConcurrentHashMap<>(); + // Velocity + // TODO gravity implementation for entity other than players + protected Vector velocity = new Vector(); // Movement in block per second private Set viewers = new CopyOnWriteArraySet<>(); private Data data; private Set passengers = new CopyOnWriteArraySet<>(); @@ -54,10 +56,7 @@ public abstract class Entity implements Viewable, DataContainer { private long scheduledRemoveTime; private int entityType; private long lastUpdate; - - // Velocity - // TODO gravity implementation for entity other than players - protected float velocityX, velocityY, velocityZ; // Movement in block per second + private Map, Callback> eventCallbacks = new ConcurrentHashMap<>(); protected long velocityTime; // Reset velocity to 0 after countdown // Synchronization @@ -106,22 +105,45 @@ public abstract class Entity implements Viewable, DataContainer { // Called when entity a new instance is set public abstract void spawn(); - public void teleport(Position position) { - if (isChunkUnloaded(position.getX(), position.getZ())) + public void teleport(Position position, Runnable callback) { + if (instance == null) return; - refreshPosition(position.getX(), position.getY(), position.getZ()); - refreshView(position.getYaw(), position.getPitch()); - EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket(); - entityTeleportPacket.entityId = getEntityId(); - entityTeleportPacket.position = position; - entityTeleportPacket.onGround = onGround; - sendPacketToViewers(entityTeleportPacket); + if (instance.hasEnabledAutoChunkLoad()) { + instance.loadChunk(position, chunk -> { + refreshPosition(position.getX(), position.getY(), position.getZ()); + refreshView(position.getYaw(), position.getPitch()); + EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket(); + entityTeleportPacket.entityId = getEntityId(); + entityTeleportPacket.position = position; + entityTeleportPacket.onGround = onGround; + sendPacketToViewers(entityTeleportPacket); + if (callback != null) + callback.run(); + }); + } else { + if (isChunkUnloaded(position.getX(), position.getZ())) + return; + refreshPosition(position.getX(), position.getY(), position.getZ()); + refreshView(position.getYaw(), position.getPitch()); + EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket(); + entityTeleportPacket.entityId = getEntityId(); + entityTeleportPacket.position = position; + entityTeleportPacket.onGround = onGround; + sendPacketToViewers(entityTeleportPacket); + if (callback != null) + callback.run(); + } + } + + public void teleport(Position position) { + teleport(position, null); } @Override public void addViewer(Player player) { this.viewers.add(player); + player.viewableEntity.add(this); player.getPlayerConnection().sendPacket(getVelocityPacket()); } @@ -135,6 +157,7 @@ public abstract class Entity implements Viewable, DataContainer { destroyEntitiesPacket.entityIds = new int[]{getEntityId()}; player.getPlayerConnection().sendPacket(destroyEntitiesPacket); } + player.viewableEntity.remove(this); } @Override @@ -180,7 +203,7 @@ public abstract class Entity implements Viewable, DataContainer { sendPacketToViewersAndSelf(getVelocityPacket()); } else { float tps = Main.TICK_PER_SECOND; - refreshPosition(position.getX() + velocityX / tps, position.getY() + velocityY / tps, position.getZ() + velocityZ / tps); + refreshPosition(position.getX() + velocity.getX() / tps, position.getY() + velocity.getY() / tps, position.getZ() + velocity.getZ() / tps); if (this instanceof ObjectEntity) { sendPacketToViewers(getVelocityPacket()); } else if (this instanceof EntityCreature) { @@ -207,11 +230,11 @@ public abstract class Entity implements Viewable, DataContainer { } public void setEventCallback(Class eventClass, Callback callback) { - this.eventCallbacks.put((Class) eventClass, callback); + this.eventCallbacks.put(eventClass, callback); } public Callback getEventCallback(Class eventClass) { - return this.eventCallbacks.getOrDefault(eventClass, null); + return this.eventCallbacks.get(eventClass); } public void callEvent(Class eventClass, E event) { @@ -261,17 +284,17 @@ public abstract class Entity implements Viewable, DataContainer { spawn(); } + public Vector getVelocity() { + return velocity; + } + public void setVelocity(Vector velocity, long velocityTime) { - this.velocityX = velocity.getX(); - this.velocityY = velocity.getY(); - this.velocityZ = velocity.getZ(); + this.velocity = velocity; this.velocityTime = System.currentTimeMillis() + velocityTime; } public void resetVelocity() { - this.velocityX = 0; - this.velocityY = 0; - this.velocityZ = 0; + this.velocity = new Vector(); this.velocityTime = 0; } @@ -341,6 +364,9 @@ public abstract class Entity implements Viewable, DataContainer { synchronized (instance) { instance.removeEntityFromChunk(this, lastChunk); instance.addEntityToChunk(this, newChunk); + if (this instanceof Player) + ((Player) this).onChunkChange(lastChunk, newChunk); // Refresh loaded chunk + // TODO compare with viewers and remove if too far away } } } @@ -388,9 +414,9 @@ public abstract class Entity implements Viewable, DataContainer { final float strength = 8000f / Main.TICK_PER_SECOND; EntityVelocityPacket velocityPacket = new EntityVelocityPacket(); velocityPacket.entityId = getEntityId(); - velocityPacket.velocityX = (short) (velocityX * strength); - velocityPacket.velocityY = (short) (velocityY * strength); - velocityPacket.velocityZ = (short) (velocityZ * strength); + velocityPacket.velocityX = (short) (velocity.getX() * strength); + velocityPacket.velocityY = (short) (velocity.getY() * strength); + velocityPacket.velocityZ = (short) (velocity.getZ() * strength); return velocityPacket; } diff --git a/src/main/java/fr/themode/minestom/entity/EntityCreature.java b/src/main/java/fr/themode/minestom/entity/EntityCreature.java index 8b382f82c..850196050 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityCreature.java +++ b/src/main/java/fr/themode/minestom/entity/EntityCreature.java @@ -1,5 +1,6 @@ package fr.themode.minestom.entity; +import fr.themode.minestom.event.DeathEvent; 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.SpawnMobPacket; @@ -19,11 +20,6 @@ public abstract class EntityCreature extends LivingEntity { } public void move(float x, float y, float z) { - if (velocityTime != 0) { - // Cancel movements if velocity is active - return; - } - // TODO change yaw/pitch based on next position? Position position = getPosition(); float newX = position.getX() + x; @@ -49,6 +45,8 @@ public abstract class EntityCreature extends LivingEntity { this.isDead = true; triggerStatus((byte) 3); scheduleRemove(1000); + DeathEvent deathEvent = new DeathEvent(); + callEvent(DeathEvent.class, deathEvent); } @Override diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java index bc415e823..ec29632fe 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityManager.java +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -2,15 +2,18 @@ package fr.themode.minestom.entity; import fr.themode.minestom.Main; import fr.themode.minestom.event.PlayerLoginEvent; -import fr.themode.minestom.event.PlayerSpawnPacket; +import fr.themode.minestom.event.PlayerSpawnEvent; import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.InstanceManager; +import fr.themode.minestom.utils.ChunkUtils; +import fr.themode.minestom.utils.Position; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Consumer; public class EntityManager { @@ -24,7 +27,7 @@ public class EntityManager { public void update() { waitingPlayersTick(); for (Instance instance : instanceManager.getInstances()) { - testTick1(instance); + testTick2(instance); } } @@ -37,13 +40,24 @@ public class EntityManager { PlayerLoginEvent loginEvent = new PlayerLoginEvent(); playerCache.callEvent(PlayerLoginEvent.class, loginEvent); Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstanceContainer() : loginEvent.getSpawningInstance(); - // TODO load multiple chunks around player (based on view distance) instead of only one - spawningInstance.loadChunk(playerCache.getPosition(), chunk -> { - playerCache.spawned = true; - playerCache.setInstance(spawningInstance); - PlayerSpawnPacket spawnPacket = new PlayerSpawnPacket(); - playerCache.callEvent(PlayerSpawnPacket.class, spawnPacket); - }); + Position position = playerCache.getPosition(); + + long[] visibleChunks = ChunkUtils.getVisibleChunks(position); + for (int i = 0; i < visibleChunks.length; i++) { + int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunks[i]); + int chunkX = chunkPos[0]; + int chunkZ = chunkPos[1]; + boolean isLast = i == visibleChunks.length - 1; + Consumer callback = isLast ? chunk -> { + System.out.println("END CHUNK LOADING"); + playerCache.spawned = true; + playerCache.setInstance(spawningInstance); + PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(); + playerCache.callEvent(PlayerSpawnEvent.class, spawnEvent); + } : null; + spawningInstance.loadChunk(chunkX, chunkZ, callback); // TODO loadOptionalChunk for not loading chunks when autoload is false + } + }); } } diff --git a/src/main/java/fr/themode/minestom/entity/ItemEntity.java b/src/main/java/fr/themode/minestom/entity/ItemEntity.java index fe6508875..d5981804d 100644 --- a/src/main/java/fr/themode/minestom/entity/ItemEntity.java +++ b/src/main/java/fr/themode/minestom/entity/ItemEntity.java @@ -3,7 +3,6 @@ package fr.themode.minestom.entity; import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.item.ItemStack; import fr.themode.minestom.utils.Utils; -import fr.themode.minestom.utils.Vector; public class ItemEntity extends ObjectEntity { @@ -22,7 +21,7 @@ public class ItemEntity extends ObjectEntity { @Override public void spawn() { - setVelocity(new Vector(0, 1, 0), 5000); + // setVelocity(new Vector(0, 1, 0), 5000); } @Override diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index 051b3f9eb..b9376e569 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -3,8 +3,11 @@ 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.data.Data; +import fr.themode.minestom.data.DataType; import fr.themode.minestom.entity.demo.ChickenCreature; import fr.themode.minestom.event.*; +import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.CustomBlock; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.InstanceContainer; @@ -17,6 +20,7 @@ import fr.themode.minestom.net.packet.server.ServerPacket; import fr.themode.minestom.net.packet.server.play.*; import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.utils.BlockPosition; +import fr.themode.minestom.utils.ChunkUtils; import fr.themode.minestom.utils.Position; import fr.themode.minestom.utils.Vector; import fr.themode.minestom.world.Dimension; @@ -41,10 +45,30 @@ public class Player extends LivingEntity { private GameMode gameMode; private LevelType levelType; + static { + ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo(); + //instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data")); + instanceContainer = Main.getInstanceManager().createInstanceContainer(); + instanceContainer.enableAutoChunkLoad(true); + instanceContainer.setChunkGenerator(chunkGeneratorDemo); + int loopStart = -3; + int loopEnd = 3; + long time = System.currentTimeMillis(); + for (int x = loopStart; x < loopEnd; x++) + for (int z = loopStart; z < loopEnd; z++) { + instanceContainer.loadChunk(x, z); + } + System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms"); + } + + protected Set viewableEntity = new CopyOnWriteArraySet<>(); + private float health; + private PlayerSettings settings; private PlayerInventory inventory; private short heldSlot; private Inventory openInventory; + private int food; private CustomBlock targetCustomBlock; private BlockPosition targetBlockPosition; @@ -58,21 +82,7 @@ public class Player extends LivingEntity { private float forward; private static InstanceContainer instanceContainer; - - static { - ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo(); - //instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data")); - instanceContainer = Main.getInstanceManager().createInstanceContainer(); - instanceContainer.setChunkGenerator(chunkGeneratorDemo); - int loopStart = -2; - int loopEnd = 2; - long time = System.currentTimeMillis(); - for (int x = loopStart; x < loopEnd; x++) - for (int z = loopStart; z < loopEnd; z++) { - instanceContainer.loadChunk(x, z); - } - System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms"); - } + private float foodSaturation; protected boolean spawned; @@ -82,6 +92,8 @@ public class Player extends LivingEntity { this.username = username; this.playerConnection = playerConnection; + refreshHealth(); + this.settings = new PlayerSettings(); this.inventory = new PlayerInventory(this); @@ -97,28 +109,17 @@ public class Player extends LivingEntity { Vector velocity = getPosition().clone().getDirection().multiply(6); velocity.setY(3.5f); player.setVelocity(velocity, 150); - - AnimationPacket animationPacket = new AnimationPacket(); - animationPacket.entityId = player.getEntityId(); - animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE; - sendPacketToViewersAndSelf(animationPacket); - + player.damage(2); sendMessage("ATTACK"); } - /*UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket(); - updateHealthPacket.health = -1f; - updateHealthPacket.food = 5; - updateHealthPacket.foodSaturation = 0; - playerConnection.sendPacket(updateHealthPacket);*/ }); - setEventCallback(BlockPlaceEvent.class, event -> { - /*sendMessage("Placed block! " + event.getHand()); - Data data = new Data(); - data.set("test", 5, DataType.INTEGER); - setData(data); + setEventCallback(PlayerBlockPlaceEvent.class, event -> { + sendMessage("Placed block! " + event.getHand()); + int value = getData().getOrDefault("test", 0); + getData().set("test", value + 1, DataType.INTEGER); - sendMessage("Data: " + getData().get("test"));*/ + System.out.println("OLD DATA VALUE: " + value); if (event.getHand() != Hand.MAIN) return; @@ -128,9 +129,10 @@ public class Player extends LivingEntity { sendMessage("Saved in " + (System.currentTimeMillis() - time) + " ms"); });*/ - for (Player player : Main.getConnectionManager().getOnlinePlayers()) { - player.teleport(getPosition()); - } + /*for (Player player : instance.getPlayers()) { + if (player != this) + player.teleport(getPosition()); + }*/ }); setEventCallback(PickupItemEvent.class, event -> { @@ -139,19 +141,17 @@ public class Player extends LivingEntity { setEventCallback(PlayerLoginEvent.class, event -> { event.setSpawningInstance(instanceContainer); + setData(new Data()); }); - setEventCallback(PlayerSpawnPacket.class, event -> { - setGameMode(GameMode.SURVIVAL); + setEventCallback(PlayerSpawnEvent.class, event -> { + System.out.println("SPAWN "); + setGameMode(GameMode.CREATIVE); teleport(new Position(0, 66, 0)); - for (int cx = 0; cx < 4; cx++) - for (int cz = 0; cz < 4; cz++) { - ChickenCreature chickenCreature = new ChickenCreature(); - chickenCreature.refreshPosition(0 + (float) cx * 1, 65, 0 + (float) cz * 1); - //chickenCreature.setOnFire(true); - chickenCreature.setInstance(getInstance()); - //chickenCreature.addPassenger(player); - } + + ChickenCreature chickenCreature = new ChickenCreature(); + chickenCreature.refreshPosition(2, 65, 2); + chickenCreature.setInstance(getInstance()); for (int ix = 0; ix < 4; ix++) for (int iz = 0; iz < 4; iz++) { @@ -199,7 +199,7 @@ public class Player extends LivingEntity { } this.targetLastStage = stage; if (stage > 9) { - instance.breakBlock(this, targetBlockPosition, targetCustomBlock); + instance.breakBlock(this, targetBlockPosition); resetTargetBlock(); } } @@ -271,6 +271,15 @@ public class Player extends LivingEntity { } + @Override + public void remove() { + clearBossBars(); + if (getOpenInventory() != null) + getOpenInventory().removeViewer(this); + this.viewableEntity.forEach(entity -> entity.removeViewer(this)); + super.remove(); + } + @Override public void addViewer(Player player) { super.addViewer(player); @@ -315,7 +324,7 @@ public class Player extends LivingEntity { @Override public void kill() { this.isDead = true; - // TODO set health to -1 + setHealth(-1); } public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) { @@ -331,19 +340,165 @@ public class Player extends LivingEntity { playerConnection.sendPacket(chatMessagePacket); } - @Override - public void teleport(Position position) { - super.teleport(position); // Send new position to all viewers directly - if (isChunkUnloaded(position.getX(), position.getZ())) + public void damage(float amount) { + AnimationPacket animationPacket = new AnimationPacket(); + animationPacket.entityId = getEntityId(); + animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE; + sendPacketToViewersAndSelf(animationPacket); + setHealth(health - amount); + } + + public float getHealth() { + return health; + } + + public void setHealth(float health) { + this.health = health; + sendUpdateHealthPacket(); + if (this.health <= 0) { + // Kill player + refreshIsDead(true); + EntityStatusPacket entityStatusPacket = new EntityStatusPacket(); + entityStatusPacket.entityId = getEntityId(); + entityStatusPacket.status = 3; // Death sound/animation + sendPacketToViewers(entityStatusPacket); + DeathEvent deathEvent = new DeathEvent(); + callEvent(DeathEvent.class, deathEvent); + } + } + + public int getFood() { + return food; + } + + public void setFood(int food) { + this.food = food; + sendUpdateHealthPacket(); + } + + public float getFoodSaturation() { + return foodSaturation; + } + + public void setFoodSaturation(float foodSaturation) { + this.foodSaturation = foodSaturation; + sendUpdateHealthPacket(); + } + + public void respawn() { + if (!isDead()) return; - refreshPosition(position.getX(), position.getY(), position.getZ()); - refreshView(position.getYaw(), position.getPitch()); - PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket(); - positionAndLookPacket.position = position; - positionAndLookPacket.flags = 0x00; - positionAndLookPacket.teleportId = 67; - playerConnection.sendPacket(positionAndLookPacket); + refreshHealth(); + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.dimension = getDimension(); + respawnPacket.gameMode = getGameMode(); + respawnPacket.levelType = getLevelType(); + getPlayerConnection().sendPacket(respawnPacket); + PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getPosition()); + callEvent(PlayerRespawnEvent.class, respawnEvent); + refreshIsDead(false); + + // Runnable called when teleportation is successfull (after loading and sending necessary chunk) + teleport(respawnEvent.getRespawnPosition(), () -> { + getInventory().update(); + + SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket(); + spawnPlayerPacket.entityId = getEntityId(); + spawnPlayerPacket.playerUuid = getUuid(); + spawnPlayerPacket.position = getPosition(); + sendPacketToViewers(spawnPlayerPacket); + }); + } + + private void refreshHealth() { + // TODO improve + this.health = 20; + this.food = 20; + this.foodSaturation = 5; + } + + protected void sendUpdateHealthPacket() { + UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket(); + updateHealthPacket.health = health; + updateHealthPacket.food = food; + updateHealthPacket.foodSaturation = foodSaturation; + playerConnection.sendPacket(updateHealthPacket); + } + + protected void onChunkChange(Chunk lastChunk, Chunk newChunk) { + long[] lastVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ())); + long[] newVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ())); + + // Unload old chunks + for (int l = 0; l < lastVisibleChunks.length; l++) { + long lastVisibleChunk = lastVisibleChunks[l]; + boolean contains = false; + for (int n = 0; n < newVisibleChunks.length; n++) { + long newVisibleChunk = newVisibleChunks[n]; + if (newVisibleChunk == lastVisibleChunk) { + contains = true; + break; + } + } + if (!contains) { + int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunk); + UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket(); + unloadChunkPacket.chunkX = chunkPos[0]; + unloadChunkPacket.chunkZ = chunkPos[1]; + playerConnection.sendPacket(unloadChunkPacket); + } + } + + // Load new chunks + for (int n = 0; n < newVisibleChunks.length; n++) { + long newVisibleChunk = newVisibleChunks[n]; + boolean contains = false; + for (int l = 0; l < lastVisibleChunks.length; l++) { + long lastVisibleChunk = lastVisibleChunks[l]; + if (lastVisibleChunk == newVisibleChunk) { + contains = true; + break; + } + } + if (!contains) { + int[] chunkPos = ChunkUtils.getChunkCoord(newVisibleChunk); + instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> { + if (chunk == null) { + return; // Cannot load chunk (autoload not enabled) + } + instance.sendChunk(this, chunk); + }); + } + + } + + UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(newChunk); + playerConnection.sendPacket(updateViewPositionPacket); + } + + @Override + public void teleport(Position position, Runnable callback) { + if (instance == null) + return; + super.teleport(position, () -> { + if (!instance.hasEnabledAutoChunkLoad() && isChunkUnloaded(position.getX(), position.getZ())) + return; + refreshPosition(position.getX(), position.getY(), position.getZ()); + refreshView(position.getYaw(), position.getPitch()); + PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket(); + positionAndLookPacket.position = position; + positionAndLookPacket.flags = 0x00; + positionAndLookPacket.teleportId = 67; + playerConnection.sendPacket(positionAndLookPacket); + if (callback != null) + callback.run(); + }); + } + + @Override + public void teleport(Position position) { + teleport(position, null); } public String getUsername() { @@ -465,6 +620,10 @@ public class Player extends LivingEntity { inventory.update(); } + public void clearBossBars() { + this.bossBars.forEach(bossBar -> bossBar.removeViewer(this)); + } + public void syncEquipment(EntityEquipmentPacket.Slot slot) { EntityEquipmentPacket equipmentPacket = new EntityEquipmentPacket(); equipmentPacket.entityId = getEntityId(); diff --git a/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java index 4997e7cc5..ceebf72e5 100644 --- a/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java +++ b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java @@ -1,6 +1,7 @@ package fr.themode.minestom.entity.demo; import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.utils.Vector; public class ChickenCreature extends EntityCreature { @@ -42,12 +43,11 @@ public class ChickenCreature extends EntityCreature { move(x * speed, 0, z * speed); } }*/ - move(0, 0, speed); } @Override public void spawn() { - // setVelocity(new Vector(0, 1, 0), 1000); + setVelocity(new Vector(0, 1, 0), 3000); } } diff --git a/src/main/java/fr/themode/minestom/event/DeathEvent.java b/src/main/java/fr/themode/minestom/event/DeathEvent.java new file mode 100644 index 000000000..b169b38dc --- /dev/null +++ b/src/main/java/fr/themode/minestom/event/DeathEvent.java @@ -0,0 +1,7 @@ +package fr.themode.minestom.event; + +public class DeathEvent extends Event { + + // TODO cause + +} diff --git a/src/main/java/fr/themode/minestom/event/Event.java b/src/main/java/fr/themode/minestom/event/Event.java index 8928a9232..6ab64eca4 100644 --- a/src/main/java/fr/themode/minestom/event/Event.java +++ b/src/main/java/fr/themode/minestom/event/Event.java @@ -1,4 +1,5 @@ package fr.themode.minestom.event; public class Event { + } diff --git a/src/main/java/fr/themode/minestom/event/InteractEvent.java b/src/main/java/fr/themode/minestom/event/InteractEvent.java deleted file mode 100644 index 87e6e27f4..000000000 --- a/src/main/java/fr/themode/minestom/event/InteractEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package fr.themode.minestom.event; - -import fr.themode.minestom.entity.Entity; - -public class InteractEvent extends Event { - - private Entity target; - - public InteractEvent(Entity target) { - this.target = target; - } - - public Entity getTarget() { - return target; - } -} \ No newline at end of file diff --git a/src/main/java/fr/themode/minestom/event/BlockBreakEvent.java b/src/main/java/fr/themode/minestom/event/PlayerBlockBreakEvent.java similarity index 67% rename from src/main/java/fr/themode/minestom/event/BlockBreakEvent.java rename to src/main/java/fr/themode/minestom/event/PlayerBlockBreakEvent.java index abd0d93e8..e47eb7fc4 100644 --- a/src/main/java/fr/themode/minestom/event/BlockBreakEvent.java +++ b/src/main/java/fr/themode/minestom/event/PlayerBlockBreakEvent.java @@ -2,11 +2,11 @@ package fr.themode.minestom.event; import fr.themode.minestom.utils.BlockPosition; -public class BlockBreakEvent extends CancellableEvent { +public class PlayerBlockBreakEvent extends CancellableEvent { private BlockPosition blockPosition; - public BlockBreakEvent(BlockPosition blockPosition) { + public PlayerBlockBreakEvent(BlockPosition blockPosition) { this.blockPosition = blockPosition; } diff --git a/src/main/java/fr/themode/minestom/event/BlockPlaceEvent.java b/src/main/java/fr/themode/minestom/event/PlayerBlockPlaceEvent.java similarity index 77% rename from src/main/java/fr/themode/minestom/event/BlockPlaceEvent.java rename to src/main/java/fr/themode/minestom/event/PlayerBlockPlaceEvent.java index 0b7ce5355..045d9f8d4 100644 --- a/src/main/java/fr/themode/minestom/event/BlockPlaceEvent.java +++ b/src/main/java/fr/themode/minestom/event/PlayerBlockPlaceEvent.java @@ -3,13 +3,13 @@ package fr.themode.minestom.event; import fr.themode.minestom.entity.Player; import fr.themode.minestom.utils.BlockPosition; -public class BlockPlaceEvent extends CancellableEvent { +public class PlayerBlockPlaceEvent extends CancellableEvent { private short blockId; private BlockPosition blockPosition; private Player.Hand hand; - public BlockPlaceEvent(short blockId, BlockPosition blockPosition, Player.Hand hand) { + public PlayerBlockPlaceEvent(short blockId, BlockPosition blockPosition, Player.Hand hand) { this.blockId = blockId; this.blockPosition = blockPosition; this.hand = hand; diff --git a/src/main/java/fr/themode/minestom/event/PlayerInteractEvent.java b/src/main/java/fr/themode/minestom/event/PlayerInteractEvent.java new file mode 100644 index 000000000..b7cfd13b5 --- /dev/null +++ b/src/main/java/fr/themode/minestom/event/PlayerInteractEvent.java @@ -0,0 +1,23 @@ +package fr.themode.minestom.event; + +import fr.themode.minestom.entity.Entity; +import fr.themode.minestom.entity.Player; + +public class PlayerInteractEvent extends Event { + + private Entity target; + private Player.Hand hand; + + public PlayerInteractEvent(Entity target, Player.Hand hand) { + this.target = target; + this.hand = hand; + } + + public Entity getTarget() { + return target; + } + + public Player.Hand getHand() { + return hand; + } +} \ No newline at end of file diff --git a/src/main/java/fr/themode/minestom/event/PlayerSpawnEvent.java b/src/main/java/fr/themode/minestom/event/PlayerSpawnEvent.java new file mode 100644 index 000000000..d056e8f4f --- /dev/null +++ b/src/main/java/fr/themode/minestom/event/PlayerSpawnEvent.java @@ -0,0 +1,5 @@ +package fr.themode.minestom.event; + +public class PlayerSpawnEvent extends Event { + +} diff --git a/src/main/java/fr/themode/minestom/event/PlayerSpawnPacket.java b/src/main/java/fr/themode/minestom/event/PlayerSpawnPacket.java deleted file mode 100644 index f55a8a1fa..000000000 --- a/src/main/java/fr/themode/minestom/event/PlayerSpawnPacket.java +++ /dev/null @@ -1,5 +0,0 @@ -package fr.themode.minestom.event; - -public class PlayerSpawnPacket extends Event { - -} diff --git a/src/main/java/fr/themode/minestom/event/StartDiggingEvent.java b/src/main/java/fr/themode/minestom/event/PlayerStartDiggingEvent.java similarity index 65% rename from src/main/java/fr/themode/minestom/event/StartDiggingEvent.java rename to src/main/java/fr/themode/minestom/event/PlayerStartDiggingEvent.java index d4e2716a9..018dcbcf7 100644 --- a/src/main/java/fr/themode/minestom/event/StartDiggingEvent.java +++ b/src/main/java/fr/themode/minestom/event/PlayerStartDiggingEvent.java @@ -2,11 +2,11 @@ package fr.themode.minestom.event; import fr.themode.minestom.instance.CustomBlock; -public class StartDiggingEvent extends CancellableEvent { +public class PlayerStartDiggingEvent extends CancellableEvent { private CustomBlock customBlock; - public StartDiggingEvent(CustomBlock customBlock) { + public PlayerStartDiggingEvent(CustomBlock customBlock) { this.customBlock = customBlock; } diff --git a/src/main/java/fr/themode/minestom/event/UseItemEvent.java b/src/main/java/fr/themode/minestom/event/PlayerUseItemEvent.java similarity index 76% rename from src/main/java/fr/themode/minestom/event/UseItemEvent.java rename to src/main/java/fr/themode/minestom/event/PlayerUseItemEvent.java index b23c44c79..bbbdbbe23 100644 --- a/src/main/java/fr/themode/minestom/event/UseItemEvent.java +++ b/src/main/java/fr/themode/minestom/event/PlayerUseItemEvent.java @@ -3,12 +3,12 @@ package fr.themode.minestom.event; import fr.themode.minestom.entity.Player; import fr.themode.minestom.item.ItemStack; -public class UseItemEvent extends Event { +public class PlayerUseItemEvent extends Event { private Player.Hand hand; private ItemStack itemStack; - public UseItemEvent(Player.Hand hand, ItemStack itemStack) { + public PlayerUseItemEvent(Player.Hand hand, ItemStack itemStack) { this.hand = hand; this.itemStack = itemStack; } diff --git a/src/main/java/fr/themode/minestom/instance/BlockBatch.java b/src/main/java/fr/themode/minestom/instance/BlockBatch.java index 0c7dee002..5c869e974 100644 --- a/src/main/java/fr/themode/minestom/instance/BlockBatch.java +++ b/src/main/java/fr/themode/minestom/instance/BlockBatch.java @@ -76,7 +76,7 @@ public class BlockBatch implements BlockModifier { if (blockIdentifier == null) { chunk.setBlock((byte) x, (byte) y, (byte) z, blockId); } else { - chunk.setBlock((byte) x, (byte) y, (byte) z, blockIdentifier); + chunk.setCustomBlock((byte) x, (byte) y, (byte) z, blockIdentifier); } } diff --git a/src/main/java/fr/themode/minestom/instance/Chunk.java b/src/main/java/fr/themode/minestom/instance/Chunk.java index a2bfdb6d0..4e436e88c 100644 --- a/src/main/java/fr/themode/minestom/instance/Chunk.java +++ b/src/main/java/fr/themode/minestom/instance/Chunk.java @@ -14,10 +14,10 @@ import java.util.concurrent.CopyOnWriteArraySet; public class Chunk { - private static final int CHUNK_SIZE_X = 16; - private static final int CHUNK_SIZE_Y = 256; - private static final int CHUNK_SIZE_Z = 16; - private static final int CHUNK_SIZE = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z; + public static final int CHUNK_SIZE_X = 16; + public static final int CHUNK_SIZE_Y = 256; + public static final int CHUNK_SIZE_Z = 16; + public static final int CHUNK_SIZE = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z; private Biome biome; private int chunkX, chunkZ; @@ -41,7 +41,7 @@ public class Chunk { setBlock(x, y, z, blockId, (short) 0); } - protected void setBlock(byte x, byte y, byte z, String blockId) { + protected void setCustomBlock(byte x, byte y, byte z, String blockId) { CustomBlock customBlock = Main.getBlockManager().getBlock(blockId); if (customBlock == null) throw new IllegalArgumentException("The block " + blockId + " does not exist or isn't registered"); @@ -151,15 +151,27 @@ public class Chunk { } - protected ChunkDataPacket getFreshFullDataPacket() { + public ChunkDataPacket getFreshFullDataPacket() { ChunkDataPacket fullDataPacket = new ChunkDataPacket(); fullDataPacket.chunk = this; fullDataPacket.fullChunk = true; return fullDataPacket; } + public ChunkDataPacket getFreshPartialDataPacket() { + ChunkDataPacket fullDataPacket = new ChunkDataPacket(); + fullDataPacket.chunk = this; + fullDataPacket.fullChunk = false; + return fullDataPacket; + } + protected void refreshDataPacket() { Packet packet = PacketUtils.writePacket(getFreshFullDataPacket()); this.fullDataPacket = PacketUtils.encode(packet); // TODO write packet buffer in another thread (heavy calculations) } + + @Override + public String toString() { + return "Chunk[" + chunkX + ":" + chunkZ + "]"; + } } diff --git a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java index 05f8a7eb9..a6e10e670 100644 --- a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java +++ b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java @@ -50,6 +50,7 @@ public class ChunkBatch implements BlockModifier { for (BlockData data : dataList) { data.apply(chunk); } + // System.out.println("FINISHED chunk creation " + chunk.getChunkX() + ":" + chunk.getChunkZ()); chunk.refreshDataPacket(); // TODO partial refresh instead of full instance.sendChunkUpdate(chunk); // TODO partial chunk data if (callback != null) @@ -68,7 +69,7 @@ public class ChunkBatch implements BlockModifier { if (blockIdentifier == null) { chunk.setBlock(x, y, z, blockId); } else { - chunk.setBlock(x, y, z, blockIdentifier); + chunk.setCustomBlock(x, y, z, blockIdentifier); } } diff --git a/src/main/java/fr/themode/minestom/instance/Instance.java b/src/main/java/fr/themode/minestom/instance/Instance.java index 4d3e2fe82..8fc22ebda 100644 --- a/src/main/java/fr/themode/minestom/instance/Instance.java +++ b/src/main/java/fr/themode/minestom/instance/Instance.java @@ -5,8 +5,8 @@ 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.DestroyEntitiesPacket; import fr.themode.minestom.utils.BlockPosition; +import fr.themode.minestom.utils.ChunkUtils; import fr.themode.minestom.utils.Position; import java.io.File; @@ -30,10 +30,13 @@ public abstract class Instance implements BlockModifier { this.uniqueId = uniqueId; } - public abstract void breakBlock(Player player, BlockPosition blockPosition, short blockId); + public abstract void breakBlock(Player player, BlockPosition blockPosition); public abstract void loadChunk(int chunkX, int chunkZ, Consumer callback); + // Load only if auto chunk load is enabled + public abstract void loadOptionalChunk(int chunkX, int chunkZ, Consumer callback); + public abstract Chunk getChunk(int chunkX, int chunkZ); public abstract void saveToFolder(Runnable callback); @@ -52,12 +55,18 @@ public abstract class Instance implements BlockModifier { public abstract void sendChunkUpdate(Player player, Chunk chunk); - public abstract void retrieveChunk(int chunkX, int chunkZ, Consumer callback); + protected abstract void retrieveChunk(int chunkX, int chunkZ, Consumer callback); public abstract void createChunk(int chunkX, int chunkZ, Consumer callback); public abstract void sendChunks(Player player); + public abstract void sendChunk(Player player, Chunk chunk); + + public abstract void enableAutoChunkLoad(boolean enable); + + public abstract boolean hasEnabledAutoChunkLoad(); + // protected void sendChunkUpdate(Collection players, Chunk chunk) { Buffer chunkData = chunk.getFullDataPacket(); @@ -82,11 +91,7 @@ public abstract class Instance implements BlockModifier { } public Set getChunkEntities(Chunk chunk) { - return Collections.unmodifiableSet(getEntitiesInChunk(getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()))); - } - - public void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) { - breakBlock(player, blockPosition, customBlock.getType()); + return Collections.unmodifiableSet(getEntitiesInChunk(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()))); } public void loadChunk(int chunkX, int chunkZ) { @@ -139,24 +144,21 @@ public abstract class Instance implements BlockModifier { return uniqueId; } - protected long getChunkIndex(int chunkX, int chunkZ) { - return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); - } - // UNSAFE METHODS (need most of time to be synchronized) public void addEntity(Entity entity) { Instance lastInstance = entity.getInstance(); if (lastInstance != null && lastInstance != this) { - lastInstance.removeEntity(entity); + lastInstance.removeEntity(entity); // If entity is in another instance, remove it from there and add it to this } // TODO based on distance with players - getPlayers().forEach(p -> entity.addViewer(p)); + getPlayers().forEach(p -> entity.addViewer(p)); // Add new entity to all players viewable list if (entity instanceof Player) { Player player = (Player) entity; sendChunks(player); + // Send player all current entity in the instance getObjectEntities().forEach(objectEntity -> objectEntity.addViewer(player)); getCreatures().forEach(entityCreature -> entityCreature.addViewer(player)); getPlayers().forEach(p -> p.addViewer(player)); @@ -171,28 +173,14 @@ public abstract class Instance implements BlockModifier { if (entityInstance == null || entityInstance != this) return; - entity.getViewers().forEach(p -> entity.removeViewer(p)); - - if (!(entity instanceof Player)) { - DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(); - destroyEntitiesPacket.entityIds = new int[]{entity.getEntityId()}; - - entity.getViewers().forEach(p -> p.getPlayerConnection().sendPacket(destroyEntitiesPacket)); // TODO destroy batch - } else { - // TODO optimize (cache all entities that the player see) - Player player = (Player) entity; - getObjectEntities().forEach(objectEntity -> objectEntity.removeViewer(player)); - getCreatures().forEach(entityCreature -> entityCreature.removeViewer(player)); - getPlayers().forEach(p -> p.removeViewer(player)); - - } + entity.getViewers().forEach(p -> entity.removeViewer(p)); // Remove this entity from players viewable list and send delete entities packet Chunk chunk = getChunkAt(entity.getPosition()); removeEntityFromChunk(entity, chunk); } public void addEntityToChunk(Entity entity, Chunk chunk) { - long chunkIndex = getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); + long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); Set entities = getEntitiesInChunk(chunkIndex); entities.add(entity); this.chunkEntities.put(chunkIndex, entities); @@ -206,10 +194,14 @@ public abstract class Instance implements BlockModifier { } public void removeEntityFromChunk(Entity entity, Chunk chunk) { - long chunkIndex = getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); + long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); Set entities = getEntitiesInChunk(chunkIndex); entities.remove(entity); - this.chunkEntities.put(chunkIndex, entities); + if (entities.isEmpty()) { + this.chunkEntities.remove(chunkIndex); + } else { + this.chunkEntities.put(chunkIndex, entities); + } if (entity instanceof Player) { this.players.remove(entity); } else if (entity instanceof EntityCreature) { diff --git a/src/main/java/fr/themode/minestom/instance/InstanceContainer.java b/src/main/java/fr/themode/minestom/instance/InstanceContainer.java index fa0c55b42..ae0e7a138 100644 --- a/src/main/java/fr/themode/minestom/instance/InstanceContainer.java +++ b/src/main/java/fr/themode/minestom/instance/InstanceContainer.java @@ -2,10 +2,11 @@ package fr.themode.minestom.instance; import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.entity.Player; -import fr.themode.minestom.event.BlockBreakEvent; +import fr.themode.minestom.event.PlayerBlockBreakEvent; import fr.themode.minestom.net.PacketWriter; import fr.themode.minestom.net.packet.server.play.ParticlePacket; import fr.themode.minestom.utils.BlockPosition; +import fr.themode.minestom.utils.ChunkUtils; import java.io.File; import java.util.*; @@ -25,6 +26,8 @@ public class InstanceContainer extends Instance { private ChunkGenerator chunkGenerator; private Map chunks = new ConcurrentHashMap<>(); + private boolean autoChunkLoad; + protected InstanceContainer(UUID uniqueId, File folder) { super(uniqueId); this.folder = folder; @@ -35,7 +38,7 @@ public class InstanceContainer extends Instance { Chunk chunk = getChunkAt(x, z); synchronized (chunk) { chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId); - PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + PacketWriter.writeCallbackPacket(chunk.getFreshPartialDataPacket(), buffer -> { chunk.setFullDataPacket(buffer); sendChunkUpdate(chunk); }); @@ -46,8 +49,8 @@ public class InstanceContainer extends Instance { 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); - PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + chunk.setCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId); + PacketWriter.writeCallbackPacket(chunk.getFreshPartialDataPacket(), buffer -> { chunk.setFullDataPacket(buffer); sendChunkUpdate(chunk); }); @@ -56,9 +59,16 @@ public class InstanceContainer extends Instance { // TODO deplace @Override - public void breakBlock(Player player, BlockPosition blockPosition, short blockId) { - BlockBreakEvent blockBreakEvent = new BlockBreakEvent(blockPosition); - player.callEvent(BlockBreakEvent.class, blockBreakEvent); + public void breakBlock(Player player, BlockPosition blockPosition) { + Chunk chunk = getChunkAt(blockPosition); + short blockId = chunk.getBlockId((byte) (blockPosition.getX() % 16), (byte) blockPosition.getY(), (byte) (blockPosition.getZ() % 16)); + if (blockId == 0) { + sendChunkUpdate(player, chunk); + return; + } + + PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(blockPosition); + player.callEvent(PlayerBlockBreakEvent.class, blockBreakEvent); if (!blockBreakEvent.isCancelled()) { // TODO blockbreak setBlock result int x = blockPosition.getX(); @@ -71,16 +81,16 @@ public class InstanceContainer extends Instance { particlePacket.x = x + 0.5f; particlePacket.y = y; particlePacket.z = z + 0.5f; - particlePacket.offsetX = 0.4f; - particlePacket.offsetY = 0.6f; - particlePacket.offsetZ = 0.4f; + particlePacket.offsetX = 0.45f; + particlePacket.offsetY = 0.55f; + particlePacket.offsetZ = 0.45f; particlePacket.particleData = 0.3f; - particlePacket.particleCount = 75; + particlePacket.particleCount = 100; particlePacket.blockId = blockId; player.getPlayerConnection().sendPacket(particlePacket); player.sendPacketToViewers(particlePacket); } else { - sendChunkUpdate(player, getChunkAt(blockPosition)); + sendChunkUpdate(player, chunk); } } @@ -95,9 +105,24 @@ public class InstanceContainer extends Instance { } } + @Override + public void loadOptionalChunk(int chunkX, int chunkZ, Consumer callback) { + Chunk chunk = getChunk(chunkX, chunkZ); + if (chunk != null) { + if (callback != null) + callback.accept(chunk); + } else { + if (hasEnabledAutoChunkLoad()) { + retrieveChunk(chunkX, chunkZ, callback); + } else { + callback.accept(null); + } + } + } + @Override public Chunk getChunk(int chunkX, int chunkZ) { - return chunks.get(getChunkIndex(chunkX, chunkZ)); + return chunks.get(ChunkUtils.getChunkIndex(chunkX, chunkZ)); } @Override @@ -123,54 +148,6 @@ public class InstanceContainer extends Instance { return new ChunkBatch(this, chunk); } - /*@Override - public void addEntity(Entity entity) { - Instance lastInstance = entity.getInstance(); - if (lastInstance != null && lastInstance != this) { - lastInstance.removeEntity(entity); - } - - // TODO based on distance with players - getPlayers().forEach(p -> entity.addViewer(p)); - - if (entity instanceof Player) { - Player player = (Player) entity; - sendChunks(player); - getObjectEntities().forEach(objectEntity -> objectEntity.addViewer(player)); - getCreatures().forEach(entityCreature -> entityCreature.addViewer(player)); - getPlayers().forEach(p -> p.addViewer(player)); - } - - Chunk chunk = getChunkAt(entity.getPosition()); - chunk.addEntity(entity); - } - - @Override - public void removeEntity(Entity entity) { - Instance entityInstance = entity.getInstance(); - if (entityInstance == null || entityInstance != this) - return; - - entity.getViewers().forEach(p -> entity.removeViewer(p)); - - if (!(entity instanceof Player)) { - DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(); - destroyEntitiesPacket.entityIds = new int[]{entity.getEntityId()}; - - entity.getViewers().forEach(p -> p.getPlayerConnection().sendPacket(destroyEntitiesPacket)); // TODO destroy batch - } else { - // TODO optimize (cache all entities that the player see) - Player player = (Player) entity; - getObjectEntities().forEach(objectEntity -> objectEntity.removeViewer(player)); - getCreatures().forEach(entityCreature -> entityCreature.removeViewer(player)); - getPlayers().forEach(p -> p.removeViewer(player)); - - } - - Chunk chunk = getChunkAt(entity.getPosition()); - chunk.removeEntity(entity); - }*/ - @Override public void sendChunkUpdate(Player player, Chunk chunk) { Buffer chunkData = chunk.getFullDataPacket(); @@ -180,7 +157,7 @@ public class InstanceContainer extends Instance { } @Override - public void retrieveChunk(int chunkX, int chunkZ, Consumer callback) { + protected void retrieveChunk(int chunkX, int chunkZ, Consumer callback) { if (folder != null) { // Load from file if possible CHUNK_LOADER_IO.loadChunk(chunkX, chunkZ, this, chunk -> { @@ -189,6 +166,7 @@ public class InstanceContainer extends Instance { callback.accept(chunk); }); } else { + // Folder isn't defined, create new chunk createChunk(chunkX, chunkZ, callback); } } @@ -212,40 +190,54 @@ public class InstanceContainer extends Instance { sendChunkUpdate(getPlayers(), chunk); // Update for shared instances - this.sharedInstances.forEach(sharedInstance -> { - if (!sharedInstance.getPlayers().isEmpty()) - sendChunkUpdate(sharedInstance.getPlayers(), chunk); - }); + if (!sharedInstances.isEmpty()) + this.sharedInstances.forEach(sharedInstance -> { + Set instancePlayers = sharedInstance.getPlayers(); + if (!instancePlayers.isEmpty()) + sendChunkUpdate(instancePlayers, chunk); + }); } @Override public void sendChunks(Player player) { for (Chunk chunk : getChunks()) { - Buffer chunkData = chunk.getFullDataPacket(); - if (chunkData == null) { - PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { - buffer.getData().retain(1).markReaderIndex(); - player.getPlayerConnection().sendUnencodedPacket(buffer); - buffer.getData().resetReaderIndex(); - chunk.setFullDataPacket(buffer); - }); - } else { - chunkData.getData().retain(1).markReaderIndex(); - player.getPlayerConnection().sendUnencodedPacket(chunkData); - chunkData.getData().resetReaderIndex(); - } + sendChunk(player, chunk); } } + @Override + public void sendChunk(Player player, Chunk chunk) { + Buffer chunkData = chunk.getFullDataPacket(); + if (chunkData == null) { + PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + buffer.getData().retain(1).markReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + chunk.setFullDataPacket(buffer); + }); + } else { + chunkData.getData().retain(1).markReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(chunkData); + chunkData.getData().resetReaderIndex(); + } + } + + @Override + public void enableAutoChunkLoad(boolean enable) { + this.autoChunkLoad = enable; + } + + @Override + public boolean hasEnabledAutoChunkLoad() { + return autoChunkLoad; + } + protected void addSharedInstance(SharedInstance sharedInstance) { this.sharedInstances.add(sharedInstance); } private void cacheChunk(Chunk chunk) { - //this.objectEntities.addCollection(chunk.objectEntities); - //this.creatures.addCollection(chunk.creatures); - //this.players.addCollection(chunk.players); - this.chunks.put(getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()), chunk); + this.chunks.put(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()), chunk); } public void setChunkGenerator(ChunkGenerator chunkGenerator) { diff --git a/src/main/java/fr/themode/minestom/instance/SharedInstance.java b/src/main/java/fr/themode/minestom/instance/SharedInstance.java index f3d35f5ed..76a4f7ff2 100644 --- a/src/main/java/fr/themode/minestom/instance/SharedInstance.java +++ b/src/main/java/fr/themode/minestom/instance/SharedInstance.java @@ -21,8 +21,8 @@ public class SharedInstance extends Instance { } @Override - public void breakBlock(Player player, BlockPosition blockPosition, short blockId) { - instanceContainer.breakBlock(player, blockPosition, blockId); + public void breakBlock(Player player, BlockPosition blockPosition) { + instanceContainer.breakBlock(player, blockPosition); } @Override @@ -30,6 +30,11 @@ public class SharedInstance extends Instance { instanceContainer.loadChunk(chunkX, chunkZ, callback); } + @Override + public void loadOptionalChunk(int chunkX, int chunkZ, Consumer callback) { + instanceContainer.loadOptionalChunk(chunkX, chunkZ, callback); + } + @Override public Chunk getChunk(int chunkX, int chunkZ) { return instanceContainer.getChunk(chunkX, chunkZ); @@ -60,12 +65,6 @@ public class SharedInstance extends Instance { return instanceContainer.getChunks(); } - @Override - public UUID getUniqueId() { - // FIXME: share same UUID ? - return null; - } - @Override public File getFolder() { return instanceContainer.getFolder(); @@ -96,6 +95,21 @@ public class SharedInstance extends Instance { instanceContainer.sendChunks(player); } + @Override + public void sendChunk(Player player, Chunk chunk) { + instanceContainer.sendChunk(player, chunk); + } + + @Override + public void enableAutoChunkLoad(boolean enable) { + instanceContainer.enableAutoChunkLoad(enable); + } + + @Override + public boolean hasEnabledAutoChunkLoad() { + return instanceContainer.hasEnabledAutoChunkLoad(); + } + @Override public void setBlock(int x, int y, int z, short blockId) { instanceContainer.setBlock(x, y, z, blockId); diff --git a/src/main/java/fr/themode/minestom/listener/BlockPlacementListener.java b/src/main/java/fr/themode/minestom/listener/BlockPlacementListener.java index ea095506b..5ffbeda29 100644 --- a/src/main/java/fr/themode/minestom/listener/BlockPlacementListener.java +++ b/src/main/java/fr/themode/minestom/listener/BlockPlacementListener.java @@ -1,7 +1,7 @@ package fr.themode.minestom.listener; import fr.themode.minestom.entity.Player; -import fr.themode.minestom.event.BlockPlaceEvent; +import fr.themode.minestom.event.PlayerBlockPlaceEvent; import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.net.packet.client.play.ClientPlayerBlockPlacementPacket; @@ -23,9 +23,10 @@ public class BlockPlacementListener { int offsetZ = blockFace == ClientPlayerDiggingPacket.BlockFace.NORTH ? -1 : blockFace == ClientPlayerDiggingPacket.BlockFace.SOUTH ? 1 : 0; blockPosition.add(offsetX, offsetY, offsetZ); - BlockPlaceEvent blockPlaceEvent = new BlockPlaceEvent((short) 10, blockPosition, packet.hand); - player.callEvent(BlockPlaceEvent.class, blockPlaceEvent); - if (!blockPlaceEvent.isCancelled()) { + PlayerBlockPlaceEvent playerBlockPlaceEvent = new PlayerBlockPlaceEvent((short) 10, blockPosition, packet.hand); + player.callEvent(PlayerBlockPlaceEvent.class, playerBlockPlaceEvent); + // TODO if player does not have block in hand, then call InteractEvent with blockPosition + if (!playerBlockPlaceEvent.isCancelled()) { instance.setBlock(blockPosition, "custom_block"); // TODO consume block in hand for survival players } else { diff --git a/src/main/java/fr/themode/minestom/listener/PlayerDiggingListener.java b/src/main/java/fr/themode/minestom/listener/PlayerDiggingListener.java index 1de8869f5..a3f6bd23e 100644 --- a/src/main/java/fr/themode/minestom/listener/PlayerDiggingListener.java +++ b/src/main/java/fr/themode/minestom/listener/PlayerDiggingListener.java @@ -2,7 +2,7 @@ package fr.themode.minestom.listener; import fr.themode.minestom.entity.GameMode; import fr.themode.minestom.entity.Player; -import fr.themode.minestom.event.StartDiggingEvent; +import fr.themode.minestom.event.PlayerStartDiggingEvent; import fr.themode.minestom.instance.CustomBlock; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.net.packet.client.play.ClientPlayerDiggingPacket; @@ -27,9 +27,9 @@ public class PlayerDiggingListener { if (instance != null) { CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); if (customBlock != null) { - StartDiggingEvent startDiggingEvent = new StartDiggingEvent(customBlock); - player.callEvent(StartDiggingEvent.class, startDiggingEvent); - if (!startDiggingEvent.isCancelled()) { + PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(customBlock); + player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent); + if (!playerStartDiggingEvent.isCancelled()) { player.refreshTargetBlock(customBlock, blockPosition); } addEffect(player); @@ -52,8 +52,7 @@ public class PlayerDiggingListener { } else { Instance instance = player.getInstance(); if (instance != null) { - short blockId = instance.getBlockId(blockPosition); - instance.breakBlock(player, blockPosition, blockId); + instance.breakBlock(player, blockPosition); } } break; diff --git a/src/main/java/fr/themode/minestom/listener/StatusListener.java b/src/main/java/fr/themode/minestom/listener/StatusListener.java index d64256894..89b0db5ef 100644 --- a/src/main/java/fr/themode/minestom/listener/StatusListener.java +++ b/src/main/java/fr/themode/minestom/listener/StatusListener.java @@ -1,25 +1,14 @@ package fr.themode.minestom.listener; import fr.themode.minestom.entity.Player; -import fr.themode.minestom.event.PlayerRespawnEvent; import fr.themode.minestom.net.packet.client.play.ClientStatusPacket; -import fr.themode.minestom.net.packet.server.play.RespawnPacket; public class StatusListener { public static void listener(ClientStatusPacket packet, Player player) { switch (packet.action) { case PERFORM_RESPAWN: - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.dimension = player.getDimension(); - respawnPacket.gameMode = player.getGameMode(); - respawnPacket.levelType = player.getLevelType(); - player.getPlayerConnection().sendPacket(respawnPacket); - PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(player.getPosition()); - player.callEvent(PlayerRespawnEvent.class, respawnEvent); - player.refreshIsDead(false); - player.teleport(respawnEvent.getRespawnPosition()); - player.getInventory().update(); + player.respawn(); break; case REQUEST_STATS: // TODO stats diff --git a/src/main/java/fr/themode/minestom/listener/UseEntityListener.java b/src/main/java/fr/themode/minestom/listener/UseEntityListener.java index 63a435056..685e5cefd 100644 --- a/src/main/java/fr/themode/minestom/listener/UseEntityListener.java +++ b/src/main/java/fr/themode/minestom/listener/UseEntityListener.java @@ -1,10 +1,10 @@ package fr.themode.minestom.listener; import fr.themode.minestom.entity.Entity; -import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.entity.LivingEntity; import fr.themode.minestom.entity.Player; import fr.themode.minestom.event.AttackEvent; -import fr.themode.minestom.event.InteractEvent; +import fr.themode.minestom.event.PlayerInteractEvent; import fr.themode.minestom.net.packet.client.play.ClientUseEntityPacket; public class UseEntityListener { @@ -15,17 +15,17 @@ public class UseEntityListener { return; ClientUseEntityPacket.Type type = packet.type; if (type == ClientUseEntityPacket.Type.ATTACK) { - if (entity instanceof EntityCreature && ((EntityCreature) entity).isDead()) // Can't attack dead entities + if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead()) // Can't attack dead entities return; AttackEvent attackEvent = new AttackEvent(entity); player.callEvent(AttackEvent.class, attackEvent); } else if (type == ClientUseEntityPacket.Type.INTERACT) { - InteractEvent interactEvent = new InteractEvent(entity); - player.callEvent(InteractEvent.class, interactEvent); + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand); + player.callEvent(PlayerInteractEvent.class, playerInteractEvent); } else { - InteractEvent interactEvent = new InteractEvent(entity); // TODO find difference with INTERACT - player.callEvent(InteractEvent.class, interactEvent); + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand); // TODO find difference with INTERACT + player.callEvent(PlayerInteractEvent.class, playerInteractEvent); } } diff --git a/src/main/java/fr/themode/minestom/listener/UseItemListener.java b/src/main/java/fr/themode/minestom/listener/UseItemListener.java index 658a89477..da1ceb90e 100644 --- a/src/main/java/fr/themode/minestom/listener/UseItemListener.java +++ b/src/main/java/fr/themode/minestom/listener/UseItemListener.java @@ -1,7 +1,7 @@ package fr.themode.minestom.listener; import fr.themode.minestom.entity.Player; -import fr.themode.minestom.event.UseItemEvent; +import fr.themode.minestom.event.PlayerUseItemEvent; import fr.themode.minestom.inventory.PlayerInventory; import fr.themode.minestom.item.ItemStack; import fr.themode.minestom.net.packet.client.play.ClientUseItemPacket; @@ -12,8 +12,8 @@ public class UseItemListener { PlayerInventory inventory = player.getInventory(); Player.Hand hand = packet.hand; ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand(); - UseItemEvent useItemEvent = new UseItemEvent(hand, itemStack); - player.callEvent(UseItemEvent.class, useItemEvent); + PlayerUseItemEvent playerUseItemEvent = new PlayerUseItemEvent(hand, itemStack); + player.callEvent(PlayerUseItemEvent.class, playerUseItemEvent); // TODO check if item in main or off hand is food or item with animation (bow/crossbow/riptide) // TODO in material enum? 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 255e4b6ca..4ede546aa 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 @@ -80,7 +80,7 @@ public class LoginStartPacket implements ClientPreplayPacket { joinGamePacket.dimension = dimension; joinGamePacket.maxPlayers = 0; // Unused joinGamePacket.levelType = levelType; - joinGamePacket.viewDistance = 14; + joinGamePacket.viewDistance = Main.CHUNK_VIEW_DISTANCE; joinGamePacket.reducedDebugInfo = false; connection.sendPacket(joinGamePacket); diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java index 9a66f68d2..9d76e29b1 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java @@ -82,11 +82,11 @@ public class ChunkDataPacket implements ServerPacket { Utils.writeVarInt(buffer, blockEntities.size()); for (Integer index : blockEntities) { - BlockPosition blockPosition = SerializerUtils.indexToBlockPosition(index, chunk.getChunkX(), chunk.getChunkZ()); + BlockPosition blockPosition = SerializerUtils.indexToChunkBlockPosition(index); CompoundTag blockEntity = new CompoundTag(); - blockEntity.put("x", new DoubleTag(blockPosition.getX())); + blockEntity.put("x", new DoubleTag(blockPosition.getX() + 16 * chunk.getChunkX())); blockEntity.put("y", new DoubleTag(blockPosition.getY())); - blockEntity.put("z", new DoubleTag(blockPosition.getZ())); + blockEntity.put("z", new DoubleTag(blockPosition.getZ() + 16 * chunk.getChunkZ())); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { blockEntity.serialize(new DataOutputStream(os), 100); diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPlayerPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPlayerPacket.java index 21749dc65..6541fcceb 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPlayerPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPlayerPacket.java @@ -12,6 +12,7 @@ public class SpawnPlayerPacket implements ServerPacket { public int entityId; public UUID playerUuid; public Position position; + public Buffer metadata; @Override public void write(Buffer buffer) { @@ -23,7 +24,11 @@ public class SpawnPlayerPacket implements ServerPacket { buffer.putDouble(position.getZ()); buffer.putByte((byte) (position.getYaw() * 256f / 360f)); buffer.putByte((byte) (position.getPitch() * 256f / 360f)); - buffer.putByte((byte) 0xff); // TODO Metadata + if (metadata != null) { + buffer.putBuffer(metadata); + } else { + buffer.putByte((byte) 0xff); + } } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/UnloadChunkPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/UnloadChunkPacket.java new file mode 100644 index 000000000..3408c282b --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/UnloadChunkPacket.java @@ -0,0 +1,20 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; + +public class UnloadChunkPacket implements ServerPacket { + + public int chunkX, chunkZ; + + @Override + public void write(Buffer buffer) { + buffer.putInt(chunkX); + buffer.putInt(chunkZ); + } + + @Override + public int getId() { + return 0x1D; + } +} diff --git a/src/main/java/fr/themode/minestom/utils/ChunkUtils.java b/src/main/java/fr/themode/minestom/utils/ChunkUtils.java new file mode 100644 index 000000000..9bf1f2bd3 --- /dev/null +++ b/src/main/java/fr/themode/minestom/utils/ChunkUtils.java @@ -0,0 +1,35 @@ +package fr.themode.minestom.utils; + +import fr.themode.minestom.Main; + +public class ChunkUtils { + + public static long getChunkIndex(int chunkX, int chunkZ) { + return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); + } + + public static int[] getChunkCoord(long index) { + int chunkX = (int) (index >> 32); + int chunkZ = (int) index; + return new int[]{chunkX, chunkZ}; + } + + public static long[] getVisibleChunks(final Position position) { + final int viewDistance = Main.CHUNK_VIEW_DISTANCE; + + long[] visibleChunks = new long[MathUtils.square(viewDistance + 1)]; + final int startLoop = -(viewDistance / 2); + final int endLoop = viewDistance / 2 + 1; + int counter = 0; + for (int x = startLoop; x < endLoop; x++) { + for (int z = startLoop; z < endLoop; z++) { + int chunkX = Math.floorDiv((int) (position.getX() + 16 * x), 16); + int chunkZ = Math.floorDiv((int) (position.getZ() + 16 * z), 16); + visibleChunks[counter] = getChunkIndex(chunkX, chunkZ); + counter++; + } + } + return visibleChunks; + } + +} diff --git a/src/main/java/fr/themode/minestom/utils/MathUtils.java b/src/main/java/fr/themode/minestom/utils/MathUtils.java index d0a052098..d432c5c77 100644 --- a/src/main/java/fr/themode/minestom/utils/MathUtils.java +++ b/src/main/java/fr/themode/minestom/utils/MathUtils.java @@ -2,6 +2,10 @@ package fr.themode.minestom.utils; public class MathUtils { + public static int square(int num) { + return num * num; + } + public static float square(float num) { return num * num; } diff --git a/src/main/java/fr/themode/minestom/utils/Position.java b/src/main/java/fr/themode/minestom/utils/Position.java index 2a3cc8dff..16b7bf785 100644 --- a/src/main/java/fr/themode/minestom/utils/Position.java +++ b/src/main/java/fr/themode/minestom/utils/Position.java @@ -21,10 +21,11 @@ public class Position { this(0, 0, 0); } - public void add(float x, float y, float z) { + public Position add(float x, float y, float z) { this.x += x; this.y += y; this.z += z; + return this; } public float getDistance(Position position) { diff --git a/src/main/java/fr/themode/minestom/utils/SerializerUtils.java b/src/main/java/fr/themode/minestom/utils/SerializerUtils.java index 5a313b432..01a050bd9 100644 --- a/src/main/java/fr/themode/minestom/utils/SerializerUtils.java +++ b/src/main/java/fr/themode/minestom/utils/SerializerUtils.java @@ -32,11 +32,22 @@ public class SerializerUtils { return new byte[]{x, y, z}; } - public static BlockPosition indexToBlockPosition(int index, int chunkX, int chunkZ) { + public static BlockPosition indexToChunkBlockPosition(int index) { byte z = (byte) (index >> 12 & 0xF); byte y = (byte) (index >> 4 & 0xFF); byte x = (byte) (index >> 0 & 0xF); - return new BlockPosition(x + 16 * chunkX, y, z + 16 * chunkZ); + return new BlockPosition(x, y, z); + } + + public static long positionToLong(int x, int y, int z) { + return (((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF); + } + + public static BlockPosition longToBlockPosition(long value) { + int x = (int) (value >> 38); + int y = (int) (value & 0xFFF); + int z = (int) (value << 26 >> 38); + return new BlockPosition(x, y, z); } } diff --git a/src/main/java/fr/themode/minestom/utils/Utils.java b/src/main/java/fr/themode/minestom/utils/Utils.java index 783867d27..18fb4ce72 100644 --- a/src/main/java/fr/themode/minestom/utils/Utils.java +++ b/src/main/java/fr/themode/minestom/utils/Utils.java @@ -121,7 +121,7 @@ public class Utils { } public static void writePosition(Buffer buffer, int x, int y, int z) { - buffer.putLong((((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF)); + buffer.putLong(SerializerUtils.positionToLong(x, y, z)); } public static void writePosition(Buffer buffer, BlockPosition blockPosition) { @@ -129,11 +129,7 @@ public class Utils { } public static BlockPosition readPosition(Buffer buffer) { - long val = buffer.getLong(); - int x = (int) (val >> 38); - int y = (int) (val & 0xFFF); - int z = (int) (val << 26 >> 38); - return new BlockPosition(x, y, z); + return SerializerUtils.longToBlockPosition(buffer.getLong()); } public static void writeUuid(Buffer buffer, UUID uuid) {