9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 19:39:11 +00:00

refactor furniture framework

This commit is contained in:
XiaoMoMi
2025-03-31 04:02:28 +08:00
parent a820cbc2c2
commit 31a6f490e3
24 changed files with 431 additions and 182 deletions

View File

@@ -0,0 +1,67 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.entity.DisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.furniture.AbstractFurnitureElement;
import net.momirealms.craftengine.core.entity.furniture.Billboard;
import net.momirealms.craftengine.core.entity.furniture.ItemDisplayContext;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class BukkitFurnitureElement extends AbstractFurnitureElement {
private List<Object> cachedValues;
public BukkitFurnitureElement(Key item,
Billboard billboard,
ItemDisplayContext transform,
Vector3f scale,
Vector3f translation,
Vector3f offset,
Quaternionf rotation) {
super(item, billboard, transform, scale, translation, offset, rotation);
}
@Override
public void addSpawnPackets(int entityId, double x, double y, double z, float yaw, Consumer<Object> packets) {
try {
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - yaw), 0).conjugate().transform(new Vector3f(position()));
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityId, 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$ClientboundSetEntityDataPacket.newInstance(entityId, getCachedValues()));
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct element spawn packet", e);
}
}
private synchronized List<Object> getCachedValues() {
if (this.cachedValues == null) {
this.cachedValues = new ArrayList<>();
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(item(), null);
if (item == null) {
CraftEngine.instance().logger().warn("Failed to create furniture element for " + item() + " because item " + item() + " not found");
item = BukkitItemManager.instance().wrap(new ItemStack(Material.STONE));
}
DisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), this.cachedValues);
DisplayEntityData.Scale.addEntityDataIfNotDefaultValue(scale(), this.cachedValues);
DisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(rotation(), this.cachedValues);
DisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(billboard().id(), this.cachedValues);
DisplayEntityData.Translation.addEntityDataIfNotDefaultValue(translation(), this.cachedValues);
DisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(transform().id(), this.cachedValues);
}
return this.cachedValues;
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.nms.CollisionEntity;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
@@ -115,7 +116,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
}
ItemDisplayContext transform = ItemDisplayContext.valueOf(element.getOrDefault("transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
Billboard billboard = Billboard.valueOf(element.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
FurnitureElement furnitureElement = new FurnitureElement(Key.of(key), billboard, transform,
FurnitureElement furnitureElement = new BukkitFurnitureElement(Key.of(key), billboard, transform,
MiscUtils.getVector3f(element.getOrDefault("scale", "1")),
MiscUtils.getVector3f(element.getOrDefault("translation", "0")),
MiscUtils.getVector3f(element.getOrDefault("position", "0")),
@@ -126,32 +127,10 @@ public class BukkitFurnitureManager implements FurnitureManager {
List<Map<String, Object>> hitboxConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("hitboxes", List.of());
List<HitBox> hitboxes = new ArrayList<>();
for (Map<String, Object> config : hitboxConfigs) {
List<String> seats = (List<String>) config.getOrDefault("seats", List.of());
Seat[] seatArray = seats.stream()
.map(arg -> {
String[] split = arg.split(" ");
if (split.length == 1) return new Seat(MiscUtils.getVector3f(split[0]), 0, false);
return new Seat(MiscUtils.getVector3f(split[0]), Float.parseFloat(split[1]), true);
})
.toArray(Seat[]::new);
Vector3f position = MiscUtils.getVector3f(config.getOrDefault("position", "0"));
float width = MiscUtils.getAsFloat(config.getOrDefault("width", "1"));
float height = MiscUtils.getAsFloat(config.getOrDefault("height", "1"));
HitBox hitBox = new HitBox(
position,
new Vector3f(width, height, width),
seatArray,
(boolean) config.getOrDefault("interactive", true)
);
hitboxes.add(hitBox);
hitboxes.add(HitBoxTypes.fromMap(config));
}
if (hitboxes.isEmpty()) {
hitboxes.add(new HitBox(
new Vector3f(),
new Vector3f(1,1,1),
new Seat[0],
true
));
hitboxes.add(InteractionHitBox.DEFAULT);
}
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
if (ruleSection != null) {
@@ -164,6 +143,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
new Collider[0],
rotationRule,
alignmentRule
));
@@ -171,6 +151,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
new Collider[0],
RotationRule.ANY,
AlignmentRule.CENTER
));
@@ -222,6 +203,13 @@ public class BukkitFurnitureManager implements FurnitureManager {
tryLeavingSeat(player, vehicle);
}
}
// for (World world : Bukkit.getWorlds()) {
// for (Entity entity : world.getEntities()) {
// if (entity instanceof CollisionEntity) {
// entity.remove();
// }
// }
// }
}
@Override
@@ -252,6 +240,10 @@ public class BukkitFurnitureManager implements FurnitureManager {
for (int sub : furniture.interactionEntityIds()) {
this.furnitureByInteractionEntityId.remove(sub);
}
} else if (entity instanceof Interaction interaction) {
if (interaction instanceof CollisionEntity) {
entity.remove();
}
}
}

View File

