From fc751acb7582ba80b28e6151a4e5aacbb29718ae Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 26 May 2020 00:07:35 +0200 Subject: [PATCH] Added EntityArmorStand & Hologram --- src/main/java/fr/themode/demo/PlayerInit.java | 10 +- .../themode/demo/commands/SimpleCommand.java | 19 +- .../net/minestom/server/entity/Entity.java | 71 +++- .../minestom/server/entity/ObjectEntity.java | 5 - .../server/entity/hologram/Hologram.java | 74 ++++ .../server/entity/type/EntityArmorStand.java | 361 ++++++++++++++++++ .../server/event/item/ArmorEquipEvent.java | 12 +- .../server/instance/InstanceContainer.java | 6 +- 8 files changed, 525 insertions(+), 33 deletions(-) create mode 100644 src/main/java/net/minestom/server/entity/hologram/Hologram.java create mode 100644 src/main/java/net/minestom/server/entity/type/EntityArmorStand.java diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index a1cfe6f41..ff96b20cb 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -7,10 +7,8 @@ import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.benchmark.ThreadResult; import net.minestom.server.entity.*; import net.minestom.server.entity.damage.DamageType; -import net.minestom.server.entity.fakeplayer.FakePlayer; -import net.minestom.server.entity.fakeplayer.FakePlayerController; +import net.minestom.server.entity.hologram.Hologram; import net.minestom.server.event.entity.EntityAttackEvent; -import net.minestom.server.event.entity.EntityDeathEvent; import net.minestom.server.event.item.ItemDropEvent; import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.item.PickupItemEvent; @@ -152,13 +150,15 @@ public class PlayerInit { //ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); //chickenCreature.setInstance(player.getInstance()); - FakePlayer fakePlayer = new FakePlayer(UUID.randomUUID(), "test", true); + /*FakePlayer fakePlayer = new FakePlayer(UUID.randomUUID(), "test", true); fakePlayer.addEventCallback(EntityDeathEvent.class, e -> { fakePlayer.getController().respawn(); }); fakePlayer.setArrowCount(25); FakePlayerController controller = fakePlayer.getController(); - controller.sendChatMessage("I am a bot!"); + controller.sendChatMessage("I am a bot!");*/ + + Hologram hologram = new Hologram(player.getInstance(), player.getPosition(), "Hey guy"); }); diff --git a/src/main/java/fr/themode/demo/commands/SimpleCommand.java b/src/main/java/fr/themode/demo/commands/SimpleCommand.java index 531c8c37a..4ff31d581 100644 --- a/src/main/java/fr/themode/demo/commands/SimpleCommand.java +++ b/src/main/java/fr/themode/demo/commands/SimpleCommand.java @@ -1,14 +1,7 @@ package fr.themode.demo.commands; -import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandProcessor; import net.minestom.server.entity.Player; -import net.minestom.server.entity.fakeplayer.FakePlayer; -import net.minestom.server.entity.fakeplayer.FakePlayerController; -import net.minestom.server.timer.TaskRunnable; -import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.time.TimeUnit; -import net.minestom.server.utils.time.UpdateOption; public class SimpleCommand implements CommandProcessor { @Override @@ -24,7 +17,7 @@ public class SimpleCommand implements CommandProcessor { @Override public boolean process(Player player, String command, String[] args) { - for (Player p : MinecraftServer.getConnectionManager().getOnlinePlayers()) { + /*for (Player p : MinecraftServer.getConnectionManager().getOnlinePlayers()) { if (!(p instanceof FakePlayer)) continue; FakePlayer fakePlayer = (FakePlayer) p; @@ -37,11 +30,13 @@ public class SimpleCommand implements CommandProcessor { public void run() { controller.stopDigging(blockPosition); } - }, new UpdateOption(15, TimeUnit.TICK)); - } + }, new UpdateOption(7, TimeUnit.TICK)); - //System.gc(); - //player.sendMessage("Garbage collector called"); + break; + }*/ + + System.gc(); + player.sendMessage("Garbage collector called"); return true; } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index b10e77cad..cf15b97c1 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -2,6 +2,7 @@ package net.minestom.server.entity; import net.minestom.server.MinecraftServer; import net.minestom.server.Viewable; +import net.minestom.server.chat.Chat; import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.data.Data; @@ -46,6 +47,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected static final byte METADATA_OPTCHAT = 5; protected static final byte METADATA_SLOT = 6; protected static final byte METADATA_BOOLEAN = 7; + protected static final byte METADATA_ROTATION = 8; + protected static final byte METADATA_POSITION = 9; protected static final byte METADATA_POSE = 18; protected Instance instance; @@ -90,7 +93,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected boolean glowing; protected boolean usingElytra; protected int air = 300; - protected String customName = ""; + protected String customName; protected boolean customNameVisible; protected boolean silent; protected boolean noGravity; @@ -590,6 +593,15 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { sendMetadataIndex(0); } + public boolean isInvisible() { + return invisible; + } + + public void setInvisible(boolean invisible) { + this.invisible = invisible; + sendMetadataIndex(0); + } + public void setGlowing(boolean glowing) { this.glowing = glowing; sendMetadataIndex(0); @@ -602,6 +614,33 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return glowing; } + public String getCustomName() { + return customName; + } + + public void setCustomName(String customName) { + this.customName = customName; + sendMetadataIndex(2); + } + + public boolean isCustomNameVisible() { + return customNameVisible; + } + + public void setCustomNameVisible(boolean customNameVisible) { + this.customNameVisible = customNameVisible; + sendMetadataIndex(3); + } + + public boolean isSilent() { + return silent; + } + + public void setSilent(boolean silent) { + this.silent = silent; + sendMetadataIndex(4); + } + /** * @param noGravity should the entity ignore gravity */ @@ -831,6 +870,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return packet -> { fillMetadataIndex(packet, 0); fillMetadataIndex(packet, 1); + fillMetadataIndex(packet, 2); + fillMetadataIndex(packet, 3); + fillMetadataIndex(packet, 4); fillMetadataIndex(packet, 5); fillMetadataIndex(packet, 6); }; @@ -871,6 +913,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { case 2: fillCustomNameMetaData(packet); break; + case 3: + fillCustomNameVisibleMetaData(packet); + break; + case 4: + fillSilentMetaData(packet); + break; case 5: fillNoGravityMetaData(packet); break; @@ -910,9 +958,26 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } private void fillCustomNameMetaData(PacketWriter packet) { + boolean hasCustomName = customName != null && !customName.isEmpty(); + packet.writeByte((byte) 2); - packet.writeByte(METADATA_CHAT); - packet.writeSizedString(customName); + packet.writeByte(METADATA_OPTCHAT); + packet.writeBoolean(hasCustomName); + if (hasCustomName) { + packet.writeSizedString(Chat.toJsonString(Chat.fromLegacyText(customName))); + } + } + + private void fillCustomNameVisibleMetaData(PacketWriter packet) { + packet.writeByte((byte) 3); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(customNameVisible); + } + + private void fillSilentMetaData(PacketWriter packet) { + packet.writeByte((byte) 4); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(silent); } private void fillNoGravityMetaData(PacketWriter packet) { diff --git a/src/main/java/net/minestom/server/entity/ObjectEntity.java b/src/main/java/net/minestom/server/entity/ObjectEntity.java index d984b3bc3..9b2b7b7ec 100644 --- a/src/main/java/net/minestom/server/entity/ObjectEntity.java +++ b/src/main/java/net/minestom/server/entity/ObjectEntity.java @@ -39,9 +39,4 @@ public abstract class ObjectEntity extends Entity { return super.addViewer(player); // Add player to viewers list } - @Override - public boolean removeViewer(Player player) { - return super.removeViewer(player); - } - } diff --git a/src/main/java/net/minestom/server/entity/hologram/Hologram.java b/src/main/java/net/minestom/server/entity/hologram/Hologram.java new file mode 100644 index 000000000..913c96709 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/hologram/Hologram.java @@ -0,0 +1,74 @@ +package net.minestom.server.entity.hologram; + +import net.minestom.server.entity.type.EntityArmorStand; +import net.minestom.server.instance.Instance; +import net.minestom.server.utils.Position; +import net.minestom.server.utils.validate.Check; + +public class Hologram { + + private static final float OFFSET_Y = -0.9875f; + + private HologramEntity entity; + + private Position position; + private String text; + + private boolean removed; + + public Hologram(Instance instance, Position spawnPosition, String text, boolean autoViewable) { + this.entity = new HologramEntity(spawnPosition.clone().add(0, OFFSET_Y, 0)); + this.entity.setInstance(instance); + this.entity.setAutoViewable(autoViewable); + + this.position = spawnPosition; + setText(text); + } + + public Hologram(Instance instance, Position spawnPosition, String text) { + this(instance, spawnPosition, text, true); + } + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + position = position.add(0, OFFSET_Y, 0); + this.position = position; + this.entity.teleport(position); + } + + public String getText() { + return text; + } + + public void setText(String text) { + checkRemoved(); + this.text = text; + this.entity.setCustomName(text); + } + + public void remove() { + this.removed = true; + this.entity.remove(); + } + + private void checkRemoved() { + Check.stateCondition(removed, "You cannot interact with a removed Hologram"); + } + + private class HologramEntity extends EntityArmorStand { + + public HologramEntity(Position spawnPosition) { + super(spawnPosition); + setSmall(true); + + setNoGravity(true); + setCustomName(""); + setCustomNameVisible(true); + setInvisible(true); + } + + } +} diff --git a/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java b/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java new file mode 100644 index 000000000..57671d1ec --- /dev/null +++ b/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java @@ -0,0 +1,361 @@ +package net.minestom.server.entity.type; + +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.ObjectEntity; +import net.minestom.server.entity.Player; +import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.inventory.EquipmentHandler; +import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; +import net.minestom.server.utils.Position; +import net.minestom.server.utils.Vector; +import net.minestom.server.utils.item.ItemStackUtils; + +import java.util.function.Consumer; + +public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { + + private boolean small; + private boolean hasArms; + private boolean noBasePlate; + private boolean setMarker; + + private Vector headRotation; + private Vector bodyRotation; + private Vector leftArmRotation; + private Vector rightArmRotation; + private Vector leftLegRotation; + private Vector rightLegRotation; + + // Equipments + private ItemStack mainHandItem; + private ItemStack offHandItem; + + private ItemStack helmet; + private ItemStack chestplate; + private ItemStack leggings; + private ItemStack boots; + + public EntityArmorStand(Position spawnPosition) { + super(EntityType.ARMOR_STAND, spawnPosition); + + // Refresh BoundingBox + setSmall(false); + + setHeadRotation(new Vector(0, 0, 0)); + setBodyRotation(new Vector(0, 0, 0)); + setLeftArmRotation(new Vector(-10f, 0, -10f)); + setRightArmRotation(new Vector(-15f, 0, -10f)); + setLeftLegRotation(new Vector(-1f, 0, -1f)); + setRightLegRotation(new Vector(1, 0, 1)); + + this.mainHandItem = ItemStack.getAirItem(); + this.offHandItem = ItemStack.getAirItem(); + + this.helmet = ItemStack.getAirItem(); + this.chestplate = ItemStack.getAirItem(); + this.leggings = ItemStack.getAirItem(); + this.boots = ItemStack.getAirItem(); + + } + + @Override + public boolean addViewer(Player player) { + boolean result = super.addViewer(player); + syncEquipments(); + return result; + } + + @Override + public int getObjectData() { + return 0; + } + + @Override + public void update() { + + } + + @Override + public void spawn() { + + } + + + @Override + public Consumer getMetadataConsumer() { + return packet -> { + super.getMetadataConsumer().accept(packet); + fillMetadataIndex(packet, 14); + fillMetadataIndex(packet, 15); + fillMetadataIndex(packet, 16); + fillMetadataIndex(packet, 17); + fillMetadataIndex(packet, 18); + fillMetadataIndex(packet, 19); + fillMetadataIndex(packet, 20); + }; + } + + @Override + protected void fillMetadataIndex(PacketWriter packet, int index) { + super.fillMetadataIndex(packet, index); + if (index == 14) { + packet.writeByte((byte) 14); + packet.writeByte(METADATA_BYTE); + byte dataValue = 0; + if (isSmall()) + dataValue += 1; + if (hasArms) + dataValue += 2; + if (hasNoBasePlate()) + dataValue += 4; + if (hasMarker()) + dataValue += 8; + packet.writeByte(dataValue); + } else if (index == 15) { + packet.writeByte((byte) 15); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(headRotation)); + packet.writeFloat(getRotationY(headRotation)); + packet.writeFloat(getRotationZ(headRotation)); + } else if (index == 16) { + packet.writeByte((byte) 16); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(bodyRotation)); + packet.writeFloat(getRotationY(bodyRotation)); + packet.writeFloat(getRotationZ(bodyRotation)); + } else if (index == 17) { + packet.writeByte((byte) 17); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(leftArmRotation)); + packet.writeFloat(getRotationY(leftArmRotation)); + packet.writeFloat(getRotationZ(leftArmRotation)); + } else if (index == 18) { + packet.writeByte((byte) 18); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(rightArmRotation)); + packet.writeFloat(getRotationY(rightArmRotation)); + packet.writeFloat(getRotationZ(rightArmRotation)); + } else if (index == 19) { + packet.writeByte((byte) 19); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(leftLegRotation)); + packet.writeFloat(getRotationY(leftLegRotation)); + packet.writeFloat(getRotationZ(leftLegRotation)); + } else if (index == 20) { + packet.writeByte((byte) 20); + packet.writeByte(METADATA_ROTATION); + packet.writeFloat(getRotationX(rightLegRotation)); + packet.writeFloat(getRotationY(rightLegRotation)); + packet.writeFloat(getRotationZ(rightLegRotation)); + } + } + + @Override + public ItemStack getItemInMainHand() { + return mainHandItem; + } + + @Override + public void setItemInMainHand(ItemStack itemStack) { + this.mainHandItem = ItemStackUtils.notNull(itemStack); + syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); + } + + @Override + public ItemStack getItemInOffHand() { + return offHandItem; + } + + @Override + public void setItemInOffHand(ItemStack itemStack) { + this.offHandItem = ItemStackUtils.notNull(itemStack); + syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND); + } + + @Override + public ItemStack getHelmet() { + return helmet; + } + + @Override + public void setHelmet(ItemStack itemStack) { + this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET); + syncEquipment(EntityEquipmentPacket.Slot.HELMET); + } + + @Override + public ItemStack getChestplate() { + return chestplate; + } + + @Override + public void setChestplate(ItemStack itemStack) { + this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); + syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE); + } + + @Override + public ItemStack getLeggings() { + return leggings; + } + + @Override + public void setLeggings(ItemStack itemStack) { + this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); + syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS); + } + + @Override + public ItemStack getBoots() { + return boots; + } + + @Override + public void setBoots(ItemStack itemStack) { + this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); + syncEquipment(EntityEquipmentPacket.Slot.BOOTS); + } + + public boolean isSmall() { + return small; + } + + public void setSmall(boolean small) { + this.small = small; + sendMetadataIndex(14); + + if (small) { + setBoundingBox(0.25f, 0.9875f, 0.25f); + } else { + setBoundingBox(0.5f, 1.975f, 0.5f); + } + } + + public boolean hasArms() { + return hasArms; + } + + public void setHasArms(boolean hasArms) { + this.hasArms = hasArms; + sendMetadataIndex(14); + } + + public boolean hasNoBasePlate() { + return noBasePlate; + } + + public void setNoBasePlate(boolean noBasePlate) { + this.noBasePlate = noBasePlate; + sendMetadataIndex(14); + } + + public boolean hasMarker() { + return setMarker; + } + + public void setMarker(boolean setMarker) { + this.setMarker = setMarker; + sendMetadataIndex(14); + } + + public Vector getHeadRotation() { + return headRotation; + } + + public void setHeadRotation(Vector headRotation) { + this.headRotation = headRotation; + sendMetadataIndex(15); + } + + public Vector getBodyRotation() { + return bodyRotation; + } + + public void setBodyRotation(Vector bodyRotation) { + this.bodyRotation = bodyRotation; + sendMetadataIndex(16); + } + + public Vector getLeftArmRotation() { + return leftArmRotation; + } + + public void setLeftArmRotation(Vector leftArmRotation) { + this.leftArmRotation = leftArmRotation; + sendMetadataIndex(17); + } + + public Vector getRightArmRotation() { + return rightArmRotation; + } + + public void setRightArmRotation(Vector rightArmRotation) { + this.rightArmRotation = rightArmRotation; + sendMetadataIndex(18); + } + + public Vector getLeftLegRotation() { + return leftLegRotation; + } + + public void setLeftLegRotation(Vector leftLegRotation) { + this.leftLegRotation = leftLegRotation; + sendMetadataIndex(19); + } + + public Vector getRightLegRotation() { + return rightLegRotation; + } + + public void setRightLegRotation(Vector rightLegRotation) { + this.rightLegRotation = rightLegRotation; + sendMetadataIndex(20); + } + + private float getRotationX(Vector vector) { + return vector != null ? vector.getX() : 0; + } + + private float getRotationY(Vector vector) { + return vector != null ? vector.getY() : 0; + } + + private float getRotationZ(Vector vector) { + return vector != null ? vector.getZ() : 0; + } + + // Equipments + public void syncEquipments() { + for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) { + syncEquipment(slot); + } + } + + protected void syncEquipment(EntityEquipmentPacket.Slot slot) { + EntityEquipmentPacket entityEquipmentPacket = getEquipmentPacket(slot); + if (entityEquipmentPacket == null) + return; + + sendPacketToViewers(entityEquipmentPacket); + } + + private ItemStack getEquipmentItem(ItemStack itemStack, ArmorEquipEvent.ArmorSlot armorSlot) { + itemStack = ItemStackUtils.notNull(itemStack); + + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(this, itemStack, armorSlot); + callEvent(ArmorEquipEvent.class, armorEquipEvent); + return armorEquipEvent.getArmorItem(); + } + + protected EntityEquipmentPacket getEquipmentPacket(EntityEquipmentPacket.Slot slot) { + ItemStack itemStack = getEquipment(slot); + + EntityEquipmentPacket equipmentPacket = new EntityEquipmentPacket(); + equipmentPacket.entityId = getEntityId(); + equipmentPacket.slot = slot; + equipmentPacket.itemStack = itemStack; + return equipmentPacket; + } +} diff --git a/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java b/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java index 04e32f680..35cb8eeaa 100644 --- a/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java +++ b/src/main/java/net/minestom/server/event/item/ArmorEquipEvent.java @@ -1,24 +1,24 @@ package net.minestom.server.event.item; -import net.minestom.server.entity.LivingEntity; +import net.minestom.server.entity.Entity; import net.minestom.server.event.Event; import net.minestom.server.item.ItemStack; import net.minestom.server.utils.item.ItemStackUtils; public class ArmorEquipEvent extends Event { - private LivingEntity livingEntity; + private Entity entity; private ItemStack armorItem; private ArmorSlot armorSlot; - public ArmorEquipEvent(LivingEntity livingEntity, ItemStack armorItem, ArmorSlot armorSlot) { - this.livingEntity = livingEntity; + public ArmorEquipEvent(Entity entity, ItemStack armorItem, ArmorSlot armorSlot) { + this.entity = entity; this.armorItem = armorItem; this.armorSlot = armorSlot; } - public LivingEntity getEntity() { - return livingEntity; + public Entity getEntity() { + return entity; } public ItemStack getArmorItem() { diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index d3b839114..c54e91db0 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -283,8 +283,10 @@ public class InstanceContainer extends Instance { unloadChunkPacket.chunkZ = chunkZ; chunk.sendPacketToViewers(unloadChunkPacket); - for (Player viewer : chunk.getViewers()) { - chunk.removeViewer(viewer); + if (!ChunkUtils.isChunkUnloaded(this, chunk)) { + for (Player viewer : chunk.getViewers()) { + chunk.removeViewer(viewer); + } } this.chunks.remove(index);