9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-29 03:49:15 +00:00

feat(entity): 实现潜影贝碰撞箱

This commit is contained in:
jhqwqmc
2025-03-31 12:57:39 +08:00
parent 70d853f671
commit 5e6842f911
11 changed files with 333 additions and 16 deletions

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.util.Reflections;
import java.util.Optional;
public class BaseEntityData<T> extends SimpleEntityData<T> {
public static final BaseEntityData<Byte> SharedFlags = new BaseEntityData<>(0, EntityDataValue.Serializers$BYTE, (byte) 0);
public static final BaseEntityData<Integer> AirSupply = new BaseEntityData<>(1, EntityDataValue.Serializers$INT, 300);
public static final BaseEntityData<Optional<Object>> CustomName = new BaseEntityData<>(2, EntityDataValue.Serializers$OPTIONAL_COMPONENT, Optional.empty());
public static final BaseEntityData<Boolean> CustomNameVisible = new BaseEntityData<>(3, EntityDataValue.Serializers$BOOLEAN, false);
public static final BaseEntityData<Boolean> Silent = new BaseEntityData<>(4, EntityDataValue.Serializers$BOOLEAN, false);
public static final BaseEntityData<Boolean> NoGravity = new BaseEntityData<>(5, EntityDataValue.Serializers$BOOLEAN, false);
public static final BaseEntityData<Object> Pose = new BaseEntityData<>(6, EntityDataValue.Serializers$POSE, Reflections.instance$Pose$STANDING);
public static final BaseEntityData<Integer> TicksFrozen = new BaseEntityData<>(7, EntityDataValue.Serializers$INT, 0);
public BaseEntityData(int id, Object serializer, T defaultValue) {
super(id, serializer, defaultValue);
}
}

View File

@@ -0,0 +1,29 @@
package net.momirealms.craftengine.bukkit.entity;
import java.util.List;
public interface EntityData<T> {
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<Object> list) {
if (!defaultValue().equals(value)) {
list.add(EntityDataValue.create(id(), serializer(), value));
}
}
default void addEntityData(T value, List<Object> list) {
list.add(EntityDataValue.create(id(), serializer(), value));
}
static <T> EntityData<T> of(int id, Object serializer, T defaultValue) {
return new SimpleEntityData<>(id, serializer, defaultValue);
}
}

View File

@@ -0,0 +1,19 @@
package net.momirealms.craftengine.bukkit.entity;
import java.util.List;
import java.util.Optional;
public class LivingEntityData<T> extends BaseEntityData<T> {
public static final LivingEntityData<Byte> LivingEntityFlags = new LivingEntityData<>(8, EntityDataValue.Serializers$BYTE, (byte) 0);
public static final LivingEntityData<Float> Health = new LivingEntityData<>(9, EntityDataValue.Serializers$FLOAT, 1.0f);
public static final LivingEntityData<List<Object>> EffectParticles = new LivingEntityData<>(10, EntityDataValue.Serializers$PARTICLES, List.of());
public static final LivingEntityData<Boolean> EffectAmbience = new LivingEntityData<>(11, EntityDataValue.Serializers$BOOLEAN, false);
public static final LivingEntityData<Integer> ArrowCount = new LivingEntityData<>(12, EntityDataValue.Serializers$INT, 0);
public static final LivingEntityData<Integer> StingerCount = new LivingEntityData<>(13, EntityDataValue.Serializers$INT, 0);
public static final LivingEntityData<Optional<Object>> SleepingPos = new LivingEntityData<>(14, EntityDataValue.Serializers$OPTIONAL_BLOCK_POS, Optional.empty());
public LivingEntityData(int id, Object serializer, T defaultValue) {
super(id, serializer, defaultValue);
}
}

View File