@@ -0,0 +1,12 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.HitBoxTypes;
public class BukkitHitBoxTypes extends HitBoxTypes {
public static void init() {}
static {
register(INTERACTION, InteractionHitBox.FACTORY);
}
}

View File

@@ -0,0 +1,81 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.entity.InteractionEntityData;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
public class InteractionHitBox extends AbstractHitBox {
public static final Factory FACTORY = new Factory();
public static final InteractionHitBox DEFAULT = new InteractionHitBox(new Seat[0], new Vector3f(), new Vector3f(1,1,1), true);
private final Vector3f position;
private final Vector3f size;
private final boolean responsive;
private final List<Object> cachedValues = new ArrayList<>();
public InteractionHitBox(Seat[] seats, Vector3f position, Vector3f size, boolean responsive) {
super(seats);
this.position = position;
this.size = size;
this.responsive = responsive;
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(size.y, cachedValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(size.x, cachedValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(responsive, cachedValues);
}
public boolean responsive() {
return responsive;
}
public Vector3f position() {
return position;
}
public Vector3f size() {
return size;
}
@Override
public Key type() {
return HitBoxTypes.INTERACTION;
}
@Override
public void addSpawnPackets(int entityId, double x, double y, double z, float yaw, Consumer<Object> packets) {
Vector3f offset = QuaternionUtils.toQuaternionf(0f, Math.toRadians(180f - yaw), 0f).conjugate().transform(new Vector3f(position()));
try {
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityId, UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0
));
packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, List.copyOf(this.cachedValues)));
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct hitbox spawn packet", e);
}
}
public static class Factory implements HitBoxFactory {
@Override
public HitBox create(Map<String, Object> arguments) {
Vector3f position = MiscUtils.getVector3f(arguments.getOrDefault("position", "0"));
float width = MiscUtils.getAsFloat(arguments.getOrDefault("width", "1"));
float height = MiscUtils.getAsFloat(arguments.getOrDefault("height", "1"));
return new InteractionHitBox(
HitBoxFactory.getSeats(arguments),
position,
new Vector3f(width, height, width),
(boolean) arguments.getOrDefault("interactive", true)
);
}
}
}

View File

