diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java index b3e826684..81a9993ed 100644 --- a/src/main/java/fr/themode/minestom/Main.java +++ b/src/main/java/fr/themode/minestom/Main.java @@ -8,7 +8,6 @@ import fr.adamaq01.ozao.net.server.backend.tcp.TCPServer; import fr.themode.minestom.entity.EntityManager; import fr.themode.minestom.entity.Player; import fr.themode.minestom.instance.BlockManager; -import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.InstanceManager; import fr.themode.minestom.instance.demo.StoneBlock; import fr.themode.minestom.listener.PacketListenerManager; @@ -30,8 +29,8 @@ public class Main { 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 + public static final int CHUNK_VIEW_DISTANCE = 5; + public static final int ENTITY_VIEW_DISTANCE = 2; // Networking private static ConnectionManager connectionManager; @@ -68,16 +67,9 @@ public class Main { Player player = connectionManager.getPlayer(packetProcessor.getPlayerConnection(connection)); if (player != null) { - Instance instance = player.getInstance(); - - - if (instance != null) { - instance.removeEntity(player); - } - player.remove(); - connectionManager.removePlayer(packetProcessor.getPlayerConnection(connection)); + connectionManager.removePlayer(player.getPlayerConnection()); } packetProcessor.removePlayerConnection(connection); } diff --git a/src/main/java/fr/themode/minestom/Viewable.java b/src/main/java/fr/themode/minestom/Viewable.java index bb82c936e..4063e7f38 100644 --- a/src/main/java/fr/themode/minestom/Viewable.java +++ b/src/main/java/fr/themode/minestom/Viewable.java @@ -54,19 +54,25 @@ public interface Viewable { default void sendPacketToViewersAndSelf(ServerPacket packet) { if (this instanceof Player) { - PacketWriter.writeCallbackPacket(packet, buffer -> { - int size = getViewers().size(); - buffer.getData().retain(size + 1).markReaderIndex(); - ((Player) this).getPlayerConnection().writeUnencodedPacket(buffer); - buffer.getData().resetReaderIndex(); - if (size != 0) { - for (Player viewer : getViewers()) { - buffer.getData().resetReaderIndex(); - viewer.getPlayerConnection().writeUnencodedPacket(buffer); - } - } - }); + UNSAFE_sendPacketToViewersAndSelf(packet); + } else { + sendPacketToViewers(packet); } } + private void UNSAFE_sendPacketToViewersAndSelf(ServerPacket packet) { + PacketWriter.writeCallbackPacket(packet, buffer -> { + int size = getViewers().size(); + buffer.getData().retain(size + 1).markReaderIndex(); + ((Player) this).getPlayerConnection().writeUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + if (size != 0) { + for (Player viewer : getViewers()) { + buffer.getData().resetReaderIndex(); + viewer.getPlayerConnection().writeUnencodedPacket(buffer); + } + } + }); + } + } diff --git a/src/main/java/fr/themode/minestom/chat/Chat.java b/src/main/java/fr/themode/minestom/chat/Chat.java index 9c622ed06..3842f2a2f 100644 --- a/src/main/java/fr/themode/minestom/chat/Chat.java +++ b/src/main/java/fr/themode/minestom/chat/Chat.java @@ -2,7 +2,14 @@ package fr.themode.minestom.chat; public class Chat { + public static String rawText(String text) { return "{\"text\": \"" + text + "\"}"; } + + /** + * Different types of chat message: + * Colored one (simplest) + * Colored + event (hover/click) + */ } diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java index 2d08a5d77..61ddc9471 100644 --- a/src/main/java/fr/themode/minestom/entity/Entity.java +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -11,9 +11,8 @@ import fr.themode.minestom.event.Event; import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.net.packet.server.play.*; -import fr.themode.minestom.utils.Position; -import fr.themode.minestom.utils.Utils; import fr.themode.minestom.utils.Vector; +import fr.themode.minestom.utils.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -60,7 +59,7 @@ public abstract class Entity implements Viewable, DataContainer { protected long velocityTime; // Reset velocity to 0 after countdown // Synchronization - private long synchronizationDelay = 2000; // In ms + private long synchronizationDelay = 1500; // In ms private long lastSynchronizationTime; // Metadata @@ -107,7 +106,7 @@ public abstract class Entity implements Viewable, DataContainer { public void teleport(Position position, Runnable callback) { if (instance == null) - return; + throw new IllegalStateException("You need to use Entity#setInstance before teleporting an entity!"); Runnable runnable = () -> { refreshPosition(position.getX(), position.getY(), position.getZ()); @@ -219,7 +218,7 @@ public abstract class Entity implements Viewable, DataContainer { sendPositionSynchronization(); } - this.lastUpdate = System.currentTimeMillis(); + this.lastUpdate = time; } if (shouldRemove()) { @@ -300,23 +299,31 @@ public abstract class Entity implements Viewable, DataContainer { return getPosition().getDistance(entity.getPosition()); } + public Entity getVehicle() { + return vehicle; + } + public void addPassenger(Entity entity) { - // TODO if entity already has a vehicle, leave it before? + if (instance == null) + throw new IllegalStateException("You need to set an instance using Entity#setInstance"); + if (entity.getVehicle() != null) { + entity.getVehicle().removePassenger(entity); + } + this.passengers.add(entity); entity.vehicle = this; - if (instance != null) { - SetPassengersPacket passengersPacket = new SetPassengersPacket(); - passengersPacket.vehicleEntityId = getEntityId(); - int[] passengers = new int[this.passengers.size()]; - int counter = 0; - for (Entity passenger : this.passengers) { - passengers[counter++] = passenger.getEntityId(); - } + sendPassengersPacket(); + } - passengersPacket.passengersId = passengers; - sendPacketToViewers(passengersPacket); - } + public void removePassenger(Entity entity) { + if (instance == null) + throw new IllegalStateException("You need to set an instance using Entity#setInstance"); + if (!passengers.contains(entity)) + return; + this.passengers.remove(entity); + entity.vehicle = null; + sendPassengersPacket(); } public boolean hasPassenger() { @@ -327,6 +334,20 @@ public abstract class Entity implements Viewable, DataContainer { return Collections.unmodifiableSet(passengers); } + protected void sendPassengersPacket() { + SetPassengersPacket passengersPacket = new SetPassengersPacket(); + passengersPacket.vehicleEntityId = getEntityId(); + + int[] passengers = new int[this.passengers.size()]; + int counter = 0; + for (Entity passenger : this.passengers) { + passengers[counter++] = passenger.getEntityId(); + } + + passengersPacket.passengersId = passengers; + sendPacketToViewersAndSelf(passengersPacket); + } + public void triggerStatus(byte status) { EntityStatusPacket statusPacket = new EntityStatusPacket(); statusPacket.entityId = getEntityId(); @@ -373,7 +394,44 @@ public abstract class Entity implements Viewable, DataContainer { if (this instanceof Player) ((Player) this).onChunkChange(lastChunk, newChunk); // Refresh loaded chunk - // TODO compare with viewers and remove if too far away + // Refresh entity viewable list + long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), Main.ENTITY_VIEW_DISTANCE); + long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), Main.ENTITY_VIEW_DISTANCE); + + int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity); + for (int index : oldChunksEntity) { + int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunksEntity[index]); + Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + if (chunk == null) + continue; + instance.getChunkEntities(chunk).forEach(entity -> { + if (entity instanceof Player) { + Player player = (Player) entity; + removeViewer(player); + if (this instanceof Player) { + player.removeViewer((Player) this); + } + } + }); + } + + int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity); + for (int index : newChunksEntity) { + int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunksEntity[index]); + Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + if (chunk == null) + continue; + instance.getChunkEntities(chunk).forEach(entity -> { + if (entity instanceof Player) { + Player player = (Player) entity; + addViewer(player); + if (this instanceof Player) { + player.addViewer((Player) this); + } + } + }); + } + } } } @@ -448,12 +506,8 @@ public abstract class Entity implements Viewable, DataContainer { EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket(); metaDataPacket.entityId = getEntityId(); metaDataPacket.data = buffer; - if (this instanceof Player) { - Player player = (Player) this; - player.sendPacketToViewersAndSelf(metaDataPacket); - } else { - sendPacketToViewers(metaDataPacket); - } + + sendPacketToViewersAndSelf(metaDataPacket); } private void fillMetadataIndex(Buffer buffer, int index) { diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java index 89c8c17e8..850cc0996 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityManager.java +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -42,7 +42,7 @@ public class EntityManager { Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstanceContainer() : loginEvent.getSpawningInstance(); Position position = playerCache.getPosition(); - long[] visibleChunks = ChunkUtils.getVisibleChunks(position); + long[] visibleChunks = ChunkUtils.getChunksInRange(position, Main.CHUNK_VIEW_DISTANCE); for (int i = 0; i < visibleChunks.length; i++) { int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunks[i]); int chunkX = chunkPos[0]; diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index 8fc182c35..2c9de4b53 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -142,7 +142,7 @@ public class Player extends LivingEntity { setEventCallback(PlayerSpawnEvent.class, event -> { System.out.println("SPAWN"); - setGameMode(GameMode.CREATIVE); + setGameMode(GameMode.SURVIVAL); teleport(new Position(0, 66, 0)); /*ChickenCreature chickenCreature = new ChickenCreature(); @@ -434,12 +434,9 @@ public class Player extends LivingEntity { float dz = newChunk.getChunkZ() - lastChunk.getChunkZ(); double distance = Math.sqrt(dx * dx + dz * dz); boolean isFar = distance >= Main.CHUNK_VIEW_DISTANCE / 2; - if (isFar) { - updatePlayerPosition(); - } - long[] lastVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ())); - long[] updatedVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ())); + long[] lastVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), Main.CHUNK_VIEW_DISTANCE); + long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), Main.CHUNK_VIEW_DISTANCE); int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks); @@ -461,19 +458,18 @@ public class Player extends LivingEntity { int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunks[index]); instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> { if (chunk == null) { - return; // Cannot load chunk (autoload not enabled) + return; // Cannot load chunk (auto load not enabled) } instance.sendChunk(this, chunk); - if (isFar && isLast) + if (isFar && isLast) { updatePlayerPosition(); + } }); } } @Override public void teleport(Position position, Runnable callback) { - if (instance == null) - return; super.teleport(position, () -> { if (!instance.hasEnabledAutoChunkLoad() && isChunkUnloaded(position.getX(), position.getZ())) return; @@ -585,7 +581,7 @@ public class Player extends LivingEntity { } OpenWindowPacket openWindowPacket = new OpenWindowPacket(); - openWindowPacket.windowId = inventory.getUniqueId(); + openWindowPacket.windowId = inventory.getWindowId(); openWindowPacket.windowType = inventory.getInventoryType().getWindowType(); openWindowPacket.title = inventory.getTitle(); playerConnection.sendPacket(openWindowPacket); @@ -599,7 +595,7 @@ public class Player extends LivingEntity { if (openInventory == null) { closeWindowPacket.windowId = 0; } else { - closeWindowPacket.windowId = openInventory.getUniqueId(); + closeWindowPacket.windowId = openInventory.getWindowId(); openInventory.removeViewer(this); refreshOpenInventory(null); } @@ -623,7 +619,7 @@ public class Player extends LivingEntity { return equipmentPacket; } - protected void updateViewPosition(Chunk chunk) { + public void updateViewPosition(Chunk chunk) { UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(chunk); playerConnection.sendPacket(updateViewPositionPacket); } diff --git a/src/main/java/fr/themode/minestom/instance/BlockBatch.java b/src/main/java/fr/themode/minestom/instance/BlockBatch.java index 5c869e974..f0858ad81 100644 --- a/src/main/java/fr/themode/minestom/instance/BlockBatch.java +++ b/src/main/java/fr/themode/minestom/instance/BlockBatch.java @@ -51,16 +51,22 @@ public class BlockBatch implements BlockModifier { this.data.put(chunk, blockData); } - public void flush() { + public void flush(Runnable callback) { + int counter = 0; for (Map.Entry> entry : data.entrySet()) { + counter++; Chunk chunk = entry.getKey(); List dataList = entry.getValue(); + boolean isLast = counter == data.size(); batchesPool.execute(() -> { synchronized (chunk) { for (BlockData data : dataList) { data.apply(chunk); } - instance.sendChunkUpdate(chunk); // TODO partial chunk data + chunk.refreshDataPacket(); + instance.sendChunkUpdate(chunk); + if (isLast && callback != null) + callback.run(); } }); } diff --git a/src/main/java/fr/themode/minestom/instance/BlockModifier.java b/src/main/java/fr/themode/minestom/instance/BlockModifier.java index 59ce41af8..c6de21d88 100644 --- a/src/main/java/fr/themode/minestom/instance/BlockModifier.java +++ b/src/main/java/fr/themode/minestom/instance/BlockModifier.java @@ -1,6 +1,7 @@ package fr.themode.minestom.instance; import fr.themode.minestom.utils.BlockPosition; +import fr.themode.minestom.utils.Position; public interface BlockModifier { @@ -12,8 +13,16 @@ public interface BlockModifier { setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId); } + default void setBlock(Position position, short blockId) { + setBlock(position.toBlockPosition(), blockId); + } + default void setBlock(BlockPosition blockPosition, String blockId) { setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId); } + default void setBlock(Position position, String blockId) { + setBlock(position.toBlockPosition(), blockId); + } + } diff --git a/src/main/java/fr/themode/minestom/instance/Chunk.java b/src/main/java/fr/themode/minestom/instance/Chunk.java index 4e436e88c..66ee9fcd1 100644 --- a/src/main/java/fr/themode/minestom/instance/Chunk.java +++ b/src/main/java/fr/themode/minestom/instance/Chunk.java @@ -7,8 +7,9 @@ import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; import fr.themode.minestom.utils.PacketUtils; import fr.themode.minestom.utils.SerializerUtils; -import java.io.*; -import java.nio.file.Files; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -34,7 +35,6 @@ public class Chunk { this.biome = biome; this.chunkX = chunkX; this.chunkZ = chunkZ; - //refreshDataPacket(); // TODO remove } protected void setBlock(byte x, byte y, byte z, short blockId) { @@ -132,25 +132,6 @@ public class Chunk { return result; } - protected void loadFromFile(File file) throws IOException { - System.out.println("LOAD FROM FILE"); - byte[] array = Files.readAllBytes(file.toPath()); - DataInputStream stream = new DataInputStream(new ByteArrayInputStream(array)); - this.chunkX = stream.readInt(); - this.chunkZ = stream.readInt(); - System.out.println("chunk: " + chunkX + " : " + chunkZ); - try { - while (true) { - int index = stream.readInt(); - boolean isCustomBlock = stream.readBoolean(); - short block = stream.readShort(); - } - } catch (EOFException e) { - System.out.println("END"); - } - - } - public ChunkDataPacket getFreshFullDataPacket() { ChunkDataPacket fullDataPacket = new ChunkDataPacket(); fullDataPacket.chunk = this; diff --git a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java index a6e10e670..86660d403 100644 --- a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java +++ b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java @@ -8,6 +8,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; +/** + * Use chunk coordinate (0-16) instead of world's + */ public class ChunkBatch implements BlockModifier { private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_BATCH); @@ -50,9 +53,9 @@ 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 + + chunk.refreshDataPacket(); + instance.sendChunkUpdate(chunk); if (callback != null) callback.accept(chunk); }); diff --git a/src/main/java/fr/themode/minestom/instance/Instance.java b/src/main/java/fr/themode/minestom/instance/Instance.java index 8fc22ebda..78ad6834c 100644 --- a/src/main/java/fr/themode/minestom/instance/Instance.java +++ b/src/main/java/fr/themode/minestom/instance/Instance.java @@ -1,6 +1,7 @@ package fr.themode.minestom.instance; import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.Main; import fr.themode.minestom.entity.Entity; import fr.themode.minestom.entity.EntityCreature; import fr.themode.minestom.entity.ObjectEntity; @@ -152,16 +153,20 @@ public abstract class Instance implements BlockModifier { 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)); // 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)); + + // Send player all visible entities + long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entity.getPosition(), Main.ENTITY_VIEW_DISTANCE); + for (long chunkIndex : visibleChunksEntity) { + getEntitiesInChunk(chunkIndex).forEach(ent -> { + ent.addViewer(player); + if (ent instanceof Player) { + player.addViewer((Player) ent); + } + }); + } } Chunk chunk = getChunkAt(entity.getPosition()); diff --git a/src/main/java/fr/themode/minestom/instance/InstanceContainer.java b/src/main/java/fr/themode/minestom/instance/InstanceContainer.java index ae0e7a138..f4b4efa12 100644 --- a/src/main/java/fr/themode/minestom/instance/InstanceContainer.java +++ b/src/main/java/fr/themode/minestom/instance/InstanceContainer.java @@ -207,19 +207,15 @@ public class InstanceContainer extends Instance { @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(); + PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + buffer.getData().retain(1).markReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + }); + // TODO use cached chunk data + /*chunkData.getData().retain(1).markReaderIndex(); player.getPlayerConnection().sendUnencodedPacket(chunkData); - chunkData.getData().resetReaderIndex(); - } + chunkData.getData().resetReaderIndex();*/ } @Override diff --git a/src/main/java/fr/themode/minestom/inventory/Inventory.java b/src/main/java/fr/themode/minestom/inventory/Inventory.java index 6742aee18..7e03c648e 100644 --- a/src/main/java/fr/themode/minestom/inventory/Inventory.java +++ b/src/main/java/fr/themode/minestom/inventory/Inventory.java @@ -50,7 +50,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View return title; } - public int getUniqueId() { + public int getWindowId() { return id; } @@ -77,7 +77,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View return Arrays.copyOf(itemStacks, itemStacks.length); } - public void updateItems() { + public void update() { WindowItemsPacket windowItemsPacket = getWindowItemsPacket(); getViewers().forEach(p -> p.getPlayerConnection().sendPacket(windowItemsPacket)); } @@ -117,7 +117,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View private WindowItemsPacket getWindowItemsPacket() { WindowItemsPacket windowItemsPacket = new WindowItemsPacket(); - windowItemsPacket.windowId = getUniqueId(); + windowItemsPacket.windowId = getWindowId(); windowItemsPacket.count = (short) itemStacks.length; windowItemsPacket.items = itemStacks; return windowItemsPacket; diff --git a/src/main/java/fr/themode/minestom/inventory/InventoryProperty.java b/src/main/java/fr/themode/minestom/inventory/InventoryProperty.java new file mode 100644 index 000000000..780ceb3a9 --- /dev/null +++ b/src/main/java/fr/themode/minestom/inventory/InventoryProperty.java @@ -0,0 +1,40 @@ +package fr.themode.minestom.inventory; + +public enum InventoryProperty { + + FURNACE_FIRE_ICON((short) 0), + FURNACE_MAXIMUM_FUEL_BURN_TIME((short) 1), + FURNACE_PROGRESS_ARROW((short) 2), + FURNACE_MAXIMUM_PROGRESS((short) 3), + + ENCHANTMENT_TABLE_LEVEL_REQUIREMENT_TOP((short) 0), + ENCHANTMENT_TABLE_LEVEL_REQUIREMENT_MIDDLE((short) 1), + ENCHANTMENT_TABLE_LEVEL_REQUIREMENT_BOTTOM((short) 2), + ENCHANTMENT_TABLE_SEED((short) 3), + ENCHANTMENT_TABLE_ENCH_ID_TOP((short) 4), + ENCHANTMENT_TABLE_ENCH_ID_MIDDLE((short) 5), + ENCHANTMENT_TABLE_ENCH_ID_BOTTOM((short) 6), + ENCHANTMENT_TABLE_ENCH_LEVEL_TOP((short) 7), + ENCHANTMENT_TABLE_ENCH_LEVEL_MIDDLE((short) 8), + ENCHANTMENT_TABLE_ENCH_LEVEL_BOTTOM((short) 9), + + BEACON_POWER_LEVEL((short) 0), + BEACON_FIRST_POTION((short) 1), + BEACON_SECOND_POTION((short) 2), + + ANVIL_REPAIR_COST((short) 0), + + BREWING_STAND_BREW_TIME((short) 0), + BREWING_STAND_FUEL_TIME((short) 1); + + + private short property; + + InventoryProperty(short property) { + this.property = property; + } + + public short getProperty() { + return property; + } +} diff --git a/src/main/java/fr/themode/minestom/inventory/type/VillagerInventory.java b/src/main/java/fr/themode/minestom/inventory/type/VillagerInventory.java new file mode 100644 index 000000000..110daf8e8 --- /dev/null +++ b/src/main/java/fr/themode/minestom/inventory/type/VillagerInventory.java @@ -0,0 +1,91 @@ +package fr.themode.minestom.inventory.type; + +import fr.themode.minestom.inventory.Inventory; +import fr.themode.minestom.inventory.InventoryType; +import fr.themode.minestom.net.packet.server.play.TradeListPacket; +import fr.themode.minestom.utils.ArrayUtils; + +public class VillagerInventory extends Inventory { + + protected TradeListPacket tradeListPacket; + + public VillagerInventory(String title) { + super(InventoryType.MERCHANT, title); + setupPacket(); + } + + public TradeListPacket.Trade[] getTrades() { + return tradeListPacket.trades; + } + + public void addTrade(TradeListPacket.Trade trade) { + TradeListPacket.Trade[] oldTrades = getTrades(); + int length = oldTrades.length + 1; + TradeListPacket.Trade[] trades = new TradeListPacket.Trade[length]; + System.arraycopy(oldTrades, 0, trades, 0, oldTrades.length); + trades[length] = trade; + this.tradeListPacket.trades = trades; + update(); + } + + public void removeTrade(int index) { + TradeListPacket.Trade[] oldTrades = getTrades(); + int length = oldTrades.length - 1; + TradeListPacket.Trade[] trades = new TradeListPacket.Trade[length]; + ArrayUtils.removeElement(trades, index); + this.tradeListPacket.trades = trades; + update(); + } + + public int getVillagerLevel() { + return tradeListPacket.villagerLevel; + } + + public void setVillagerLevel(int level) { + this.tradeListPacket.villagerLevel = level; + update(); + } + + public int getExperience() { + return tradeListPacket.experience; + } + + public void setExperience(int experience) { + this.tradeListPacket.experience = experience; + update(); + } + + public boolean isRegularVillager() { + return tradeListPacket.regularVillager; + } + + public void setRegularVillager(boolean regularVillager) { + this.tradeListPacket.regularVillager = regularVillager; + update(); + } + + public boolean canRestock() { + return tradeListPacket.canRestock; + } + + public void setCanRestock(boolean canRestock) { + this.tradeListPacket.canRestock = canRestock; + update(); + } + + @Override + public void update() { + super.update(); + sendPacketToViewers(tradeListPacket); // Refresh window + } + + private void setupPacket() { + this.tradeListPacket = new TradeListPacket(); + this.tradeListPacket.windowId = getWindowId(); + this.tradeListPacket.trades = new TradeListPacket.Trade[0]; + this.tradeListPacket.villagerLevel = 0; + this.tradeListPacket.experience = 0; + this.tradeListPacket.regularVillager = false; + this.tradeListPacket.canRestock = false; + } +} diff --git a/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java b/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java index 6525e1ace..c12b9211c 100644 --- a/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java +++ b/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java @@ -7,8 +7,9 @@ import fr.themode.minestom.net.packet.client.play.ClientChatMessagePacket; public class ChatMessageListener { public static void listener(ClientChatMessagePacket packet, Player player) { - // TODO commands check - Main.getConnectionManager().getOnlinePlayers().forEach(p -> p.sendMessage(String.format("<%s> %s", player.getUsername(), packet.message))); + String message = packet.message; + Main.getConnectionManager().getOnlinePlayers().forEach(p -> p.sendMessage(String.format("<%s> %s", player.getUsername(), message))); + } } diff --git a/src/main/java/fr/themode/minestom/net/ConnectionManager.java b/src/main/java/fr/themode/minestom/net/ConnectionManager.java index 4b1dd47ac..8e179df61 100644 --- a/src/main/java/fr/themode/minestom/net/ConnectionManager.java +++ b/src/main/java/fr/themode/minestom/net/ConnectionManager.java @@ -4,11 +4,12 @@ import fr.themode.minestom.entity.Player; import fr.themode.minestom.net.player.PlayerConnection; import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; public class ConnectionManager { - private volatile Set players = new HashSet<>(); - private volatile Map connectionPlayerMap = new HashMap<>(); + private Set players = new CopyOnWriteArraySet<>(); + private Map connectionPlayerMap = Collections.synchronizedMap(new HashMap<>()); public Player getPlayer(PlayerConnection connection) { return connectionPlayerMap.get(connection); diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java index 597ea5aba..2b3953143 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java @@ -15,7 +15,10 @@ public class ClientPacketsHandler { } public ClientPacket getPacketInstance(int id) { - return constructorAccessMap.get(id).newInstance(); + ClientPacket packet = constructorAccessMap.get(id).newInstance(); + if (packet == null) + System.err.println("Packet id 0x" + Integer.toHexString(id) + " isn't registered!"); + return packet; } } diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ExplosionPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ExplosionPacket.java new file mode 100644 index 000000000..7f42c6f7b --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ExplosionPacket.java @@ -0,0 +1,31 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; + +public class ExplosionPacket implements ServerPacket { + + public float x, y, z; + public float radius; // UNUSED + public byte[] records; + public float playerMotionX, playerMotionY, playerMotionZ; + + @Override + public void write(Buffer buffer) { + buffer.putFloat(x); + buffer.putFloat(y); + buffer.putFloat(z); + buffer.putFloat(radius); + buffer.putInt(records.length); + for (byte record : records) + buffer.putByte(record); + buffer.putFloat(playerMotionX); + buffer.putFloat(playerMotionY); + buffer.putFloat(playerMotionZ); + } + + @Override + public int getId() { + return 0x1C; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/TradeListPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/TradeListPacket.java new file mode 100644 index 000000000..2576ec613 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/TradeListPacket.java @@ -0,0 +1,66 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.item.ItemStack; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +public class TradeListPacket implements ServerPacket { + + public int windowId; + public Trade[] trades; + public int villagerLevel; + public int experience; + public boolean regularVillager; + public boolean canRestock; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, windowId); + buffer.putByte((byte) trades.length); + for (Trade trade : trades) { + trade.write(buffer); + } + Utils.writeVarInt(buffer, villagerLevel); + Utils.writeVarInt(buffer, experience); + buffer.putBoolean(regularVillager); + buffer.putBoolean(canRestock); + } + + @Override + public int getId() { + return 0x27; + } + + public static class Trade { + + public ItemStack inputItem1; + public ItemStack result; + public ItemStack inputItem2; + public boolean tradeDisabled; + public int tradeUsesNumber; + public int maxTradeUsesNumber; + public int exp; + public int specialPrice; + public float priceMultiplier; + public int demand; + + + private void write(Buffer buffer) { + Utils.writeItemStack(buffer, inputItem1); + Utils.writeItemStack(buffer, result); + buffer.putBoolean(inputItem2 != null); + if (inputItem2 != null) + Utils.writeItemStack(buffer, inputItem2); + buffer.putBoolean(tradeDisabled); + buffer.putInt(tradeUsesNumber); + buffer.putInt(maxTradeUsesNumber); + buffer.putInt(exp); + buffer.putInt(specialPrice); + buffer.putFloat(priceMultiplier); + buffer.putInt(demand); + } + + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/WindowPropertyPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/WindowPropertyPacket.java new file mode 100644 index 000000000..e1fad3dd4 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/WindowPropertyPacket.java @@ -0,0 +1,23 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; + +public class WindowPropertyPacket implements ServerPacket { + + public byte windowId; + public short property; + public short value; + + @Override + public void write(Buffer buffer) { + buffer.putByte(windowId); + buffer.putShort(property); + buffer.putShort(value); + } + + @Override + public int getId() { + return 0x15; + } +} diff --git a/src/main/java/fr/themode/minestom/utils/ArrayUtils.java b/src/main/java/fr/themode/minestom/utils/ArrayUtils.java index af3a993d1..7b533f715 100644 --- a/src/main/java/fr/themode/minestom/utils/ArrayUtils.java +++ b/src/main/java/fr/themode/minestom/utils/ArrayUtils.java @@ -17,6 +17,10 @@ public class ArrayUtils { return result; } + public static void removeElement(Object[] arr, int index) { + System.arraycopy(arr, index + 1, arr, index, arr.length - 1 - index); + } + /** * @param a * @param b diff --git a/src/main/java/fr/themode/minestom/utils/ChunkUtils.java b/src/main/java/fr/themode/minestom/utils/ChunkUtils.java index 9bf1f2bd3..2e2b33b21 100644 --- a/src/main/java/fr/themode/minestom/utils/ChunkUtils.java +++ b/src/main/java/fr/themode/minestom/utils/ChunkUtils.java @@ -1,7 +1,5 @@ package fr.themode.minestom.utils; -import fr.themode.minestom.Main; - public class ChunkUtils { public static long getChunkIndex(int chunkX, int chunkZ) { @@ -14,12 +12,11 @@ public class ChunkUtils { return new int[]{chunkX, chunkZ}; } - public static long[] getVisibleChunks(final Position position) { - final int viewDistance = Main.CHUNK_VIEW_DISTANCE; + public static long[] getChunksInRange(final Position position, int range) { - long[] visibleChunks = new long[MathUtils.square(viewDistance + 1)]; - final int startLoop = -(viewDistance / 2); - final int endLoop = viewDistance / 2 + 1; + long[] visibleChunks = new long[MathUtils.square(range + 1)]; + final int startLoop = -(range / 2); + final int endLoop = range / 2 + 1; int counter = 0; for (int x = startLoop; x < endLoop; x++) { for (int z = startLoop; z < endLoop; z++) { diff --git a/src/main/java/fr/themode/minestom/utils/EntityUtils.java b/src/main/java/fr/themode/minestom/utils/EntityUtils.java new file mode 100644 index 000000000..2355bf865 --- /dev/null +++ b/src/main/java/fr/themode/minestom/utils/EntityUtils.java @@ -0,0 +1,29 @@ +package fr.themode.minestom.utils; + +import fr.themode.minestom.Main; +import fr.themode.minestom.entity.Entity; +import fr.themode.minestom.instance.Chunk; + +public class EntityUtils { + + public static boolean areVisible(Entity ent1, Entity ent2) { + if (ent1.getInstance() == null || ent2.getInstance() == null) + return false; + if (!ent1.getInstance().equals(ent2.getInstance())) + return false; + + Chunk chunk = ent1.getInstance().getChunkAt(ent1.getPosition()); + + long[] visibleChunksEntity = ChunkUtils.getChunksInRange(ent2.getPosition(), Main.ENTITY_VIEW_DISTANCE); + for (long visibleChunk : visibleChunksEntity) { + int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunk); + int chunkX = chunkPos[0]; + int chunkZ = chunkPos[1]; + if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) + return true; + } + + return false; + } + +} diff --git a/src/main/java/fr/themode/minestom/utils/Position.java b/src/main/java/fr/themode/minestom/utils/Position.java index 16b7bf785..1e16cb5f9 100644 --- a/src/main/java/fr/themode/minestom/utils/Position.java +++ b/src/main/java/fr/themode/minestom/utils/Position.java @@ -132,6 +132,10 @@ public class Position { this.pitch = pitch; } + public BlockPosition toBlockPosition() { + return new BlockPosition((int) getX(), (int) getY(), (int) getZ()); + } + @Override public String toString() { return "Position[" + x + ":" + y + ":" + z + "] (" + yaw + "/" + pitch + ")";