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

Merge pull request #489 from Xiao-MoMi/furniture-refactor

Furniture refactor
This commit is contained in:
XiaoMoMi
2025-12-04 05:47:02 +08:00
committed by GitHub
129 changed files with 3290 additions and 2369 deletions

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.compatibility.model.bettermodel;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -59,13 +58,12 @@ public class BetterModelBlockEntityElementConfig implements BlockEntityElementCo
return BetterModelBlockEntityElement.class;
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<BetterModelBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public BetterModelBlockEntityElementConfig create(Map<String, Object> arguments) {
String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.better_model.missing_model");
return (BlockEntityElementConfig<E>) new BetterModelBlockEntityElementConfig(
return new BetterModelBlockEntityElementConfig(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.compatibility.model.modelengine;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -49,13 +48,12 @@ public class ModelEngineBlockEntityElementConfig implements BlockEntityElementCo
return ModelEngineBlockEntityElement.class;
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ModelEngineBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ModelEngineBlockEntityElementConfig create(Map<String, Object> arguments) {
String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.model_engine.missing_model");
return (BlockEntityElementConfig<E>) new ModelEngineBlockEntityElementConfig(
return new ModelEngineBlockEntityElementConfig(
model,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),

View File

@@ -25,7 +25,7 @@ public class CondIsFurniture extends Condition {
@Override
public boolean check(Event event) {
return entities.check(event, entity -> {
BukkitFurniture baseEntity = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity);
BukkitFurniture baseEntity = CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity);
return baseEntity != null;
}, isNegated());
}

View File

@@ -22,7 +22,7 @@ public class EffRemoveFurniture extends Effect {
@Override
protected void execute(Event e) {
for (Entity entity : entities.getArray(e)) {
Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity);
Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity);
if (bukkitFurniture != null) {
bukkitFurniture.destroy();
}

View File

@@ -49,7 +49,7 @@ public class EvtCustomClick extends SkriptEvent {
EventValues.registerEventValue(FurnitureInteractEvent.class, Location.class, FurnitureInteractEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Player.class, FurnitureInteractEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(CustomBlockInteractEvent.class, Block.class, event -> null, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureInteractEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
}

View File

@@ -30,14 +30,14 @@ public class EvtCustomFurniture extends SkriptEvent {
.description("Called when a furniture is broken by a player.");
EventValues.registerEventValue(FurnitureBreakEvent.class, Location.class, FurnitureBreakEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Player.class, FurnitureBreakEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurnitureBreakEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
Skript.registerEvent("Place Furniture", EvtCustomFurniture.class, FurniturePlaceEvent.class, "(plac(e|ing)|build[ing]) of [(custom|ce|craft-engine)] furniture[s] [[of] %-strings%]")
.description("Called when a player places a furniture.");
EventValues.registerEventValue(FurniturePlaceEvent.class, Location.class, FurniturePlaceEvent::location, EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Player.class, FurniturePlaceEvent::player, EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Entity.class, event -> event.furniture().baseEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, Entity.class, event -> event.furniture().getBukkitEntity(), EventValues.TIME_NOW);
EventValues.registerEventValue(FurniturePlaceEvent.class, World.class, event -> event.location().getWorld(), EventValues.TIME_NOW);
}

View File

@@ -16,7 +16,7 @@ public class ExprEntityFurnitureID extends SimplePropertyExpression<Object, Stri
@Override
public @Nullable String convert(Object object) {
if (object instanceof Entity entity) {
return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity))
return Optional.ofNullable(CraftEngineFurniture.getLoadedFurnitureByMetaEntity(entity))
.map(it -> it.id().toString())
.orElse(null);
}

View File

@@ -6,11 +6,12 @@ import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
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.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.LootTable;
@@ -20,11 +21,13 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,7 +49,7 @@ public final class CraftEngineFurniture {
* @return a non-null map containing all loaded custom furniture
*/
@NotNull
public static Map<Key, CustomFurniture> loadedFurniture() {
public static Map<Key, FurnitureConfig> loadedFurniture() {
return BukkitFurnitureManager.instance().loadedFurniture();
}
@@ -56,10 +59,49 @@ public final class CraftEngineFurniture {
* @param id id
* @return the custom furniture
*/
public static CustomFurniture byId(@NotNull Key id) {
public static FurnitureConfig byId(@NotNull Key id) {
return BukkitFurnitureManager.instance().furnitureById(id).orElse(null);
}
/**
* Performs ray tracing to find the furniture entity that the player is currently targeting
*
* @param player The player performing the ray trace
* @param maxDistance Maximum ray trace distance (in blocks)
* @return The furniture being targeted by the player, or null if no furniture is found
*/
@Nullable
public static BukkitFurniture rayTrace(Player player, double maxDistance) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Location eyeLocation = serverPlayer.getEyeLocation();
RayTraceResult result = player.getWorld().rayTrace(eyeLocation, eyeLocation.getDirection(), maxDistance, FluidCollisionMode.NEVER, true, 0d, CraftEngineFurniture::isCollisionEntity);
if (result == null)
return null;
Entity hitEntity = result.getHitEntity();
if (hitEntity == null)
return null;
return getLoadedFurnitureByCollider(hitEntity);
}
/**
* Performs ray tracing to find the furniture entity that the player is currently targeting
*
* @param player The player performing the ray trace
* @return The furniture being targeted by the player, or null if no furniture is found
*/
@Nullable
public static BukkitFurniture rayTrace(Player player) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Location eyeLocation = serverPlayer.getEyeLocation();
RayTraceResult result = player.getWorld().rayTrace(eyeLocation, eyeLocation.getDirection(), serverPlayer.getCachedInteractionRange(), FluidCollisionMode.NEVER, true, 0d, CraftEngineFurniture::isCollisionEntity);
if (result == null)
return null;
Entity hitEntity = result.getHitEntity();
if (hitEntity == null)
return null;
return getLoadedFurnitureByCollider(hitEntity);
}
/**
* Places furniture at certain location
*
@@ -69,9 +111,9 @@ public final class CraftEngineFurniture {
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId) {
CustomFurniture furniture = byId(furnitureId);
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furnitureId, furniture.getAnyAnchorType());
return place(location, furniture, furniture.anyVariantName(), false);
}
/**
@@ -83,10 +125,24 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType) {
CustomFurniture furniture = byId(furnitureId);
return place(location, furnitureId, anchorType.variantName());
}
/**
* Places furniture at certain location
*
* @param location location
* @param furnitureId furniture to place
* @param variant variant type
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId, String variant) {
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.ofVariant(variant), true);
}
/**
@@ -98,8 +154,9 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true);
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, FurnitureConfig furniture, AnchorType anchorType) {
return place(location, furniture, anchorType.variantName(), true);
}
/**
@@ -112,10 +169,27 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) {
CustomFurniture furniture = byId(furnitureId);
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound);
return place(location, furniture, anchorType.variantName(), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furnitureId furniture to place
* @param variant variant
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture place(Location location, Key furnitureId, String variant, boolean playSound) {
FurnitureConfig furniture = byId(furnitureId);
if (furniture == null) return null;
return place(location, furniture, variant, playSound);
}
/**
@@ -128,8 +202,51 @@ public final class CraftEngineFurniture {
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound);
@Deprecated(since = "0.0.66", forRemoval = true)
public static BukkitFurniture place(Location location, FurnitureConfig furniture, AnchorType anchorType, boolean playSound) {
return place(location, furniture, anchorType.variantName(), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param variant variant
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, String variant, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.ofVariant(variant), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param data furniture data
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, CompoundTag data, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, FurnitureDataAccessor.of(data), playSound);
}
/**
* Places furniture at certain location
*
* @param location location
* @param furniture furniture to place
* @param dataAccessor furniture data accessor
* @param playSound whether to play place sounds
* @return the loaded furniture
*/
@NotNull
public static BukkitFurniture place(Location location, FurnitureConfig furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return BukkitFurnitureManager.instance().place(location, furniture, dataAccessor, playSound);
}
/**
@@ -165,18 +282,30 @@ public final class CraftEngineFurniture {
}
/**
* Gets the base furniture by the base entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId());
public static BukkitFurniture getLoadedFurnitureByMetaEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(baseEntity.getEntityId());
}
/**
* Gets the base furniture by the seat entity
* Gets the furniture by the meta entity
*
* @param baseEntity base entity
* @return the loaded furniture
*/
@Nullable
@Deprecated(since = "0.0.66")
public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return getLoadedFurnitureByMetaEntity(baseEntity);
}
/**
* Gets the furniture by the seat entity
*
* @param seat seat entity
* @return the loaded furniture
@@ -186,7 +315,22 @@ public final class CraftEngineFurniture {
if (isSeat(seat)) {
CompoundTag seatExtraData = BukkitSeatManager.instance().getSeatExtraData(seat);
int entityId = seatExtraData.getInt("entity_id");
BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId);
BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entityId);
}
return null;
}
/**
* Gets the furniture by the collider entity
*
* @param collider collider entity
* @return the loaded furniture
*/
@Nullable
public static BukkitFurniture getLoadedFurnitureByCollider(@NotNull Entity collider) {
Object nmsEntity = FastNMS.INSTANCE.method$CraftEntity$getHandle(collider);
if (nmsEntity instanceof CollisionEntity collisionEntity) {
return BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(collisionEntity.getEntityId());
}
return null;
}
@@ -199,7 +343,7 @@ public final class CraftEngineFurniture {
*/
public static boolean remove(@NotNull Entity entity) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
furniture.destroy();
return true;
@@ -217,7 +361,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
return true;
@@ -237,7 +381,7 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(entity)) return false;
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId());
Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(entity.getEntityId());
if (furniture == null) return false;
remove(furniture, player, dropLoot, playSound);
return true;
@@ -286,16 +430,16 @@ public final class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!furniture.isValid()) return;
Location location = ((BukkitFurniture) furniture).dropLocation();
Location location = ((BukkitFurniture) furniture).getDropLocation();
furniture.destroy();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config().lootTable();
LootTable<ItemStack> lootTable = (LootTable<ItemStack>) furniture.config.lootTable();
World world = new BukkitWorld(location.getWorld());
WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ());
if (dropLoot && lootTable != null) {
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position)
.withParameter(DirectContextParameters.FURNITURE, furniture)
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.extraData().item().orElse(null));
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.dataAccessor.item().orElse(null));
if (player != null) {
Item<?> itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
builder.withParameter(DirectContextParameters.PLAYER, player)
@@ -307,7 +451,7 @@ public final class CraftEngineFurniture {
}
}
if (playSound) {
world.playBlockSound(position, furniture.config().settings().sounds().breakSound());
world.playBlockSound(position, furniture.config.settings().sounds().breakSound());
}
}
}

View File

@@ -1,11 +1,10 @@
package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureVariant;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
@@ -15,25 +14,22 @@ import org.jetbrains.annotations.NotNull;
public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private boolean cancelled;
private final CustomFurniture furniture;
private final FurnitureConfig furniture;
private final Location location;
private final AnchorType anchorType;
private final BlockFace clickedFace;
private final FurnitureVariant variant;
private final Block clickedBlock;
private final InteractionHand hand;
public FurnitureAttemptPlaceEvent(@NotNull Player player,
@NotNull CustomFurniture furniture,
@NotNull AnchorType anchorType,
@NotNull FurnitureConfig furniture,
@NotNull FurnitureVariant variant,
@NotNull Location location,
@NotNull BlockFace clickedFace,
@NotNull InteractionHand hand,
@NotNull Block clickedBlock) {
super(player);
this.furniture = furniture;
this.location = location;
this.anchorType = anchorType;
this.clickedFace = clickedFace;
this.variant = variant;
this.clickedBlock = clickedBlock;
this.hand = hand;
}
@@ -48,19 +44,14 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
return hand;
}
@NotNull
public BlockFace clickedFace() {
return clickedFace;
}
@NotNull
public Player player() {
return getPlayer();
}
@NotNull
public AnchorType anchorType() {
return anchorType;
public FurnitureVariant variant() {
return variant;
}
@NotNull
@@ -69,7 +60,7 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can
}
@NotNull
public CustomFurniture furniture() {
public FurnitureConfig furniture() {
return furniture;
}

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.api.event;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -16,23 +16,23 @@ public final class FurnitureInteractEvent extends PlayerEvent implements Cancell
private final BukkitFurniture furniture;
private final InteractionHand hand;
private final Location interactionPoint;
private final HitBox hitBox;
private final FurnitureHitBox furnitureHitBox;
public FurnitureInteractEvent(@NotNull Player player,
@NotNull BukkitFurniture furniture,
@NotNull InteractionHand hand,
@NotNull Location interactionPoint,
@NotNull HitBox hitBox) {
@NotNull FurnitureHitBox furnitureHitBox) {
super(player);
this.furniture = furniture;
this.hand = hand;
this.interactionPoint = interactionPoint;
this.hitBox = hitBox;
this.furnitureHitBox = furnitureHitBox;
}
@NotNull
public HitBox hitBox() {
return hitBox;
public FurnitureHitBox hitBox() {
return furnitureHitBox;
}
@NotNull

View File

@@ -2,7 +2,6 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -76,13 +75,12 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
return this.position.equals(that.position);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ItemBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ItemBlockEntityElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemBlockEntityElementConfig(
return new ItemBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position")
);

View File

@@ -3,11 +3,10 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
import com.google.common.base.Objects;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.Key;
@@ -19,7 +18,6 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
@@ -159,13 +157,12 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<ItemDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public ItemDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item"));
return (BlockEntityElementConfig<E>) new ItemDisplayBlockEntityElementConfig(
return new ItemDisplayBlockEntityElementConfig(
player -> BukkitItemManager.instance().createWrappedItem(itemId, player),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),
@@ -173,8 +170,8 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
ItemDisplayContext.valueOf(arguments.getOrDefault("display-context", "none").toString().toUpperCase(Locale.ROOT)),
Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)),
ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength")
);

View File