@@ -1,13 +1,9 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.entity.DisplayEntityData;
import net.momirealms.craftengine.bukkit.entity.InteractionEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
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.furniture.*;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.ArrayUtils;
import net.momirealms.craftengine.core.util.Key;
@@ -19,7 +15,6 @@ import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
@@ -35,8 +30,6 @@ public class LoadedFurniture {
private final Map<Integer, HitBox> hitBoxes;
// location
private Location location;
// cached spawn packet
private Object cachedSpawnPacket;
// base entity
private final WeakReference<Entity> baseEntity;
private final int baseEntityId;
@@ -48,6 +41,9 @@ public class LoadedFurniture {
private final Set<Vector3f> occupiedSeats = Collections.synchronizedSet(new HashSet<>());
private final Vector<Entity> seats = new Vector<>();
// cached spawn packet
private Object cachedSpawnPacket;
public LoadedFurniture(Entity baseEntity,
CustomFurniture furniture,
AnchorType anchorType) {
@@ -60,77 +56,39 @@ public class LoadedFurniture {
this.hitBoxes = new HashMap<>();
this.elements = new HashMap<>();
List<Integer> entityIds = new ArrayList<>();
List<Integer> interactionEntityIds = new ArrayList<>();
List<Integer> hitBoxEntityIds = new ArrayList<>();
CustomFurniture.Placement placement = furniture.getPlacement(anchorType);
for (FurnitureElement element : placement.elements()) {
int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
entityIds.add(entityId);
this.elements.put(entityId, element);
}
for (HitBox hitBox : placement.hitbox()) {
for (HitBox hitBox : placement.hitboxes()) {
int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
entityIds.add(entityId);
interactionEntityIds.add(entityId);
hitBoxEntityIds.add(entityId);
this.hitBoxes.put(entityId, hitBox);
}
this.subEntityIds = entityIds;
this.interactionEntityIds = interactionEntityIds;
this.resetSpawnPackets();
this.interactionEntityIds = hitBoxEntityIds;
}
private void resetSpawnPackets() {
try {
List<Object> packets = new ArrayList<>();
for (Map.Entry<Integer, FurnitureElement> entry : elements.entrySet()) {
int entityId = entry.getKey();
FurnitureElement element = entry.getValue();
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(element.item(), null);
if (item == null) {
CraftEngine.instance().logger().warn("Failed to create furniture element for " + id + " because item " + element.item() + " not found");
continue;
public synchronized Object spawnPacket() {
if (this.cachedSpawnPacket == null) {
try {
List<Object> packets = new ArrayList<>();
for (Map.Entry<Integer, FurnitureElement> entry : this.elements.entrySet()) {
entry.getValue().addSpawnPackets(entry.getKey(), this.location.getX(), this.location.getY(), this.location.getZ(), this.location.getYaw(), packets::add);
}
item.load();
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate().transform(new Vector3f(element.offset()));
Object addEntityPacket = Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityId, UUID.randomUUID(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z, 0, this.location.getYaw(),
Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0
);
ArrayList<Object> values = new ArrayList<>();
DisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), values);
DisplayEntityData.Scale.addEntityDataIfNotDefaultValue(element.scale(), values);
DisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(element.rotation(), values);
DisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(element.billboard().id(), values);
DisplayEntityData.Translation.addEntityDataIfNotDefaultValue(element.translation(), values);
DisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(element.transform().id(), values);
Object setDataPacket = Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, values);
packets.add(addEntityPacket);
packets.add(setDataPacket);
for (Map.Entry<Integer, HitBox> entry : this.hitBoxes.entrySet()) {
entry.getValue().addSpawnPackets(entry.getKey(), this.location.getX(), this.location.getY(), this.location.getZ(), this.location.getYaw(), packets::add);
}
this.cachedSpawnPacket = Reflections.constructor$ClientboundBundlePacket.newInstance(packets);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id, e);
}
for (Map.Entry<Integer, HitBox> entry : hitBoxes.entrySet()) {
int entityId = entry.getKey();
HitBox hitBox = entry.getValue();
Vector3f offset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate().transform(new Vector3f(hitBox.offset()));
Object addEntityPacket = Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityId, UUID.randomUUID(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z, 0, this.location.getYaw(),
Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0
);
ArrayList<Object> values = new ArrayList<>();
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(hitBox.size().y, values);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(hitBox.size().x, values);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(hitBox.responsive(), values);
Object setDataPacket = Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, values);
packets.add(addEntityPacket);
packets.add(setDataPacket);
}
this.cachedSpawnPacket = Reflections.constructor$ClientboundBundlePacket.newInstance(packets);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id, e);
}
return this.cachedSpawnPacket;
}
@NotNull
@@ -274,8 +232,4 @@ public class LoadedFurniture {
this.addSeatEntity(seatEntity);
seatEntity.addPassenger(player);
}
public @NotNull Object spawnPacket() {
return cachedSpawnPacket;
}
}

View File

@@ -15,7 +15,6 @@ import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;

View File

@@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.font.BukkitImageManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.behavior.BukkitItemBehaviors;
@@ -142,6 +143,7 @@ public class BukkitCraftEngine extends CraftEngine {
}
BukkitBlockBehaviors.init();
BukkitItemBehaviors.init();
BukkitHitBoxTypes.init();
super.packManager = new BukkitPackManager(this);
super.senderFactory = new BukkitSenderFactory(this);
super.itemManager = new BukkitItemManager(this);

View File

@@ -1,15 +1,16 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.nms.CollisionEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import java.util.List;
public class TestCommand extends BukkitCommandFeature<CommandSender> {
public TestCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
@@ -19,10 +20,18 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.handler(context -> {
List<Holder<Key>> holders = plugin().itemManager().tagToItems(Key.of("minecraft:planks"));
for (Holder<Key> holder : holders) {
context.sender().sendMessage(holder.registeredName());
Player player = context.sender();
Location location = player.getLocation();
try {
Object level = Reflections.field$CraftWorld$ServerLevel.get(player.getWorld());
Object aabb = FastNMS.INSTANCE.constructor$AABB(location.getBlockX(), location.getBlockY(), location.getBlockZ(),
location.getBlockX() + 1, location.getBlockY() + 1, location.getBlockZ() + 1);
CollisionEntity nmsEntity = FastNMS.INSTANCE.createCollisionEntity(level, aabb, location.getBlockX() + 0.5, location.getBlockY(), location.getBlockZ() + 0.5, true, false);
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(level, nmsEntity);
} catch (Exception e) {
e.printStackTrace();
}
});
}

View File

@@ -2426,6 +2426,12 @@ public class Reflections {
)
);
public static final Constructor<?> constructor$AABB = requireNonNull(
ReflectionUtils.getConstructor(
clazz$AABB, double.class, double.class, double.class, double.class, double.class, double.class
)
);
public static final Class<?> clazz$BlockGetter = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.level.BlockGetter"),
@@ -5753,22 +5759,27 @@ public class Reflections {
)
);
public static final Field field$Level$random = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$Level, clazz$RandomSource, 0
public static final Field field$Entity$boundingBox = requireNonNull(
ReflectionUtils.getInstanceDeclaredField(
clazz$Entity, clazz$AABB, 0
)
);
public static final Class<?> clazz$Mth = requireNonNull(
public static final Class<?> clazz$CraftShulker = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("util.Mth"),
BukkitReflectionUtils.assembleMCClass("util.MathHelper")
BukkitReflectionUtils.assembleCBClass("entity.CraftShulker")
)
);
public static final Method method$nextInt = requireNonNull(
public static final Class<?> clazz$Shulker = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.entity.monster.Shulker")
)
);
public static final Method method$CraftShulker$getHandle = requireNonNull(
ReflectionUtils.getMethod(
clazz$Mth, int.class, clazz$RandomSource, int.class, int.class
clazz$CraftShulker, clazz$Shulker, 0
)
);
}

View File

@@ -395,6 +395,4 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
ceChunk.load();
}
}