diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index 5624e7bd8..7dc8f070f 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -140,6 +140,9 @@ furniture: - "xxx:invalid_furniture" # Whether to hide the entity containing metadata hide-base-entity: true + # Removed collision box entity texture + removed-collision-box-entity-texture-1_20_2: true + removed-collision-box-entity-texture-1_20: false image: # Prevent players from using images set in minecraft:default font diff --git a/bukkit/loader/src/main/resources/internal/textures/entity/shulker/shulker.png b/bukkit/loader/src/main/resources/internal/textures/entity/shulker/shulker.png new file mode 100644 index 000000000..b6c9cade6 Binary files /dev/null and b/bukkit/loader/src/main/resources/internal/textures/entity/shulker/shulker.png differ diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BaseEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BaseEntityData.java new file mode 100644 index 000000000..1f8736c4c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BaseEntityData.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.bukkit.entity; + +import net.momirealms.craftengine.bukkit.util.Reflections; + +import java.util.Optional; + +public class BaseEntityData extends SimpleEntityData { + + public static final BaseEntityData SharedFlags = new BaseEntityData<>(0, EntityDataValue.Serializers$BYTE, (byte) 0); + public static final BaseEntityData AirSupply = new BaseEntityData<>(1, EntityDataValue.Serializers$INT, 300); + public static final BaseEntityData> CustomName = new BaseEntityData<>(2, EntityDataValue.Serializers$OPTIONAL_COMPONENT, Optional.empty()); + public static final BaseEntityData CustomNameVisible = new BaseEntityData<>(3, EntityDataValue.Serializers$BOOLEAN, false); + public static final BaseEntityData Silent = new BaseEntityData<>(4, EntityDataValue.Serializers$BOOLEAN, false); + public static final BaseEntityData NoGravity = new BaseEntityData<>(5, EntityDataValue.Serializers$BOOLEAN, false); + public static final BaseEntityData Pose = new BaseEntityData<>(6, EntityDataValue.Serializers$POSE, Reflections.instance$Pose$STANDING); + public static final BaseEntityData TicksFrozen = new BaseEntityData<>(7, EntityDataValue.Serializers$INT, 0); + + public BaseEntityData(int id, Object serializer, T defaultValue) { + super(id, serializer, defaultValue); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/EntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/EntityData.java new file mode 100644 index 000000000..6a35b9d71 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/EntityData.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.bukkit.entity; + +import java.util.List; + +public interface EntityData { + + Object serializer(); + int id(); + T defaultValue(); + + default Object createEntityDataIfNotDefaultValue(T value) { + if (defaultValue().equals(value)) return null; + return EntityDataValue.create(id(), serializer(), value); + } + + default void addEntityDataIfNotDefaultValue(T value, List list) { + if (!defaultValue().equals(value)) { + list.add(EntityDataValue.create(id(), serializer(), value)); + } + } + + default void addEntityData(T value, List list) { + list.add(EntityDataValue.create(id(), serializer(), value)); + } + + static EntityData of(int id, Object serializer, T defaultValue) { + return new SimpleEntityData<>(id, serializer, defaultValue); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/LivingEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/LivingEntityData.java new file mode 100644 index 000000000..4efe9a5f0 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/LivingEntityData.java @@ -0,0 +1,19 @@ +package net.momirealms.craftengine.bukkit.entity; + +import java.util.List; +import java.util.Optional; + +public class LivingEntityData extends BaseEntityData { + + public static final LivingEntityData LivingEntityFlags = new LivingEntityData<>(8, EntityDataValue.Serializers$BYTE, (byte) 0); + public static final LivingEntityData Health = new LivingEntityData<>(9, EntityDataValue.Serializers$FLOAT, 1.0f); + public static final LivingEntityData> EffectParticles = new LivingEntityData<>(10, EntityDataValue.Serializers$PARTICLES, List.of()); + public static final LivingEntityData EffectAmbience = new LivingEntityData<>(11, EntityDataValue.Serializers$BOOLEAN, false); + public static final LivingEntityData ArrowCount = new LivingEntityData<>(12, EntityDataValue.Serializers$INT, 0); + public static final LivingEntityData StingerCount = new LivingEntityData<>(13, EntityDataValue.Serializers$INT, 0); + public static final LivingEntityData> SleepingPos = new LivingEntityData<>(14, EntityDataValue.Serializers$OPTIONAL_BLOCK_POS, Optional.empty()); + + public LivingEntityData(int id, Object serializer, T defaultValue) { + super(id, serializer, defaultValue); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/MobData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/MobData.java new file mode 100644 index 000000000..c7093e344 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/MobData.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.bukkit.entity; + +public class MobData extends LivingEntityData { + + public static final MobData MobFlags = new MobData<>(15, EntityDataValue.Serializers$BYTE, (byte) 0); + + public MobData(int id, Object serializer, T defaultValue) { + super(id, serializer, defaultValue); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/ShulkerData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/ShulkerData.java new file mode 100644 index 000000000..8353a78e3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/ShulkerData.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.bukkit.entity; + +import net.momirealms.craftengine.bukkit.util.Reflections; + +public class ShulkerData extends MobData { + + public static final ShulkerData AttachFace = new ShulkerData<>(16, EntityDataValue.Serializers$DIRECTION, Reflections.instance$Direction$DOWN); + public static final ShulkerData Peek = new ShulkerData<>(17, EntityDataValue.Serializers$BYTE, (byte) 0); + public static final ShulkerData Color = new ShulkerData<>(18, EntityDataValue.Serializers$BYTE, (byte) 16); + + public ShulkerData(int id, Object serializer, T defaultValue) { + super(id, serializer, defaultValue); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/SimpleEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/SimpleEntityData.java new file mode 100644 index 000000000..f2c079409 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/SimpleEntityData.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.bukkit.entity; + +public class SimpleEntityData implements EntityData { + + private final int id; + private final Object serializer; + private final T defaultValue; + + public SimpleEntityData(int id, Object serializer, T defaultValue) { + this.id = id; + this.serializer = serializer; + this.defaultValue = defaultValue; + } + + @Override + public int id() { + return id; + } + + @Override + public Object serializer() { + return serializer; + } + + @Override + public T defaultValue() { + return defaultValue; + } +} 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 a7a8f24a2..9772d52a9 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 @@ -1,8 +1,10 @@ package net.momirealms.craftengine.bukkit.entity.furniture; import net.momirealms.craftengine.bukkit.nms.CollisionEntity; +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.Reflections; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.Pack; @@ -362,9 +364,66 @@ public class BukkitFurnitureManager implements FurnitureManager { } Vector3f seatPos = MiscUtils.getVector3f(vector3f); furniture.removeOccupiedSeat(seatPos); + + Location vehicleLocation = vehicle.getLocation(); + Location originalLocation = vehicleLocation.clone(); + originalLocation.setY(furniture.location().getY()); + Location targetLocation = originalLocation.clone().add(vehicleLocation.getDirection()); + if (!isSafeLocation(targetLocation)) { + targetLocation = findSafeLocationNearby(originalLocation); + if (targetLocation == null) return; + } + targetLocation.setYaw(player.getLocation().getYaw()); + targetLocation.setPitch(player.getLocation().getPitch()); + player.teleport(targetLocation); } protected boolean isSeatCarrierType(Entity entity) { return (entity instanceof ArmorStand || entity instanceof ItemDisplay); } + + private boolean isSafeLocation(Location location) { + World world = location.getWorld(); + if (world == null) return false; + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + if (!world.getBlockAt(x, y - 1, z).getType().isSolid()) return false; + if (!world.getBlockAt(x, y, z).isPassable()) return false; + if (isEntityBlocking(location)) return false; + return world.getBlockAt(x, y + 1, z).isPassable(); + } + + @Nullable + private Location findSafeLocationNearby(Location center) { + World world = center.getWorld(); + if (world == null) return null; + int centerX = center.getBlockX(); + int centerY = center.getBlockY(); + int centerZ = center.getBlockZ(); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) continue; + int x = centerX + dx; + int z = centerZ + dz; + Location nearbyLocation = new Location(world, x + 0.5, centerY, z + 0.5); + if (isSafeLocation(nearbyLocation)) return nearbyLocation; + } + } + return null; + } + + private boolean isEntityBlocking(Location location) { + World world = location.getWorld(); + if (world == null) return true; + try { + Collection nearbyEntities = world.getNearbyEntities(location, 0.5, 2, 0.5); + for (Entity bukkitEntity : nearbyEntities) { + if (bukkitEntity instanceof Player) continue; + Object nmsEntity = Reflections.method$CraftEntity$getHandle.invoke(bukkitEntity); + return (boolean) Reflections.method$Entity$canBeCollidedWith.invoke(nmsEntity); + } + } catch (Exception ignored) {} + return false; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBoxTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBoxTypes.java index aa689fa40..d089c3110 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBoxTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBoxTypes.java @@ -9,6 +9,6 @@ public class BukkitHitBoxTypes extends HitBoxTypes { static { register(INTERACTION, InteractionHitBox.FACTORY); register(SHULKER, ShulkerHitBox.FACTORY); - register(BOAT, BoatHitBox.FACTORY); + register(BOAT, HappyGhastHitBox.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BoatHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/HappyGhastHitBox.java similarity index 71% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BoatHitBox.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/HappyGhastHitBox.java index 004ed7131..6b566d4e7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BoatHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/HappyGhastHitBox.java @@ -9,11 +9,13 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; -public class BoatHitBox extends AbstractHitBox { +public class HappyGhastHitBox extends AbstractHitBox { public static final Factory FACTORY = new Factory(); + private final double scale; - public BoatHitBox(Seat[] seats, Vector3f position) { + public HappyGhastHitBox(Seat[] seats, Vector3f position, double scale) { super(seats, position); + this.scale = scale; } @Override @@ -21,9 +23,13 @@ public class BoatHitBox extends AbstractHitBox { return HitBoxTypes.BOAT; } + public double scale() { + return scale; + } + @Override public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Consumer packets) { - // 生成无重力船 + // todo 乐魂 } @Override @@ -35,9 +41,11 @@ public class BoatHitBox extends AbstractHitBox { @Override public HitBox create(Map arguments) { - return new BoatHitBox( + double scale = MiscUtils.getAsDouble(arguments.getOrDefault("scale", "1")); + return new HappyGhastHitBox( HitBoxFactory.getSeats(arguments), - MiscUtils.getVector3f(arguments.getOrDefault("position", "0")) + MiscUtils.getVector3f(arguments.getOrDefault("position", "0")), + scale ); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/ShulkerHitBox.java index 262bc35b2..37261b4ea 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/ShulkerHitBox.java @@ -1,14 +1,14 @@ package net.momirealms.craftengine.bukkit.entity.furniture; +import io.netty.buffer.Unpooled; +import net.momirealms.craftengine.bukkit.entity.ShulkerData; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.furniture.*; -import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.*; import org.joml.Vector3f; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; @@ -19,6 +19,7 @@ public class ShulkerHitBox extends AbstractHitBox { private final byte peek; // todo或许还能做个方向,但是会麻烦点,和 yaw 有关 private final Direction direction; + private List cachedValues; public ShulkerHitBox(Seat[] seats, Vector3f position, double scale, byte peek, Direction direction) { super(seats, position); @@ -46,10 +47,42 @@ public class ShulkerHitBox extends AbstractHitBox { @Override public void addSpawnPackets(int[] entityIds, double x, double y, double z, float yaw, Consumer packets) { - // 1.生成假的展示实体 - // 2.生成假的潜影贝 - // 3.潜影贝骑展示实体 - // 4.潜影贝数据设置(隐身,尺寸,peek,方向) + Vector3f offset = QuaternionUtils.toQuaternionf(0f, Math.toRadians(180f - yaw), 0f).conjugate().transform(new Vector3f(position())); + try { + packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + entityIds[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, + Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0 + )); + packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + entityIds[1], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, + Reflections.instance$EntityType$SHULKER, 0, Reflections.instance$Vec3$Zero, 0 + )); + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], getCachedValues())); + Object friendlyByteBuf = Reflections.constructor$FriendlyByteBuf.newInstance(Unpooled.buffer()); + Reflections.method$FriendlyByteBuf$writeVarInt.invoke(friendlyByteBuf, entityIds[0]); + Reflections.method$FriendlyByteBuf$writeVarIntArray.invoke(friendlyByteBuf, (Object) new int[] {entityIds[1]}); + packets.accept(Reflections.constructor$ClientboundSetPassengersPacket.newInstance(friendlyByteBuf)); + if (VersionHelper.isVersionNewerThan1_20_5()) { + Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); + Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, scale); + packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance))); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e); + } + } + + private synchronized List getCachedValues() { + if (this.cachedValues == null) { + this.cachedValues = new ArrayList<>(); + ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedValues); + ShulkerData.AttachFace.addEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(direction), this.cachedValues); + ShulkerData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues); + ShulkerData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues); + ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedValues); // 无ai + ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); // 不可见 + } + return this.cachedValues; } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java index 25d566d1e..1f587764e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/DirectionUtils.java @@ -29,4 +29,15 @@ public class DirectionUtils { case EAST -> BlockFace.EAST; }; } + + public static Object toNMSDirection(Direction direction) { + return switch (direction) { + case UP -> Reflections.instance$Direction$UP; + case DOWN -> Reflections.instance$Direction$DOWN; + case NORTH -> Reflections.instance$Direction$NORTH; + case SOUTH -> Reflections.instance$Direction$SOUTH; + case WEST -> Reflections.instance$Direction$WEST; + case EAST -> Reflections.instance$Direction$EAST; + }; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index e6fae8b2d..54a5dfa74 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -2808,6 +2808,7 @@ public class Reflections { public static final Object instance$Holder$Attribute$block_break_speed; public static final Object instance$Holder$Attribute$block_interaction_range; + public static final Object instance$Holder$Attribute$scale; static { try { @@ -2821,9 +2822,15 @@ public class Reflections { @SuppressWarnings("unchecked") Optional blockInteractionRangeHolder = (Optional) method$Registry$getHolder0.invoke(instance$BuiltInRegistries$ATTRIBUTE, block_interaction_range); instance$Holder$Attribute$block_interaction_range = blockInteractionRangeHolder.orElse(null); + + Object scale = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", VersionHelper.isVersionNewerThan1_21_2() ? "scale" : "generic.scale"); + @SuppressWarnings("unchecked") + Optional scaleHolder = (Optional) method$Registry$getHolder0.invoke(instance$BuiltInRegistries$ATTRIBUTE, scale); + instance$Holder$Attribute$scale = scaleHolder.orElse(null); } else { instance$Holder$Attribute$block_break_speed = null; instance$Holder$Attribute$block_interaction_range = null; + instance$Holder$Attribute$scale = null; } } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -3141,6 +3148,7 @@ public class Reflections { public static final Object instance$MobEffecr$mining_fatigue; public static final Object instance$MobEffecr$haste; + public static final Object instance$MobEffecr$invisibility; // for 1.20.1-1.20.4 static { @@ -3149,6 +3157,8 @@ public class Reflections { instance$MobEffecr$mining_fatigue = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, mining_fatigue); Object haste = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "haste"); instance$MobEffecr$haste = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, haste); + Object invisibility = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "invisibility"); + instance$MobEffecr$invisibility = method$Registry$get.invoke(instance$BuiltInRegistries$MOB_EFFECT, invisibility); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -3665,6 +3675,7 @@ public class Reflections { public static final Object instance$EntityType$ITEM_DISPLAY; public static final Object instance$EntityType$FALLING_BLOCK; public static final Object instance$EntityType$INTERACTION; + public static final Object instance$EntityType$SHULKER; static { try { @@ -3676,6 +3687,8 @@ public class Reflections { instance$EntityType$FALLING_BLOCK = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fallingBlock); Object interaction = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "interaction"); instance$EntityType$INTERACTION = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, interaction); + Object shulker = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "shulker"); + instance$EntityType$SHULKER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, shulker); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -5773,7 +5786,8 @@ public class Reflections { public static final Class clazz$Shulker = requireNonNull( ReflectionUtils.getClazz( - BukkitReflectionUtils.assembleMCClass("world.entity.monster.Shulker") + BukkitReflectionUtils.assembleMCClass("world.entity.monster.Shulker"), + BukkitReflectionUtils.assembleMCClass("world.entity.monster.EntityShulker") ) ); @@ -5782,4 +5796,124 @@ public class Reflections { clazz$CraftShulker, clazz$Shulker, 0 ) ); + + public static final Class clazz$Pose = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.entity.Pose"), + BukkitReflectionUtils.assembleMCClass("world.entity.EntityPose") + ) + ); + + public static final Method method$Pose$values = requireNonNull( + ReflectionUtils.getStaticMethod( + clazz$Pose, clazz$Pose.arrayType() + ) + ); + + public static final Object instance$Pose$STANDING; + public static final Object instance$Pose$FALL_FLYING; + public static final Object instance$Pose$SLEEPING; + public static final Object instance$Pose$SWIMMING; + public static final Object instance$Pose$SPIN_ATTACK; + public static final Object instance$Pose$CROUCHING; + public static final Object instance$Pose$LONG_JUMPING; + public static final Object instance$Pose$DYING; + public static final Object instance$Pose$CROAKING; + public static final Object instance$Pose$USING_TONGUE; + public static final Object instance$Pose$SITTING; + public static final Object instance$Pose$ROARING; + public static final Object instance$Pose$SNIFFING; + public static final Object instance$Pose$EMERGING; + public static final Object instance$Pose$DIGGING; + public static final Object instance$Pose$SLIDING; + public static final Object instance$Pose$SHOOTING; + public static final Object instance$Pose$INHALING; + public static final Object[] instance$Poses; + + static { + try { + instance$Poses = (Object[]) method$Pose$values.invoke(null); + instance$Pose$STANDING = instance$Poses[0]; + instance$Pose$FALL_FLYING = instance$Poses[1]; + instance$Pose$SLEEPING = instance$Poses[2]; + instance$Pose$SWIMMING = instance$Poses[3]; + instance$Pose$SPIN_ATTACK = instance$Poses[4]; + instance$Pose$CROUCHING = instance$Poses[5]; + instance$Pose$LONG_JUMPING = instance$Poses[6]; + instance$Pose$DYING = instance$Poses[7]; + instance$Pose$CROAKING = instance$Poses[8]; + instance$Pose$USING_TONGUE = instance$Poses[9]; + instance$Pose$SITTING = instance$Poses[10]; + instance$Pose$ROARING = instance$Poses[11]; + instance$Pose$SNIFFING = instance$Poses[12]; + instance$Pose$EMERGING = instance$Poses[13]; + instance$Pose$DIGGING = instance$Poses[14]; + if (VersionHelper.isVersionNewerThan1_20_3()) { + instance$Pose$SLIDING = instance$Poses[15]; + instance$Pose$SHOOTING = instance$Poses[16]; + instance$Pose$INHALING = instance$Poses[17]; + } else { + instance$Pose$SLIDING = null; + instance$Pose$SHOOTING = null; + instance$Pose$INHALING = null; + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public static final Class clazz$Attributes = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.entity.ai.attributes.Attributes"), + BukkitReflectionUtils.assembleMCClass("world.entity.ai.attributes.GenericAttributes") + ) + ); + + // 1.20.5+ + public static final Constructor constructor$AttributeInstance = + ReflectionUtils.getConstructor( + clazz$AttributeInstance, clazz$Holder, Consumer.class + ); + + public static final Method method$AttributeInstance$setBaseValue = requireNonNull( + ReflectionUtils.getMethod( + clazz$AttributeInstance, void.class, double.class + ) + ); + + public static final Constructor constructor$ClientboundSetPassengersPacket = requireNonNull( + ReflectionUtils.getDeclaredConstructor( + clazz$ClientboundSetPassengersPacket, clazz$FriendlyByteBuf + ) + ); + + public static final Method method$FriendlyByteBuf$writeVarInt = requireNonNull( + ReflectionUtils.getMethod( + clazz$FriendlyByteBuf, clazz$FriendlyByteBuf, new String[]{"writeVarInt", "d", "c"}, int.class + ) + ); + + public static final Method method$FriendlyByteBuf$writeVarIntArray = requireNonNull( + ReflectionUtils.getMethod( + clazz$FriendlyByteBuf, clazz$FriendlyByteBuf, int[].class + ) + ); + + public static final Method method$Entity$canBeCollidedWith = requireNonNull( + VersionHelper.isVersionNewerThan1_20_5() + ? ReflectionUtils.getMethod(clazz$Entity, boolean.class, new String[]{"canBeCollidedWith"}) + : VersionHelper.isVersionNewerThan1_20_3() + ? ReflectionUtils.getMethod(clazz$Entity, boolean.class, new String[]{"bz"}) + : VersionHelper.isVersionNewerThan1_20_2() + ? ReflectionUtils.getMethod(clazz$Entity, boolean.class, new String[]{"bx"}) + : VersionHelper.isVersionNewerThan1_20() + ? ReflectionUtils.getMethod(clazz$Entity, boolean.class, new String[]{"bu"}) + : ReflectionUtils.getMethod(clazz$Entity, boolean.class, new String[]{"canBeCollidedWith", "bu", "bx", "bz"}) + ); + + public static final Method method$CraftEntity$getHandle = requireNonNull( + ReflectionUtils.getMethod( + clazz$CraftEntity, clazz$Entity, 0 + ) + ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index f0193f44b..c6394fc05 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -27,6 +27,9 @@ import org.jetbrains.annotations.NotNull; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -36,6 +39,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -50,6 +54,7 @@ public abstract class AbstractPackManager implements PackManager { public static final Set VANILLA_ITEM_TEXTURES = new HashSet<>(); public static final Set VANILLA_BLOCK_TEXTURES = new HashSet<>(); public static final Set VANILLA_FONT_TEXTURES = new HashSet<>(); + public static final List SHULKER_PNG = new ArrayList<>(1); private final CraftEngine plugin; private final BiConsumer eventDispatcher; @@ -85,6 +90,8 @@ public abstract class AbstractPackManager implements PackManager { loadInternalList("internal/textures/block/_list.json", VANILLA_BLOCK_TEXTURES::add); loadInternalList("internal/textures/item/_list.json", VANILLA_ITEM_TEXTURES::add); loadInternalList("internal/textures/font/_list.json", VANILLA_FONT_TEXTURES::add); + + loadInternalPng("internal/textures/entity/shulker/shulker.png", SHULKER_PNG::add); } private void loadInternalData(String path, BiConsumer callback) { @@ -118,6 +125,16 @@ public abstract class AbstractPackManager implements PackManager { } } + private void loadInternalPng(String path, Consumer callback) { + try (InputStream inputStream = this.plugin.resourceStream(path)) { + if (inputStream != null) { + callback.accept(inputStream.readAllBytes()); + } + } catch (IOException e) { + this.plugin.logger().warn("Failed to load " + path, e); + } + } + @Override public Path resourcePackPath() { return this.plugin.dataFolderPath() @@ -482,6 +499,7 @@ public abstract class AbstractPackManager implements PackManager { this.generateCustomSounds(generatedPackPath); this.generateClientLang(generatedPackPath); this.generateEquipments(generatedPackPath); + this.generateShulker(generatedPackPath); Path zipFile = resourcePackPath(); try { @@ -512,6 +530,81 @@ public abstract class AbstractPackManager implements PackManager { } } + private void generateShulker(Path generatedPackPath) { + try { + if (ConfigManager.removedCollisionBoxEntityTextureLegacy()) { + File shulkerFile = generatedPackPath.resolve("assets/minecraft/textures/entity/shulker/shulker.png").toFile(); + shulkerFile.getParentFile().mkdirs(); + if (!shulkerFile.exists()) { + try (OutputStream out = new FileOutputStream(shulkerFile)) { + out.write(SHULKER_PNG.get(0)); + } + } else { + this.modifyShulker(shulkerFile, shulkerFile); + } + } + if (ConfigManager.removedCollisionBoxEntityTexture()) { + File overlaysFile = generatedPackPath.resolve("1_20_2_ce/assets/minecraft/textures/entity/shulker/shulker.png").toFile(); + overlaysFile.getParentFile().mkdirs(); + File shulkerFile = generatedPackPath.resolve("assets/minecraft/textures/entity/shulker/shulker.png").toFile(); + File packMetaFile = generatedPackPath.resolve("pack.mcmeta").toFile(); + boolean modifyPackMetaFile = false; + if (!shulkerFile.exists() && packMetaFile.exists()) { + try (OutputStream out = new FileOutputStream(overlaysFile)) { + out.write(SHULKER_PNG.get(0)); + } + modifyPackMetaFile = true; + } else if (packMetaFile.exists()) { + this.modifyShulker(shulkerFile, overlaysFile); + modifyPackMetaFile = true; + } + if (modifyPackMetaFile) { + JsonObject packMcmeta = GsonHelper.readJsonFile(packMetaFile.toPath()).getAsJsonObject(); + JsonArray entries = packMcmeta.getAsJsonObject("overlays").getAsJsonArray("entries"); + JsonObject entrie = new JsonObject(); + JsonObject formats = new JsonObject(); + formats.addProperty("min_inclusive", 16); + formats.addProperty("max_inclusive", 34); + entrie.add("formats", formats); + entrie.addProperty("directory", "1_20_2_ce"); + entries.add(entrie); + GsonHelper.writeJsonFile(packMcmeta, packMetaFile.toPath()); + } + } + } catch (IOException e) { + this.plugin.logger().warn("Error creating shulker.png", e); + } + } + + private void modifyShulker(File shulkerFile, File saveFile) throws IOException { + BufferedImage originalImage = ImageIO.read(shulkerFile); + BufferedImage argbImage; + if (originalImage.getType() == BufferedImage.TYPE_INT_ARGB) { + argbImage = originalImage; + } else { + argbImage = new BufferedImage( + originalImage.getWidth(), + originalImage.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + Graphics2D g = argbImage.createGraphics(); + g.drawImage(originalImage, 0, 0, null); + g.dispose(); + } + int startX = 0; + int startY = argbImage.getHeight() - 12; + int width = 24; + int heightRegion = 12; + for (int y = startY; y < startY + heightRegion; y++) { + for (int x = startX; x < startX + width; x++) { + int pixel = argbImage.getRGB(x, y); + int transparentPixel = pixel & 0x00FFFFFF; + argbImage.setRGB(x, y, transparentPixel); + } + } + ImageIO.write(argbImage, "PNG", saveFile); + } + private void generateEquipments(Path generatedPackPath) { for (EquipmentGeneration generator : this.plugin.itemManager().equipmentsToGenerate()) { EquipmentData equipmentData = generator.modernData(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java index 46586334c..6cf57a455 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java @@ -104,6 +104,8 @@ public class ConfigManager implements Reloadable { protected boolean furniture$remove_invalid_furniture_on_chunk_load$enable; protected Set furniture$remove_invalid_furniture_on_chunk_load$list; protected boolean furniture$hide_base_entity; + protected boolean furniture$removed_collision_box_entity_texture_1_20_2; + protected boolean furniture$removed_collision_box_entity_texture_1_20; protected boolean block$sound_system$enable; protected boolean recipe$enable; @@ -260,6 +262,8 @@ public class ConfigManager implements Reloadable { furniture$remove_invalid_furniture_on_chunk_load$enable = config.getBoolean("furniture.remove-invalid-furniture-on-chunk-load.enable", false); furniture$remove_invalid_furniture_on_chunk_load$list = new HashSet<>(config.getStringList("furniture.remove-invalid-furniture-on-chunk-load.list")); furniture$hide_base_entity = config.getBoolean("furniture.hide-base-entity", true); + furniture$removed_collision_box_entity_texture_1_20_2 = config.getBoolean("furniture.removed-collision-box-entity-texture-1_20_2", true); + furniture$removed_collision_box_entity_texture_1_20 = config.getBoolean("furniture.removed-collision-box-entity-texture-1_20", false); // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); @@ -553,6 +557,14 @@ public class ConfigManager implements Reloadable { return instance().furniture$hide_base_entity; } + public static boolean removedCollisionBoxEntityTexture() { + return instance().furniture$removed_collision_box_entity_texture_1_20_2; + } + + public static boolean removedCollisionBoxEntityTextureLegacy() { + return instance().furniture$removed_collision_box_entity_texture_1_20; + } + public YamlDocument loadOrCreateYamlData(String fileName) { File file = new File(this.plugin.dataFolderFile(), fileName); if (!file.exists()) { diff --git a/gradle.properties b/gradle.properties index 69e99ac8b..f88d4b1f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] project_version=0.0.41 -config_version=18 +config_version=19 lang_version=3 project_group=net.momirealms latest_minecraft_version=1.21.4