@@ -4,10 +4,9 @@ import com.google.common.base.Objects;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.util.AdventureHelper;
@@ -135,13 +134,12 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
Objects.equal(rotation, that.rotation);
}
public static class Factory implements BlockEntityElementConfigFactory {
public static class Factory implements BlockEntityElementConfigFactory<TextDisplayBlockEntityElement> {
@SuppressWarnings("unchecked")
@Override
public <E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> arguments) {
public TextDisplayBlockEntityElementConfig create(Map<String, Object> arguments) {
String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.block.state.entity_renderer.text_display.missing_text");
return (BlockEntityElementConfig<E>) new TextDisplayBlockEntityElementConfig(
return new TextDisplayBlockEntityElementConfig(
text,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"),

View File

@@ -2,12 +2,14 @@ package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.data.EntityData;
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.world.WorldPosition;
import java.lang.ref.WeakReference;
import java.util.UUID;
@@ -38,6 +40,11 @@ public class BukkitEntity extends AbstractEntity {
public void tick() {
}
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(platformEntity().getLocation());
}
@Override
public int entityID() {
return platformEntity().getEntityId();
@@ -78,6 +85,11 @@ public class BukkitEntity extends AbstractEntity {
return EntityUtils.getEntityType(platformEntity());
}
@Override
public boolean isValid() {
return platformEntity().isValid();
}
@Override
public String name() {
return platformEntity().getName();

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.entity;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.entity.ItemEntity;
import net.momirealms.craftengine.core.entity.item.ItemEntity;
import org.bukkit.entity.Item;
public class BukkitItemEntity extends BukkitEntity implements ItemEntity {

View File

@@ -39,7 +39,7 @@ public class BukkitEntityData<T> implements EntityData<T> {
}
@Override
public Object create(Object entityDataAccessor, Object value) {
public Object create(Object entityDataAccessor, T value) {
return EntityDataValue.create(entityDataAccessor, value);
}
}

View File

@@ -21,7 +21,7 @@ public class BukkitCollider implements Collider {
@Override
public int entityId() {
return this.collisionEntity.getId();
return this.collisionEntity.getEntityId();
}
@Override

View File

@@ -1,74 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.AbstractCustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureSettings;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public class BukkitCustomFurniture extends AbstractCustomFurniture {
protected BukkitCustomFurniture(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<AnchorType, Placement> placements,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@Nullable LootTable<?> lootTable) {
super(id, settings, placements, events, lootTable);
}
public static Builder builder() {
return new BuilderImpl();
}
public static class BuilderImpl implements Builder {
private Key id;
private Map<AnchorType, Placement> placements;
private FurnitureSettings settings;
private Map<EventTrigger, List<Function<Context>>> events;
private LootTable<?> lootTable;
@Override
public CustomFurniture build() {
return new BukkitCustomFurniture(id, settings, placements, events, lootTable);
}
@Override
public Builder id(Key id) {
this.id = id;
return this;
}
@Override
public Builder placement(Map<AnchorType, Placement> placements) {
this.placements = placements;
return this;
}
@Override
public Builder settings(FurnitureSettings settings) {
this.settings = settings;
return this;
}
@Override
public Builder lootTable(LootTable<?> lootTable) {
this.lootTable = lootTable;
return this;
}
@Override
public Builder events(Map<EventTrigger, List<Function<Context>>> events) {
this.events = events;
return this;
}
}
}

View File

@@ -1,290 +1,69 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.BukkitEntity;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.Optional;
public class BukkitFurniture implements Furniture {
private final CustomFurniture furniture;
private final CustomFurniture.Placement placement;
private FurnitureExtraData extraData;
// location
private final Location location;
// base entity
private final WeakReference<Entity> baseEntity;
private final int baseEntityId;
// colliders
private final Collider[] colliderEntities;
// cache
private final List<Integer> fakeEntityIds;
private final List<Integer> entityIds;
private final Map<Integer, BukkitHitBox> hitBoxes = new Int2ObjectArrayMap<>();
private final Map<Integer, HitBoxPart> hitBoxParts = new Int2ObjectArrayMap<>();
private final boolean minimized;
private final boolean hasExternalModel;
// cached spawn packet
private Object cachedSpawnPacket;
private Object cachedMinimizedSpawnPacket;
public class BukkitFurniture extends Furniture {
private final WeakReference<ItemDisplay> metaEntity;
private Location location;
public BukkitFurniture(Entity baseEntity,
CustomFurniture furniture,
FurnitureExtraData extraData) {
this.extraData = extraData;
this.baseEntityId = baseEntity.getEntityId();
this.location = baseEntity.getLocation();
this.baseEntity = new WeakReference<>(baseEntity);
this.furniture = furniture;
this.minimized = furniture.settings().minimized();
this.placement = furniture.getValidPlacement(extraData.anchorType().orElseGet(furniture::getAnyAnchorType));
List<Integer> fakeEntityIds = new IntArrayList();
List<Integer> mainEntityIds = new IntArrayList();
mainEntityIds.add(this.baseEntityId);
// 绑定外部模型
Optional<ExternalModel> optionalExternal = placement.externalModel();
if (optionalExternal.isPresent()) {
try {
optionalExternal.get().bindModel(new BukkitEntity(baseEntity));
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
}
this.hasExternalModel = true;
} else {
this.hasExternalModel = false;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
List<Object> packets = new ArrayList<>();
List<Object> minimizedPackets = new ArrayList<>();
List<Collider> colliders = new ArrayList<>(4);
WorldPosition position = position();
// 初始化家具的元素
for (FurnitureElement element : placement.elements()) {
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
fakeEntityIds.add(entityId);
element.initPackets(this, entityId, conjugated, packet -> {
packets.add(packet);
if (this.minimized) minimizedPackets.add(packet);
});
}
// 初始化碰撞箱
for (HitBoxConfig hitBoxConfig : this.placement.hitBoxConfigs()) {
int[] ids = hitBoxConfig.acquireEntityIds(CoreReflections.instance$Entity$ENTITY_COUNTER::incrementAndGet);
List<HitBoxPart> aabbs = new ArrayList<>();
hitBoxConfig.initPacketsAndColliders(ids, position, conjugated, (packet, canBeMinimized) -> {
packets.add(packet);
if (this.minimized && !canBeMinimized) {
minimizedPackets.add(packet);
}
}, colliders::add, part -> {
this.hitBoxParts.put(part.entityId(), part);
aabbs.add(part);
});
BukkitHitBox hitBox = new BukkitHitBox(this, hitBoxConfig, aabbs.toArray(new HitBoxPart[0]));
for (int entityId : ids) {
fakeEntityIds.add(entityId);
mainEntityIds.add(entityId);
this.hitBoxes.put(entityId, hitBox);
}
}
// 初始化缓存的家具包
try {
this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
if (this.minimized) {
this.cachedMinimizedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(minimizedPackets);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to init spawn packets for furniture " + id(), e);
}
this.fakeEntityIds = fakeEntityIds;
this.entityIds = mainEntityIds;
this.colliderEntities = colliders.toArray(new Collider[0]);
public BukkitFurniture(ItemDisplay metaEntity, FurnitureConfig config, FurnitureDataAccessor data) {
super(new BukkitEntity(metaEntity), data, config);
this.metaEntity = new WeakReference<>(metaEntity);
this.location = metaEntity.getLocation();
}
@Override
public void initializeColliders() {
public void addCollidersToWorld() {
Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld());
for (Collider entity : this.colliderEntities) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
for (Collider entity : super.colliders) {
Entity bukkitEntity = FastNMS.INSTANCE.method$Entity$getBukkitEntity(entity.handle());
bukkitEntity.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_COLLISION, PersistentDataType.BYTE, (byte) 1);
}
}
@NotNull
public Object spawnPacket(Player player) {
// TODO hasPermission might be slow, can we use a faster way in the future?
// TODO Make it based on conditions. So we can dynamically control which furniture should be sent to the player
if (!this.minimized || player.hasPermission(FurnitureManager.FURNITURE_ADMIN_NODE)) {
return this.cachedSpawnPacket;
} else {
return this.cachedMinimizedSpawnPacket;
bukkitEntity.setPersistent(false);
if (!bukkitEntity.isValid()) {
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(world, entity.handle());
}
}
}
@Override
public WorldPosition position() {
return LocationUtils.toWorldPosition(this.location);
}
@NotNull
public Location location() {
return this.location.clone();
}
@NotNull
public Entity baseEntity() {
Entity entity = this.baseEntity.get();
if (entity == null) {
throw new RuntimeException("Base entity not found. It might be unloaded.");
public void destroy() {
Optional.ofNullable(this.metaEntity.get()).ifPresent(Entity::remove);
for (Collider entity : super.colliders) {
entity.destroy();
}
return entity;
}
@Override
public boolean isValid() {
return baseEntity().isValid();
}
@NotNull
public Location dropLocation() {
Optional<Vector3f> dropOffset = this.placement.dropOffset();
// 获取掉落物的位置,受到家具变种的影响
public Location getDropLocation() {
Optional<Vector3f> dropOffset = this.getCurrentVariant().dropOffset();
if (dropOffset.isEmpty()) {
return location();
return this.location;
}
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(dropOffset.get()));
return new Location(this.location.getWorld(), this.location.getX() + offset.x, this.location.getY() + offset.y, this.location.getZ() - offset.z);
}
@Override
public void destroy() {
if (!isValid()) {
return;
}
this.baseEntity().remove();
this.destroyColliders();
this.destroySeats();
public Location location() {
return location;
}
@Override
public void destroyColliders() {
for (Collider entity : this.colliderEntities) {
if (entity != null)
entity.destroy();
}
}
@Override
public void destroySeats() {
for (HitBox hitBox : this.hitBoxes.values()) {
for (Seat<HitBox> seat : hitBox.seats()) {
seat.destroy();
}
}
}
@Override
public UUID uuid() {
return this.baseEntity().getUniqueId();
}
@Override
public int baseEntityId() {
return this.baseEntityId;
}
@NotNull
public List<Integer> entityIds() {
return Collections.unmodifiableList(this.entityIds);
}
@NotNull
public List<Integer> fakeEntityIds() {
return Collections.unmodifiableList(this.fakeEntityIds);
}
public Collider[] collisionEntities() {
return this.colliderEntities;
}
@Override
public @Nullable HitBox hitBoxByEntityId(int id) {
return this.hitBoxes.get(id);
}
@Override
public @Nullable HitBoxPart hitBoxPartByEntityId(int id) {
return this.hitBoxParts.get(id);
}
@Override
public @NotNull AnchorType anchorType() {
return this.placement.anchorType();
}
@Override
public @NotNull Key id() {
return this.furniture.id();
}
@Override
public @NotNull CustomFurniture config() {
return this.furniture;
}
@Override
public boolean hasExternalModel() {
return hasExternalModel;
}
@Override
public FurnitureExtraData extraData() {
return this.extraData;
}
@Override
public void setExtraData(FurnitureExtraData extraData) {
this.extraData = extraData;
this.save();
}
@Override
public void save() {
try {
this.baseEntity().getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, this.extraData.toBytes());
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to save furniture data.", e);
}
public Entity getBukkitEntity() {
return this.metaEntity.get();
}
}

View File

@@ -1,177 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.AbstractFurnitureElement;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureElement;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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 final List<Object> commonValues;
public BukkitFurnitureElement(Key item,
Billboard billboard,
ItemDisplayContext transform,
Vector3f scale,
Vector3f translation,
Vector3f position,
Quaternionf rotation,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
super(item, billboard, transform, scale, translation, position, rotation, shadowRadius, shadowStrength, applyDyedColor);
this.commonValues = new ArrayList<>();
ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(scale(), this.commonValues);
ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(rotation(), this.commonValues);
ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(billboard().id(), this.commonValues);
ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(translation(), this.commonValues);
ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(transform().id(), this.commonValues);
ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(shadowRadius, this.commonValues);
ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(shadowStrength, this.commonValues);
}
@Override
public void initPackets(Furniture furniture, int entityId, @NotNull Quaternionf conjugated, Consumer<Object> packets) {
WorldPosition position = furniture.position();
Vector3f offset = conjugated.transform(new Vector3f(position()));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), position.x() + offset.x, position.y() + offset.y, position.z() - offset.z, 0, position.yRot(),
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
));
if (applyDyedColor()) {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, getCachedValues(
furniture.extraData().dyedColor().orElse(null),
furniture.extraData().fireworkExplosionColors().orElse(null)
)));
} else {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, getCachedValues(null, null)));
}
}
private synchronized List<Object> getCachedValues(@Nullable Color color, int @Nullable [] colors) {
List<Object> cachedValues = new ArrayList<>(this.commonValues);
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(item(), null);
if (item == null) {
item = BukkitItemManager.instance().wrap(new ItemStack(Material.BARRIER));
} else {
if (color != null) {
item.dyedColor(color);
}
if (colors != null) {
item.fireworkExplosion(new FireworkExplosion(
FireworkExplosion.Shape.SMALL_BALL,
new IntArrayList(colors),
new IntArrayList(),
false,
false
));
}
}
ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), cachedValues);
return cachedValues;
}
public static Builder builder() {
return new BuilderImpl();
}
public static class BuilderImpl implements Builder {
private boolean applyDyedColor;
private Key item;
private Billboard billboard;
private ItemDisplayContext transform;
private Vector3f scale;
private Vector3f translation;
private Vector3f position;
private Quaternionf rotation;
private float shadowRadius;
private float shadowStrength;
@Override
public Builder applyDyedColor(boolean applyDyedColor) {
this.applyDyedColor = applyDyedColor;
return this;
}
@Override
public Builder item(Key item) {
this.item = item;
return this;
}
@Override
public Builder billboard(Billboard billboard) {
this.billboard = billboard;
return this;
}
@Override
public Builder transform(ItemDisplayContext transform) {
this.transform = transform;
return this;
}
@Override
public Builder scale(Vector3f scale) {
this.scale = scale;
return this;
}
@Override
public Builder translation(Vector3f translation) {
this.translation = translation;
return this;
}
@Override
public Builder position(Vector3f position) {
this.position = position;
return this;
}
@Override
public Builder rotation(Quaternionf rotation) {
this.rotation = rotation;
return this;
}
@Override
public Builder shadowStrength(float shadowStrength) {
this.shadowStrength = shadowStrength;
return this;
}
@Override
public Builder shadowRadius(float shadowRadius) {
this.shadowRadius = shadowRadius;
return this;
}
@Override
public FurnitureElement build() {
return new BukkitFurnitureElement(item, billboard, transform, scale, translation, position, rotation, shadowRadius, shadowStrength, applyDyedColor);
}
}
}

View File

@@ -1,22 +1,26 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionHitBoxConfig;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionFurnitureHitboxConfig;
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.plugin.network.handler.FurniturePacketHandler;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.plugin.CraftEngine;
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.VersionHelper;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.*;
import org.bukkit.entity.*;
import org.bukkit.event.HandlerList;
@@ -29,18 +33,23 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
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_COLLISION = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_COLLISION);
private static BukkitFurnitureManager instance;
public static Class<?> COLLISION_ENTITY_CLASS = Interaction.class;
public static Object NMS_COLLISION_ENTITY_TYPE = MEntityTypes.INTERACTION;
public static ColliderType COLLISION_ENTITY_TYPE = ColliderType.INTERACTION;
private static BukkitFurnitureManager instance;
private final BukkitCraftEngine plugin;
private final Map<Integer, BukkitFurniture> furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f);
private final Map<Integer, BukkitFurniture> furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f);
private final Map<Integer, BukkitFurniture> byMetaEntityId = new ConcurrentHashMap<>(256, 0.5f);
private final Map<Integer, BukkitFurniture> byVirtualEntityId = new ConcurrentHashMap<>(512, 0.5f);
private final Map<Integer, BukkitFurniture> byColliderEntityId = new ConcurrentHashMap<>(512, 0.5f);
// Event listeners
private final FurnitureEventListener furnitureEventListener;
@@ -52,53 +61,54 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
super(plugin);
instance = this;
this.plugin = plugin;
this.furnitureEventListener = new FurnitureEventListener(this);
this.furnitureEventListener = new FurnitureEventListener(this, plugin.worldManager());
}
@Override
public Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) {
return this.place(LocationUtils.toLocation(position), furniture, extraData, playSound);
public Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor dataAccessor, boolean playSound) {
return this.place(LocationUtils.toLocation(position), furniture, dataAccessor, playSound);
}
public BukkitFurniture place(Location location, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) {
Optional<AnchorType> optionalAnchorType = extraData.anchorType();
if (optionalAnchorType.isEmpty() || !furniture.isAllowedPlacement(optionalAnchorType.get())) {
extraData.anchorType(furniture.getAnyAnchorType());
}
public BukkitFurniture place(Location location, FurnitureConfig furniture, FurnitureDataAccessor data, boolean playSound) {
Entity furnitureEntity = EntityUtils.spawnEntity(location.getWorld(), location, EntityType.ITEM_DISPLAY, entity -> {
ItemDisplay display = (ItemDisplay) entity;
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_KEY, PersistentDataType.STRING, furniture.id().toString());
try {
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, extraData.toBytes());
display.getPersistentDataContainer().set(BukkitFurnitureManager.FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY, data.toBytes());
} catch (IOException e) {
this.plugin.logger().warn("Failed to set furniture PDC for " + furniture.id().toString(), e);
}
handleBaseEntityLoadEarly(display);
handleMetaEntityDuringChunkLoad(display);
});
if (playSound) {
SoundData data = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, data.id().toString(), SoundCategory.BLOCKS, data.volume().get(), data.pitch().get());
SoundData sound = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, sound.id().toString(), SoundCategory.BLOCKS, sound.volume().get(), sound.pitch().get());
}
return loadedFurnitureByRealEntityId(furnitureEntity.getEntityId());
return loadedFurnitureByMetaEntityId(furnitureEntity.getEntityId());
}
@Override
public void delayedInit() {
// 确定碰撞箱实体类型
COLLISION_ENTITY_TYPE = Config.colliderType();
COLLISION_ENTITY_CLASS = Config.colliderType() == ColliderType.INTERACTION ? Interaction.class : Boat.class;
NMS_COLLISION_ENTITY_TYPE = Config.colliderType() == ColliderType.INTERACTION ? MEntityTypes.INTERACTION : MEntityTypes.OAK_BOAT;
COLLISION_ENTITY_TYPE = Config.colliderType();
// 注册事件
Bukkit.getPluginManager().registerEvents(this.furnitureEventListener, this.plugin.javaPlugin());
// 对世界上已有实体的记录
if (VersionHelper.isFolia()) {
BiConsumer<Entity, Runnable> taskExecutor = (entity, runnable) -> entity.getScheduler().run(this.plugin.javaPlugin(), (t) -> runnable.run(), () -> {});
for (World world : Bukkit.getWorlds()) {
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) {
taskExecutor.accept(entity, () -> handleBaseEntityLoadEarly(display));
taskExecutor.accept(entity, () -> handleMetaEntityDuringChunkLoad(display));
} else if (entity instanceof Interaction interaction) {
taskExecutor.accept(entity, () -> handleCollisionEntityLoadOnEntitiesLoad(interaction));
taskExecutor.accept(entity, () -> handleCollisionEntityDuringChunkLoad(interaction));
} else if (entity instanceof Boat boat) {
taskExecutor.accept(entity, () -> handleCollisionEntityLoadOnEntitiesLoad(boat));
taskExecutor.accept(entity, () -> handleCollisionEntityDuringChunkLoad(boat));
}
}
}
@@ -107,11 +117,11 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay display) {
handleBaseEntityLoadEarly(display);
handleMetaEntityDuringChunkLoad(display);
} else if (entity instanceof Interaction interaction) {
handleCollisionEntityLoadOnEntitiesLoad(interaction);
handleCollisionEntityDuringChunkLoad(interaction);
} else if (entity instanceof Boat boat) {
handleCollisionEntityLoadOnEntitiesLoad(boat);
handleCollisionEntityDuringChunkLoad(boat);
}
}
}
@@ -125,150 +135,168 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
}
@Override
public boolean isFurnitureRealEntity(int entityId) {
return this.furnitureByRealEntityId.containsKey(entityId);
public boolean isFurnitureMetaEntity(int entityId) {
return this.byMetaEntityId.containsKey(entityId);
}
@Nullable
@Override
public BukkitFurniture loadedFurnitureByRealEntityId(int entityId) {
return this.furnitureByRealEntityId.get(entityId);
public BukkitFurniture loadedFurnitureByMetaEntityId(int entityId) {
return this.byMetaEntityId.get(entityId);
}
@Override
@Nullable
public BukkitFurniture loadedFurnitureByEntityId(int entityId) {
return this.furnitureByEntityId.get(entityId);
}
@Override
protected CustomFurniture.Builder furnitureBuilder() {
return BukkitCustomFurniture.builder();
public BukkitFurniture loadedFurnitureByVirtualEntityId(int entityId) {
return this.byVirtualEntityId.get(entityId);
}
@Nullable
@Override
protected FurnitureElement.Builder furnitureElementBuilder() {
return BukkitFurnitureElement.builder();
public BukkitFurniture loadedFurnitureByColliderEntityId(int entityId) {
return this.byColliderEntityId.get(entityId);
}
protected void handleBaseEntityUnload(Entity entity) {
// 当元数据实体被卸载了
protected void handleMetaEntityUnload(ItemDisplay entity) {
// 不是持久化的
if (!entity.isPersistent()) {
return;
}
int id = entity.getEntityId();
BukkitFurniture furniture = this.furnitureByRealEntityId.remove(id);
BukkitFurniture furniture = this.byMetaEntityId.remove(id);
if (furniture != null) {
Location location = entity.getLocation();
// 区块还在加载的时候,就重复卸载了。为极其特殊情况
boolean isPreventing = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (!isPreventing) {
furniture.destroySeats();
}
for (int sub : furniture.entityIds()) {
this.furnitureByEntityId.remove(sub);
for (int sub : furniture.virtualEntityIds()) {
this.byVirtualEntityId.remove(sub);
}
for (int sub : furniture.colliderEntityIds()) {
this.byColliderEntityId.remove(sub);
}
}
}
// 保险起见collision实体卸载也移除一下
protected void handleCollisionEntityUnload(Entity entity) {
int id = entity.getEntityId();
this.furnitureByRealEntityId.remove(id);
this.byColliderEntityId.remove(id);
}
@SuppressWarnings("deprecation") // just a misleading name `getTrackedPlayers`
protected void handleBaseEntityLoadLate(ItemDisplay display, int depth) {
// must be a furniture item
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
// 检查这个区块的实体是否已经被加载了
private boolean isEntitiesLoaded(Location location) {
CEWorld ceWorld = this.plugin.worldManager().getWorld(location.getWorld());
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (ceChunk == null) return false;
return ceChunk.isEntitiesLoaded();
}
protected void handleMetaEntityDuringChunkLoad(ItemDisplay entity) {
// 实体可能不是持久的
if (!entity.isPersistent()) {
return;
}
// 获取家具pdc
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
CustomFurniture customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
if (previous != null) return;
Location location = display.getLocation();
boolean above1_20_1 = VersionHelper.isOrAbove1_20_2();
boolean preventChange = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (above1_20_1) {
if (!preventChange) {
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
furniture.initializeColliders();
for (Player player : display.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds()));
this.plugin.networkManager().sendPacket(BukkitAdaptors.adapt(player), furniture.spawnPacket(player));
}
}
} else {
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
for (Player player : display.getTrackedPlayers()) {
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
serverPlayer.entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds()));
this.plugin.networkManager().sendPacket(serverPlayer, furniture.spawnPacket(player));
}
if (preventChange) {
this.plugin.scheduler().sync().runLater(furniture::initializeColliders, 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
} else {
furniture.initializeColliders();
}
}
if (depth > 2) return;
this.plugin.scheduler().sync().runLater(() -> handleBaseEntityLoadLate(display, depth + 1), 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
protected void handleCollisionEntityLoadLate(Entity entity, int depth) {
// remove the entity if it's not a collision entity, it might be wrongly copied by WorldEdit
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(entity) instanceof CollisionEntity) {
return;
}
// not a collision entity
Byte flag = entity.getPersistentDataContainer().get(FURNITURE_COLLISION, PersistentDataType.BYTE);
if (flag == null || flag != 1) {
return;
}
Location location = entity.getLocation();
World world = location.getWorld();
int chunkX = location.getBlockX() >> 4;
int chunkZ = location.getBlockZ() >> 4;
if (!FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world), chunkX, chunkZ)) {
entity.remove();
return;
}
if (depth > 2) return;
plugin.scheduler().sync().runLater(() -> {
handleCollisionEntityLoadLate(entity, depth + 1);
}, 1, world, chunkX, chunkZ);
}
public void handleBaseEntityLoadEarly(ItemDisplay display) {
String id = display.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
// Remove the entity if it's not a valid furniture
// 处理无效的家具
if (Config.handleInvalidFurniture()) {
String mapped = Config.furnitureMappings().get(id);
if (mapped != null) {
if (mapped.isEmpty()) {
display.remove();
entity.remove();
return;
} else {
id = mapped;
display.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
entity.getPersistentDataContainer().set(FURNITURE_KEY, PersistentDataType.STRING, id);
}
}
}
// 获取家具配置
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isPresent()) {
CustomFurniture customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
if (previous != null) return;
BukkitFurniture furniture = addNewFurniture(display, customFurniture);
furniture.initializeColliders(); // safely do it here
Optional<FurnitureConfig> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
// 只对1.20.2及以上生效1.20.1比较特殊
if (!VersionHelper.isOrAbove1_20_2()) {
return;
}
// 已经在其他事件里加载过了
FurnitureConfig customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.byMetaEntityId.get(entity.getEntityId());
if (previous != null) return;
// 创建新的家具
createFurnitureInstance(entity, customFurniture);
}
@SuppressWarnings("deprecation")
protected void handleMetaEntityAfterChunkLoad(ItemDisplay entity) {
// 实体可能不是持久的
if (!entity.isPersistent()) {
return;
}
// 获取家具pdc
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
// 这个区块还处于加载实体中这个时候不处理1.20.1需要特殊处理)
Location location = entity.getLocation();
if (VersionHelper.isOrAbove1_20_2() && !isEntitiesLoaded(location)) {
return;
}
// 获取家具配置
Key key = Key.of(id);
Optional<FurnitureConfig> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
// 已经在其他事件里加载过了
FurnitureConfig customFurniture = optionalFurniture.get();
BukkitFurniture previous = this.byMetaEntityId.get(entity.getEntityId());
if (previous != null) return;
createFurnitureInstance(entity, customFurniture);
// 补发一次包,修复
for (Player player : entity.getTrackedPlayers()) {
BukkitAdaptors.adapt(player).sendPacket(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entity.getEntityId(), entity.getUniqueId(), location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw(),
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
}
}
public void handleCollisionEntityLoadOnEntitiesLoad(Entity collisionEntity) {
protected void handleCollisionEntityAfterChunkLoad(Entity entity) {
// 如果是碰撞实体,那么就忽略
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(entity) instanceof CollisionEntity) {
return;
}
// 看看有没有碰撞实体的pdc
Byte flag = entity.getPersistentDataContainer().get(FURNITURE_COLLISION, PersistentDataType.BYTE);
if (flag == null || flag != 1) {
return;
}
// 实体未加载
Location location = entity.getLocation();
if (!isEntitiesLoaded(location)) {
return;
}
// 移除被WorldEdit错误复制的碰撞实体
runSafeEntityOperation(location, entity::remove);
}
public void handleCollisionEntityDuringChunkLoad(Entity collisionEntity) {
// faster
if (FastNMS.INSTANCE.method$CraftEntity$getHandle(collisionEntity) instanceof CollisionEntity) {
collisionEntity.remove();
@@ -284,34 +312,43 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
collisionEntity.remove();
}
private FurnitureExtraData getFurnitureExtraData(Entity baseEntity) throws IOException {
private FurnitureDataAccessor getFurnitureDataAccessor(Entity baseEntity) {
byte[] extraData = baseEntity.getPersistentDataContainer().get(FURNITURE_EXTRA_DATA_KEY, PersistentDataType.BYTE_ARRAY);
if (extraData == null) return FurnitureExtraData.builder().build();
return FurnitureExtraData.fromBytes(extraData);
if (extraData == null) return new FurnitureDataAccessor(null);
try {
return FurnitureDataAccessor.fromBytes(extraData);
} catch (IOException e) {
// 损坏了?一般不会
return new FurnitureDataAccessor(null);
}
}
private synchronized BukkitFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture) {
FurnitureExtraData extraData;
try {
extraData = getFurnitureExtraData(display);
} catch (IOException e) {
extraData = FurnitureExtraData.builder().build();
plugin.logger().warn("Furniture extra data could not be loaded", e);
// 创建家具实例,并初始化碰撞实体
private BukkitFurniture createFurnitureInstance(ItemDisplay display, FurnitureConfig furniture) {
BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, getFurnitureDataAccessor(display));
this.byMetaEntityId.put(display.getEntityId(), bukkitFurniture);
for (int entityId : bukkitFurniture.virtualEntityIds()) {
this.byVirtualEntityId.put(entityId, bukkitFurniture);
}
BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, extraData);
this.furnitureByRealEntityId.put(bukkitFurniture.baseEntityId(), bukkitFurniture);
for (int entityId : bukkitFurniture.entityIds()) {
this.furnitureByEntityId.put(entityId, bukkitFurniture);
}
for (Collider collisionEntity : bukkitFurniture.collisionEntities()) {
int collisionEntityId = FastNMS.INSTANCE.method$Entity$getId(collisionEntity.handle());
this.furnitureByRealEntityId.put(collisionEntityId, bukkitFurniture);
for (Collider collisionEntity : bukkitFurniture.colliders()) {
this.byColliderEntityId.put(collisionEntity.entityId(), bukkitFurniture);
}
Location location = display.getLocation();
runSafeEntityOperation(location, bukkitFurniture::addCollidersToWorld);
return bukkitFurniture;
}
private void runSafeEntityOperation(Location location, Runnable action) {
boolean preventChange = FastNMS.INSTANCE.method$ServerLevel$isPreventingStatusUpdates(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld()), location.getBlockX() >> 4, location.getBlockZ() >> 4);
if (preventChange) {
this.plugin.scheduler().sync().runLater(action, 1, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4);
} else {
action.run();
}
}
@Override
protected HitBoxConfig defaultHitBox() {
return InteractionHitBoxConfig.DEFAULT;
protected FurnitureHitBoxConfig<?> defaultHitBox() {
return InteractionFurnitureHitboxConfig.DEFAULT;
}
}

View File

@@ -1,70 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.HitBoxPart;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.world.EntityHitResult;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.sparrow.nbt.CompoundTag;
import java.util.Optional;
public class BukkitHitBox implements HitBox {
private final Furniture furniture;
private final HitBoxConfig config;
private final HitBoxPart[] parts;
private final Seat<HitBox>[] seats;
public BukkitHitBox(Furniture furniture, HitBoxConfig config, HitBoxPart[] parts) {
this.parts = parts;
this.config = config;
this.furniture = furniture;
this.seats = createSeats(config);
}
@SuppressWarnings("unchecked")
private Seat<HitBox>[] createSeats(HitBoxConfig config) {
SeatConfig[] seatConfigs = config.seats();
Seat<HitBox>[] seats = new Seat[seatConfigs.length];
for (int i = 0; i < seatConfigs.length; i++) {
seats[i] = new BukkitSeat<>(this, seatConfigs[i]);
}
return seats;
}
@Override
public HitBoxPart[] parts() {
return this.parts;
}
@Override
public HitBoxConfig config() {
return this.config;
}
@Override
public Seat<HitBox>[] seats() {
return this.seats;
}
@Override
public Optional<EntityHitResult> clip(Vec3d min, Vec3d max) {
for (HitBoxPart hbe : this.parts) {
Optional<EntityHitResult> result = hbe.aabb().clip(min, max);
if (result.isPresent()) {
return result;
}
}
return Optional.empty();
}
@Override
public void saveCustomData(CompoundTag data) {
data.putString("type", "furniture");
data.putInt("entity_id", this.furniture.baseEntityId());
}
}

View File

@@ -2,6 +2,9 @@ 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.world.BukkitWorldManager;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.chunk.CEChunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.event.EventHandler;
@@ -16,9 +19,11 @@ import java.util.List;
public class FurnitureEventListener implements Listener {
private final BukkitFurnitureManager manager;
private final BukkitWorldManager worldManager;
public FurnitureEventListener(final BukkitFurnitureManager manager) {
public FurnitureEventListener(final BukkitFurnitureManager manager, final BukkitWorldManager worldManager) {
this.manager = manager;
this.worldManager = worldManager;
}
/*
@@ -29,21 +34,26 @@ public class FurnitureEventListener implements Listener {
List<Entity> entities = event.getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
CEWorld world = this.worldManager.getWorld(event.getWorld());
CEChunk ceChunk = world.getChunkAtIfLoaded(event.getChunk().getChunkKey());
if (ceChunk != null) {
ceChunk.setEntitiesLoaded(true);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
public void onWorldLoad(WorldLoadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadEarly(itemDisplay);
this.manager.handleMetaEntityDuringChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadOnEntitiesLoad(entity);
this.manager.handleCollisionEntityDuringChunkLoad(entity);
}
}
}
@@ -52,9 +62,9 @@ public class FurnitureEventListener implements Listener {
public void onEntityLoad(EntityAddToWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleBaseEntityLoadLate(itemDisplay, 0);
this.manager.handleMetaEntityAfterChunkLoad(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityLoadLate(entity, 0);
this.manager.handleCollisionEntityAfterChunkLoad(entity);
}
}
@@ -65,8 +75,8 @@ public class FurnitureEventListener implements Listener {
public void onChunkUnload(ChunkUnloadEvent event) {
Entity[] entities = event.getChunk().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -77,8 +87,8 @@ public class FurnitureEventListener implements Listener {
public void onWorldUnload(WorldUnloadEvent event) {
List<Entity> entities = event.getWorld().getEntities();
for (Entity entity : entities) {
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}
@@ -88,8 +98,8 @@ public class FurnitureEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityUnload(EntityRemoveFromWorldEvent event) {
Entity entity = event.getEntity();
if (entity instanceof ItemDisplay) {
this.manager.handleBaseEntityUnload(entity);
if (entity instanceof ItemDisplay itemDisplay) {
this.manager.handleMetaEntityUnload(itemDisplay);
} else if (BukkitFurnitureManager.COLLISION_ENTITY_CLASS.isInstance(entity)) {
this.manager.handleCollisionEntityUnload(entity);
}

View File

@@ -0,0 +1,15 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigs;
public class BukkitFurnitureElementConfigs extends FurnitureElementConfigs {
static {
register(ITEM_DISPLAY, ItemDisplayFurnitureElementConfig.FACTORY);
}
private BukkitFurnitureElementConfigs() {}
public static void init() {
}
}

View File

@@ -0,0 +1,61 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureColorSource;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElement;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class ItemDisplayFurnitureElement implements FurnitureElement {
private final ItemDisplayFurnitureElementConfig config;
private final WorldPosition position;
private final int entityId;
private final Object despawnPacket;
private final FurnitureColorSource colorSource;
public ItemDisplayFurnitureElement(Furniture furniture, ItemDisplayFurnitureElementConfig config) {
this.config = config;
this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
WorldPosition furniturePos = furniture.position();
Vec3d position = Furniture.getRelativePosition(furniturePos, config.position());
this.position = new WorldPosition(furniturePos.world, position.x, position.y, position.z, furniturePos.xRot, furniturePos.yRot);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
this.colorSource = furniture.dataAccessor.getColorSource();
}
@Override
public void show(Player player) {
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of(
FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, UUID.randomUUID(),
this.position.x, this.position.y, this.position.z, 0, this.position.yRot,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
),
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player, this.colorSource))
)), false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public int[] virtualEntityIds() {
return new int[] {this.entityId};
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
}

View File

@@ -0,0 +1,191 @@
package net.momirealms.craftengine.bukkit.entity.furniture.element;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.core.entity.display.Billboard;
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureColorSource;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
public class ItemDisplayFurnitureElementConfig implements FurnitureElementConfig<ItemDisplayFurnitureElement> {
public static final Factory FACTORY = new Factory();
public final BiFunction<Player, FurnitureColorSource, List<Object>> metadata;
public final Key itemId;
public final Vector3f scale;
public final Vector3f position;
public final Vector3f translation;
public final float xRot;
public final float yRot;
public final Quaternionf rotation;
public final ItemDisplayContext displayContext;
public final Billboard billboard;
public final float shadowRadius;
public final float shadowStrength;
public final boolean applyDyedColor;
public ItemDisplayFurnitureElementConfig(Key itemId,
Vector3f scale,
Vector3f position,
Vector3f translation,
float xRot,
float yRot,
Quaternionf rotation,
ItemDisplayContext displayContext,
Billboard billboard,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
this.scale = scale;
this.position = position;
this.translation = translation;
this.xRot = xRot;
this.yRot = yRot;
this.rotation = rotation;
this.displayContext = displayContext;
this.billboard = billboard;
this.shadowRadius = shadowRadius;
this.shadowStrength = shadowStrength;
this.applyDyedColor = applyDyedColor;
this.itemId = itemId;
BiFunction<Player, FurnitureColorSource, Item<?>> itemFunction = (player, colorSource) -> {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player);
if (applyDyedColor && colorSource != null && wrappedItem != null) {
Optional.ofNullable(colorSource.dyedColor()).ifPresent(wrappedItem::dyedColor);
Optional.ofNullable(colorSource.fireworkColors()).ifPresent(colors -> wrappedItem.fireworkExplosion(new FireworkExplosion(
FireworkExplosion.Shape.SMALL_BALL,
new IntArrayList(colors),
new IntArrayList(),
false,
false
)));
}
return Optional.ofNullable(wrappedItem).orElseGet(() -> BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, null));
};
this.metadata = (player, source) -> {
List<Object> dataValues = new ArrayList<>();
ItemDisplayEntityData.DisplayedItem.addEntityData(itemFunction.apply(player, source).getLiteralObject(), dataValues);
ItemDisplayEntityData.Scale.addEntityData(this.scale, dataValues);
ItemDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues);
ItemDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues);
ItemDisplayEntityData.Translation.addEntityData(this.translation, dataValues);
ItemDisplayEntityData.DisplayType.addEntityData(this.displayContext.id(), dataValues);
ItemDisplayEntityData.ShadowRadius.addEntityData(this.shadowRadius, dataValues);
ItemDisplayEntityData.ShadowStrength.addEntityData(this.shadowStrength, dataValues);
return dataValues;
};
}
public Vector3f scale() {
return this.scale;
}
public Vector3f position() {
return this.position;
}
public Vector3f translation() {
return this.translation;
}
public float xRot() {
return this.xRot;
}
public float yRot() {
return this.yRot;
}
public Quaternionf rotation() {
return this.rotation;
}
public ItemDisplayContext displayContext() {
return this.displayContext;
}
public Billboard billboard() {
return this.billboard;
}
public float shadowRadius() {
return this.shadowRadius;
}
public float shadowStrength() {
return this.shadowStrength;
}
public boolean applyDyedColor() {
return this.applyDyedColor;
}
public Key itemId() {
return this.itemId;
}
@Override
public ItemDisplayFurnitureElement create(@NotNull Furniture furniture) {
return new ItemDisplayFurnitureElement(furniture, this);
}
public static class Factory implements FurnitureElementConfigFactory<ItemDisplayFurnitureElement> {
@Override
public ItemDisplayFurnitureElementConfig create(Map<String, Object> arguments) {
Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item_display.missing_item"));
boolean applyDyedColor = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color");
return new ItemDisplayFurnitureElementConfig(
itemId,
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"),
ResourceConfigUtils.getAsVector3f(arguments.get("translation"), "translation"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"),
ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"),
ResourceConfigUtils.getAsEnum(ResourceConfigUtils.get(arguments, "display-context", "display-transform"), ItemDisplayContext.class, ItemDisplayContext.NONE),
ResourceConfigUtils.getAsEnum(arguments.get("billboard"), Billboard.class, Billboard.FIXED),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-radius", 0f), "shadow-radius"),
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength"),
applyDyedColor
);
}
}
@Override
public String toString() {
return "ItemDisplayFurnitureElementConfig{" +
"metadata=" + metadata +
", itemId=" + itemId +
", scale=" + scale +
", position=" + position +
", translation=" + translation +
", xRot=" + xRot +
", yRot=" + yRot +
", rotation=" + rotation +
", displayContext=" + displayContext +
", billboard=" + billboard +
", shadowRadius=" + shadowRadius +
", shadowStrength=" + shadowStrength +
", applyDyedColor=" + applyDyedColor +
'}';
}
}

View File

@@ -0,0 +1,52 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.collision.AABB;
import net.momirealms.sparrow.nbt.CompoundTag;
public abstract class AbstractFurnitureHitBox implements FurnitureHitBox {
protected final Furniture furniture;
protected Seat<FurnitureHitBox>[] seats;
public AbstractFurnitureHitBox(Furniture furniture, FurnitureHitBoxConfig<?> config) {
this.furniture = furniture;
this.seats = createSeats(config);
}
@SuppressWarnings("unchecked")
private Seat<FurnitureHitBox>[] createSeats(FurnitureHitBoxConfig<?> config) {
SeatConfig[] seatConfigs = config.seats();
Seat<FurnitureHitBox>[] seats = new Seat[seatConfigs.length];
for (int i = 0; i < seatConfigs.length; i++) {
seats[i] = new BukkitSeat<>(this, seatConfigs[i]);
}
return seats;
}
@Override
public void saveCustomData(CompoundTag data) {
data.putString("type", "furniture");
// 用于通过座椅找到原始家具
data.putInt("entity_id", this.furniture.entityId());
}
@Override
public Seat<FurnitureHitBox>[] seats() {
return this.seats;
}
protected Collider createCollider(World world, Vec3d position, AABB ceAABB, boolean canCollide, boolean blocksBuilding, boolean canBeHitByProjectile) {
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
return new BukkitCollider(world.serverWorld(), nmsAABB, position.x, position.y, position.z, canBeHitByProjectile, canCollide, blocksBuilding);
}
}

View File

@@ -0,0 +1,18 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxTypes;
import net.momirealms.craftengine.core.util.VersionHelper;
public class BukkitFurnitureHitboxTypes extends FurnitureHitBoxTypes {
public static void init() {}
static {
register(INTERACTION, InteractionFurnitureHitboxConfig.FACTORY);
register(SHULKER, ShulkerFurnitureHitboxConfig.FACTORY);
register(CUSTOM, CustomFurnitureHitboxConfig.FACTORY);
if (VersionHelper.isOrAbove1_21_6()) {
register(HAPPY_GHAST, HappyGhastFurnitureHitboxConfig.FACTORY);
}
}
}

View File

@@ -1,15 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxTypes;
public class BukkitHitBoxTypes extends HitBoxTypes {
public static void init() {}
static {
register(INTERACTION, InteractionHitBoxConfig.FACTORY);
register(SHULKER, ShulkerHitBoxConfig.FACTORY);
register(HAPPY_GHAST, HappyGhastHitBoxConfig.FACTORY);
register(CUSTOM, CustomHitBoxConfig.FACTORY);
}
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class CustomFurnitureHitbox extends AbstractFurnitureHitBox {
private final CustomFurnitureHitboxConfig config;
private final Collider collider;
private final Object spawnPacket;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final int entityId;
public CustomFurnitureHitbox(Furniture furniture, CustomFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
Vec3d pos = Furniture.getRelativePosition(position, config.position());
AABB aabb = AABB.makeBoundingBox(pos, config.width(), config.height());
this.collider = createCollider(furniture.world(), pos, aabb, false, config.blocksBuilding(), config.canBeHitByProjectile());
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
List<Object> packets = new ArrayList<>(3);
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), position.x, position.y, position.z, 0, position.yRot,
config.entityType(), 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, config.cachedValues()));
if (VersionHelper.isOrAbove1_20_5()) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId, Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.part = new FurnitureHitboxPart(entityId, aabb, pos, false);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
this.entityId = entityId;
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public CustomFurnitureHitboxConfig config() {
return this.config;
}
}

View File

@@ -0,0 +1,119 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class CustomFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<CustomFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
private final float scale;
private final Object entityType;
private final List<Object> cachedValues = new ArrayList<>();
private final float width;
private final float height;
public CustomFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
float width,
float height,
boolean fixed,
float scale,
Object type) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.entityType = type;
this.width = fixed ? width : width * scale;
this.height = fixed ? height : height * scale;
BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues);
}
public float scale() {
return this.scale;
}
public Object entityType() {
return this.entityType;
}
public List<Object> cachedValues() {
return this.cachedValues;
}
public float width() {
return width;
}
public float height() {
return height;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, this.width, this.height));
}
}
@Override
public void collectBoundingBox(Consumer<AABB> aabbConsumer) {
aabbConsumer.accept(AABB.makeBoundingBox(this.position, this.width, this.height));
}
@Override
public CustomFurnitureHitbox create(Furniture furniture) {
return new CustomFurnitureHitbox(furniture, this);
}
public static class Factory implements FurnitureHitBoxConfigFactory<CustomFurnitureHitbox> {
@Override
public CustomFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
String type = (String) arguments.getOrDefault("entity-type", "slime");
Object nmsEntityType = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ENTITY_TYPE, KeyUtils.toResourceLocation(Key.of(type)));
if (nmsEntityType == null) {
throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.custom.invalid_entity", new IllegalArgumentException("EntityType not found: " + type), type);
}
float width;
float height;
boolean fixed;
try {
Object dimensions = CoreReflections.field$EntityType$dimensions.get(nmsEntityType);
width = CoreReflections.field$EntityDimensions$width.getFloat(dimensions);
height = CoreReflections.field$EntityDimensions$height.getFloat(dimensions);
fixed = CoreReflections.field$EntityDimensions$fixed.getBoolean(dimensions);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to get dimensions for " + nmsEntityType, e);
}
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new CustomFurnitureHitboxConfig(SeatConfig.fromObj(arguments.get("seats")), position, canUseItemOn, blocksBuilding, canBeHitByProjectile, width, height, fixed, scale, nmsEntityType);
}
}
}

View File

@@ -1,99 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.entity.EntityType;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class CustomHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
private final float scale;
private final EntityType entityType;
private final List<Object> cachedValues = new ArrayList<>();
public CustomHitBoxConfig(SeatConfig[] seats, Vector3f position, EntityType type, float scale, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, false, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.entityType = type;
BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues);
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues);
}
public EntityType entityType() {
return this.entityType;
}
public float scale() {
return this.scale;
}
@Override
public Key type() {
return HitBoxTypes.CUSTOM;
}
@Override
public void initPacketsAndColliders(int[] entityId, WorldPosition position, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> collider, Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId[0], UUID.randomUUID(), position.x() + offset.x, position.y() + offset.y, position.z() - offset.z, 0, position.yRot(),
FastNMS.INSTANCE.method$CraftEntityType$toNMSEntityType(this.entityType), 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true);
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId[0], Collections.singletonList(attributeInstance)), false);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct custom hitbox spawn packet", e);
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
String type = (String) arguments.getOrDefault("entity-type", "slime");
EntityType entityType = Registry.ENTITY_TYPE.get(new NamespacedKey("minecraft", type));
if (entityType == null) {
throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.custom.invalid_entity", new IllegalArgumentException("EntityType not found: " + type), type);
}
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new CustomHitBoxConfig(SeatConfig.fromObj(arguments.get("seats")), position, entityType, scale, blocksBuilding, canBeHitByProjectile);
}
}
}

View File

@@ -0,0 +1,95 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class HappyGhastFurnitureHitbox extends AbstractFurnitureHitBox {
private final HappyGhastFurnitureHitboxConfig config;
private final Collider collider;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final Vec3d pos;
private final List<Object> packets;
private final int entityId;
private final float yaw;
public HappyGhastFurnitureHitbox(Furniture furniture, HappyGhastFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
this.pos = Furniture.getRelativePosition(position, config.position());
double bbSize = 4 * config.scale();
AABB aabb = AABB.makeBoundingBox(this.pos, bbSize, bbSize);
this.yaw = position.yRot;
this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
this.packets = new ArrayList<>(3);
this.packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, config.cachedValues()));
if (config.scale() != 1) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
this.packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(this.entityId, Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
this.packets.add(FastNMS.INSTANCE.constructor$ClientboundEntityPositionSyncPacket(this.entityId, this.pos.x, this.pos.y, this.pos.z, 0, position.yRot, false));
this.collider = createCollider(furniture.world(), this.pos, aabb, config.hardCollision(), config.blocksBuilding(), config.canBeHitByProjectile());
this.part = new FurnitureHitboxPart(this.entityId, aabb, this.pos, false);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(entityId); }});
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public void show(Player player) {
List<Object> packets = new ArrayList<>();
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
this.entityId, UUID.randomUUID(), this.pos.x, player.y() - (this.config.scale() * 4 + 16), this.pos.z, 0, this.yaw,
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.addAll(this.packets);
player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets), false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public HappyGhastFurnitureHitboxConfig config() {
return this.config;
}
}

View File

@@ -0,0 +1,88 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class HappyGhastFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<HappyGhastFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
private final double scale;
private final boolean hardCollision;
private final List<Object> cachedValues = new ArrayList<>(3);
public HappyGhastFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
double scale,
boolean hardCollision) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.hardCollision = hardCollision;
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(hardCollision, this.cachedValues);
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedValues); // NO AI
HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); // Invisible
}
public double scale() {
return scale;
}
public boolean hardCollision() {
return hardCollision;
}
public List<Object> cachedValues() {
return cachedValues;
}
@Override
public HappyGhastFurnitureHitbox create(Furniture furniture) {
return new HappyGhastFurnitureHitbox(furniture, this);
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, 4 * this.scale, 4 * this.scale));
}
}
@Override
public void collectBoundingBox(Consumer<AABB> aabbConsumer) {
aabbConsumer.accept(AABB.makeBoundingBox(this.position, 4 * this.scale, 4 * this.scale));
}
public static class Factory implements FurnitureHitBoxConfigFactory<HappyGhastFurnitureHitbox> {
@Override
public FurnitureHitBoxConfig<HappyGhastFurnitureHitbox> create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
double scale = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("scale", 1), "scale");
boolean hardCollision = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("hard-collision", true), "hard-collision");
return new HappyGhastFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, canUseItemOn, blocksBuilding, canBeHitByProjectile,
scale, hardCollision
);
}
}
}

View File

@@ -1,138 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
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.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class HappyGhastHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
private final double scale;
private final boolean hardCollision;
private final List<Object> cachedValues = new ArrayList<>();
public HappyGhastHitBoxConfig(SeatConfig[] seats, Vector3f position, double scale, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile, boolean hardCollision) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.hardCollision = hardCollision;
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(hardCollision, this.cachedValues);
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedValues); // NO AI
HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); // Invisible
}
@Override
public Key type() {
return HitBoxTypes.HAPPY_GHAST;
}
public double scale() {
return this.scale;
}
public boolean hardCollision() {
return this.hardCollision;
}
@Override
public void initPacketsAndColliders(int[] entityIds,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[0], List.copyOf(this.cachedValues)), true);
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[0], Collections.singletonList(attributeInstance)), false);
}
if (this.hardCollision) {
collider.accept(this.createCollider(position.world(), offset, x, y, z, entityIds[0], aabb));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct custom hitbox spawn packet", e);
}
}
public Collider createCollider(World world, Vector3f offset, double x, double y, double z, int entityId, Consumer<HitBoxPart> aabb) {
AABB ceAABB = createAABB(offset, x, y, z);
Object level = world.serverWorld();
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
aabb.accept(new HitBoxPart(entityId, ceAABB, new Vec3d(x, y, z)));
return new BukkitCollider(level, nmsAABB, x, y, z, this.canBeHitByProjectile(), true, this.blocksBuilding());
}
public AABB createAABB(Vector3f offset, double x, double y, double z) {
double baseSize = 4.0 * this.scale;
double halfSize = baseSize * 0.5;
double minX = x - halfSize + offset.x();
double maxX = x + halfSize + offset.x();
double minY = y + offset.y();
double maxY = y + baseSize + offset.y();
double minZ = z - halfSize - offset.z();
double maxZ = z + halfSize - offset.z();
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
if (!this.hardCollision) return;
Vector3f offset = conjugated.transform(new Vector3f(position()));
AABB aabb = createAABB(offset, x, y, z);
aabbs.accept(aabb);
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
if (!VersionHelper.isOrAbove1_21_6()) {
throw new UnsupportedOperationException("HappyGhastHitBox is only supported on 1.21.6+");
}
double scale = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("scale", 1), "scale");
boolean hardCollision = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("hard-collision", true), "hard-collision");
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new HappyGhastHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position"),
scale, canUseOn, blocksBuilding, canBeHitByProjectile, hardCollision
);
}
}
}

View File

@@ -0,0 +1,76 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class InteractionFurnitureHitbox extends AbstractFurnitureHitBox {
private final InteractionFurnitureHitboxConfig config;
private final Collider collider;
private final Object spawnPacket;
private final Object despawnPacket;
private final FurnitureHitboxPart part;
private final int entityId;
public InteractionFurnitureHitbox(Furniture furniture, InteractionFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
WorldPosition position = furniture.position();
Vec3d pos = Furniture.getRelativePosition(position, config.position());
AABB aabb = AABB.makeBoundingBox(pos, config.size().x, config.size().y);
this.collider = createCollider(furniture.world(), pos, aabb, false, config.blocksBuilding(), config.canBeHitByProjectile());
int interactionId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of(
FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
interactionId, UUID.randomUUID(), pos.x, pos.y, pos.z, 0, position.yRot,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
),
FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(interactionId, config.cachedValues())
));
this.part = new FurnitureHitboxPart(interactionId, aabb, pos, config.responsive());
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList() {{ add(interactionId); }});
this.entityId = interactionId;
}
@Override
public List<Collider> colliders() {
return List.of(this.collider);
}
@Override
public List<FurnitureHitboxPart> parts() {
return List.of(this.part);
}
@Override
public InteractionFurnitureHitboxConfig config() {
return this.config;
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
collector.accept(this.entityId);
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
}

View File

@@ -0,0 +1,118 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData;
import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class InteractionFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<InteractionFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
public static final InteractionFurnitureHitboxConfig DEFAULT = new InteractionFurnitureHitboxConfig();
private final Vector3f size;
private final boolean responsive;
private final boolean invisible;
private final List<Object> cachedValues = new ArrayList<>(4);
public InteractionFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
boolean invisible,
Vector3f size,
boolean interactive) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.size = size;
this.responsive = interactive;
this.invisible = invisible;
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(size.y, cachedValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(size.x, cachedValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedValues);
if (invisible) {
BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedValues);
}
}
private InteractionFurnitureHitboxConfig() {
super(new SeatConfig[0], new Vector3f(), false, false, false);
this.size = new Vector3f(1);
this.responsive = true;
this.invisible = false;
}
public Vector3f size() {
return size;
}
public boolean responsive() {
return responsive;
}
public boolean invisible() {
return invisible;
}
public List<Object> cachedValues() {
return cachedValues;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position);
aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, size.x, size.y));
}
}
@Override
public void collectBoundingBox(Consumer<AABB> aabbConsumer) {
aabbConsumer.accept(AABB.makeBoundingBox(this.position, size.x, size.y));
}
@Override
public InteractionFurnitureHitbox create(Furniture furniture) {
return new InteractionFurnitureHitbox(furniture, this);
}
public static class Factory implements FurnitureHitBoxConfigFactory<InteractionFurnitureHitbox> {
@Override
public InteractionFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
float width;
float height;
if (arguments.containsKey("scale")) {
String[] split = arguments.get("scale").toString().split(",");
width = Float.parseFloat(split[0]);
height = Float.parseFloat(split[1]);
} else {
width = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("width", 1), "width");
height = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("height", 1), "height");
}
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
boolean invisible = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("invisible", false), "invisible");
return new InteractionFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, canUseOn, blocksBuilding, canBeHitByProjectile, invisible,
new Vector3f(width, height, width),
interactive
);
}
}
}

View File

@@ -1,123 +0,0 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class InteractionHitBoxConfig extends AbstractHitBoxConfig {
public static final Factory FACTORY = new Factory();
public static final InteractionHitBoxConfig DEFAULT = new InteractionHitBoxConfig(new SeatConfig[0], new Vector3f(), new Vector3f(1,1,1), true, false, false, false);
private final Vector3f size;
private final boolean responsive;
private final List<Object> cachedValues = new ArrayList<>();
public InteractionHitBoxConfig(SeatConfig[] seats, Vector3f position, Vector3f size, boolean responsive, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
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 this.responsive;
}
public Vector3f size() {
return this.size;
}
@Override
public Key type() {
return HitBoxTypes.INTERACTION;
}
@Override
public void initPacketsAndColliders(int[] entityId,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId[0], UUID.randomUUID(), vec3d.x, vec3d.y, vec3d.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true);
aabb.accept(new HitBoxPart(entityId[0], AABB.fromInteraction(vec3d, this.size.x, this.size.y), vec3d));
if (blocksBuilding() || this.canBeHitByProjectile()) {
AABB ceAABB = AABB.fromInteraction(vec3d, this.size.x, this.size.y);
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
collider.accept(new BukkitCollider(position.world().serverWorld(), nmsAABB, x, y, z, this.canBeHitByProjectile(), false, this.blocksBuilding()));
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
if (blocksBuilding()) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
AABB ceAABB = AABB.fromInteraction(new Vec3d(x + offset.x, y + offset.y, z - offset.z), this.size.x, this.size.y);
aabbs.accept(ceAABB);
}
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float width;
float height;
if (arguments.containsKey("scale")) {
String[] split = arguments.get("scale").toString().split(",");
width = Float.parseFloat(split[0]);
height = Float.parseFloat(split[1]);
} else {
width = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("width", 1), "width");
height = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("height", 1), "height");
}
boolean canUseOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", false), "can-use-item-on");
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", false), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new InteractionHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position,
new Vector3f(width, height, width),
interactive, canUseOn, blocksBuilding, canBeHitByProjectile
);
}
}
}

View File

@@ -0,0 +1,136 @@
package net.momirealms.craftengine.bukkit.entity.furniture.hitbox;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ShulkerFurnitureHitbox extends AbstractFurnitureHitBox {
private final ShulkerFurnitureHitboxConfig config;
private final List<FurnitureHitboxPart> parts;
private final List<Collider> colliders;
private final Object spawnPacket;
private final Object despawnPacket;
private final int[] entityIds;
public ShulkerFurnitureHitbox(Furniture furniture, ShulkerFurnitureHitboxConfig config) {
super(furniture, config);
this.config = config;
this.entityIds = acquireEntityIds(CoreReflections.instance$Entity$ENTITY_COUNTER::incrementAndGet);
WorldPosition position = furniture.position();
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - position.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(config.position()));
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
double originalY = y + offset.y;
double integerPart = Math.floor(originalY);
double fractionalPart = originalY - integerPart;
double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY;
List<Object> packets = new ArrayList<>();
List<Collider> colliders = new ArrayList<>();
List<FurnitureHitboxPart> parts = new ArrayList<>();
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw,
MEntityTypes.SHULKER, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], config.cachedShulkerValues()));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]));
// fix some special occasions
if (originalY != processedY) {
double deltaY = originalY - processedY;
short ya = (short) (deltaY * 8192);
try {
packets.add(NetworkReflections.constructor$ClientboundMoveEntityPacket$Pos.newInstance(
entityIds[1], (short) 0, ya, (short) 0, true
));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to construct ClientboundMoveEntityPacket$Pos", e);
}
}
if (VersionHelper.isOrAbove1_20_5() && config.scale() != 1) {
try {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, config.scale());
packets.add(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)));
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to apply scale attribute", e);
}
}
config.spawner().accept(entityIds, position.world(), x, y, z, yaw, offset, packets::add, colliders::add, parts::add);
this.parts = parts;
this.colliders = colliders;
this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(new IntArrayList(entityIds));
}
@Override
public List<Collider> colliders() {
return this.colliders;
}
@Override
public List<FurnitureHitboxPart> parts() {
return this.parts;
}
@Override
public void collectVirtualEntityId(Consumer<Integer> collector) {
for (int entityId : entityIds) {
collector.accept(entityId);
}
}
@Override
public void show(Player player) {
player.sendPacket(this.spawnPacket, false);
}
@Override
public void hide(Player player) {
player.sendPacket(this.despawnPacket, false);
}
@Override
public ShulkerFurnitureHitboxConfig config() {
return this.config;
}
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
if (config.interactionEntity()) {
if (config.direction().stepY() != 0) {
// 展示实体 // 潜影贝 // 交互实体
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
} else {
// 展示实体 // 潜影贝 // 交互实体1 // 交互实体2
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
}
} else {
// 展示实体 // 潜影贝
return new int[] {entityIdSupplier.get(), entityIdSupplier.get()};
}
}
}

View File

@@ -5,13 +5,18 @@ import net.momirealms.craftengine.bukkit.entity.data.ShulkerData;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitCollider;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MAttributeHolders;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.Collider;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.hitbox.AbstractFurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -19,30 +24,39 @@ import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
public class ShulkerFurnitureHitboxConfig extends AbstractFurnitureHitBoxConfig<ShulkerFurnitureHitbox> {
public static final Factory FACTORY = new Factory();
// 1.20.6+
private final float scale;
private final byte peek;
private final boolean interactive;
private final boolean interactionEntity;
private final Direction direction;
private final List<Object> cachedShulkerValues = new ArrayList<>();
private final DirectionalShulkerSpawner spawner;
private final List<Object> cachedShulkerValues = new ArrayList<>(6);
private final AABBCreator aabbCreator;
public ShulkerHitBoxConfig(SeatConfig[] seats, Vector3f position, Direction direction, float scale, byte peek, boolean interactionEntity, boolean interactive, boolean canUseOn, boolean blocksBuilding, boolean canBeHitByProjectile) {
super(seats, position, canUseOn, blocksBuilding, canBeHitByProjectile);
this.direction = direction;
public ShulkerFurnitureHitboxConfig(SeatConfig[] seats,
Vector3f position,
boolean canUseItemOn,
boolean blocksBuilding,
boolean canBeHitByProjectile,
float scale,
byte peek,
boolean interactive,
boolean interactionEntity,
Direction direction) {
super(seats, position, canUseItemOn, blocksBuilding, canBeHitByProjectile);
this.scale = scale;
this.peek = peek;
this.interactive = interactive;
this.interactionEntity = interactionEntity;
this.direction = direction;
ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues);
ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, this.cachedShulkerValues);
@@ -51,9 +65,10 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // NO AI
ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible
float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale;
List<Object> cachedInteractionValues = new ArrayList<>();
if (this.direction == Direction.UP) {
InteractionEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedInteractionValues);
float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale;
if (direction == Direction.UP) {
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
@@ -63,32 +78,28 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.UP, offset, x, y, z);
} else if (this.direction == Direction.DOWN) {
} else if (direction == Direction.DOWN) {
InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues);
InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues);
InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues);
this.spawner = (entityIds, world, x, y, z, yaw, offset, packets, collider, aabb) -> {
collider.accept(this.createCollider(Direction.DOWN, world, offset, x, y, z, entityIds[1], aabb));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(CoreReflections.instance$Direction$UP))), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(CoreReflections.instance$Direction$UP))));
if (interactionEntity) {
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f - shulkerHeight + scale, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y - shulkerHeight + scale, z - offset.z);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d, scale, shulkerHeight), vec3d));
}
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
Vec3d vec3d = new Vec3d(x + offset.x, y + offset.y - shulkerHeight + scale, z - offset.z);
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d, scale, shulkerHeight), vec3d, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> createAABB(Direction.DOWN, offset, x, y, z);
@@ -100,27 +111,25 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
Direction shulkerAnchor = getOriginalDirection(direction, Direction.fromYaw(yaw));
Direction shulkerDirection = shulkerAnchor.opposite();
collider.accept(this.createCollider(shulkerDirection, world, offset, x, y, z, entityIds[1], aabb));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))));
if (interactionEntity) {
// first interaction
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true);
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)));
// second interaction
double distance = shulkerHeight - scale;
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[3], UUID.randomUUID(), x + offset.x + shulkerDirection.stepX() * distance, y + offset.y - 0.005f, z - offset.z + shulkerDirection.stepZ() * distance, 0, yaw,
MEntityTypes.INTERACTION, 0, CoreReflections.instance$Vec3$Zero, 0
), true);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)), true);
if (canUseOn) {
Vec3d vec3d1 = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
Vec3d vec3d2 = new Vec3d(x + offset.x + shulkerDirection.stepX() * distance, y + offset.y, z - offset.z + shulkerDirection.stepZ() * distance);
aabb.accept(new HitBoxPart(entityIds[2], AABB.fromInteraction(vec3d1, scale, scale), vec3d1));
aabb.accept(new HitBoxPart(entityIds[3], AABB.fromInteraction(vec3d2, scale, scale), vec3d2));
}
));
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)));
Vec3d vec3d1 = new Vec3d(x + offset.x, y + offset.y, z - offset.z);
Vec3d vec3d2 = new Vec3d(x + offset.x + shulkerDirection.stepX() * distance, y + offset.y, z - offset.z + shulkerDirection.stepZ() * distance);
aabb.accept(new FurnitureHitboxPart(entityIds[2], AABB.makeBoundingBox(vec3d1, scale, scale), vec3d1, interactive));
aabb.accept(new FurnitureHitboxPart(entityIds[3], AABB.makeBoundingBox(vec3d2, scale, scale), vec3d2, interactive));
}
};
this.aabbCreator = (x, y, z, yaw, offset) -> {
@@ -131,16 +140,91 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
}
}
public Collider createCollider(Direction direction, World world, Vector3f offset, double x, double y, double z, int entityId, Consumer<HitBoxPart> aabb) {
public static float getPhysicalPeek(float peek) {
return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
}
@Override
public void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer) {
if (this.blocksBuilding) {
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - targetPos.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(position()));
aabbConsumer.accept(this.aabbCreator.create(targetPos.x, targetPos.y, targetPos.z, targetPos.yRot, offset));
}
}
@Override
public void collectBoundingBox(Consumer<AABB> aabbConsumer) {
aabbConsumer.accept(this.aabbCreator.create(position.x, position.y, position.z, 180, new Vector3f(0)));
}
public float scale() {
return this.scale;
}
public byte peek() {
return this.peek;
}
public boolean interactive() {
return this.interactive;
}
public boolean interactionEntity() {
return this.interactionEntity;
}
public Direction direction() {
return this.direction;
}
public DirectionalShulkerSpawner spawner() {
return this.spawner;
}
public List<Object> cachedShulkerValues() {
return this.cachedShulkerValues;
}
@Override
public ShulkerFurnitureHitbox create(Furniture furniture) {
return new ShulkerFurnitureHitbox(furniture, this);
}
@FunctionalInterface
public interface AABBCreator {
AABB create(double x, double y, double z, float yaw, Vector3f offset);
}
@FunctionalInterface
public interface DirectionalShulkerSpawner {
void accept(int[] entityIds,
World world,
double x,
double y,
double z,
float yaw,
Vector3f offset,
Consumer<Object> packets,
Consumer<Collider> collider,
Consumer<FurnitureHitboxPart> aabb);
}
public Collider createCollider(Direction direction, World world,
Vector3f offset, double x, double y, double z,
int entityId,
Consumer<FurnitureHitboxPart> aabb) {
AABB ceAABB = createAABB(direction, offset, x, y, z);
Object level = world.serverWorld();
Object nmsAABB = FastNMS.INSTANCE.constructor$AABB(ceAABB.minX, ceAABB.minY, ceAABB.minZ, ceAABB.maxX, ceAABB.maxY, ceAABB.maxZ);
aabb.accept(new HitBoxPart(entityId, ceAABB, new Vec3d(x, y, z)));
aabb.accept(new FurnitureHitboxPart(entityId, ceAABB, new Vec3d(x, y, z), false));
return new BukkitCollider(level, nmsAABB, x, y, z, this.canBeHitByProjectile(), true, this.blocksBuilding());
}
public AABB createAABB(Direction direction, Vector3f offset, double x, double y, double z) {
float peek = getPhysicalPeek(this.peek() * 0.01F);
public AABB createAABB(Direction direction, Vector3f relativePos, double x, double y, double z) {
float peek = getPhysicalPeek(this.peek * 0.01F);
double x1 = -this.scale * 0.5;
double y1 = 0.0;
double z1 = -this.scale * 0.5;
@@ -166,156 +250,15 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
} else if (dz < 0) {
z1 += dz;
}
double minX = x + x1 + offset.x();
double maxX = x + x2 + offset.x();
double minY = y + y1 + offset.y();
double maxY = y + y2 + offset.y();
double minZ = z + z1 - offset.z();
double maxZ = z + z2 - offset.z();
double minX = x + x1 + relativePos.x();
double maxX = x + x2 + relativePos.x();
double minY = y + y1 + relativePos.y();
double maxY = y + y2 + relativePos.y();
double minZ = z + z1 - relativePos.z();
double maxZ = z + z2 - relativePos.z();
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
private static float getPhysicalPeek(float peek) {
return 0.5F - MiscUtils.sin((0.5F + peek) * 3.1415927F) * 0.5F;
}
public boolean interactionEntity() {
return interactionEntity;
}
public boolean interactive() {
return interactive;
}
public Direction direction() {
return direction;
}
public byte peek() {
return peek;
}
public float scale() {
return scale;
}
@Override
public Key type() {
return HitBoxTypes.SHULKER;
}
@Override
public void initPacketsAndColliders(int[] entityIds,
WorldPosition position,
Quaternionf conjugated,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
double x = position.x();
double y = position.y();
double z = position.z();
float yaw = position.yRot();
double originalY = y + offset.y;
double integerPart = Math.floor(originalY);
double fractionalPart = originalY - integerPart;
double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY;
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw,
MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw,
MEntityTypes.SHULKER, 0, CoreReflections.instance$Vec3$Zero, 0
), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.copyOf(this.cachedShulkerValues)), false);
// add passengers
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]), false);
// fix some special occasions
if (originalY != processedY) {
double deltaY = originalY - processedY;
short ya = (short) (deltaY * 8192);
packets.accept(NetworkReflections.constructor$ClientboundMoveEntityPacket$Pos.newInstance(
entityIds[1], (short) 0, ya, (short) 0, true
), false);
}
// set shulker scale
if (VersionHelper.isOrAbove1_20_5() && this.scale != 1) {
Object attributeInstance = CoreReflections.constructor$AttributeInstance.newInstance(MAttributeHolders.SCALE, (Consumer<?>) (o) -> {});
CoreReflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale);
packets.accept(NetworkReflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false);
}
this.spawner.accept(entityIds, position.world(), x, y, z, yaw, offset, packets, collider, aabb);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e);
}
}
@Override
public void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
aabbs.accept(this.aabbCreator.create(x, y, z, yaw, offset));
}
@FunctionalInterface
interface DirectionalShulkerSpawner {
void accept(int[] entityIds,
World world,
double x,
double y,
double z,
float yaw,
Vector3f offset,
BiConsumer<Object, Boolean> packets,
Consumer<Collider> collider,
Consumer<HitBoxPart> aabb);
}
@FunctionalInterface
interface AABBCreator {
AABB create(double x, double y, double z, float yaw, Vector3f offset);
}
@Override
public int[] acquireEntityIds(Supplier<Integer> entityIdSupplier) {
if (this.interactionEntity) {
if (this.direction.stepY() != 0) {
// 展示实体 // 潜影贝 // 交互实体
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
} else {
// 展示实体 // 潜影贝 // 交互实体1 // 交互实体2
return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()};
}
} else {
// 展示实体 // 潜影贝
return new int[] {entityIdSupplier.get(), entityIdSupplier.get()};
}
}
public static class Factory implements HitBoxConfigFactory {
@Override
public HitBoxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", "0"), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", "1"), "scale");
byte peek = (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("peek", 0), "peek");
Direction directionEnum = Optional.ofNullable(arguments.get("direction")).map(it -> Direction.valueOf(it.toString().toUpperCase(Locale.ENGLISH))).orElse(Direction.UP);
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean interactionEntity = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interaction-entity", true), "interaction-entity");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new ShulkerHitBoxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position, directionEnum,
scale, peek, interactionEntity, interactive, canUseItemOn, blocksBuilding, canBeHitByProjectile
);
}
}
public static Direction getOriginalDirection(Direction newDirection, Direction oldDirection) {
switch (newDirection) {
case NORTH -> {
@@ -357,4 +300,26 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig {
default -> throw new IllegalStateException("Unexpected value: " + newDirection);
}
}
public static class Factory implements FurnitureHitBoxConfigFactory<ShulkerFurnitureHitbox> {
@Override
public ShulkerFurnitureHitboxConfig create(Map<String, Object> arguments) {
Vector3f position = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0), "position");
float scale = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1), "scale");
byte peek = (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("peek", 0), "peek");
Direction directionEnum = ResourceConfigUtils.getAsEnum(arguments.get("direction"), Direction.class, Direction.UP);
boolean interactive = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interactive", true), "interactive");
boolean interactionEntity = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("interaction-entity", true), "interaction-entity");
boolean canUseItemOn = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-use-item-on", true), "can-use-item-on");
boolean canBeHitByProjectile = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-be-hit-by-projectile", true), "can-be-hit-by-projectile");
boolean blocksBuilding = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blocks-building", true), "blocks-building");
return new ShulkerFurnitureHitboxConfig(
SeatConfig.fromObj(arguments.get("seats")),
position,
canUseItemOn, blocksBuilding, canBeHitByProjectile,
scale, peek, interactive, interactionEntity, directionEnum
);
}
}
}

View File

@@ -6,13 +6,10 @@ import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
@@ -27,28 +24,31 @@ import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.Location;
import org.bukkit.World;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
public class FurnitureItemBehavior extends ItemBehavior {
public static final Factory FACTORY = new Factory();
private static final Set<String> ALLOWED_ANCHOR_TYPES = Set.of("wall", "ceiling", "ground");
private final Key id;
private final Map<AnchorType, Rule> rules;
public FurnitureItemBehavior(Key id) {
public FurnitureItemBehavior(Key id, Map<AnchorType, Rule> rules) {
this.id = id;
this.rules = rules;
}
public Key furnitureId() {
return id;
return this.id;
}
@Override
@@ -57,12 +57,11 @@ public class FurnitureItemBehavior extends ItemBehavior {
}
public InteractionResult place(UseOnContext context) {
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id);
Optional<FurnitureConfig> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id);
if (optionalCustomFurniture.isEmpty()) {
CraftEngine.instance().logger().warn("Furniture " + this.id + " not found");
return InteractionResult.FAIL;
}
CustomFurniture customFurniture = optionalCustomFurniture.get();
Direction clickedFace = context.getClickedFace();
AnchorType anchorType = switch (clickedFace) {
@@ -71,78 +70,80 @@ public class FurnitureItemBehavior extends ItemBehavior {
case DOWN -> AnchorType.CEILING;
};
CustomFurniture.Placement placement = customFurniture.getPlacement(anchorType);
if (placement == null) {
FurnitureConfig customFurniture = optionalCustomFurniture.get();
FurnitureVariant variant = customFurniture.getVariant(anchorType.variantName());
if (variant == null) {
return InteractionResult.FAIL;
}
Rule rule = this.rules.get(anchorType);
if (rule == null) {
rule = Rule.DEFAULT;
}
Player player = context.getPlayer();
// todo adventure check
if (player != null && player.isAdventureMode()) {
return InteractionResult.FAIL;
}
Vec3d clickedPosition = context.getClickLocation();
// trigger event
org.bukkit.entity.Player bukkitPlayer = player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null;
World world = (World) context.getLevel().platformWorld();
// get position and rotation for placement
Vec3d finalPlacePosition;
double furnitureYaw;
if (anchorType == AnchorType.WALL) {
furnitureYaw = Direction.getYaw(clickedFace);
if (clickedFace == Direction.EAST || clickedFace == Direction.WEST) {
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.y(), clickedPosition.z()));
Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.y(), clickedPosition.z()));
finalPlacePosition = new Vec3d(clickedPosition.x(), xz.left(), xz.right());
} else {
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.y()));
Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.y()));
finalPlacePosition = new Vec3d(xz.left(), xz.right(), clickedPosition.z());
}
} else {
furnitureYaw = placement.rotationRule().apply(180 + (player != null ? player.yRot() : 0));
Pair<Double, Double> xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
furnitureYaw = rule.rotationRule().apply(180 + (player != null ? player.yRot() : 0));
Pair<Double, Double> xz = rule.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z()));
finalPlacePosition = new Vec3d(xz.left(), clickedPosition.y(), xz.right());
}
// trigger event
org.bukkit.entity.Player bukkitPlayer = player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null;
World world = (World) context.getLevel().platformWorld();
Location furnitureLocation = new Location(world, finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, 0);
WorldPosition furniturePos = LocationUtils.toWorldPosition(furnitureLocation);
List<AABB> aabbs = new ArrayList<>();
for (HitBoxConfig hitBoxConfig : placement.hitBoxConfigs()) {
hitBoxConfig.initShapeForPlacement(finalPlacePosition.x(), finalPlacePosition.y(), finalPlacePosition.z(), (float) furnitureYaw, QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - furnitureYaw), 0).conjugate(), aabbs::add);
// 收集阻挡的碰撞箱
for (FurnitureHitBoxConfig<?> hitBoxConfig : variant.hitBoxConfigs()) {
hitBoxConfig.prepareForPlacement(furniturePos, aabbs::add);
}
// 检查方块、实体阻挡
if (!aabbs.isEmpty()) {
if (!FastNMS.INSTANCE.checkEntityCollision(context.getLevel().serverWorld(), aabbs.stream().map(it -> FastNMS.INSTANCE.constructor$AABB(it.minX, it.minY, it.minZ, it.maxX, it.maxY, it.maxZ)).toList())) {
return InteractionResult.FAIL;
}
}
// 检查其他插件兼容性
if (!BukkitCraftEngine.instance().antiGriefProvider().canPlace(bukkitPlayer, furnitureLocation)) {
return InteractionResult.FAIL;
}
// 触发尝试放置的事件
if (player != null) {
FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, anchorType, furnitureLocation.clone(),
DirectionUtils.toBlockFace(clickedFace), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
FurnitureAttemptPlaceEvent attemptPlaceEvent = new FurnitureAttemptPlaceEvent(bukkitPlayer, customFurniture, variant, furnitureLocation.clone(), context.getHand(), world.getBlockAt(context.getClickedPos().x(), context.getClickedPos().y(), context.getClickedPos().z()));
if (EventUtils.fireAndCheckCancel(attemptPlaceEvent)) {
return InteractionResult.FAIL;
}
}
Item<?> item = context.getItem();
// 不可能
if (ItemUtils.isEmpty(item)) return InteractionResult.FAIL;
BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place(
furnitureLocation.clone(), customFurniture,
FurnitureExtraData.builder()
.item(item.copyWithCount(1))
.anchorType(anchorType)
.dyedColor(item.dyedColor().orElse(null))
.fireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null))
.build(), false);
// 获取家具物品的一些属性
FurnitureDataAccessor dataAccessor = FurnitureDataAccessor.of(new CompoundTag());
dataAccessor.setVariant(variant.name());
dataAccessor.setItem(item.copyWithCount(1));
dataAccessor.setDyedColor(item.dyedColor().orElse(null));
dataAccessor.setFireworkExplosionColors(item.fireworkExplosion().map(explosion -> explosion.colors().toIntArray()).orElse(null));
// 放置家具
BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place(furnitureLocation.clone(), customFurniture, dataAccessor, false);
// 触发放置事件
if (player != null) {
FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, bukkitFurniture, furnitureLocation, context.getHand());
if (EventUtils.fireAndCheckCancel(placeEvent)) {
@@ -150,7 +151,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
return InteractionResult.FAIL;
}
}
// 触发ce事件
Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder()
.withParameter(DirectContextParameters.FURNITURE, bukkitFurniture)
@@ -163,14 +164,13 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (dummy.isCancelled()) {
return InteractionResult.SUCCESS_AND_CANCEL;
}
// 后续处理
if (player != null) {
if (!player.canInstabuild()) {
item.count(item.count() - 1);
}
player.swingHand(context.getHand());
}
context.getLevel().playBlockSound(finalPlacePosition, customFurniture.settings().sounds().placeSound());
return InteractionResult.SUCCESS;
}
@@ -183,17 +183,59 @@ public class FurnitureItemBehavior extends ItemBehavior {
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior"));
}
Map<String, Object> rulesMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("rules"), "rules");
Key furnitureId;
if (id instanceof Map<?,?> map) {
Map<String, Object> furnitureSection;
if (map.containsKey(key.toString())) {
// 防呆
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false)));
furnitureSection = MiscUtils.castToMap(map.get(key.toString()), false);
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection));
} else {
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, MiscUtils.castToMap(map, false)));
furnitureSection = MiscUtils.castToMap(map, false);
BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection));
}
furnitureId = key;
// 兼容老版本
if (rulesMap == null) {
Map<String, Object> placementSection = ResourceConfigUtils.getAsMapOrNull(furnitureSection.get("placement"), "placement");
if (placementSection != null) {
rulesMap = new HashMap<>();
for (Map.Entry<String, Object> entry : placementSection.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> innerMap) {
if (innerMap.containsKey("rules")) {
Map<String, Object> rules = ResourceConfigUtils.getAsMap(innerMap.get("rules"), "rules");
if (ALLOWED_ANCHOR_TYPES.contains(entry.getKey())) {
rulesMap.put(entry.getKey(), rules);
}
}
}
}
}
}
return new FurnitureItemBehavior(key);
} else {
return new FurnitureItemBehavior(Key.of(id.toString()));
furnitureId = Key.of(id.toString());
}
Map<AnchorType, Rule> rules = new EnumMap<>(AnchorType.class);
if (rulesMap != null) {
for (Map.Entry<String, Object> entry : rulesMap.entrySet()) {
try {
AnchorType type = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ROOT));
Map<String, Object> ruleSection = MiscUtils.castToMap(entry.getValue(), true);
rules.put(type, new Rule(
ResourceConfigUtils.getAsEnum(ruleSection.get("alignment"), AlignmentRule.class, AlignmentRule.ANY),
ResourceConfigUtils.getAsEnum(ruleSection.get("rotation"), RotationRule.class, RotationRule.ANY)
));
} catch (IllegalArgumentException ignored) {
Debugger.FURNITURE.debug(() -> "Invalid anchor type: " + entry.getKey());
}
}
}
return new FurnitureItemBehavior(furnitureId, rules);
}
}
public record Rule(AlignmentRule alignmentRule, RotationRule rotationRule) {
public static final Rule DEFAULT = new Rule(AlignmentRule.ANY, RotationRule.ANY);
}
}

View File

@@ -457,9 +457,11 @@ public class ItemEventListener implements Listener {
}
} else {
// fixme 如何取消堆叠数量>1的物品的默认replacement
Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer);
if (replacementItem != null) {
PlayerUtils.giveItem(serverPlayer, 1, replacementItem);
if (replacement != null) {
Item<ItemStack> replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer);
if (replacementItem != null) {
PlayerUtils.giveItem(serverPlayer, 1, replacementItem);
}
}
}
}

View File

@@ -7,7 +7,8 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.entity.furniture.element.BukkitFurnitureElementConfigs;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitFurnitureHitboxTypes;
import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager;
import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeatManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
@@ -160,8 +161,9 @@ public class BukkitCraftEngine extends CraftEngine {
super.onPluginLoad();
BukkitBlockBehaviors.init();
BukkitItemBehaviors.init();
BukkitHitBoxTypes.init();
BukkitFurnitureHitboxTypes.init();
BukkitBlockEntityElementConfigs.init();
BukkitFurnitureElementConfigs.init();
// 初始化 onload 阶段的兼容性
super.compatibilityManager().onLoad();
// 创建网络管理器
@@ -369,6 +371,11 @@ public class BukkitCraftEngine extends CraftEngine {
return (BukkitFontManager) fontManager;
}
@Override
public BukkitWorldManager worldManager() {
return (BukkitWorldManager) worldManager;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void saveResource(String resourcePath) {

View File

@@ -43,6 +43,7 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new TestCommand(this, plugin),
new SetLocaleCommand(this, plugin),
new SetEntityViewDistanceScaleCommand(this, plugin),
new ToggleEntityCullingCommand(this, plugin),
new UnsetLocaleCommand(this, plugin),
new DebugGetBlockStateRegistryIdCommand(this, plugin),
new DebugGetBlockInternalIdCommand(this, plugin),

View File

@@ -4,8 +4,7 @@ import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
import net.momirealms.craftengine.core.entity.furniture.CustomFurniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureConfig;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
@@ -19,10 +18,11 @@ import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.standard.EnumParser;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -42,21 +42,30 @@ public class DebugSpawnFurnitureCommand extends BukkitCommandFeature<CommandSend
return CompletableFuture.completedFuture(plugin().furnitureManager().cachedSuggestions());
}
}))
.optional("anchor-type", EnumParser.enumParser(AnchorType.class))
.optional("variant", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<FurnitureConfig> optionalCustomFurniture = furnitureManager.furnitureById(id);
return optionalCustomFurniture.<CompletableFuture<? extends Iterable<? extends Suggestion>>>map(config -> CompletableFuture.completedFuture(config.variants().keySet().stream().map(Suggestion::suggestion).toList())).orElseGet(() -> CompletableFuture.completedFuture(List.of()));
}
}))
.flag(FlagKeys.SILENT_FLAG)
.handler(context -> {
NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<CustomFurniture> optionalCustomFurniture = furnitureManager.furnitureById(id);
Optional<FurnitureConfig> optionalCustomFurniture = furnitureManager.furnitureById(id);
if (optionalCustomFurniture.isEmpty()) {
return;
}
Location location = context.get("location");
CustomFurniture customFurniture = optionalCustomFurniture.get();
AnchorType anchorType = (AnchorType) context.optional("anchor-type").orElse(customFurniture.getAnyAnchorType());
FurnitureConfig customFurniture = optionalCustomFurniture.get();
String variant = (String) context.optional("variant").orElse(customFurniture.anyVariantName());
boolean playSound = context.flags().hasFlag("silent");
CraftEngineFurniture.place(location, customFurniture, anchorType, playSound);
CraftEngineFurniture.place(location, customFurniture, variant, playSound);
});
}

View File

@@ -1,12 +1,14 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -27,6 +29,14 @@ public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature<Comm
.required("player", PlayerParser.playerParser())
.required("scale", DoubleParser.doubleParser(0.125, 8))
.handler(context -> {
if (!Config.enableEntityCulling()) {
context.sender().sendMessage(Component.text("Entity culling is not enabled on this server").color(NamedTextColor.RED));
return;
}
if (Config.entityCullingViewDistance() <= 0) {
context.sender().sendMessage(Component.text("View distance is not enabled on this server").color(NamedTextColor.RED));
return;
}
Player player = context.get("player");
double scale = context.get("scale");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);

View File

@@ -0,0 +1,56 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.parser.PlayerParser;
import org.incendo.cloud.parser.standard.BooleanParser;
import java.util.Optional;
public class ToggleEntityCullingCommand extends BukkitCommandFeature<CommandSender> {
public ToggleEntityCullingCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.required("player", PlayerParser.playerParser())
.optional("state", BooleanParser.booleanParser())
.handler(context -> {
if (!Config.enableEntityCulling()) {
context.sender().sendMessage(Component.text("Entity culling is not enabled on this server").color(NamedTextColor.RED));
return;
}
Player player = context.get("player");
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
Optional<Boolean> state = context.optional("state");
boolean isEnabled = serverPlayer.enableEntityCulling();
if (state.isPresent()) {
serverPlayer.setEnableEntityCulling(state.get());
handleFeedback(context, MessageConstants.COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS, Component.text(state.get()), Component.text(player.getName()));
} else {
serverPlayer.setEnableEntityCulling(!isEnabled);
handleFeedback(context, MessageConstants.COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS, Component.text(!isEnabled), Component.text(player.getName()));
}
});
}
@Override
public String getFeatureID() {
return "toggle_entity_culling";
}
}

View File

@@ -59,8 +59,8 @@ import net.momirealms.craftengine.bukkit.world.BukkitWorldManager;
import net.momirealms.craftengine.core.advancement.network.AdvancementHolder;
import net.momirealms.craftengine.core.advancement.network.AdvancementProgress;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.furniture.HitBox;
import net.momirealms.craftengine.core.entity.furniture.HitBoxPart;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.font.FontManager;
@@ -359,8 +359,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(new EntityEventListener(), NetworkReflections.clazz$ClientboundEntityEventPacket);
registerNMSPacketConsumer(new MovePosAndRotateEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$PosRot);
registerNMSPacketConsumer(new MovePosEntityListener(), NetworkReflections.clazz$ClientboundMoveEntityPacket$Pos);
registerNMSPacketConsumer(new RotateHeadListener(), NetworkReflections.clazz$ClientboundRotateHeadPacket);
registerNMSPacketConsumer(new SetEntityMotionListener(), NetworkReflections.clazz$ClientboundSetEntityMotionPacket);
registerNMSPacketConsumer(new FinishConfigurationListener(), NetworkReflections.clazz$ClientboundFinishConfigurationPacket);
registerNMSPacketConsumer(new LoginFinishedListener(), NetworkReflections.clazz$ClientboundLoginFinishedPacket);
registerNMSPacketConsumer(new UpdateTagsListener(), NetworkReflections.clazz$ClientboundUpdateTagsPacket);
@@ -1248,7 +1246,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
CraftEngine.instance().logger().warn("Failed to get entityId from ServerboundPickItemFromEntityPacket", e);
return;
}
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByVirtualEntityId(entityId);
if (furniture == null) return;
Player player = (Player) user.platformPlayer();
if (player == null) return;
@@ -1274,7 +1272,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Throwable {
Key itemId = furniture.config().settings().itemId();
if (itemId == null) return;
pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.baseEntity()));
pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.getBukkitEntity()));
}
}
@@ -1470,6 +1468,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.clearTrackedChunks();
player.clearTrackedBlockEntities();
player.clearTrackedFurniture();
} else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
}
@@ -1746,9 +1745,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
int entityId = ProtectedFieldVisitor.get().field$ClientboundMoveEntityPacket$entityId(packet);
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
EntityPacketHandler handler = user.entityPacketHandlers().get(entityId);
if (handler != null) {
handler.handleMoveAndRotate(user, event, packet);
@@ -1768,41 +1764,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
}
public static class RotateHeadListener implements NMSPacketListener {
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
int entityId;
try {
entityId = (int) NetworkReflections.methodHandle$ClientboundRotateHeadPacket$entityIdGetter.invokeExact(packet);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundRotateHeadPacket", t);
return;
}
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
}
}
public static class SetEntityMotionListener implements NMSPacketListener {
@Override
public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) {
if (!VersionHelper.isOrAbove1_21_6()) return;
int entityId;
try {
entityId = (int) NetworkReflections.methodHandle$ClientboundSetEntityMotionPacket$idGetter.invokeExact(packet);
} catch (Throwable t) {
CraftEngine.instance().logger().warn("Failed to get entity id from ClientboundSetEntityMotionPacket", t);
return;
}
if (BukkitFurnitureManager.instance().isFurnitureRealEntity(entityId)) {
event.setCancelled(true);
}
}
}
public static class FinishConfigurationListener implements NMSPacketListener {
@SuppressWarnings("unchecked")
@@ -3352,7 +3313,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
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)) {
changed = true;
}
}
@@ -3701,35 +3662,41 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
public void onPacketReceive(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer();
int entityId = hasModelEngine() ? plugin.compatibilityManager().interactionToBaseEntity(buf.readVarInt()) : buf.readVarInt();
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByVirtualEntityId(entityId);
if (furniture == null) return;
int actionType = buf.readVarInt();
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
if (serverPlayer.isSpectatorMode()) return;
Player platformPlayer = serverPlayer.platformPlayer();
Location location = furniture.baseEntity().getLocation();
Location location = furniture.location();
Runnable mainThreadTask;
if (actionType == 1) {
// ATTACK
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeBoolean(usingSecondaryAction);
}
mainThreadTask = () -> {
// todo 冒险模式破坏工具白名单
if (serverPlayer.isAdventureMode() ||
!furniture.isValid()) return;
if (serverPlayer.isAdventureMode() || !furniture.isValid()) return;
// todo 重构家具时候注意需要准备加载好的hitbox类以获取hitbox坐标
if (!serverPlayer.canInteractPoint(new Vec3d(location.getX(), location.getY(), location.getZ()), 16d)) {
return;
// 先检查碰撞箱部分是否存在
FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
if (hitBox == null) return;
for (FurnitureHitboxPart part : hitBox.parts()) {
if (part.entityId() == entityId) {
// 检查玩家是否能破坏此点
if (!serverPlayer.canInteractPoint(part.pos(), 16d)) {
return;
}
}
}
FurnitureAttemptBreakEvent preBreakEvent = new FurnitureAttemptBreakEvent(serverPlayer.platformPlayer(), furniture);
@@ -3767,11 +3734,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
float z = buf.readFloat();
InteractionHand hand = buf.readVarInt() == 0 ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeFloat(x).writeFloat(y).writeFloat(z);
buf.writeVarInt(hand == InteractionHand.MAIN_HAND ? 0 : 1);
@@ -3784,34 +3751,39 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
// 先检查碰撞箱部分是否存在
HitBoxPart hitBoxPart = furniture.hitBoxPartByEntityId(entityId);
if (hitBoxPart == null) return;
Vec3d pos = hitBoxPart.pos();
// 检测距离
if (!serverPlayer.canInteractPoint(pos, 16d)) {
FurnitureHitBox hitBox = furniture.hitboxByEntityId(entityId);
if (hitBox == null) return;
FurnitureHitboxPart part = null;
for (FurnitureHitboxPart p : hitBox.parts()) {
if (p.entityId() == entityId) {
Vec3d pos = p.pos();
// 检测距离
if (!serverPlayer.canInteractPoint(pos, 16d)) {
return;
}
part = p;
}
}
if (part == null) {
return;
}
// 检测
// 检测能否交互碰撞箱
Location eyeLocation = platformPlayer.getEyeLocation();
Vector direction = eyeLocation.getDirection();
Location endLocation = eyeLocation.clone();
endLocation.add(direction.multiply(serverPlayer.getCachedInteractionRange()));
Optional<EntityHitResult> result = hitBoxPart.aabb().clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation));
Optional<EntityHitResult> result = part.aabb().clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation));
if (result.isEmpty()) {
return;
}
EntityHitResult hitResult = result.get();
Vec3d hitLocation = hitResult.hitLocation();
// 获取正确的交互点
Location interactionPoint = new Location(platformPlayer.getWorld(), hitLocation.x, hitLocation.y, hitLocation.z);
HitBox hitbox = furniture.hitBoxByEntityId(entityId);
if (hitbox == null) {
return;
}
// 触发事件
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint, hitbox);
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint, hitBox);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
return;
}
@@ -3833,7 +3805,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
// 必须从网络包层面处理,否则无法获取交互的具体实体
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitbox.config().canUseItemOn()) {
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty() && hitBox.config().canUseItemOn()) {
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) {
for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) {
@@ -3853,9 +3825,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
);
} else {
if (!serverPlayer.isSecondaryUseActive()) {
for (Seat<HitBox> seat : hitbox.seats()) {
for (Seat<FurnitureHitBox> seat : hitBox.seats()) {
if (!seat.isOccupied()) {
if (seat.spawnSeat(serverPlayer, furniture.position())) {
if (!part.interactive()) {
serverPlayer.swingHand(InteractionHand.MAIN_HAND);
}
break;
}
}
@@ -3866,11 +3841,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} else if (actionType == 0) {
int hand = buf.readVarInt();
boolean usingSecondaryAction = buf.readBoolean();
if (entityId != furniture.baseEntityId()) {
if (entityId != furniture.entityId()) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(furniture.baseEntityId());
buf.writeVarInt(furniture.entityId());
buf.writeVarInt(actionType);
buf.writeVarInt(hand);
buf.writeBoolean(usingSecondaryAction);
@@ -3978,10 +3953,18 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.handlers[MEntityTypes.ITEM_DISPLAY$registryId] = (user, event) -> {
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByMetaEntityId(id);
if (furniture != null) {
user.entityPacketHandlers().put(id, new FurniturePacketHandler(furniture.fakeEntityIds()));
user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false);
EntityPacketHandler previous = serverPlayer.entityPacketHandlers().put(id, new FurniturePacketHandler(id, furniture.virtualEntityIds()));
if (Config.enableEntityCulling()) {
serverPlayer.addTrackedFurniture(id, furniture);
} else {
// 修复addEntityToWorld包比事件先发的问题 (WE)
if (previous == null || previous instanceof ItemDisplayPacketHandler) {
furniture.show(serverPlayer);
}
}
if (Config.hideBaseEntity() && !furniture.hasExternalModel()) {
event.setCancelled(true);
}
@@ -3994,7 +3977,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
// Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) {
event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);
@@ -4005,7 +3988,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
// Cancel collider entity packet
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(id);
BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByColliderEntityId(id);
if (furniture != null) {
event.setCancelled(true);
user.entityPacketHandlers().put(id, FurnitureCollisionPacketHandler.INSTANCE);

View File

@@ -1,22 +1,26 @@
package net.momirealms.craftengine.bukkit.plugin.network.handler;
import it.unimi.dsi.fastutil.ints.IntList;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
import net.momirealms.craftengine.core.plugin.network.NMSPacketEvent;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import java.util.List;
public class FurniturePacketHandler implements EntityPacketHandler {
private final List<Integer> fakeEntities;
private final int metaEntityId;
private final int[] virtualHitboxEntities;
public FurniturePacketHandler(List<Integer> fakeEntities) {
this.fakeEntities = fakeEntities;
public FurniturePacketHandler(int metaEntityId, int[] virtualHitboxEntities) {
this.virtualHitboxEntities = virtualHitboxEntities;
this.metaEntityId = metaEntityId;
}
@Override
public boolean handleEntitiesRemove(IntList entityIds) {
entityIds.addAll(this.fakeEntities);
public boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) {
((Player) user).removeTrackedFurniture(this.metaEntityId);
for (int entityId : this.virtualHitboxEntities) {
entityIds.add(entityId);
}
return true;
}

View File

@@ -4594,4 +4594,27 @@ public final class CoreReflections {
throw new ReflectionInitException("Failed to init EmptyBlockGetter$INSTANCE", e);
}
}
public static final Class<?> clazz$EntityDimensions = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.entity.EntitySize",
"world.entity.EntityDimensions"
)
);
public static final Field field$EntityType$dimensions = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityType, clazz$EntityDimensions, 0)
);
public static final Field field$EntityDimensions$width = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, float.class, 0)
);
public static final Field field$EntityDimensions$height = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, float.class, 1)
);
public static final Field field$EntityDimensions$fixed = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$EntityDimensions, boolean.class, 0)
);
}

View File

@@ -25,6 +25,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
import net.momirealms.craftengine.core.entity.data.EntityData;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.player.GameMode;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -74,6 +75,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class BukkitServerPlayer extends Player {
public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale");
public static final Key ENTITY_CULLING_VIEW_DISTANCE_SCALE = Key.of("craftengine:entity_culling_view_distance_scale");
public static final Key ENABLE_ENTITY_CULLING = Key.of("craftengine:enable_entity_culling");
private final BukkitCraftEngine plugin;
// connection state
@@ -143,9 +145,12 @@ public class BukkitServerPlayer extends Player {
private int lastStopMiningTick;
// 跟踪到的方块实体渲染器
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
private final Map<Integer, VirtualCullableObject> trackedFurniture = new ConcurrentHashMap<>();
private final EntityCulling culling;
private Vec3d firstPersonCameraVec3;
private Vec3d thirdPersonCameraVec3;
// 是否启用实体剔除
private boolean enableEntityCulling;
// 玩家眼睛所在位置
private Location eyeLocation;
@@ -174,6 +179,7 @@ public class BukkitServerPlayer extends Player {
byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY);
String locale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(SELECTED_LOCALE_KEY), PersistentDataType.STRING);
Double scale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE);
this.enableEntityCulling = Optional.ofNullable(player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENABLE_ENTITY_CULLING), PersistentDataType.BOOLEAN)).orElse(true);
this.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0));
this.selectedLocale = TranslationManager.parseLocale(locale);
this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f);
@@ -540,7 +546,7 @@ public class BukkitServerPlayer extends Player {
Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer);
unsureEyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + bukkitPlayer.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos));
}
if (Config.predictBreaking() && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) {
if (Config.predictBreaking() && this.eyeLocation != null && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) {
// if it's not destroying blocks, we do predict
if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
this.predictNextBlockToMine();
@@ -596,41 +602,60 @@ public class BukkitServerPlayer extends Player {
@Override
public void entityCullingTick() {
this.culling.restoreTokenOnTick();
if (this.firstPersonCameraVec3 == null || this.thirdPersonCameraVec3 == null) {
return;
}
boolean useRayTracing = Config.entityCullingRayTracing();
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
CullingData cullingData = cullableObject.cullable.cullingData();
if (cullingData != null) {
boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing);
// 之前可见
if (cullableObject.isShown) {
boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing);
if (!firstPersonVisible && !thirdPersonVisible) {
cullableObject.setShown(this, false);
}
}
// 之前不可见
else {
// 但是第一人称可见了
if (firstPersonVisible) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
}
cullableObject.setShown(this, true);
continue;
}
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
continue;
}
cullableObject.setShown(this, true);
}
// 仍然不可见
}
} else {
if (this.enableEntityCulling) {
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
cullEntity(useRayTracing, cullableObject);
}
for (VirtualCullableObject cullableObject : this.trackedFurniture.values()) {
cullEntity(useRayTracing, cullableObject);
}
} else {
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
cullableObject.setShown(this, true);
}
for (VirtualCullableObject cullableObject : this.trackedFurniture.values()) {
cullableObject.setShown(this, true);
}
}
}
private void cullEntity(boolean useRayTracing, VirtualCullableObject cullableObject) {
CullingData cullingData = cullableObject.cullable.cullingData();
if (cullingData != null) {
boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing);
// 之前可见
if (cullableObject.isShown) {
boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing);
if (!firstPersonVisible && !thirdPersonVisible) {
cullableObject.setShown(this, false);
}
}
// 之前不可见
else {
// 但是第一人称可见了
if (firstPersonVisible) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
return;
}
cullableObject.setShown(this, true);
return;
}
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
// 下次再说
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
return;
}
cullableObject.setShown(this, true);
}
// 仍然不可见
}
} else {
cullableObject.setShown(this, true);
}
}
@@ -1341,6 +1366,17 @@ public class BukkitServerPlayer extends Player {
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value);
}
@Override
public void setEnableEntityCulling(boolean enable) {
this.enableEntityCulling = enable;
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENABLE_ENTITY_CULLING), PersistentDataType.BOOLEAN, enable);
}
@Override
public boolean enableEntityCulling() {
return enableEntityCulling;
}
@Override
public void giveExperiencePoints(int xpPoints) {
platformPlayer().giveExp(xpPoints);
@@ -1406,6 +1442,24 @@ public class BukkitServerPlayer extends Player {
this.trackedBlockEntityRenderers.clear();
}
@Override
public void addTrackedFurniture(int entityId, Furniture furniture) {
this.trackedFurniture.put(entityId, new VirtualCullableObject(furniture));
}
@Override
public void removeTrackedFurniture(int entityId) {
VirtualCullableObject remove = this.trackedFurniture.remove(entityId);
if (remove != null && remove.isShown()) {
remove.cullable().hide(this);
}
}
@Override
public void clearTrackedFurniture() {
this.trackedFurniture.clear();
}
@Override
public WorldPosition eyePosition() {
return LocationUtils.toWorldPosition(this.getEyeLocation());

View File

@@ -101,6 +101,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
injectChunkGenerator(ceWorld);
for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, false);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
}
ceWorld.setTicking(true);
} catch (Exception e) {
@@ -154,6 +158,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
CEWorld ceWorld = this.worlds.get(uuid);
for (Chunk chunk : world.getLoadedChunks()) {
handleChunkLoad(ceWorld, chunk, true);
CEChunk loadedChunk = ceWorld.getChunkAtIfLoaded(chunk.getChunkKey());
if (loadedChunk != null) {
loadedChunk.setEntitiesLoaded(true);
}
}
ceWorld.setTicking(true);
} else {

View File

@@ -135,6 +135,12 @@ set_entity_view_distance_scale:
usage:
- /ce feature entity-view-distance-scale set
toggle_entity_culling:
enable: true
permission: ce.command.admin.toggle_entity_culling
usage:
- /ce feature toggle-entity-culling
# Debug commands
debug_set_block:
enable: true

View File

@@ -558,7 +558,7 @@ chunk-system:
client-optimization:
# Requires a restart to fully apply.
entity-culling:
enable: true
enable: false
# Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure.
ray-tracing: true
# Cull entities based on distance

View File

@@ -6,22 +6,23 @@ items:
model: minecraft:item/custom/bench
behavior:
type: furniture_item
rules:
ground:
rotation: four
alignment: center
furniture:
settings:
item: default:bench
sounds:
break: minecraft:block.bamboo_wood.break
place: minecraft:block.bamboo_wood.place
placement:
variants:
ground:
loot-spawn-offset: 0.5,0.5,0
rules:
rotation: FOUR
alignment: CENTER
elements:
- item: default:bench
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0.5,0,0
translation: 0,0.5,0
shadow-radius: 1

View File

@@ -7,15 +7,28 @@ items:
behavior:
type: furniture_item
furniture: default:flower_basket
rules:
ground:
rotation: any
alignment: any
wall:
rotation: any
alignment: any
ceiling:
rotation: any
alignment: any
default:flower_basket_ground:
material: nether_brick
model: minecraft:item/custom/flower_basket_ground
item-name: <!i><l10n:item.flower_basket>
default:flower_basket_wall:
material: nether_brick
model: minecraft:item/custom/flower_basket_wall
item-name: <!i><l10n:item.flower_basket>
default:flower_basket_ceiling:
material: nether_brick
model: minecraft:item/custom/flower_basket_ceiling
item-name: <!i><l10n:item.flower_basket>
furniture:
default:flower_basket:
settings:
@@ -27,15 +40,12 @@ furniture:
template: default:loot_table/furniture
arguments:
item: default:flower_basket
placement:
variants:
ground:
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:flower_basket_ground
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,0,0
translation: 0,0.5,0
shadow-radius: 0.5
@@ -50,12 +60,10 @@ furniture:
height: 0.5
interactive: true
wall:
rules:
alignment: ANY
elements:
- item: default:flower_basket_wall
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,0,0.2
translation: 0,0,0
hitboxes:
@@ -76,13 +84,10 @@ furniture:
height: 0.75
interactive: true
ceiling:
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:flower_basket_ceiling
display-transform: NONE
billboard: FIXED
display-transform: none
billboard: fixed
position: 0,-0.46,0
translation: 0,0,0
hitboxes:

View File

@@ -6,18 +6,19 @@ items:
model: minecraft:item/custom/wooden_chair
behavior:
type: furniture_item
rules:
ground:
rotation: any
alignment: any
furniture:
settings:
item: default:wooden_chair
sounds:
break: minecraft:block.bamboo_wood.break
place: minecraft:block.bamboo_wood.place
placement:
variants:
ground:
loot-spawn-offset: 0,0.4,0
rules:
rotation: ANY
alignment: ANY
elements:
- item: default:wooden_chair
display-transform: NONE
@@ -26,14 +27,25 @@ items:
billboard: FIXED
translation: 0,0.5,0
hitboxes:
- position: 0,0,0
type: interaction
blocks-building: true
width: 0.7
height: 1.2
interactive: true
seats:
- 0,0,-0.1 0
$$>=1.20.5:
- position: 0,0,0
type: custom
entity-type: slime
invisible: true
can-use-item-on: true
blocks-building: true
seats:
- 0,0,-0.1 0
$$fallback:
- position: 0,0,0
type: interaction
blocks-building: true
invisible: true
width: 0.7
height: 1.2
interactive: true
seats:
- 0,0,-0.1 0
loot:
template: default:loot_table/furniture
arguments:

View File

@@ -160,8 +160,8 @@ warning.config.sound.missing_name: "<yellow>Problem in Datei <arg:0> gefunden -
warning.config.jukebox_song.duplicate: "<yellow>Problem in Datei <arg:0> gefunden - Doppelter Jukebox-Song '<arg:1>'. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problem in Datei <arg:0> gefunden - Beim Jukebox-Song '<arg:1>' fehlt das erforderliche 'sound'-Argument.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problem in Datei <arg:0> gefunden - Doppeltes Furniture '<arg:1>'. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'placement'-Argument.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'item'-Argument für eines seiner Elemente.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'variants'-Argument.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problem in Datei <arg:0> gefunden - Beim Furniture '<arg:1>' fehlt das erforderliche 'item'-Argument für eines seiner Elemente.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet einen unbekannten Einstellungs-Typ '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet einen ungültigen Hitbox-Typ '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problem in Datei <arg:0> gefunden - Das Furniture '<arg:1>' verwendet eine benutzerdefinierte Hitbox mit ungültigem Entity-Typ '<arg:2>'.</yellow>"

View File

@@ -72,9 +72,10 @@ command.upload.on_progress: "<white>Started uploading progress. Check the consol
command.send_resource_pack.success.single: "<white>Sent resource pack to <arg:0>.</white>"
command.send_resource_pack.success.multiple: "<white>Send resource packs to <arg:0> players.</white>"
command.locale.set.failure: "<red>Invalid locale format: <arg:0></red>"
command.locale.set.success: "<white>Updated selected locale to <arg:0> for <arg:1></white>"
command.locale.set.success: "<white>Selected locale has been set to <arg:0> for <arg:1></white>"
command.locale.unset.success: "<white>Cleared selected locale for <arg:0></white>"
command.entity_view_distance_scale.set.success: "<white>Updated entity view distance scale to <arg:0> for <arg:1></white>"
command.entity_view_distance_scale.set.success: "<white>Entity view distance scale updated to <arg:0> for <arg:1></white>"
command.entity_culling.toggle.success: "<white>Entity culling status updated to <arg:0> for <arg:1></white>"
warning.network.resource_pack.unverified_uuid: "<yellow>Player <arg:0> is attempting to request a resource pack using a UUID (<arg:1>) that is not authenticated by the server.</yellow>"
warning.config.pack.duplicated_files: "<red>Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section.</red>"
warning.config.yaml.duplicated_key: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2>, this might cause unexpected results.</red>"
@@ -189,8 +190,9 @@ warning.config.sound.missing_name: "<yellow>Issue found in file <arg:0> - The so
warning.config.jukebox_song.duplicate: "<yellow>Issue found in file <arg:0> - Duplicated jukebox song '<arg:1>'. Please check if there is the same configuration in other files.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Issue found in file <arg:0> - The jukebox song '<arg:1>' is missing the required 'sound' argument.</yellow>"
warning.config.furniture.duplicate: "<yellow>Issue found in file <arg:0> - Duplicated furniture '<arg:1>'. Please check if there is the same configuration in other files.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'placement' argument.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'item' argument for one of its elements.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'variants' argument.</yellow>"
warning.config.furniture.element.invalid_type: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an invalid element type '<arg:2>'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'item' argument for 'item_display' element.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an unknown setting type '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using an invalid hitbox type '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is using a custom hitbox with invalid entity type '<arg:2>'.</yellow>"

View File

@@ -111,8 +111,8 @@ warning.config.sound.missing_name: "<yellow>Problema encontrado en el archivo <a
warning.config.jukebox_song.duplicate: "<yellow>Problema encontrado en el archivo <arg:0> - Canción de tocadiscos duplicada '<arg:1>'. Verifica si hay la misma configuración en otros archivos.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problema encontrado en el archivo <arg:0> - La canción de tocadiscos '<arg:1>' carece del argumento requerido 'sound'.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problema encontrado en el archivo <arg:0> - Mueble duplicado '<arg:1>'. Verifica si hay la misma configuración en otros archivos.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'placement'.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'item' para uno de sus elementos.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'variants'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' carece del argumento requerido 'item' para uno de sus elementos.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un tipo de configuración desconocido '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un tipo de hitbox inválido '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problema encontrado en el archivo <arg:0> - El mueble '<arg:1>' está usando un hitbox personalizado con un tipo de entidad inválido '<arg:2>'.</yellow>"

View File

@@ -175,8 +175,8 @@ warning.config.sound.missing_name: "<yellow>Problème trouvé dans le fichier <a
warning.config.jukebox_song.duplicate: "<yellow>Problème trouvé dans le fichier <arg:0> - Chanson de jukebox dupliquée '<arg:1>'. Vérifiez sil existe la même configuration dans dautres fichiers.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Problème trouvé dans le fichier <arg:0> - La chanson de jukebox '<arg:1>' manque largument obligatoire 'sound'.</yellow>"
warning.config.furniture.duplicate: "<yellow>Problème trouvé dans le fichier <arg:0> - Meuble dupliqué '<arg:1>'. Vérifiez sil existe la même configuration dans dautres fichiers.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'placement'.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'item' pour un de ses éléments.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'variants'.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' manque largument obligatoire 'item' pour un de ses éléments.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un type de paramètre inconnu '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un type de hitbox invalide '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Problème trouvé dans le fichier <arg:0> - Le meuble '<arg:1>' utilise un hitbox personnalisé avec un type dentité invalide '<arg:2>'.</yellow>"

View File

@@ -148,8 +148,8 @@ warning.config.sound.missing_name: "<yellow>Проблема найдена в
warning.config.jukebox_song.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная песня музыкального автомата '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>Проблема найдена в файле <arg:0> - В песне музыкального автомата '<arg:1>' отсутствует необходимый 'sound' аргумент.</yellow>"
warning.config.furniture.duplicate: "<yellow>Проблема найдена в файле <arg:0> - Дублированная мебель '<arg:1>'. Проверьте, есть ли такая же конфигурация в других файлах.</yellow>"
warning.config.furniture.missing_placement: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'placement' аргумент.</yellow>"
warning.config.furniture.element.missing_item: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'item' аргумент для одного из его элементов.</yellow>"
warning.config.furniture.missing_variants: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'variants' аргумент.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>Проблема найдена в файле <arg:0> - В мебели '<arg:1>' отсутствует необходимый 'item' аргумент для одного из его элементов.</yellow>"
warning.config.furniture.settings.unknown: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует неизвестный тип настройки '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует недопустимый тип хитбокса '<arg:2>'.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>Проблема найдена в файле <arg:0> - Мебель '<arg:1>' использует пользовательский хитбокс с недопустимым типом сущности '<arg:2>'.</yellow>"

View File

@@ -110,8 +110,8 @@ warning.config.sound.missing_name: "<yellow><arg:0> dosyasında sorun bulundu -
warning.config.jukebox_song.duplicate: "<yellow><arg:0> dosyasında sorun bulundu - Yinelenen müzik çalar şarkısı '<arg:1>'. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin.</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' müzik çalar şarkısı gerekli 'sound' argümanı eksik.</yellow>"
warning.config.furniture.duplicate: "<yellow><arg:0> dosyasında sorun bulundu - Yinelenen mobilya '<arg:1>'. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin.</yellow>"
warning.config.furniture.missing_placement: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası gerekli 'placement' argümanı eksik.</yellow>"
warning.config.furniture.element.missing_item: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik.</yellow>"
warning.config.furniture.missing_variants: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası gerekli 'variants' argümanı eksik.</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik.</yellow>"
warning.config.furniture.settings.unknown: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası bilinmeyen bir ayar türü '<arg:2>' kullanıyor.</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası geçersiz bir hitbox türü '<arg:2>' kullanıyor.</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow><arg:0> dosyasında sorun bulundu - '<arg:1>' mobilyası, geçersiz varlık türü '<arg:2>' olan özel bir hitbox kullanıyor.</yellow>"

View File

@@ -183,8 +183,8 @@ warning.config.sound.missing_name: "<yellow>在文件 <arg:0> 发现问题 - 音
warning.config.jukebox_song.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的唱片机歌曲 '<arg:1>' 请检查其他文件中是否存在相同配置</yellow>"
warning.config.jukebox_song.missing_sound: "<yellow>在文件 <arg:0> 发现问题 - 唱片机歌曲 '<arg:1>' 缺少必需的 'sound' 参数</yellow>"
warning.config.furniture.duplicate: "<yellow>在文件 <arg:0> 发现问题 - 重复的家具 '<arg:1>' 请检查其他文件中是否存在相同配置</yellow>"
warning.config.furniture.missing_placement: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 缺少必需的 'placement' 参数</yellow>"
warning.config.furniture.element.missing_item: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的某个元素缺少必需的 'item' 参数</yellow>"
warning.config.furniture.missing_variants: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 缺少必需的 'variants' 参数</yellow>"
warning.config.furniture.element.item_display.missing_item: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的 'item_display' 元素缺少必需的 'item' 参数</yellow>"
warning.config.furniture.settings.unknown: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 使用了未知的设置类型 '<arg:2>'</yellow>"
warning.config.furniture.hitbox.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 使用了无效的碰撞箱类型 '<arg:2>'</yellow>"
warning.config.furniture.hitbox.custom.invalid_entity: "<yellow>在文件 <arg:0> 发现问题 - 家具 '<arg:1>' 的自定义碰撞箱使用了无效的实体类型 '<arg:2>'</yellow>"

View File

@@ -711,12 +711,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
private CullingData parseCullingData(Object arguments) {
if (arguments instanceof Boolean b && !b) {
return null;
}
if (!(arguments instanceof Map)) {
return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
}
if (arguments instanceof Boolean b && !b) return null;
if (!(arguments instanceof Map)) return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
return new CullingData(
ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"),

View File

@@ -41,6 +41,7 @@ public class BlockSettings {
float friction = 0.6f;
float speedFactor = 1f;
float jumpFactor = 1f;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private BlockSettings() {}
@@ -107,9 +108,29 @@ public class BlockSettings {
newSettings.speedFactor = settings.speedFactor;
newSettings.jumpFactor = settings.jumpFactor;
newSettings.friction = settings.friction;
newSettings.customData = new IdentityHashMap<>(settings.customData);
return newSettings;
}
@SuppressWarnings("unchecked")
public <T> T getCustomData(CustomDataType<T> type) {
return (T) this.customData.get(type);
}
public void clearCustomData() {
this.customData.clear();
}
@Nullable
@SuppressWarnings("unchecked")
public <T> T removeCustomData(CustomDataType<?> type) {
return (T) this.customData.remove(type);
}
public <T> void addCustomData(CustomDataType<T> key, T value) {
this.customData.put(key, value);
}
public Set<Key> tags() {
return tags;
}
@@ -542,7 +563,7 @@ public class BlockSettings {
}));
}
private static void registerFactory(String id, Modifier.Factory factory) {
public static void registerFactory(String id, Modifier.Factory factory) {
FACTORIES.put(id, factory);
}
}

View File

@@ -24,7 +24,7 @@ public abstract class BlockEntity {
this.type = type;
}
public final CompoundTag saveAsTag() {
public CompoundTag saveAsTag() {
CompoundTag tag = new CompoundTag();
this.saveId(tag);
this.savePos(tag);

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.util.Key;
public final class BlockEntityTypeKeys {
private BlockEntityTypeKeys() {}
public static final Key INACTIVE = Key.of("craftengine:inactive");
public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle");

View File

@@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceKey;
public abstract class BlockEntityTypes {
public static final BlockEntityType<InactiveBlockEntity> INACTIVE = register(BlockEntityTypeKeys.INACTIVE);
public static <T extends BlockEntity> BlockEntityType<T> register(Key id) {
BlockEntityType<T> type = new BlockEntityType<>(id);

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftengine.core.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.sparrow.nbt.CompoundTag;
public class InactiveBlockEntity extends BlockEntity {
private final CompoundTag tag;
public InactiveBlockEntity(BlockPos pos,
ImmutableBlockState blockState,
CompoundTag tag) {
super(BlockEntityTypes.INACTIVE, pos, blockState);
this.tag = tag;
}
@Override
public CompoundTag saveAsTag() {
return this.tag;
}
}

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.block.entity.render.element;
import java.util.Map;
@FunctionalInterface
public interface BlockEntityElementConfigFactory {
public interface BlockEntityElementConfigFactory<E extends BlockEntityElement> {
<E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> args);
BlockEntityElementConfig<E> create(Map<String, Object> args);
}

View File

@@ -15,14 +15,15 @@ public abstract class BlockEntityElementConfigs {
public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display");
public static final Key ITEM = Key.of("craftengine:item");
public static void register(Key key, BlockEntityElementConfigFactory type) {
((WritableRegistry<BlockEntityElementConfigFactory>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
public static void register(Key key, BlockEntityElementConfigFactory<?> type) {
((WritableRegistry<BlockEntityElementConfigFactory<?>>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
.register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type);
}
public static <E extends BlockEntityElement> BlockEntityElementConfig<E> fromMap(Map<String, Object> arguments) {
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY);
BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
@SuppressWarnings("unchecked")
BlockEntityElementConfigFactory<E> factory = (BlockEntityElementConfigFactory<E>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
if (factory == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString());
}

View File

@@ -11,6 +11,8 @@ import java.util.UUID;
public interface Entity {
Key type();
boolean isValid();
double x();
double y();

View File

@@ -12,14 +12,14 @@ public interface EntityData<T> {
Object entityDataAccessor();
Object create(Object entityDataAccessor, Object value);
Object create(Object entityDataAccessor, T value);
default Object createEntityDataIfNotDefaultValue(T value) {
if (defaultValue().equals(value)) return null;
return create(entityDataAccessor(), value);
}
default Object createEntityData(Object value) {
default Object createEntityData(T value) {
return create(entityDataAccessor(), value);
}

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum Billboard {
FIXED(0),

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum ItemDisplayContext {
NONE(0),

View File

@@ -1,89 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public abstract class AbstractCustomFurniture implements CustomFurniture {
private final Key id;
private final FurnitureSettings settings;
private final Map<AnchorType, Placement> placements;
private final Map<EventTrigger, List<Function<Context>>> events;
@Nullable
private final LootTable<?> lootTable;
private final AnchorType anyType;
protected AbstractCustomFurniture(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<AnchorType, Placement> placements,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@Nullable LootTable<?> lootTable) {
this.id = id;
this.settings = settings;
this.placements = placements;
this.lootTable = lootTable;
this.events = events;
this.anyType = placements.keySet().stream().findFirst().orElse(null);
}
@Override
public void execute(Context context, EventTrigger trigger) {
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
function.run(context);
}
}
@Override
public Key id() {
return this.id;
}
@Override
public Map<AnchorType, Placement> placements() {
return this.placements;
}
@Override
public FurnitureSettings settings() {
return this.settings;
}
@Override
public @Nullable LootTable<?> lootTable() {
return this.lootTable;
}
@Override
public AnchorType getAnyAnchorType() {
return this.anyType;
}
@Override
public boolean isAllowedPlacement(AnchorType anchorType) {
return this.placements.containsKey(anchorType);
}
@Override
public Placement getPlacement(AnchorType anchorType) {
return this.placements.get(anchorType);
}
@Override
public Placement getValidPlacement(AnchorType anchorType) {
Placement placement = this.placements.get(anchorType);
if (placement == null) {
return this.placements.get(getAnyAnchorType());
}
return placement;
}
}

View File

@@ -1,92 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.util.Key;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public abstract class AbstractFurnitureElement implements FurnitureElement {
private final Key item;
private final Billboard billboard;
private final ItemDisplayContext transform;
private final Vector3f scale;
private final Vector3f translation;
private final Vector3f position;
private final Quaternionf rotation;
private final boolean applyDyedColor;
private final float shadowRadius;
private final float shadowStrength;
public AbstractFurnitureElement(Key item,
Billboard billboard,
ItemDisplayContext transform,
Vector3f scale,
Vector3f translation,
Vector3f position,
Quaternionf rotation,
float shadowRadius,
float shadowStrength,
boolean applyDyedColor) {
this.billboard = billboard;
this.transform = transform;
this.scale = scale;
this.translation = translation;
this.item = item;
this.rotation = rotation;
this.position = position;
this.applyDyedColor = applyDyedColor;
this.shadowRadius = shadowRadius;
this.shadowStrength = shadowStrength;
}
@Override
public float shadowRadius() {
return shadowRadius;
}
@Override
public float shadowStrength() {
return shadowStrength;
}
@Override
public boolean applyDyedColor() {
return applyDyedColor;
}
@Override
public Quaternionf rotation() {
return rotation;
}
@Override
public Key item() {
return item;
}
@Override
public Billboard billboard() {
return billboard;
}
@Override
public ItemDisplayContext transform() {
return transform;
}
@Override
public Vector3f scale() {
return scale;
}
@Override
public Vector3f translation() {
return translation;
}
@Override
public Vector3f position() {
return position;
}
}

View File

@@ -1,19 +1,25 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigs;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxTypes;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.PendingConfigSection;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.incendo.cloud.suggestion.Suggestion;
import org.joml.Vector3f;
@@ -21,7 +27,7 @@ import java.nio.file.Path;
import java.util.*;
public abstract class AbstractFurnitureManager implements FurnitureManager {
protected final Map<Key, CustomFurniture> byId = new HashMap<>();
protected final Map<Key, FurnitureConfig> byId = new HashMap<>();
private final CraftEngine plugin;
private final FurnitureParser furnitureParser;
// Cached command suggestions
@@ -56,12 +62,12 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
}
@Override
public Optional<CustomFurniture> furnitureById(Key id) {
public Optional<FurnitureConfig> furnitureById(Key id) {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public Map<Key, CustomFurniture> loadedFurniture() {
public Map<Key, FurnitureConfig> loadedFurniture() {
return Collections.unmodifiableMap(this.byId);
}
@@ -70,11 +76,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
this.byId.clear();
}
protected abstract HitBoxConfig defaultHitBox();
protected abstract FurnitureElement.Builder furnitureElementBuilder();
protected abstract CustomFurniture.Builder furnitureBuilder();
protected abstract FurnitureHitBoxConfig<?> defaultHitBox();
public class FurnitureParser extends IdSectionConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
@@ -107,91 +109,90 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
return LoadingSequence.FURNITURE;
}
@SuppressWarnings("unchecked")
@Override
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
if (AbstractFurnitureManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate");
}
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
Object placementObj = section.get("placement");
Map<String, Object> placementMap = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(placementObj, "warning.config.furniture.missing_placement"), false);
if (placementMap.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.furniture.missing_placement");
}
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
// anchor type
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), false);
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
// furniture display elements
List<FurnitureElement> elements = new ArrayList<>();
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
for (Map<String, Object> element : elementConfigs) {
FurnitureElement furnitureElement = furnitureElementBuilder()
.item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item")))
.applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color"))
.billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED))
.transform(ResourceConfigUtils.getOrDefault(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
.rotation(ResourceConfigUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"))
.shadowRadius(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-radius", 0f), "shadow-radius"))
.shadowStrength(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-strength", 1f), "shadow-strength"))
.build();
elements.add(furnitureElement);
}
// external model providers
Map<String, Object> variantsMap = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "variants", "placement", "variant"), "warning.config.furniture.missing_variants"), "variants");
if (variantsMap.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.furniture.missing_variants");
}
Map<String, FurnitureVariant> variants = new HashMap<>();
for (Map.Entry<String, Object> e0 : variantsMap.entrySet()) {
String variantName = e0.getKey();
Map<String, Object> variantArguments = ResourceConfigUtils.getAsMap(e0.getValue(), variantName);
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(variantArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
List<FurnitureElementConfig<?>> elements = ResourceConfigUtils.parseConfigAsList(variantArguments.get("elements"), FurnitureElementConfigs::fromMap);
// fixme 外部模型不应该在这
Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", placementArguments.get("model-engine").toString()));
} else if (placementArguments.containsKey("better-model")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", placementArguments.get("better-model").toString()));
if (variantArguments.containsKey("model-engine")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", variantArguments.get("model-engine").toString()));
} else if (variantArguments.containsKey("better-model")) {
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", variantArguments.get("better-model").toString()));
} else {
externalModel = Optional.empty();
}
// add hitboxes
List<HitBoxConfig> hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap);
List<FurnitureHitBoxConfig<?>> hitboxes = ResourceConfigUtils.parseConfigAsList(variantArguments.get("hitboxes"), FurnitureHitBoxTypes::fromMap);
if (hitboxes.isEmpty() && externalModel.isEmpty()) {
hitboxes = List.of(defaultHitBox());
}
// rules
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
if (ruleSection != null) {
placements.put(anchorType, new CustomFurniture.Placement(
anchorType,
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBoxConfig[0]),
ResourceConfigUtils.getOrDefault(ruleSection.get("rotation"), o -> RotationRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), RotationRule.ANY),
ResourceConfigUtils.getOrDefault(ruleSection.get("alignment"), o -> AlignmentRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), AlignmentRule.CENTER),
externalModel,
optionalLootSpawnOffset
));
} else {
placements.put(anchorType, new CustomFurniture.Placement(
anchorType,
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBoxConfig[0]),
RotationRule.ANY,
AlignmentRule.CENTER,
externalModel,
optionalLootSpawnOffset
));
List<AABB> aabbs = new ArrayList<>();
for (FurnitureHitBoxConfig<?> hitBox : hitboxes) {
hitBox.collectBoundingBox(aabbs::add);
}
double minX = 0;
double minY = 0;
double minZ = 0;
double maxX = 0;
double maxY = 0;
double maxZ = 0;
for (AABB aabb : aabbs) {
minX = Math.min(minX, aabb.minX);
minY = Math.min(minY, aabb.minY);
minZ = Math.min(minZ, aabb.minZ);
maxX = Math.max(maxX, aabb.maxX);
maxY = Math.max(maxY, aabb.maxY);
maxZ = Math.max(maxZ, aabb.maxZ);
}
AABB maxAABB = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
variants.put(variantName, new FurnitureVariant(
variantName,
parseCullingData(section.get("entity-culling"), maxAABB),
elements.toArray(new FurnitureElementConfig[0]),
hitboxes.toArray(new FurnitureHitBoxConfig[0]),
externalModel,
optionalLootSpawnOffset
));
}
CustomFurniture furniture = furnitureBuilder()
FurnitureConfig furniture = FurnitureConfig.builder()
.id(id)
.settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)))
.placement(placements)
.variants(variants)
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
.build();
AbstractFurnitureManager.this.byId.put(id, furniture);
}
private CullingData parseCullingData(Object arguments, AABB maxHitbox) {
if (arguments instanceof Boolean b && !b)
return null;
if (!(arguments instanceof Map))
return new CullingData(maxHitbox, Config.entityCullingViewDistance(), 0.5, true);
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
return new CullingData(
ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", maxHitbox), "aabb"),
ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"),
ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.5), "aabb-expansion"),
ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing")
);
}
}
}

View File

@@ -1,20 +1,26 @@
package net.momirealms.craftengine.core.entity.furniture;
public enum AnchorType {
GROUND(0),
WALL(1),
CEILING(2);
GROUND(0, "ground"),
WALL(1, "wall"),
CEILING(2, "ceiling");
private final int id;
private final String variantName;
AnchorType(int id) {
AnchorType(int id, String variantName) {
this.id = id;
this.variantName = variantName;
}
public int getId() {
return id;
}
public String variantName() {
return variantName;
}
public static AnchorType byId(int id) {
return values()[id];
}

View File

@@ -1,60 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.List;
import java.util.Map;
import java.util.Optional;
// TODO 家具的设计存在问题。家具也应该存在不同的状态,而不是根据放置规则直接决定状态类型
public interface CustomFurniture {
void execute(Context context, EventTrigger trigger);
Key id();
Map<AnchorType, Placement> placements();
FurnitureSettings settings();
@Nullable
LootTable<?> lootTable();
AnchorType getAnyAnchorType();
boolean isAllowedPlacement(AnchorType anchorType);
Placement getPlacement(AnchorType anchorType);
Placement getValidPlacement(AnchorType anchorType);
interface Builder {
Builder id(Key id);
Builder placement(Map<AnchorType, Placement> placements);
Builder settings(FurnitureSettings settings);
Builder lootTable(LootTable<?> lootTable);
Builder events(Map<EventTrigger, List<Function<Context>>> events);
CustomFurniture build();
}
record Placement(AnchorType anchorType,
FurnitureElement[] elements,
HitBoxConfig[] hitBoxConfigs,
RotationRule rotationRule,
AlignmentRule alignmentRule,
Optional<ExternalModel> externalModel,
Optional<Vector3f> dropOffset) {
}
}

View File

@@ -1,48 +1,218 @@
package net.momirealms.craftengine.core.entity.furniture;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.entity.Entity;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElement;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.world.Cullable;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.NotNull;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.Optional;
import java.util.UUID;
public interface Furniture {
void initializeColliders();
public abstract class Furniture implements Cullable {
public final FurnitureConfig config;
public final FurnitureDataAccessor dataAccessor;
public final Entity metaDataEntity;
WorldPosition position();
protected CullingData cullingData;
protected FurnitureVariant currentVariant;
protected FurnitureElement[] elements;
protected Collider[] colliders;
protected FurnitureHitBox[] hitboxes;
protected Int2ObjectMap<FurnitureHitBox> hitboxMap;
protected int[] virtualEntityIds;
protected int[] colliderEntityIds;
boolean isValid();
private boolean hasExternalModel;
void destroy();
protected Furniture(Entity metaDataEntity, FurnitureDataAccessor data, FurnitureConfig config) {
this.config = config;
this.dataAccessor = data;
this.metaDataEntity = metaDataEntity;
this.setVariant(config.getVariant(data));
}
void destroyColliders();
public Entity metaDataEntity() {
return this.metaDataEntity;
}
void destroySeats();
public FurnitureVariant getCurrentVariant() {
return this.currentVariant;
}
UUID uuid();
public void setVariant(FurnitureVariant variant) {
this.currentVariant = variant;
this.hitboxMap = new Int2ObjectOpenHashMap<>();
// 初始化家具元素
IntList virtualEntityIds = new IntArrayList();
FurnitureElementConfig<?>[] elementConfigs = variant.elementConfigs();
this.elements = new FurnitureElement[elementConfigs.length];
for (int i = 0; i < elementConfigs.length; i++) {
FurnitureElement element = elementConfigs[i].create(this);
this.elements[i] = element;
element.collectVirtualEntityId(virtualEntityIds::addLast);
}
// 初始化碰撞箱
FurnitureHitBoxConfig<?>[] furnitureHitBoxConfigs = variant.hitBoxConfigs();
ObjectArrayList<Collider> colliders = new ObjectArrayList<>(furnitureHitBoxConfigs.length);
this.hitboxes = new FurnitureHitBox[furnitureHitBoxConfigs.length];
for (int i = 0; i < furnitureHitBoxConfigs.length; i++) {
FurnitureHitBox hitbox = furnitureHitBoxConfigs[i].create(this);
this.hitboxes[i] = hitbox;
for (FurnitureHitboxPart part : hitbox.parts()) {
this.hitboxMap.put(part.entityId(), hitbox);
}
hitbox.collectVirtualEntityId(virtualEntityIds::addLast);
colliders.addAll(hitbox.colliders());
}
// 虚拟碰撞箱的实体id
this.virtualEntityIds = virtualEntityIds.toIntArray();
this.colliders = colliders.toArray(new Collider[0]);
this.colliderEntityIds = colliders.stream().mapToInt(Collider::entityId).toArray();
this.cullingData = createCullingData(variant.cullingData());
// 外部模型
Optional<ExternalModel> externalModel = variant.externalModel();
if (externalModel.isPresent()) {
this.hasExternalModel = true;
try {
externalModel.get().bindModel((AbstractEntity) this.metaDataEntity);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
}
} else {
this.hasExternalModel = false;
}
}
int baseEntityId();
private CullingData createCullingData(CullingData parent) {
if (parent == null) return null;
AABB aabb = parent.aabb;
WorldPosition position = position();
Vec3d pos1 = getRelativePosition(position, new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ));
Vec3d pos2 = getRelativePosition(position, new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ));
return new CullingData(new AABB(pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z), parent.maxDistance, parent.aabbExpansion, parent.rayTracing);
}
@Nullable
HitBox hitBoxByEntityId(int id);
public FurnitureHitBox hitboxByEntityId(int entityId) {
return this.hitboxMap.get(entityId);
}
@Nullable HitBoxPart hitBoxPartByEntityId(int id);
@Nullable
@Override
public CullingData cullingData() {
return this.cullingData;
}
@NotNull
AnchorType anchorType();
public Key id() {
return this.config.id();
}
@NotNull
Key id();
// 会发给玩家的包
public int[] virtualEntityIds() {
return this.virtualEntityIds;
}
@NotNull
CustomFurniture config();
public int[] colliderEntityIds() {
return colliderEntityIds;
}
boolean hasExternalModel();
public UUID uuid() {
return this.metaDataEntity.uuid();
}
FurnitureExtraData extraData();
@Override
public void show(Player player) {
for (FurnitureElement element : this.elements) {
element.show(player);
}
for (FurnitureHitBox hitbox : this.hitboxes) {
hitbox.show(player);
}
}
void setExtraData(FurnitureExtraData extraData);
@Override
public void hide(Player player) {
for (FurnitureElement element : this.elements) {
element.hide(player);
}
for (FurnitureHitBox hitbox : this.hitboxes) {
hitbox.hide(player);
}
}
void save();
public abstract void addCollidersToWorld();
public void destroySeats() {
for (FurnitureHitBox hitbox : this.hitboxes) {
for (Seat<FurnitureHitBox> seat : hitbox.seats()) {
seat.destroy();
}
}
}
public boolean isValid() {
return this.metaDataEntity.isValid();
}
public abstract void destroy();
public FurnitureConfig config() {
return this.config;
}
public FurnitureDataAccessor dataAccessor() {
return this.dataAccessor;
}
public Collider[] colliders() {
return this.colliders;
}
public WorldPosition position() {
return this.metaDataEntity.position();
}
public int entityId() {
return this.metaDataEntity.entityID();
}
public boolean hasExternalModel() {
return hasExternalModel;
}
public Vec3d getRelativePosition(Vector3f position) {
return getRelativePosition(this.position(), position);
}
public static Vec3d getRelativePosition(WorldPosition location, Vector3f position) {
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - location.yRot()), 0f).conjugate();
Vector3f offset = conjugated.transform(new Vector3f(position));
return new Vec3d(location.x + offset.x, location.y + offset.y, location.z - offset.z);
}
public World world() {
return this.metaDataEntity.world();
}
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.util.Color;
public record FurnitureColorSource(Color dyedColor, int[] fireworkColors) {
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
public interface FurnitureConfig {
void execute(Context context, EventTrigger trigger);
Key id();
FurnitureSettings settings();
@Nullable
LootTable<?> lootTable();
Map<String, FurnitureVariant> variants();
default FurnitureVariant anyVariant() {
return variants().values().stream().findFirst().get();
}
default String anyVariantName() {
return variants().keySet().stream().findFirst().get();
}
@Nullable
FurnitureVariant getVariant(String variantName);
@NotNull
FurnitureBehavior behavior();
@NotNull
default FurnitureVariant getVariant(FurnitureDataAccessor accessor) {
Optional<String> optionalVariant = accessor.variant();
String variantName = null;
if (optionalVariant.isPresent()) {
variantName = optionalVariant.get();
} else {
@SuppressWarnings("deprecation")
Optional<AnchorType> optionalAnchorType = accessor.anchorType();
if (optionalAnchorType.isPresent()) {
variantName = optionalAnchorType.get().name().toLowerCase(Locale.ROOT);
accessor.setVariant(variantName);
accessor.removeCustomData(FurnitureDataAccessor.ANCHOR_TYPE);
}
}
if (variantName == null) {
return anyVariant();
}
FurnitureVariant variant = getVariant(variantName);
if (variant == null) {
return anyVariant();
}
return variant;
}
static Builder builder() {
return new FurnitureConfigImpl.BuilderImpl();
}
interface Builder {
Builder id(Key id);
Builder variants(Map<String, FurnitureVariant> variants);
Builder settings(FurnitureSettings settings);
Builder lootTable(LootTable<?> lootTable);
Builder events(Map<EventTrigger, List<Function<Context>>> events);
Builder behavior(FurnitureBehavior behavior);
FurnitureConfig build();
}
}

View File

@@ -0,0 +1,129 @@
package net.momirealms.craftengine.core.entity.furniture;
import com.google.common.collect.ImmutableMap;
import net.momirealms.craftengine.core.entity.furniture.behavior.EmptyFurnitureBehavior;
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
import net.momirealms.craftengine.core.plugin.context.function.Function;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
class FurnitureConfigImpl implements FurnitureConfig {
private final Key id;
private final FurnitureSettings settings;
private final Map<String, FurnitureVariant> variants;
private final Map<EventTrigger, List<Function<Context>>> events;
private final FurnitureBehavior behavior;
@Nullable
private final LootTable<?> lootTable;
private FurnitureConfigImpl(@NotNull Key id,
@NotNull FurnitureSettings settings,
@NotNull Map<String, FurnitureVariant> variants,
@NotNull Map<EventTrigger, List<Function<Context>>> events,
@NotNull FurnitureBehavior behavior,
@Nullable LootTable<?> lootTable) {
this.id = id;
this.settings = settings;
this.variants = ImmutableMap.copyOf(variants);
this.lootTable = lootTable;
this.behavior = behavior;
this.events = events;
}
@Override
public void execute(Context context, EventTrigger trigger) {
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
function.run(context);
}
}
@Override
public Key id() {
return this.id;
}
@Override
public FurnitureSettings settings() {
return this.settings;
}
@Override
public @Nullable LootTable<?> lootTable() {
return this.lootTable;
}
@Override
public Map<String, FurnitureVariant> variants() {
return this.variants;
}
@Override
public @NotNull FurnitureBehavior behavior() {
return this.behavior;
}
@Nullable
@Override
public FurnitureVariant getVariant(String variantName) {
return this.variants.get(variantName);
}
public static class BuilderImpl implements Builder {
private Key id;
private Map<String, FurnitureVariant> variants;
private FurnitureSettings settings;
private Map<EventTrigger, List<Function<Context>>> events;
private LootTable<?> lootTable;
private FurnitureBehavior behavior = EmptyFurnitureBehavior.INSTANCE;
@Override
public FurnitureConfig build() {
return new FurnitureConfigImpl(this.id, this.settings, this.variants, this.events, this.behavior, this.lootTable);
}
@Override
public Builder id(Key id) {
this.id = id;
return this;
}
@Override
public Builder variants(Map<String, FurnitureVariant> variants) {
this.variants = variants;
return this;
}
@Override
public Builder settings(FurnitureSettings settings) {
this.settings = settings;
return this;
}
@Override
public Builder lootTable(LootTable<?> lootTable) {
this.lootTable = lootTable;
return this;
}
@Override
public Builder events(Map<EventTrigger, List<Function<Context>>> events) {
this.events = events;
return this;
}
@Override
public Builder behavior(FurnitureBehavior behavior) {
this.behavior = behavior;
return this;
}
}
}

View File

@@ -0,0 +1,139 @@
package 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.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Optional;
public class FurnitureDataAccessor {
public static final String ITEM = "item";
public static final String DYED_COLOR = "dyed_color";
public static final String FIREWORK_EXPLOSION_COLORS = "firework_explosion_colors";
public static final String VARIANT = "variant";
@ApiStatus.Obsolete
public static final String ANCHOR_TYPE = "anchor_type";
private final CompoundTag data;
public FurnitureDataAccessor(CompoundTag data) {
this.data = data == null ? new CompoundTag() : data;
}
public static FurnitureDataAccessor of(CompoundTag data) {
return new FurnitureDataAccessor(data);
}
public static FurnitureDataAccessor ofVariant(String variant) {
FurnitureDataAccessor accessor = new FurnitureDataAccessor(new CompoundTag());
accessor.setVariant(variant);
return accessor;
}
public CompoundTag copyTag() {
return this.data.copy();
}
@ApiStatus.Internal
public CompoundTag unsafeTag() {
return this.data;
}
public void addCustomData(String key, Tag value) {
this.data.put(key, value);
}
@Nullable
public Tag getCustomData(String key) {
return this.data.get(key);
}
public void removeCustomData(String key) {
this.data.remove(key);
}
public Optional<Item<?>> item() {
byte[] data = this.data.getByteArray(ITEM);
if (data == null) return Optional.empty();
try {
return Optional.of(CraftEngine.instance().itemManager().fromByteArray(data));
} catch (Exception e) {
Debugger.FURNITURE.warn(() -> "Failed to read furniture item data", e);
return Optional.empty();
}
}
public void setItem(Item<?> item) {
this.data.putByteArray(ITEM, item.toByteArray());
}
public FurnitureColorSource getColorSource() {
return new FurnitureColorSource(dyedColor().orElse(null), fireworkExplosionColors().orElse(null));
}
public Optional<int[]> fireworkExplosionColors() {
if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS));
return Optional.empty();
}
public void setFireworkExplosionColors(int[] colors) {
if (colors == null) {
this.data.remove(FIREWORK_EXPLOSION_COLORS);
return;
}
this.data.putIntArray(FIREWORK_EXPLOSION_COLORS, colors);
}
public Optional<Color> dyedColor() {
if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR)));
return Optional.empty();
}
public void setDyedColor(@Nullable Color color) {
if (color == null) {
this.data.remove(DYED_COLOR);
return;
}
this.data.putInt(DYED_COLOR, color.color());
}
public Optional<String> variant() {
return Optional.ofNullable(this.data.getString(VARIANT));
}
public void setVariant(String variant) {
this.data.putString(VARIANT, variant);
}
@SuppressWarnings("deprecation")
@ApiStatus.Obsolete
public Optional<AnchorType> anchorType() {
if (this.data.containsKey(ANCHOR_TYPE)) return Optional.of(AnchorType.byId(this.data.getInt(ANCHOR_TYPE)));
return Optional.empty();
}
@ApiStatus.Obsolete
public FurnitureDataAccessor anchorType(@SuppressWarnings("deprecation") AnchorType type) {
this.data.putInt(ANCHOR_TYPE, type.getId());
return this;
}
public static FurnitureDataAccessor fromBytes(final byte[] data) throws IOException {
return new FurnitureDataAccessor(NBT.fromBytes(data));
}
public static byte[] toBytes(final FurnitureDataAccessor data) throws IOException {
return NBT.toBytes(data.data);
}
public byte[] toBytes() throws IOException {
return toBytes(this);
}
}

View File

@@ -1,59 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.Billboard;
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.function.Consumer;
public interface FurnitureElement {
Quaternionf rotation();
Key item();
Billboard billboard();
ItemDisplayContext transform();
float shadowRadius();
float shadowStrength();
boolean applyDyedColor();
Vector3f scale();
Vector3f translation();
Vector3f position();
void initPackets(Furniture furniture, int entityId, @NotNull Quaternionf conjugated, Consumer<Object> packets);
interface Builder {
Builder item(Key item);
Builder billboard(Billboard billboard);
Builder transform(ItemDisplayContext transform);
Builder scale(Vector3f scale);
Builder translation(Vector3f translation);
Builder position(Vector3f position);
Builder rotation(Quaternionf rotation);
Builder applyDyedColor(boolean applyDyedColor);
Builder shadowStrength(float shadowStrength);
Builder shadowRadius(float shadowRadius);
FurnitureElement build();
}
}

View File

@@ -1,117 +0,0 @@
package 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.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import java.io.IOException;
import java.util.Optional;
public class FurnitureExtraData {
public static final String ITEM = "item";
public static final String DYED_COLOR = "dyed_color";
public static final String FIREWORK_EXPLOSION_COLORS = "firework_explosion_colors";
public static final String ANCHOR_TYPE = "anchor_type";
private final CompoundTag data;
public FurnitureExtraData(CompoundTag data) {
this.data = data;
}
public static FurnitureExtraData of(CompoundTag data) {
return new FurnitureExtraData(data);
}
public CompoundTag copyTag() {
return this.data.copy();
}
public CompoundTag unsafeTag() {
return this.data;
}
public Optional<Item<?>> item() {
byte[] data = this.data.getByteArray(ITEM);
if (data == null) return Optional.empty();
try {
return Optional.of(CraftEngine.instance().itemManager().fromByteArray(data));
} catch (Exception e) {
Debugger.FURNITURE.warn(() -> "Failed to read furniture item data", e);
return Optional.empty();
}
}
public Optional<int[]> fireworkExplosionColors() {
if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS));
return Optional.empty();
}
public Optional<Color> dyedColor() {
if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR)));
return Optional.empty();
}
public Optional<AnchorType> anchorType() {
if (this.data.containsKey(ANCHOR_TYPE)) return Optional.of(AnchorType.byId(this.data.getInt(ANCHOR_TYPE)));
return Optional.empty();
}
public FurnitureExtraData anchorType(AnchorType type) {
this.data.putInt(ANCHOR_TYPE, type.getId());
return this;
}
public static Builder builder() {
return new Builder();
}
public static FurnitureExtraData fromBytes(final byte[] data) throws IOException {
return new FurnitureExtraData(NBT.fromBytes(data));
}
public static byte[] toBytes(final FurnitureExtraData data) throws IOException {
return NBT.toBytes(data.data);
}
public byte[] toBytes() throws IOException {
return toBytes(this);
}
public static class Builder {
private final CompoundTag data;
public Builder() {
this.data = new CompoundTag();
}
public Builder item(Item<?> item) {
this.data.putByteArray(ITEM, item.toByteArray());
return this;
}
public Builder dyedColor(Color color) {
if (color == null) return this;
this.data.putInt(DYED_COLOR, color.color());
return this;
}
public Builder fireworkExplosionColors(int[] colors) {
if (colors == null) return this;
this.data.putIntArray(FIREWORK_EXPLOSION_COLORS, colors);
return this;
}
public Builder anchorType(AnchorType type) {
this.data.putInt(ANCHOR_TYPE, type.getId());
return this;
}
public FurnitureExtraData build() {
return new FurnitureExtraData(data);
}
}
}

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.AbstractEntity;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.util.Key;
@@ -25,22 +24,20 @@ public interface FurnitureManager extends Manageable {
Collection<Suggestion> cachedSuggestions();
Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound);
Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor extraData, boolean playSound);
Optional<CustomFurniture> furnitureById(Key id);
Optional<FurnitureConfig> furnitureById(Key id);
Map<Key, CustomFurniture> loadedFurniture();
Map<Key, FurnitureConfig> loadedFurniture();
boolean isFurnitureRealEntity(int entityId);
boolean isFurnitureMetaEntity(int entityId);
@Nullable
Furniture loadedFurnitureByRealEntityId(int entityId);
Furniture loadedFurnitureByMetaEntityId(int entityId);
@Nullable
default Furniture loadedFurnitureByRealEntity(AbstractEntity entity) {
return loadedFurnitureByRealEntityId(entity.entityID());
}
Furniture loadedFurnitureByVirtualEntityId(int entityId);
@Nullable
Furniture loadedFurnitureByEntityId(int entityId);
Furniture loadedFurnitureByColliderEntityId(int entityId);
}

View File

@@ -1,11 +1,13 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.CustomDataType;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
public class FurnitureSettings {
@@ -13,6 +15,7 @@ public class FurnitureSettings {
FurnitureSounds sounds = FurnitureSounds.EMPTY;
@Nullable
Key itemId;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private FurnitureSettings() {}
@@ -29,6 +32,7 @@ public class FurnitureSettings {
newSettings.sounds = settings.sounds;
newSettings.itemId = settings.itemId;
newSettings.minimized = settings.minimized;
newSettings.customData = new IdentityHashMap<>(settings.customData);
return newSettings;
}
@@ -45,6 +49,25 @@ public class FurnitureSettings {
return settings;
}
@SuppressWarnings("unchecked")
public <T> T getCustomData(CustomDataType<T> type) {
return (T) this.customData.get(type);
}
public void clearCustomData() {
this.customData.clear();
}
@Nullable
@SuppressWarnings("unchecked")
public <T> T removeCustomData(CustomDataType<?> type) {
return (T) this.customData.remove(type);
}
public <T> void addCustomData(CustomDataType<T> key, T value) {
this.customData.put(key, value);
}
public FurnitureSounds sounds() {
return sounds;
}
@@ -103,7 +126,7 @@ public class FurnitureSettings {
}));
}
private static void registerFactory(String id, FurnitureSettings.Modifier.Factory factory) {
public static void registerFactory(String id, FurnitureSettings.Modifier.Factory factory) {
FACTORIES.put(id, factory);
}
}

View File

@@ -0,0 +1,17 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.Optional;
public record FurnitureVariant(String name,
@Nullable CullingData cullingData,
FurnitureElementConfig<?>[] elementConfigs,
FurnitureHitBoxConfig<?>[] hitBoxConfigs,
Optional<ExternalModel> externalModel,
Optional<Vector3f> dropOffset) {
}

View File

@@ -1,19 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.seat.Seat;
import net.momirealms.craftengine.core.entity.seat.SeatOwner;
import net.momirealms.craftengine.core.world.EntityHitResult;
import net.momirealms.craftengine.core.world.Vec3d;
import java.util.Optional;
public interface HitBox extends SeatOwner {
Seat<HitBox>[] seats();
Optional<EntityHitResult> clip(Vec3d min, Vec3d max);
HitBoxPart[] parts();
HitBoxConfig config();
}

View File

@@ -1,34 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public interface HitBoxConfig {
Key type();
void initPacketsAndColliders(int[] entityId, WorldPosition position, Quaternionf conjugated,
BiConsumer<Object, Boolean> packets, Consumer<Collider> collider, Consumer<HitBoxPart> aabb);
void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs);
int[] acquireEntityIds(Supplier<Integer> entityIdSupplier);
SeatConfig[] seats();
Vector3f position();
boolean blocksBuilding();
boolean canBeHitByProjectile();
boolean canUseItemOn();
}

View File

@@ -1,8 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import java.util.Map;
public interface HitBoxConfigFactory {
HitBoxConfig create(Map<String, Object> arguments);
}

View File

@@ -1,7 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.collision.AABB;
public record HitBoxPart(int entityId, AABB aabb, Vec3d pos) {
}

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.core.entity.furniture.behavior;
public final class EmptyFurnitureBehavior implements FurnitureBehavior {
private EmptyFurnitureBehavior() {}
public static final EmptyFurnitureBehavior INSTANCE = new EmptyFurnitureBehavior();
}

View File

@@ -0,0 +1,15 @@
package net.momirealms.craftengine.core.entity.furniture.behavior;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.tick.FurnitureTicker;
public interface FurnitureBehavior {
default <T extends Furniture> FurnitureTicker<T> createSyncFurnitureTicker(T furniture) {
return null;
}
default <T extends Furniture> FurnitureTicker<T> createAsyncBlockEntityTicker(T furniture) {
return null;
}
}

View File

@@ -0,0 +1,20 @@
package net.momirealms.craftengine.core.entity.furniture.element;
import net.momirealms.craftengine.core.entity.player.Player;
import java.util.function.Consumer;
public interface FurnitureElement {
int[] virtualEntityIds();
void collectVirtualEntityId(Consumer<Integer> collector);
void show(Player player);
void hide(Player player);
default void deactivate() {}
default void activate() {}
}

View File

@@ -0,0 +1,9 @@
package net.momirealms.craftengine.core.entity.furniture.element;
import net.momirealms.craftengine.core.entity.furniture.Furniture;
import org.jetbrains.annotations.NotNull;
public interface FurnitureElementConfig<E extends FurnitureElement> {
E create(@NotNull Furniture furniture);
}

View File

@@ -0,0 +1,8 @@
package net.momirealms.craftengine.core.entity.furniture.element;
import java.util.Map;
public interface FurnitureElementConfigFactory<E extends FurnitureElement> {
FurnitureElementConfig<E> create(Map<String, Object> args);
}

Some files were not shown because too many files have changed in this diff Show More