From 65f0e968cc4ee505aad13561053a6da7430e80d6 Mon Sep 17 00:00:00 2001 From: iqtester Date: Mon, 2 Jun 2025 14:25:29 -0400 Subject: [PATCH] Crawl and Lay --- .../entity/furniture/BukkitFurniture.java | 21 +- .../furniture/BukkitFurnitureManager.java | 28 +- .../furniture/FurnitureEventListener.java | 38 +- .../furniture/seat/BukkitSeatEntity.java | 94 ++- .../entity/furniture/seat/CrawlSeat.java | 156 ++--- .../bukkit/entity/furniture/seat/LaySeat.java | 633 ++++++++++++++++-- .../bukkit/entity/furniture/seat/SitSeat.java | 74 +- .../DebugGetBlockInternalIdCommand.java | 11 +- .../plugin/network/BukkitNetworkManager.java | 3 + .../plugin/network/PacketConsumers.java | 41 +- .../handler/FurniturePacketHandler.java | 2 +- .../craftengine/bukkit/util/EntityUtils.java | 104 ++- .../craftengine/bukkit/util/ItemUtils.java | 2 + .../craftengine/bukkit/util/PlayerUtils.java | 6 + .../default/configuration/furniture.yml | 2 +- .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../core/entity/EquipmentSlot.java | 10 +- .../core/entity/furniture/Seat.java | 4 +- .../core/entity/furniture/SeatType.java | 4 +- .../core/entity/seat/SeatEntity.java | 22 +- .../plugin/network/EntityPacketHandler.java | 8 +- 22 files changed, 953 insertions(+), 312 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java index 5b1458d9f..92ff4c1b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java @@ -5,20 +5,16 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.entity.furniture.seat.BukkitSeatEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; -import net.momirealms.craftengine.bukkit.util.EntityUtils; -import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.bukkit.util.PlayerUtils; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.ArrayUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.QuaternionUtils; -import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.Location; @@ -201,7 +197,7 @@ public class BukkitFurniture implements Furniture { @Override public void destroySeats() { for (BukkitSeatEntity seat : this.seats.values()) { - seat.remove(); + seat.destroy(); } this.seats.clear(); } @@ -289,10 +285,13 @@ public class BukkitFurniture implements Furniture { @Override public void spawnSeatEntityForPlayer(net.momirealms.craftengine.core.entity.player.Player player, Seat seat) { - net.momirealms.craftengine.core.entity.Entity e = seat.spawn(player, this); - BukkitSeatEntity seatEntity = (BukkitSeatEntity) e; - this.seats.put(e.entityID(), seatEntity); + BukkitSeatEntity seatEntity = (BukkitSeatEntity) seat.spawn(player, this); + this.seats.put(seatEntity.playerID(), seatEntity); player.setSeat(seatEntity); + BukkitServerPlayer serverPlayer = (BukkitServerPlayer) player; + for (Player p : PlayerUtils.getTrackedBy(serverPlayer.platformPlayer())) { + BukkitNetworkManager.instance().getOnlineUser(p).entityPacketHandlers().put(seatEntity.playerID(), seatEntity); + } } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 1bb8fed98..105654c31 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -15,7 +15,6 @@ import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.*; @@ -25,7 +24,6 @@ import org.bukkit.event.Listener; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; import java.io.IOException; import java.util.Collection; @@ -38,7 +36,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { public static final NamespacedKey FURNITURE_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_KEY); public static final NamespacedKey FURNITURE_EXTRA_DATA_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_EXTRA_DATA_KEY); public static final NamespacedKey FURNITURE_SEAT_BASE_ENTITY_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY); - public static final NamespacedKey FURNITURE_SEAT_VECTOR_3F_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY); public static final NamespacedKey FURNITURE_COLLISION = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_COLLISION); public static Class COLLISION_ENTITY_CLASS = Interaction.class; public static Object NMS_COLLISION_ENTITY_TYPE = MEntityTypes.INTERACTION; @@ -321,28 +318,17 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { } protected void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { - Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); - if (baseFurniture == null) return; - BukkitFurniture furniture = loadedFurnitureByRealEntityId(baseFurniture); + net.momirealms.craftengine.core.entity.player.Player serverPlayer = BukkitAdaptors.adapt(player); + BukkitSeatEntity seatEntity = (BukkitSeatEntity) serverPlayer.seat(); + if (seatEntity == null || seatEntity.literalObject() != vehicle) return; + + BukkitFurniture furniture = seatEntity.furniture(); if (furniture == null) { - vehicle.remove(); + seatEntity.destroy(); return; } - BukkitSeatEntity seatEntity = furniture.seatByEntityId(vehicle.getEntityId()); - if (seatEntity == null) vehicle.remove(); - else { - seatEntity.dismount(BukkitAdaptors.adapt(player)); - seatEntity.remove(); - furniture.removeSeatEntity(vehicle.getEntityId()); - } - String vector3f = vehicle.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING); - if (vector3f == null) { - plugin.logger().warn("Failed to get vector3f for player " + player.getName() + "'s seat"); - return; - } - Vector3f seatPos = MiscUtils.getAsVector3f(vector3f, "seat"); - furniture.removeOccupiedSeat(seatPos); + seatEntity.dismount(serverPlayer); if (player.getVehicle() != null) return; Location vehicleLocation = vehicle.getLocation(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java index f9c526233..b45c2f251 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java @@ -2,6 +2,8 @@ package net.momirealms.craftengine.bukkit.entity.furniture; import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.ItemDisplay; @@ -10,7 +12,9 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerArmorStandManipulateEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.EntitiesLoadEvent; @@ -123,12 +127,30 @@ public class FurnitureEventListener implements Listener { // do not allow players to put item on seats @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onInteractArmorStand(PlayerInteractAtEntityEvent event) { - Entity clicked = event.getRightClicked(); - if (clicked instanceof ArmorStand armorStand) { - Integer baseFurniture = armorStand.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); - if (baseFurniture == null) return; - event.setCancelled(true); - } + public void onInteractArmorStand(PlayerArmorStandManipulateEvent event) { + ArmorStand clicked = event.getRightClicked(); + Integer baseFurniture = clicked.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); + if (baseFurniture == null) return; + event.setCancelled(true); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onMainHandChange(PlayerItemHeldEvent event) { + Player player = event.getPlayer(); + if (player.getVehicle() == null) return; + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + if (serverPlayer.seat() == null) return; + + serverPlayer.seat().event(serverPlayer, event); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerAnimation(PlayerAnimationEvent event) { + Player player = event.getPlayer(); + if (player.getVehicle() == null) return; + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + if (serverPlayer.seat() == null) return; + + serverPlayer.seat().event(serverPlayer, event); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/BukkitSeatEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/BukkitSeatEntity.java index f11dd3469..df82a045f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/BukkitSeatEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/BukkitSeatEntity.java @@ -1,74 +1,68 @@ package net.momirealms.craftengine.bukkit.entity.furniture.seat; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.entity.BukkitEntity; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; +import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.seat.SeatEntity; -import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import org.bukkit.entity.Entity; +import org.joml.Vector3f; -import java.lang.ref.WeakReference; +public abstract class BukkitSeatEntity extends BukkitEntity implements SeatEntity { + private final BukkitFurniture furniture; + private final Vector3f vector3f; + private final int playerID; -public abstract class BukkitSeatEntity extends SeatEntity { - - private final WeakReference entity; - - public BukkitSeatEntity(Entity entity) { - this.entity = new WeakReference<>(entity); + public BukkitSeatEntity(Entity entity, Furniture furniture, Vector3f vector3f, int playerID) { + super(entity); + this.furniture = (BukkitFurniture) furniture; + this.vector3f = vector3f; + this.playerID = playerID; } @Override - public void dismount(Player from) { - from.setSeat(null); + public void add(NetWorkUser from, NetWorkUser to) {} + + @Override + public void dismount(Player player) { + player.setSeat(null); + destroy(); } @Override - public double x() { - return literalObject().getLocation().getX(); + public void event(Player player, Object event) {} + + @Override + public void destroy() { + org.bukkit.entity.Entity entity = this.literalObject(); + if (entity == null) return; + + for (org.bukkit.entity.Entity passenger : entity.getPassengers()) { + entity.removePassenger(passenger); + if (passenger instanceof org.bukkit.entity.Player p && p.getEntityId() == this.playerID) { + dismount(BukkitAdaptors.adapt(p)); + return; + } + } + furniture.removeSeatEntity(playerID()); + furniture.removeOccupiedSeat(vector3f()); + entity.remove(); } @Override - public double y() { - return literalObject().getLocation().getY(); + public BukkitFurniture furniture() { + return this.furniture; } @Override - public double z() { - return literalObject().getLocation().getZ(); + public Vector3f vector3f() { + return this.vector3f; } @Override - public void tick() { - } - - @Override - public int entityID() { - return literalObject().getEntityId(); - } - - @Override - public float getXRot() { - return literalObject().getLocation().getYaw(); - } - - @Override - public float getYRot() { - return literalObject().getLocation().getPitch(); - } - - @Override - public World world() { - return new BukkitWorld(literalObject().getWorld()); - } - - @Override - public Direction getDirection() { - return Direction.NORTH; - } - - @Override - public org.bukkit.entity.Entity literalObject() { - return this.entity.get(); + public int playerID() { + return this.playerID; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/CrawlSeat.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/CrawlSeat.java index 99f74c7fd..28a44898d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/CrawlSeat.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/CrawlSeat.java @@ -1,37 +1,37 @@ package net.momirealms.craftengine.bukkit.entity.furniture.seat; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.entity.data.PlayerData; import net.momirealms.craftengine.bukkit.entity.data.ShulkerData; -import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.EntityUtils; -import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; +import net.momirealms.craftengine.bukkit.util.PlayerUtils; import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.entity.furniture.AbstractSeat; import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.furniture.Seat; import net.momirealms.craftengine.core.entity.furniture.SeatFactory; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.entity.seat.SeatEntity; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Location; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Entity; import org.bukkit.entity.Pose; -import org.bukkit.persistence.PersistentDataType; import org.joml.Vector3f; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.UUID; +import java.util.function.Consumer; public class CrawlSeat extends AbstractSeat { public static final SeatFactory FACTORY = new Factory(); @@ -51,106 +51,92 @@ public class CrawlSeat extends AbstractSeat { } @Override - public Entity spawn(Player player, Furniture furniture) { + public SeatEntity spawn(Player player, Furniture furniture) { return spawn((org.bukkit.entity.Player) player.platformPlayer(), furniture); } - public Entity spawn(org.bukkit.entity.Player player, Furniture furniture) { - Location location = ((LoadedFurniture)furniture).calculateSeatLocation(this); + public SeatEntity spawn(org.bukkit.entity.Player player, Furniture furniture) { + Location location = ((BukkitFurniture)furniture).calculateSeatLocation(this); + org.bukkit.entity.Entity seatEntity = EntityUtils.spawnSeatEntity(furniture, this, player.getWorld(), location, this.limitPlayerRotation, null); + seatEntity.addPassenger(player); + + // Fix Rider Pose int visualId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); List packets = new ArrayList<>(); packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(visualId, UUID.randomUUID(), location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw(), Reflections.instance$EntityType$SHULKER, 0, Reflections.instance$Vec3$Zero, 0)); packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(visualId, List.copyOf(visualData))); + + try { + if (VersionHelper.isOrAbove1_20_5()) { + Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); + Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, 0.6); + packets.add( + Reflections.constructor$ClientboundUpdateAttributesPacket0 + .newInstance(visualId, Collections.singletonList(attributeInstance)) + ); + packets.add(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(seatEntity.getEntityId(), visualId)); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to add crawl seat attributes", e); + } + Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets); - BukkitAdaptors.adapt(player).sendPacket(bundle, true); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + serverPlayer.sendPacket(bundle, true); - org.bukkit.entity.Entity seatEntity = this.limitPlayerRotation ? - EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isOrAbove1_20_2() ? location.subtract(0,0.9875,0) : location.subtract(0,0.990625,0), EntityType.ARMOR_STAND, entity -> { - ArmorStand armorStand = (ArmorStand) entity; - if (VersionHelper.isOrAbove1_21_3()) { - Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); - } else { - LegacyAttributeUtils.setMaxHealth(armorStand); - } - armorStand.setSmall(true); - armorStand.setInvisible(true); - armorStand.setSilent(true); - armorStand.setInvulnerable(true); - armorStand.setArms(false); - armorStand.setCanTick(false); - armorStand.setAI(false); - armorStand.setGravity(false); - armorStand.setPersistent(false); - armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); - armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, this.offset().x + ", " + this.offset().y + ", " + this.offset().z); - }) : - EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isOrAbove1_20_2() ? location : location.subtract(0,0.25,0), EntityType.ITEM_DISPLAY, entity -> { - ItemDisplay itemDisplay = (ItemDisplay) entity; - itemDisplay.setPersistent(false); - itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); - itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, this.offset().x + ", " + this.offset().y + ", " + this.offset().z); - }); - seatEntity.addPassenger(player); - // Todo 调查一下,位置y的变化是为了什么,可能是为了让玩家做下去之后的位置是seatlocation的位置 - BukkitCraftEngine.instance().scheduler().sync().runLater(() -> player.setPose(Pose.SWIMMING, true), - 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); + // Sync Pose + player.setPose(Pose.SWIMMING, true); + Object syncPosePacket = null; + try { + Object playerData = Reflections.method$Entity$getEntityData.invoke(serverPlayer.serverPlayer()); + Object dataItem = Reflections.method$SynchedEntityData$getItem.invoke(playerData, PlayerData.Pose.entityDataAccessor()); + Object dataValue = Reflections.method$SynchedEntityData$DataItem$value.invoke(dataItem); + syncPosePacket = FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(serverPlayer.entityID(), List.of(dataValue)); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to construct sync pose packet", e); + } - return new CrawlEntity(seatEntity, visualId); - // Todo 检测版本实现潜影贝缩小 + 找到合适的位置保持玩家的姿势 + 潜影贝骑乘展示实体 + Object finalSyncPosePacket = syncPosePacket; + BukkitCraftEngine.instance().scheduler().sync().runLater(() -> { + serverPlayer.sendPacket(finalSyncPosePacket, true); + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().sendPacket(BukkitAdaptors.adapt(p), finalSyncPosePacket, true); + } + }, 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); + + return new CrawlEntity(seatEntity, furniture, offset(), player.getEntityId(), visualId, syncPosePacket); } private static class CrawlEntity extends BukkitSeatEntity { - private final int visual; + private final int visualId; + private final Object syncPosePacket; - public CrawlEntity(org.bukkit.entity.Entity entity, int visual) { - super(entity); - this.visual = visual; + + public CrawlEntity(Entity entity, Furniture furniture, Vector3f vector3f, int playerID, int visualId, Object fixPosePacket) { + super(entity, furniture, vector3f, playerID); + this.visualId = visualId; + this.syncPosePacket = fixPosePacket; } @Override - public void sync(Player to) { - org.bukkit.entity.Entity entity = this.literalObject(); - for (org.bukkit.entity.Entity passenger : entity.getPassengers()) { - if (passenger instanceof org.bukkit.entity.Player) { - try { - Object serverPlayer = FastNMS.INSTANCE.method$CraftEntity$getHandle(passenger); - Reflections.method$Entity$refreshEntityData.invoke(serverPlayer, to.serverPlayer()); - } catch (Exception e) { - BukkitCraftEngine.instance().logger().warn("Failed to sync player pose", e); - } - } - } + public void add(NetWorkUser from, NetWorkUser to) { + to.sendPacket(syncPosePacket, false); } @Override - public void dismount(Player from) { - super.dismount(from); - ((org.bukkit.entity.Player) from.platformPlayer()).setPose(Pose.STANDING, false); + public void dismount(Player player) { + super.dismount(player); + ((org.bukkit.entity.Player) player.platformPlayer()).setPose(Pose.STANDING, false); try { - @SuppressWarnings("Confusing primitive array argument to var-arg method PrimitiveArrayArgumentToVariableArgMethod") - Object packet = Reflections.constructor$ClientboundRemoveEntitiesPacket.newInstance(new int[]{visual}); - from.sendPacket(packet, true); + Object packet = Reflections.constructor$ClientboundRemoveEntitiesPacket.newInstance((Object) new int[]{visualId}); + player.sendPacket(packet, false); } catch (Exception e) { - BukkitCraftEngine.instance().logger().warn("Failed to send remove entity packet", e); + BukkitCraftEngine.instance().logger().warn("Failed to remove crawl entity", e); } } - @Override - public void remove() { - org.bukkit.entity.Entity entity = this.literalObject(); - if (entity == null) return; - - for (org.bukkit.entity.Entity passenger : entity.getPassengers()) { - entity.removePassenger(passenger); - if (passenger instanceof org.bukkit.entity.Player p) { - dismount(BukkitAdaptors.adapt(p)); - } - } - entity.remove(); - } - @Override public Key type() { return Key.of("craftengine", "crawl"); @@ -161,8 +147,8 @@ public class CrawlSeat extends AbstractSeat { @Override public Seat create(List args) { - if (args.size() == 1) return new CrawlSeat(MiscUtils.getAsVector3f(args.get(0), "seats"), 0, false); - return new CrawlSeat(MiscUtils.getAsVector3f(args.get(0), "seats"), Float.parseFloat(args.get(1)), true); + if (args.size() == 1) return new CrawlSeat(MiscUtils.getAsVector3f(args.getFirst(), "seats"), 0, false); + return new CrawlSeat(MiscUtils.getAsVector3f(args.getFirst(), "seats"), Float.parseFloat(args.get(1)), true); } } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/LaySeat.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/LaySeat.java index f606a46dc..fee10a713 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/LaySeat.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/LaySeat.java @@ -1,102 +1,639 @@ package net.momirealms.craftengine.bukkit.entity.furniture.seat; -import com.google.common.collect.ForwardingMultimap; import com.google.common.collect.Multimap; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.IntList; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.entity.BukkitEntity; -import net.momirealms.craftengine.bukkit.entity.data.ShulkerData; -import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.data.LivingEntityData; +import net.momirealms.craftengine.bukkit.entity.data.PlayerData; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.EntityUtils; -import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.core.entity.Entity; +import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; +import net.momirealms.craftengine.bukkit.plugin.scheduler.impl.FoliaTask; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.*; +import net.momirealms.craftengine.core.entity.EquipmentSlot; import net.momirealms.craftengine.core.entity.furniture.AbstractSeat; import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.furniture.Seat; import net.momirealms.craftengine.core.entity.furniture.SeatFactory; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.entity.seat.SeatEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.VersionHelper; -import org.bukkit.Bukkit; +import net.momirealms.craftengine.core.plugin.network.NMSPacketEvent; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; +import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Statistic; import org.bukkit.attribute.Attribute; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.entity.Pose; -import org.bukkit.persistence.PersistentDataType; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerAnimationType; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; import org.joml.Vector3f; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.TimeUnit; public class LaySeat extends AbstractSeat { public static final SeatFactory FACTORY = new Factory(); + private final Direction facing; + private final boolean sleep; + private final boolean phantom; - public LaySeat(Vector3f offset, float yaw) { - super(offset, yaw); + public LaySeat(Vector3f offset, Direction facing, boolean sleep, boolean phantom) { + super(offset, 0); + this.facing = facing; + this.sleep = sleep; + this.phantom = phantom; } @Override - @SuppressWarnings("unchecked") - public Entity spawn(Player serverPlayer, Furniture furniture) { - Location location = ((LoadedFurniture)furniture).calculateSeatLocation(this); - org.bukkit.entity.Player player = (org.bukkit.entity.Player) serverPlayer.platformPlayer(); + @SuppressWarnings({"unchecked", "rawtypes"}) + public SeatEntity spawn(Player cePlayer, Furniture furniture) { + Location loc = ((BukkitFurniture)furniture).calculateSeatLocation(this); + + org.bukkit.entity.Player player = (org.bukkit.entity.Player) cePlayer.platformPlayer(); + Object serverPlayer = cePlayer.serverPlayer(); + + // Pose offset nearly same as vanilla + AttributeInstance attribute = VersionHelper.isOrAbove1_21_2() ? player.getAttribute(Attribute.SCALE) : null; + double scale = attribute == null ? 1 : attribute.getValue(); + loc.add(0, 0.08525 * scale, 0); - Object npc; try { + List packets = new ArrayList<>(); + // NPC Object server = Reflections.method$MinecraftServer$getServer.invoke(null); Object level = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()); - Object npcProfile = Reflections.constructor$GameProfile.newInstance(UUID.randomUUID(), player.getName()); - Object pProfile = Reflections.method$ServerPlayer$getGameProfile.invoke(serverPlayer.serverPlayer()); - Multimap properties = (Multimap) Reflections.method$GameProfile$getProperties.invoke(npcProfile); - properties.putAll((Multimap) Reflections.method$GameProfile$getProperties.invoke(pProfile)); - Object information = Reflections.method$ServerPlayer$clientInformation.invoke(serverPlayer.serverPlayer()); - npc = Reflections.constructor$ServerPlayer.newInstance(server, level, npcProfile, information); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to create NPC", e); - } + UUID uuid = UUID.randomUUID(); + Object npcProfile = Reflections.constructor$GameProfile.newInstance(uuid, player.getName()); + Object playerProfile = Reflections.method$ServerPlayer$getGameProfile.invoke(serverPlayer); + Multimap properties = (Multimap) Reflections.method$GameProfile$getProperties.invoke(npcProfile); + properties.putAll((Multimap) Reflections.method$GameProfile$getProperties.invoke(playerProfile)); + + Object npc; + if (VersionHelper.isOrAbove1_20_2()) { + Object clientInfo = Reflections.method$ServerPlayer$clientInformation.invoke(serverPlayer); + npc = Reflections.constructor$ServerPlayer.newInstance(server, level, npcProfile, clientInfo); + } else { + npc = Reflections.constructor$ServerPlayer.newInstance(server, level, npcProfile); + } + int npcId = FastNMS.INSTANCE.method$Entity$getId(npc); + Reflections.method$Entity$absSnapTo.invoke(npc, loc.getX(), loc.getY(), loc.getZ(), 0, 0); + Object npcSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(npcId, uuid, + loc.getX(), loc.getY(), loc.getZ(), 0, 0, + Reflections.instance$EntityType$PLAYER, 0, Reflections.instance$Vec3$Zero, 0); + + // Info + EnumSet enumSet = EnumSet.noneOf((Class) Reflections.clazz$ClientboundPlayerInfoUpdatePacket$Action); + enumSet.add(Reflections.instance$ClientboundPlayerInfoUpdatePacket$Action$ADD_PLAYER); + Object entry; + if (VersionHelper.isOrAbove1_21_4()) { + entry = Reflections.constructor$ClientBoundPlayerInfoUpdatePacket$Entry.newInstance( + uuid, npcProfile, false, 0, Reflections.instance$GameType$SURVIVAL, null, true, 0, null); + } else if (VersionHelper.isOrAbove1_21_3()) { + entry = Reflections.constructor$ClientBoundPlayerInfoUpdatePacket$Entry.newInstance( + uuid, npcProfile, false, 0, Reflections.instance$GameType$SURVIVAL, null, 0, null); + } else { + entry = Reflections.constructor$ClientBoundPlayerInfoUpdatePacket$Entry.newInstance( + uuid, npcProfile, false, 0, Reflections.instance$GameType$SURVIVAL, null, null); + } + Object npcInfoPacket = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enumSet, Collections.singletonList(entry)); + + // Bed + Direction bedDir = Direction.fromYaw(loc.getYaw() + Direction.getYaw(facing)); + if (bedDir == Direction.EAST || bedDir == Direction.WEST) bedDir = bedDir.opposite(); + BlockData bedData = Material.WHITE_BED.createBlockData("[facing=" + bedDir.name().toLowerCase() + ",part=head]"); + Location bedLoc = loc.clone(); + bedLoc.setY(bedLoc.getWorld().getMinHeight()); + Object bedPos = LocationUtils.toBlockPos(new BlockPos(bedLoc.getBlockX(), bedLoc.getBlockY(), bedLoc.getBlockZ())); + Object blockState = BlockStateUtils.blockDataToBlockState(bedData); + Object bedPacket = Reflections.constructor$ClientboundBlockUpdatePacket.newInstance(bedPos, blockState); + + // Data + Object npcData = Reflections.method$Entity$getEntityData.invoke(npc); + Object playerData = Reflections.method$Entity$getEntityData.invoke(serverPlayer); + Reflections.method$Entity$setInvisible.invoke(serverPlayer, true); + Reflections.method$SynchedEntityData$set.invoke(npcData, PlayerData.Pose.entityDataAccessor(), Reflections.instance$Pose$SLEEPING); + Reflections.method$SynchedEntityData$set.invoke(npcData, LivingEntityData.SleepingPos.entityDataAccessor(), Optional.of(bedPos)); + Reflections.method$SynchedEntityData$set.invoke(npcData, PlayerData.Skin.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(playerData, PlayerData.Skin.entityDataAccessor())); + Reflections.method$SynchedEntityData$set.invoke(npcData, PlayerData.Hand.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(playerData, PlayerData.Hand.entityDataAccessor())); + Reflections.method$SynchedEntityData$set.invoke(npcData, PlayerData.LShoulder.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(playerData, PlayerData.LShoulder.entityDataAccessor())); + Reflections.method$SynchedEntityData$set.invoke(npcData, PlayerData.RShoulder.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(playerData, PlayerData.RShoulder.entityDataAccessor())); + Reflections.method$SynchedEntityData$set.invoke(playerData, PlayerData.LShoulder.entityDataAccessor(), Reflections.instance$CompoundTag$Empty); + Reflections.method$SynchedEntityData$set.invoke(playerData, PlayerData.RShoulder.entityDataAccessor(), Reflections.instance$CompoundTag$Empty); + + // SetData + Reflections.method$Entity$setInvisible.invoke(serverPlayer, true); + Object npcDataPacket = FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket( + npcId, (List) Reflections.method$SynchedEntityData$packDirty.invoke(npcData) + ); + + // Remove + Object npcRemovePacket = Reflections.constructor$ClientboundRemoveEntitiesPacket.newInstance((Object) new int[]{npcId}); + + // TP + Object npcTeleportPacket; + if (VersionHelper.isOrAbove1_21_3()) { + Object positionMoveRotation = Reflections.method$PositionMoveRotation$of.invoke(null, npc); + npcTeleportPacket = Reflections.constructor$ClientboundTeleportEntityPacket.newInstance(npcId, positionMoveRotation, Set.of(), false); + } else { + npcTeleportPacket = Reflections.constructor$ClientboundTeleportEntityPacket.newInstance(npc); + } + + + // Equipment + List> emptySlots = new ArrayList<>(); + + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$MAINHAND, Reflections.instance$ItemStack$Air)); + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$OFFHAND, Reflections.instance$ItemStack$Air)); + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$HEAD, Reflections.instance$ItemStack$Air)); + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$CHEST, Reflections.instance$ItemStack$Air)); + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$LEGS, Reflections.instance$ItemStack$Air)); + emptySlots.add(Pair.of(Reflections.instance$EquipmentSlot$FEET, Reflections.instance$ItemStack$Air)); + Object emptyEquipPacket = Reflections.constructor$ClientboundSetEquipmentPacket.newInstance(player.getEntityId(), emptySlots); + + Map equipments = new HashMap<>(); + EntityEquipment equipment = player.getEquipment(); + for (org.bukkit.inventory.EquipmentSlot slot : org.bukkit.inventory.EquipmentSlot.values()) { + if ((!slot.isHand() && !slot.isArmor()) + || (VersionHelper.isOrAbove1_20_5() && slot == org.bukkit.inventory.EquipmentSlot.BODY)) { + continue; + } + EquipmentSlot slotId = EntityUtils.toCEEquipmentSlot(slot); + ItemStack item = equipment.getItem(slot); + equipments.put(slotId, item); + } + List> npcSlots = new ArrayList<>(); + equipments.forEach((slot, item) -> npcSlots.add(Pair.of(EntityUtils.fromEquipmentSlot(slot), FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(item)))); + Object fullEquipPacket = Reflections.constructor$ClientboundSetEquipmentPacket.newInstance(npcId, npcSlots); + + + packets.add(npcInfoPacket); + packets.add(npcSpawnPacket); + packets.add(bedPacket); + packets.add(npcDataPacket); + packets.add(npcTeleportPacket); + packets.add(emptyEquipPacket); + Object npcInitPackets = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets); + + // Spawn + org.bukkit.entity.Entity seatEntity = EntityUtils.spawnSeatEntity(furniture, this, player.getWorld(), loc, false, null); + seatEntity.addPassenger(player); // 0.5 higher + cePlayer.sendPacket(npcInitPackets, true); + cePlayer.sendPacket(fullEquipPacket, true); + if (player.getY() > 0) { + BukkitCraftEngine.instance().scheduler().asyncLater(() -> cePlayer.sendPacket(npcTeleportPacket, true), + 50, TimeUnit.MILLISECONDS); // over height 0 cost 2 npcTeleportPacket + } + + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(npcInitPackets, false); + BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(fullEquipPacket, false); + if (player.getY() > 0) { + BukkitCraftEngine.instance().scheduler().asyncLater(() -> BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(npcTeleportPacket, false), + 50, TimeUnit.MILLISECONDS); + } + } + + // HeadRot + Direction npcDir = bedDir.opposite(); + float npcYawOffset = 0.0f; + if (player.getY() > 0.0874218749) { + if (VersionHelper.isOrAbove1_21_2()) { + if (npcDir == Direction.SOUTH) npcYawOffset = 27; + if (npcDir == Direction.NORTH) npcYawOffset = -26; + } + } + + if (sleep) { + player.setSleepingIgnored(true); + } + + if (phantom) { + player.setStatistic(Statistic.TIME_SINCE_REST, 0); + } + + return new LayEntity( + seatEntity, + furniture, + this.offset(), + npcInitPackets, + npcRemovePacket, + npcTeleportPacket, + (BukkitServerPlayer) cePlayer, + bedLoc, + npc, + npcId, + npcDir, + npcYawOffset, + equipments, + emptyEquipPacket, + fullEquipPacket, + sleep + ); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to spawn LaySeat", e); + } return null; } private static class LayEntity extends BukkitSeatEntity { - private final WeakReference npc; + private final Object npcInitPackets; + private final Object npcRemovePacket; + private final Object npcTPPacket; + private final BukkitServerPlayer serverPlayer; + private final Object npc; + private final Location bedLoc; + private final int npcID; + private final Direction npcDir; + private final float npcYawOffset; - public LayEntity(org.bukkit.entity.Entity entity, org.bukkit.entity.Entity npc) { - super(entity); - this.npc = new WeakReference<>(npc); + // Equipment + private final PlayerMonitorTask task; + private final Map equipments; + private final Object emptyEquipPacket; + private Object updateEquipPacket; + private Object fullEquipPacket; + + private final boolean sleep; + private Object npcRotHeadPacket; + private Object npcDataPacket; + + public LayEntity( + org.bukkit.entity.Entity entity, + Furniture furniture, + Vector3f vector, + Object npcInitPackets, + Object npcRemovePacket, + Object npcTPPacket, + BukkitServerPlayer serverPlayer, + Location bedLoc, + Object npc, + int npcID, + Direction npcDir, + float npcYawOffset, + Map equipments, + Object emptyEquipPacket, + Object fullEquipPacket, + boolean sleep + ) { + super(entity, furniture, vector, serverPlayer.entityID()); + this.npcInitPackets = npcInitPackets; + this.npcRemovePacket = npcRemovePacket; + this.npcTPPacket = npcTPPacket; + this.serverPlayer = serverPlayer; + this.bedLoc = bedLoc; + this.npc = npc; + this.npcID = npcID; + this.npcDir = npcDir; + this.npcYawOffset = npcYawOffset; + + this.task = new PlayerMonitorTask(); + this.equipments = equipments; + this.emptyEquipPacket = emptyEquipPacket; + this.fullEquipPacket = fullEquipPacket; + + this.sleep = sleep; + updateNpcYaw(serverPlayer.xRot()); + updateNpcInvisible(); } @Override - public void sync(Player to) { - + public void add(NetWorkUser from, NetWorkUser to) { + to.sendPacket(this.npcInitPackets, false); + to.sendPacket(this.fullEquipPacket, false); + if (serverPlayer.y() > 0) { + BukkitCraftEngine.instance().scheduler().asyncLater(() -> { + to.sendPacket(this.npcTPPacket, false); + to.sendPacket(this.npcRotHeadPacket, false); + if (npcDataPacket != null) to.sendPacket(this.npcDataPacket, false); + }, 50, TimeUnit.MILLISECONDS); + } else { + to.sendPacket(this.npcRotHeadPacket, false); + if (npcDataPacket != null) to.sendPacket(this.npcDataPacket, false); + } } @Override - public void remove() { + public boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) { + entityIds.add(npcID); + return true; + } + @Override + public void dismount(Player from) { + super.dismount(from); + this.task.task.cancel(); + org.bukkit.entity.Player player = (org.bukkit.entity.Player) from.platformPlayer(); + Object blockPos = LocationUtils.toBlockPos(bedLoc.getBlockX(), bedLoc.getBlockY(), bedLoc.getBlockZ()); + Object blockState = BlockStateUtils.blockDataToBlockState(bedLoc.getBlock().getBlockData()); + try { + Object blockUpdatePacket = Reflections.constructor$ClientboundBlockUpdatePacket.newInstance(blockPos, blockState); + if (player.getPotionEffect(PotionEffectType.INVISIBILITY) == null) Reflections.method$Entity$setInvisible.invoke(serverPlayer.serverPlayer(), false); + from.sendPacket(this.npcRemovePacket, true); + from.sendPacket(blockUpdatePacket, true); + + Object npcData = Reflections.method$Entity$getEntityData.invoke(npc); + Object playerData = Reflections.method$Entity$getEntityData.invoke(serverPlayer.serverPlayer()); + Reflections.method$SynchedEntityData$set.invoke(playerData, PlayerData.LShoulder.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(npcData, PlayerData.LShoulder.entityDataAccessor())); + Reflections.method$SynchedEntityData$set.invoke(playerData, PlayerData.RShoulder.entityDataAccessor(), Reflections.method$SynchedEntityData$get.invoke(npcData, PlayerData.RShoulder.entityDataAccessor())); + if (player.getPotionEffect(PotionEffectType.INVISIBILITY) == null) Reflections.method$Entity$setInvisible.invoke(serverPlayer.serverPlayer(), false); + + player.updateInventory(); + + if (sleep) { + player.setSleepingIgnored(false); + } + + Object fullSlots = Reflections.method$ClientboundSetEquipmentPacket$getSlots.invoke(this.fullEquipPacket); + Object recoverEquip = Reflections.constructor$ClientboundSetEquipmentPacket.newInstance(player.getEntityId(), fullSlots); + + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitServerPlayer sp = BukkitAdaptors.adapt(p); + sp.entityPacketHandlers().remove(playerID()); + sp.sendPacket(this.npcRemovePacket, false); + sp.sendPacket(blockUpdatePacket, false); + sp.sendPacket(recoverEquip, false); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to dismount LayEntity", e); + } + } + + public void equipmentChange(Map equipmentChanges, int previousSlot) { + org.bukkit.entity.Player player = serverPlayer.platformPlayer(); + List> changedSlots = new ArrayList<>(); + + for (Map.Entry entry : equipmentChanges.entrySet()) { + Object slotId = EntityUtils.fromEquipmentSlot(entry.getKey()); + Object itemStack = FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(entry.getValue()); + changedSlots.add(Pair.of(slotId, itemStack)); + } + this.equipments.putAll(equipmentChanges); + + List> allSlots = new ArrayList<>(); + equipments.forEach((slot, item) -> + allSlots.add(Pair.of(EntityUtils.fromEquipmentSlot(slot), FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(item)))); + try { + this.updateEquipPacket = Reflections.constructor$ClientboundSetEquipmentPacket.newInstance(npcID, changedSlots); + this.fullEquipPacket = Reflections.constructor$ClientboundSetEquipmentPacket.newInstance(npcID, allSlots); + if (previousSlot != -1) { + player.updateInventory(); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle equipmentChange", e); + } + + serverPlayer.sendPacket(this.emptyEquipPacket, false); + serverPlayer.sendPacket(this.updateEquipPacket, false); + + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(this.updateEquipPacket, false); + } + } + + @Override + public void handleSetEquipment(NetWorkUser user, NMSPacketEvent event, Object packet) { + if (this.emptyEquipPacket == packet) return; + event.setCancelled(true); + } + + @Override + public void handleContainerSetSlot(NetWorkUser user, NMSPacketEvent event, Object packet) { + try { + int slot = (int) Reflections.method$ClientboundContainerSetSlotPacket$getSlot.invoke(packet); + org.bukkit.entity.Player player = (org.bukkit.entity.Player) user.platformPlayer(); + + int convertSlot; + boolean isPlayerInv; + + if (!VersionHelper.isOrAbove1_21_1()) { + Object openInventory = player.getClass().getMethod("getOpenInventory").invoke(player); + + Method convertSlotMethod = openInventory.getClass().getMethod("convertSlot", int.class); + convertSlot = (int) convertSlotMethod.invoke(openInventory, slot); + + Method getTopInventoryMethod = openInventory.getClass().getMethod("getTopInventory"); + Object topInventory = getTopInventoryMethod.invoke(openInventory); + Method getTypeMethod = topInventory.getClass().getMethod("getType"); + Object type = getTypeMethod.invoke(topInventory); + isPlayerInv = type == InventoryType.CRAFTING; + } else { + convertSlot = player.getOpenInventory().convertSlot(slot); + isPlayerInv = player.getOpenInventory().getTopInventory().getType() == InventoryType.CRAFTING; + } + + if (!(convertSlot == player.getInventory().getHeldItemSlot() || (isPlayerInv && (slot == 45 || (slot >= 5 && slot <= 8))))) return; + int containerId = (int) Reflections.method$ClientboundContainerSetSlotPacket$getContainerId.invoke(packet); + int stateId = (int) Reflections.method$ClientboundContainerSetSlotPacket$getStateId.invoke(packet); + Object replacePacket = Reflections.constructor$ClientboundContainerSetSlotPacket.newInstance(containerId, stateId, slot, Reflections.instance$ItemStack$Air); + event.replacePacket(replacePacket); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handleContainerSetSlotPacket", e); + } + } + + @Override + public void event(Player player, Object event) { + if (event instanceof PlayerAnimationEvent e) { + try { + Object animatePacket; + if (e.getAnimationType() == PlayerAnimationType.ARM_SWING) { + animatePacket = Reflections.constructor$ClientboundAnimatePacket.newInstance(npc, 0); + } else { + animatePacket = Reflections.constructor$ClientboundAnimatePacket.newInstance(npc, 3); + } + serverPlayer.sendPacket(animatePacket, true); + for (org.bukkit.entity.Player other : PlayerUtils.getTrackedBy(serverPlayer.platformPlayer())) { + BukkitNetworkManager.instance().getOnlineUser(other).sendPacket(animatePacket, true); + } + } catch (Exception exception) { + CraftEngine.instance().logger().warn("Failed to handle PlayerAnimationEvent", exception); + } + } else if (event instanceof PlayerItemHeldEvent e) { + ItemStack item = e.getPlayer().getInventory().getItem(e.getNewSlot()); + if (item == null) item = ItemUtils.AIR; + + equipmentChange(Map.of(EquipmentSlot.MAIN_HAND, item), e.getPreviousSlot()); + } } @Override public Key type() { return Key.of("craftengine", "lay"); } + + private class PlayerMonitorTask implements Runnable { + + private final SchedulerTask task; + private float lastYaw; + + private PlayerMonitorTask() { + org.bukkit.entity.Player p = serverPlayer.platformPlayer(); + BukkitCraftEngine plugin = BukkitCraftEngine.instance(); + if (VersionHelper.isFolia()) { + this.task = new FoliaTask(p.getScheduler().runAtFixedRate(plugin.javaPlugin(), (t) -> this.run(), () -> {}, 1, 1)); + } else { + this.task = plugin.scheduler().sync().runRepeating(this, 1, 1); + } + } + + @Override + public void run() { + org.bukkit.entity.Player player = serverPlayer.platformPlayer(); + if (player == null || !player.isValid()) { + this.task.cancel(); + return; + } + + // Invisible + updateNpcInvisible(); + try { + if (!player.isInvisible()) Reflections.method$Entity$setInvisible.invoke(serverPlayer.serverPlayer(), true); + } catch (Exception exception) { + CraftEngine.instance().logger().warn("Failed to set shared flag", exception); + } + + // Sync Rotation + float playerYaw = player.getYaw(); + if (lastYaw != playerYaw) { + updateNpcYaw(playerYaw); + serverPlayer.sendPacket(npcRotHeadPacket, false); + for (org.bukkit.entity.Player other : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().getOnlineUser(other).sendPacket(npcRotHeadPacket, true); + } + this.lastYaw = playerYaw; + } + + // Sync Equipment + Map newEquipments = new HashMap<>(); + for (EquipmentSlot slot : EquipmentSlot.values()) { + if (!slot.isHand() && !slot.isPlayerArmor()) continue; + ItemStack newItem = player.getEquipment().getItem(EntityUtils.toBukkitEquipmentSlot(slot)); + try { + ItemStack item = equipments.get(slot); + boolean isChange = !newItem.equals(item); + if (isChange) { + newEquipments.put(slot, newItem); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to monitor equipments change", e); + } + } + + if (!newEquipments.isEmpty()) { + equipmentChange(newEquipments, -1); + return; + } + serverPlayer.sendPacket(emptyEquipPacket, false); + } + } + + private void updateNpcYaw(float playerYaw) { + byte packYaw = getRot(playerYaw); + try { + this.npcRotHeadPacket = Reflections.constructor$ClientboundRotateHeadPacket.newInstance(npc, packYaw); + } catch (Exception exception) { + CraftEngine.instance().logger().warn("Failed to sync NPC yaw", exception); + } + } + + private byte getRot(float playerYaw) { + float npcYaw = Direction.getYaw(npcDir); + float centerYaw = normalizeYaw(npcYaw); + float playerYawNorm = normalizeYaw(playerYaw); + + float deltaYaw = normalizeYaw(playerYawNorm - centerYaw); + boolean isBehind = Math.abs(deltaYaw) > 90; + + float mappedYaw; + if (isBehind) { + float rel = Math.abs(deltaYaw) - 180; + mappedYaw = rel * (deltaYaw > 0 ? -1 : 1); + } else { + mappedYaw = deltaYaw; + } + + float limitedYaw = Math.max(-45, Math.min(45, mappedYaw)); + float finalYaw = limitedYaw + npcYawOffset; + return MCUtils.packDegrees(finalYaw); + } + + private float normalizeYaw(float yaw) { + yaw %= 360.0f; + if (yaw < -180.0f) yaw += 360.0f; + if (yaw >= 180.0f) yaw -= 360.0f; + return yaw; + } + + private void updateNpcInvisible() { + try { + org.bukkit.entity.Player player = serverPlayer.platformPlayer(); + if (player.getPotionEffect(PotionEffectType.INVISIBILITY) == null && npcDataPacket != null) { + npcDataPacket = null; + Reflections.method$Entity$setInvisible.invoke(npc, false); + Object npcData = Reflections.method$Entity$getEntityData.invoke(npc); + Object dataItem = Reflections.method$SynchedEntityData$getItem.invoke(npcData, PlayerData.SharedFlags.entityDataAccessor()); + Object dataValue = Reflections.method$SynchedEntityData$DataItem$value.invoke(dataItem); + Object packet = FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(npcID, List.of(dataValue)); + serverPlayer.sendPacket(packet, false); + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(packet, false); + } + } else if (player.getPotionEffect(PotionEffectType.INVISIBILITY) != null && npcDataPacket == null) { + Reflections.method$Entity$setInvisible.invoke(npc, true); + Object npcData = Reflections.method$Entity$getEntityData.invoke(npc); + Object dataItem = Reflections.method$SynchedEntityData$getItem.invoke(npcData, PlayerData.SharedFlags.entityDataAccessor()); + Object dataValue = Reflections.method$SynchedEntityData$DataItem$value.invoke(dataItem); + npcDataPacket = FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(npcID, List.of(dataValue)); + serverPlayer.sendPacket(npcDataPacket, false); + for (org.bukkit.entity.Player p : PlayerUtils.getTrackedBy(player)) { + BukkitNetworkManager.instance().getOnlineUser(p).sendPacket(npcDataPacket, false); + } + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to updateNpcInvisible", e); + } + } } public static class Factory implements SeatFactory { @Override - public Seat create(List arguments) { - return null; + public Seat create(List args) { + Vector3f offset = MiscUtils.getAsVector3f(args.get(0), "seats"); + Direction facing = args.size() > 1 ? parseFacing(args.get(1)) : Direction.SOUTH; + boolean sleep = args.size() > 2 && Boolean.parseBoolean(args.get(2)); + boolean phantom = args.size() > 4 && Boolean.parseBoolean(args.get(3)); + + if (facing == Direction.NORTH || facing == Direction.SOUTH) { + float temp = offset.x; + offset.x = offset.z; + offset.z = temp; + } + return new LaySeat(offset, facing, sleep, phantom); + } + + private Direction parseFacing(String facing) { + return switch (facing.toLowerCase()) { + case "back" -> Direction.NORTH; + case "left" -> Direction.WEST; + case "right" -> Direction.EAST; + default -> Direction.SOUTH; + }; } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/SitSeat.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/SitSeat.java index 0e1ef4994..7593d64be 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/SitSeat.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/seat/SitSeat.java @@ -1,29 +1,20 @@ package net.momirealms.craftengine.bukkit.entity.furniture.seat; -import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.util.EntityUtils; -import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; -import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.entity.furniture.AbstractSeat; import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.furniture.Seat; import net.momirealms.craftengine.core.entity.furniture.SeatFactory; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.entity.seat.SeatEntity; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Location; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.persistence.PersistentDataType; +import org.bukkit.entity.Entity; import org.joml.Vector3f; import java.util.List; -import java.util.Objects; public class SitSeat extends AbstractSeat { public static final SeatFactory FACTORY = new Factory(); @@ -35,62 +26,21 @@ public class SitSeat extends AbstractSeat { } @Override - public Entity spawn(Player player, Furniture furniture) { + public SeatEntity spawn(Player player, Furniture furniture) { return spawn((org.bukkit.entity.Player) player.platformPlayer(), furniture); } - public Entity spawn(org.bukkit.entity.Player player, Furniture furniture) { - Location location = ((LoadedFurniture)furniture).calculateSeatLocation(this); - org.bukkit.entity.Entity seatEntity = this.limitPlayerRotation ? - EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isOrAbove1_20_2() ? location.subtract(0,0.9875,0) : location.subtract(0,0.990625,0), EntityType.ARMOR_STAND, entity -> { - ArmorStand armorStand = (ArmorStand) entity; - if (VersionHelper.isOrAbove1_21_3()) { - Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); - } else { - LegacyAttributeUtils.setMaxHealth(armorStand); - } - armorStand.setSmall(true); - armorStand.setInvisible(true); - armorStand.setSilent(true); - armorStand.setInvulnerable(true); - armorStand.setArms(false); - armorStand.setCanTick(false); - armorStand.setAI(false); - armorStand.setGravity(false); - armorStand.setPersistent(false); - armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); - armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, this.offset().x + ", " + this.offset().y + ", " + this.offset().z); - }) : - EntityUtils.spawnEntity(player.getWorld(), VersionHelper.isOrAbove1_20_2() ? location : location.subtract(0,0.25,0), EntityType.ITEM_DISPLAY, entity -> { - ItemDisplay itemDisplay = (ItemDisplay) entity; - itemDisplay.setPersistent(false); - itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); - itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, this.offset().x + ", " + this.offset().y + ", " + this.offset().z); - }); + public SeatEntity spawn(org.bukkit.entity.Player player, Furniture furniture) { + Location location = ((BukkitFurniture)furniture).calculateSeatLocation(this); + org.bukkit.entity.Entity seatEntity = EntityUtils.spawnSeatEntity(furniture, this, player.getWorld(), location, this.limitPlayerRotation, null); seatEntity.addPassenger(player); - return new SitEntity(seatEntity); + return new SitEntity(seatEntity, furniture, offset(), player.getEntityId()); } private static class SitEntity extends BukkitSeatEntity { - public SitEntity(org.bukkit.entity.Entity entity) { - super(entity); - } - - @Override - public void sync(Player to) {} - - @Override - public void remove() { - org.bukkit.entity.Entity entity = this.literalObject(); - if (entity == null) return; - for (org.bukkit.entity.Entity passenger : entity.getPassengers()) { - entity.removePassenger(passenger); - if (passenger instanceof org.bukkit.entity.Player p) { - BukkitAdaptors.adapt(p).setSeat(null); - } - } - entity.remove(); + public SitEntity(Entity entity, Furniture furniture, Vector3f vector3f, int playerID) { + super(entity, furniture, vector3f, playerID); } @Override @@ -103,8 +53,8 @@ public class SitSeat extends AbstractSeat { @Override public Seat create(List args) { - if (args.size() == 1) return new SitSeat(MiscUtils.getAsVector3f(args.get(0), "seats"), 0, false); - return new SitSeat(MiscUtils.getAsVector3f(args.get(0), "seats"), Float.parseFloat(args.get(1)), true); + if (args.size() == 1) return new SitSeat(MiscUtils.getAsVector3f(args.getFirst(), "seats"), 0, false); + return new SitSeat(MiscUtils.getAsVector3f(args.getFirst(), "seats"), Float.parseFloat(args.get(1)), true); } } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java index 7e1a63eea..ac1e9ae7b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGetBlockInternalIdCommand.java @@ -8,8 +8,6 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.FlagKeys; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.entity.Pose; import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.Command; import org.incendo.cloud.context.CommandContext; @@ -37,13 +35,10 @@ public class DebugGetBlockInternalIdCommand extends BukkitCommandFeature { - Player player = (Player) context.sender(); - if (player.hasFixedPose()) player.setPose(Pose.STANDING, false); - else player.setPose(Pose.SWIMMING, true); String data = context.get("id"); - //ImmutableBlockState state = BlockStateParser.deserialize(data); - //if (state == null) return; - //context.sender().sendMessage(BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().handle()).toString()); + ImmutableBlockState state = BlockStateParser.deserialize(data); + if (state == null) return; + context.sender().sendMessage(BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().handle()).toString()); }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 98a7df2ac..eb6de3ea8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -17,6 +17,7 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.LibraryRefl import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.*; @@ -162,6 +163,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerNMSPacketConsumer(PacketConsumers.MOVE_POS_ENTITY, NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos); registerNMSPacketConsumer(PacketConsumers.ROTATE_HEAD, NetworkReflections.clazz$ClientboundRotateHeadPacket); registerNMSPacketConsumer(PacketConsumers.SET_ENTITY_MOTION, NetworkReflections.clazz$ClientboundSetEntityMotionPacket); + registerNMSPacketConsumer(PacketConsumers.SET_EQUIPMENT_NMS, Reflections.clazz$ClientboundSetEquipmentPacket); + registerNMSPacketConsumer(PacketConsumers.SET_CONTAINER_SLOT, Reflections.clazz$ClientboundContainerSetSlotPacket); registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, this.packetIds.clientboundLevelChunkWithLightPacket()); registerS2CByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket()); registerS2CByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index cf6ef4dbc..9cefa7cfc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -9,7 +9,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslationArgument; -import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent; @@ -180,6 +179,17 @@ public class PacketConsumers { user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE); } }; + ADD_ENTITY_HANDLERS[MEntityTypes.instance$EntityType$PLAYER$registryId] = (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + buf.readVarInt(); + UUID uuid = buf.readUUID(); + BukkitServerPlayer player = (BukkitServerPlayer) BukkitCraftEngine.instance().networkManager().getOnlineUser(uuid); + if (player == null) return; + SeatEntity seat = player.seat(); + if (seat == null) return; + user.entityPacketHandlers().put(seat.playerID(), seat); + seat.add(player, user); + }; } private static BukkitNetworkManager.Handlers simpleAddEntityHandler(EntityPacketHandler handler) { @@ -1497,7 +1507,7 @@ public class PacketConsumers { for (int i = 0, size = intList.size(); i < size; i++) { int entityId = intList.getInt(i); EntityPacketHandler handler = user.entityPacketHandlers().remove(entityId); - if (handler != null && handler.handleEntitiesRemove(intList)) { + if (handler != null && handler.handleEntitiesRemove(user, intList)) { isChange = true; } } @@ -1646,6 +1656,9 @@ public class PacketConsumers { } else { furniture.findFirstAvailableSeat(entityId).ifPresent(seat -> { if (furniture.tryOccupySeat(seat)) { + SeatEntity currentSeat = serverPlayer.seat(); + if (currentSeat != null) currentSeat.dismount(serverPlayer); + furniture.spawnSeatEntityForPlayer(serverPlayer, seat); } }); @@ -1911,6 +1924,10 @@ public class PacketConsumers { @SuppressWarnings("unchecked") public static final BiConsumer SET_ENTITY_DATA = (user, event) -> { try { + SeatEntity seat = ((BukkitServerPlayer)user).seat(); + if (seat != null) { + seat.handleSetEntityData(user, event); + } FriendlyByteBuf buf = event.getBuffer(); int id = buf.readVarInt(); EntityPacketHandler handler = user.entityPacketHandlers().get(id); @@ -2412,4 +2429,24 @@ public class PacketConsumers { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEntityMotionPacket", e); } }; + public static final TriConsumer SET_EQUIPMENT_NMS = (user, event, packet) -> { + try { + int entityId = (int) Reflections.method$ClientboundSetEquipmentPacket$getEntity.invoke(packet); + EntityPacketHandler handler = user.entityPacketHandlers().get(entityId); + if (handler != null) { + handler.handleSetEquipment(user, event, packet); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEquipmentPacket", e); + } + }; + + public static final TriConsumer SET_CONTAINER_SLOT = (user, event, packet) -> { + try { + SeatEntity seat = ((BukkitServerPlayer) user).seat(); + if (seat != null) seat.handleContainerSetSlot(user, event, packet); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEquipmentPacket", e); + } + }; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java index 0a60cfc3a..8e26a3d3a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java @@ -15,7 +15,7 @@ public class FurniturePacketHandler implements EntityPacketHandler { } @Override - public boolean handleEntitiesRemove(IntList entityIds) { + public boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) { entityIds.addAll(this.fakeEntities); return true; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index 30fd6fa4b..f002866ef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -1,18 +1,26 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.core.entity.EquipmentSlot; +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import net.momirealms.craftengine.core.entity.furniture.Seat; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.persistence.PersistentDataType; +import java.util.Objects; import java.util.function.Consumer; +import static net.momirealms.craftengine.core.entity.EquipmentSlot.BODY; +import static net.momirealms.craftengine.core.entity.EquipmentSlot.MAIN_HAND; + public class EntityUtils { private EntityUtils() {} @@ -34,4 +42,94 @@ public class EntityUtils { return LegacyEntityUtils.spawnEntity(world, loc, type, function); } } + + public static Entity spawnSeatEntity(Furniture furniture, Seat seat, World world, Location loc, boolean limitPlayerRotation, Consumer function) { + EntityType type; + if (limitPlayerRotation) { + type = EntityType.ARMOR_STAND; + loc = VersionHelper.isOrAbove1_20_2() ? loc.subtract(0,0.9875,0) : loc.subtract(0,0.990625,0); + if (function == null) { + function = entity -> { + ArmorStand armorStand = (ArmorStand) entity; + if (VersionHelper.isOrAbove1_21_3()) { + Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); + } else { + LegacyAttributeUtils.setMaxHealth(armorStand); + } + armorStand.setSmall(true); + armorStand.setInvisible(true); + armorStand.setSilent(true); + armorStand.setInvulnerable(true); + armorStand.setArms(false); + armorStand.setCanTick(false); + armorStand.setAI(false); + armorStand.setGravity(false); + armorStand.setPersistent(false); + armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); + //armorStand.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, seat.offset().x + ", " + seat.offset().y + ", " + seat.offset().z); + }; + } + } else { + type = EntityType.ITEM_DISPLAY; + loc = VersionHelper.isOrAbove1_20_2() ? loc : loc.subtract(0,0.25,0); + if (function == null) { + function = entity -> { + ItemDisplay itemDisplay = (ItemDisplay) entity; + itemDisplay.setPersistent(false); + itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER, furniture.baseEntityId()); + //itemDisplay.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY, PersistentDataType.STRING, seat.offset().x + ", " + seat.offset().y + ", " + seat.offset().z); + }; + } + } + return spawnEntity(world, loc, type, function); + } + + public static org.bukkit.inventory.EquipmentSlot toBukkitEquipmentSlot(EquipmentSlot slot) { + return switch (slot) { + case MAIN_HAND -> org.bukkit.inventory.EquipmentSlot.HAND; + case OFF_HAND -> org.bukkit.inventory.EquipmentSlot.OFF_HAND; + case HEAD -> org.bukkit.inventory.EquipmentSlot.HEAD; + case CHEST -> org.bukkit.inventory.EquipmentSlot.CHEST; + case LEGS -> org.bukkit.inventory.EquipmentSlot.LEGS; + case FEET -> org.bukkit.inventory.EquipmentSlot.FEET; + default -> org.bukkit.inventory.EquipmentSlot.BODY; + }; + } + + public static EquipmentSlot toCEEquipmentSlot(org.bukkit.inventory.EquipmentSlot slot) { + return switch (slot) { + case HAND -> MAIN_HAND; + case OFF_HAND -> EquipmentSlot.OFF_HAND; + case HEAD -> EquipmentSlot.HEAD; + case CHEST -> EquipmentSlot.CHEST; + case LEGS -> EquipmentSlot.LEGS; + case FEET -> EquipmentSlot.FEET; + case BODY -> EquipmentSlot.BODY; + default -> BODY; + }; + } + + public static Object fromEquipmentSlot(org.bukkit.inventory.EquipmentSlot slot) { + return switch (slot) { + case HAND -> Reflections.instance$EquipmentSlot$MAINHAND; + case OFF_HAND -> Reflections.instance$EquipmentSlot$OFFHAND; + case HEAD -> Reflections.instance$EquipmentSlot$HEAD; + case CHEST -> Reflections.instance$EquipmentSlot$CHEST; + case LEGS -> Reflections.instance$EquipmentSlot$LEGS; + case FEET -> Reflections.instance$EquipmentSlot$FEET; + default -> new Object(); + }; + }; + + public static Object fromEquipmentSlot(EquipmentSlot slot) { + return switch (slot) { + case MAIN_HAND -> Reflections.instance$EquipmentSlot$MAINHAND; + case OFF_HAND -> Reflections.instance$EquipmentSlot$OFFHAND; + case HEAD -> Reflections.instance$EquipmentSlot$HEAD; + case CHEST -> Reflections.instance$EquipmentSlot$CHEST; + case LEGS -> Reflections.instance$EquipmentSlot$LEGS; + case FEET -> Reflections.instance$EquipmentSlot$FEET; + default -> new Object(); + }; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemUtils.java index 1841169e8..46f8594e7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemUtils.java @@ -7,6 +7,8 @@ import org.jetbrains.annotations.Contract; public class ItemUtils { + public static final ItemStack AIR = new ItemStack(Material.AIR); + private ItemUtils() {} @Contract("null -> true") diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java index 9a0c33285..189d9bc16 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Location; import org.bukkit.entity.Item; import org.bukkit.entity.Player; @@ -20,6 +21,7 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Set; import static java.util.Objects.requireNonNull; @@ -174,4 +176,8 @@ public final class PlayerUtils { BukkitCraftEngine.instance().logger().warn("Failed to send totem animation"); } } + + public static Set getTrackedBy(Player player) { + return VersionHelper.isOrAbove1_20_2() ? player.getTrackedBy() : player.getTrackedPlayers(); + } } diff --git a/common-files/src/main/resources/resources/default/configuration/furniture.yml b/common-files/src/main/resources/resources/default/configuration/furniture.yml index 5e3018767..35b568623 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture.yml @@ -36,7 +36,7 @@ items: interactive: true interaction-entity: true seats: - - 0,0,-0.1 0 crawl + - 0,0,-0.1 0 - 1,0,-0.1 0 loot: template: default:loot_table/furniture diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 57db5e240..2e5b798c2 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -151,6 +151,7 @@ warning.config.furniture.element.missing_item: "Issue found in file Issue found in file - The furniture '' is using an unknown setting type ''." warning.config.furniture.hitbox.invalid_type: "Issue found in file - The furniture '' is using an invalid hitbox type ''." warning.config.furniture.hitbox.custom.invalid_entity: "Issue found in file - The furniture '' is using a custom hitbox with invalid entity type ''." +warning.config.furniture.seat.invalid_type: "Issue found in file - The furniture '' is using an invalid seat type ''." warning.config.item.duplicate: "Issue found in file - Duplicated item ''. Please check if there is the same configuration in other files." warning.config.item.settings.unknown: "Issue found in file - The item '' is using an unknown setting type ''." warning.config.item.settings.invulnerable.invalid_damage_source: "Issue found in file - The item '' is using an unknown damage source ''. Allowed sources: []." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 62be30309..cb80b377d 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -151,6 +151,7 @@ warning.config.furniture.element.missing_item: "在文件 发现 warning.config.furniture.settings.unknown: "在文件 发现问题 - 家具 '' 使用了未知的设置类型 ''" warning.config.furniture.hitbox.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的碰撞箱类型 ''" warning.config.furniture.hitbox.custom.invalid_entity: "在文件 发现问题 - 家具 '' 的自定义碰撞箱使用了无效的实体类型 ''" +warning.config.furniture.seat.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的座椅类型 ''" warning.config.item.duplicate: "在文件 发现问题 - 重复的物品 '' 请检查其他文件中是否存在相同配置" warning.config.item.settings.unknown: "在文件 发现问题 - 物品 '' 使用了未知的设置类型 ''" warning.config.item.settings.invulnerable.invalid_damage_source: "在文件 发现问题 - 物品 '' 物品使用了未知的伤害来源类型 '' 允许的来源: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/EquipmentSlot.java b/core/src/main/java/net/momirealms/craftengine/core/entity/EquipmentSlot.java index 55c343103..f3f9c279a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/EquipmentSlot.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/EquipmentSlot.java @@ -8,5 +8,13 @@ public enum EquipmentSlot { BODY, MAIN_HAND, OFF_HAND, - SADDLE + SADDLE; + + public boolean isHand() { + return this == MAIN_HAND || this == OFF_HAND; + } + + public boolean isPlayerArmor() { + return this == HEAD || this == CHEST || this == LEGS || this == FEET; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Seat.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Seat.java index d0f3eadd3..deb77a736 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Seat.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Seat.java @@ -1,12 +1,12 @@ package net.momirealms.craftengine.core.entity.furniture; -import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.entity.seat.SeatEntity; import org.joml.Vector3f; public interface Seat { - Entity spawn(Player player, Furniture furniture); + SeatEntity spawn(Player player, Furniture furniture); Vector3f offset(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/SeatType.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/SeatType.java index a049595d7..1a96248d3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/SeatType.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/SeatType.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.entity.furniture; import com.google.common.collect.Lists; -import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.Registries; @@ -35,7 +35,7 @@ public class SeatType { } factory = BuiltInRegistries.SEAT_FACTORY.getValue(type); if (factory == null) { - throw new LocalizedException("warning.config.furniture.hitbox.invalid_type"); + throw new LocalizedResourceConfigException("warning.config.furniture.seat.invalid_type", type.toString()); } return factory.create(split); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/seat/SeatEntity.java b/core/src/main/java/net/momirealms/craftengine/core/entity/seat/SeatEntity.java index ccb0f1694..dc621df4b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/seat/SeatEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/seat/SeatEntity.java @@ -1,14 +1,24 @@ package net.momirealms.craftengine.core.entity.seat; -import net.momirealms.craftengine.core.entity.AbstractEntity; -import net.momirealms.craftengine.core.entity.Entity; +import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import org.joml.Vector3f; -public abstract class SeatEntity extends AbstractEntity { +public interface SeatEntity extends EntityPacketHandler { - public abstract void sync(Player to); + void add(NetWorkUser from, NetWorkUser to); - public abstract void dismount(Player from); + void dismount(Player player); - public abstract void remove(); + void event(Player player, Object event); + + void destroy(); + + Furniture furniture(); + + Vector3f vector3f(); + + int playerID(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java index 15e0aa919..163556067 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java @@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.ints.IntList; public interface EntityPacketHandler { - default boolean handleEntitiesRemove(IntList entityIds) { + default boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) { return false; } @@ -19,4 +19,10 @@ public interface EntityPacketHandler { default void handleMove(NetWorkUser user, NMSPacketEvent event, Object packet) { } + + default void handleSetEquipment(NetWorkUser user, NMSPacketEvent event, Object packet) { + } + + default void handleContainerSetSlot(NetWorkUser user, NMSPacketEvent event, Object packet) { + } }