We're developing a more advanced project utilizing the PacketEvents API. We will combine everything we've learned so far.
In this example, we will develop a Minecraft Bukkit plugin that will spawn a client-sided Armor Stand, providing a click counter for each player.
First, we'll spawn an Armor Stand for the client whenever they join the server. Next, we'll wait for the client to send an "Interact" packet. We'll check if the client had interacted with our client-sided Armor Stand. If so, we'll increment the click counter for that player.
Previously, we mentioned that packets provide direct communication with the client, allowing us to give each user a unique experience. "Client-sided entity" in this context merely means that an entity is spawned for a particular client. Most importantly, the server is not informed of said entity, thus it will only be visible to the users you present it to.
importcom.github.retrooper.packetevents.event.PacketListener;importcom.github.retrooper.packetevents.event.PacketReceiveEvent;importcom.github.retrooper.packetevents.event.UserLoginEvent;importcom.github.retrooper.packetevents.protocol.entity.type.EntityTypes;importcom.github.retrooper.packetevents.protocol.packettype.PacketType;importcom.github.retrooper.packetevents.protocol.player.User;importcom.github.retrooper.packetevents.protocol.world.Location;importcom.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;importcom.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;importio.github.retrooper.packetevents.util.SpigotConversionUtil;importio.github.retrooper.packetevents.util.SpigotReflectionUtil;importorg.bukkit.entity.Player;importjava.util.Map;importjava.util.UUID;importjava.util.concurrent.ConcurrentHashMap;publicclassPacketEventsPacketListenerimplementsPacketListener {privateFakeArmorStand fakeArmorStand =null; @OverridepublicvoidonUserLogin(UserLoginEvent event) {User user =event.getUser();Player player =event.getPlayer();// Create the Armor Stand (if we haven't already)if (fakeArmorStand ==null) {// Generate a random UUIDUUID uuid =UUID.randomUUID();// Generate an Entity IDint entityId =SpigotReflectionUtil.generateEntityId(); fakeArmorStand =newFakeArmorStand(uuid, entityId); }// Spawn the Armor Stand at the user's current locationLocation spawnLocation =SpigotConversionUtil.fromBukkitLocation(player.getLocation());fakeArmorStand.spawn(user, spawnLocation); } @OverridepublicvoidonPacketReceive(PacketReceiveEvent event) {User user =event.getUser();if (event.getPacketType() !=PacketType.Play.Client.INTERACT_ENTITY) return;// They interacted with an entity.WrapperPlayClientInteractEntity packet =newWrapperPlayClientInteractEntity(event);// Retrieve that entity's IDint entityId =packet.getEntityId();// Check if the client interacted with the Armor Standif (entityId ==fakeArmorStand.entityId) {// Increment their clicksint clicks =fakeArmorStand.clicks.getOrDefault(user.getUUID(),0) +1;fakeArmorStand.clicks.put(user.getUUID(), clicks);user.sendMessage("You now have "+ clicks +" clicks on the Armor Stand!"); } }privatestaticclassFakeArmorStand {privatefinalint entityId;privatefinalUUID uuid;// Track their clicksprivatefinalMap<UUID,Integer> clicks =newConcurrentHashMap<>();publicFakeArmorStand(UUID uuid,int entityId) {this.uuid= uuid;this.entityId= entityId; }publicvoidspawn(User user,Location location) {WrapperPlayServerSpawnEntity packet =newWrapperPlayServerSpawnEntity( entityId, uuid,EntityTypes.ARMOR_STAND, location,location.getYaw(),// Head yaw0,// No additional datanull// We won't specify any initial velocity );user.sendPacket(packet); } }}