@@ -0,0 +1,10 @@
package net.momirealms.craftengine.bukkit.entity;
public class MobData<T> extends LivingEntityData<T> {
public static final MobData<Byte> MobFlags = new MobData<>(15, EntityDataValue.Serializers$BYTE, (byte) 0);
public MobData(int id, Object serializer, T defaultValue) {
super(id, serializer, defaultValue);
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.util.Reflections;
public class ShulkerData<T> extends MobData<T> {
public static final ShulkerData<Object> AttachFace = new ShulkerData<>(16, EntityDataValue.Serializers$DIRECTION, Reflections.instance$Direction$DOWN);
public static final ShulkerData<Byte> Peek = new ShulkerData<>(17, EntityDataValue.Serializers$BYTE, (byte) 0);
public static final ShulkerData<Byte> Color = new ShulkerData<>(18, EntityDataValue.Serializers$BYTE, (byte) 16);
public ShulkerData(int id, Object serializer, T defaultValue) {
super(id, serializer, defaultValue);
}
}

View File

@@ -0,0 +1,29 @@
package net.momirealms.craftengine.bukkit.entity;
public class SimpleEntityData<T> implements EntityData<T> {
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<Object> packets) {
// 生成无重力船
// todo 乐魂
}
@Override
@@ -35,9 +41,11 @@ public class BoatHitBox extends AbstractHitBox {
@Override
public HitBox create(Map<String, Object> 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
);
}
}

View File

@@ -1,14 +1,19 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.momirealms.craftengine.bukkit.entity.BaseEntityData;
import net.momirealms.craftengine.bukkit.entity.MobData;
import net.momirealms.craftengine.bukkit.entity.ShulkerData;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.MobEffectUtils;
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 +24,7 @@ public class ShulkerHitBox extends AbstractHitBox {
private final byte peek;
// todo或许还能做个方向但是会麻烦点和 yaw 有关
private final Direction direction;
private List<Object> cachedValues;
public ShulkerHitBox(Seat[] seats, Vector3f position, double scale, byte peek, Direction direction) {
super(seats, position);
@@ -46,10 +52,43 @@ public class ShulkerHitBox extends AbstractHitBox {
@Override
public void addSpawnPackets(int[] entityIds, double x, double y, double z, float yaw, Consumer<Object> 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$SHULKER, 0, Reflections.instance$Vec3$Zero, 0
));
packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[0], getCachedValues()));
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityIds[1], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0
));
ByteBuf byteBuf = Unpooled.buffer();
Object friendlyByteBuf = Reflections.constructor$FriendlyByteBuf.newInstance(byteBuf);
Reflections.method$FriendlyByteBuf$writeVarInt.invoke(friendlyByteBuf, entityIds[1]);
Reflections.method$FriendlyByteBuf$writeVarIntArray.invoke(friendlyByteBuf, (Object) new int[] {entityIds[0]});
packets.accept(Reflections.constructor$ClientboundSetPassengersPacket.newInstance(friendlyByteBuf));
if (VersionHelper.isVersionNewerThan1_20_5()) {
Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Attributes$SCALE, (Consumer<?>) (o) -> {});
Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, scale);
packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[0], Collections.singletonList(attributeInstance)));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e);
}
}
private synchronized List<Object> 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

View File

@@ -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;
};
}
}

View File

@@ -3141,6 +3141,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 +3150,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 +3668,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 +3680,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);
}
@@ -5782,4 +5788,135 @@ 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 Method method$AttributeInstance$getModifier = requireNonNull(
ReflectionUtils.getMethod(
clazz$AttributeInstance, clazz$AttributeModifier, clazz$ResourceLocation
)
);
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 Field field$Attributes$SCALE =
ReflectionUtils.getDeclaredField(
clazz$Attributes, "SCALE"
);
// 1.20.5+
public static final Object instance$Attributes$SCALE = Optional.ofNullable(field$Attributes$SCALE)
.map(it -> {
try {
return it.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.orElse(null);
public static final Constructor<?> constructor$AttributeInstance = requireNonNull(
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$MobEffectInstance = requireNonNull(
ReflectionUtils.getConstructor(
clazz$MobEffectInstance, clazz$Holder, int.class, int.class, boolean.class, boolean.class, boolean.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
)
);
}