diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 949b79fe4..13d93e5cd 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -41,8 +41,8 @@ dependencies { compileOnly("io.github.toxicity188:bettermodel:1.14.0") compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") // MMOItems - compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") - compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") + compileOnly("net.Indyuce:MMOItems-API:6.10.1-SNAPSHOT") + compileOnly("io.lumine:MythicLib-dist:1.7.1-SNAPSHOT") // Nexo compileOnly("com.nexomc:nexo:1.13.0") // LuckPerms @@ -62,7 +62,7 @@ dependencies { // McMMO compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.2.038") // MMOCore - compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT") + compileOnly("net.Indyuce:MMOCore-API:1.13.1-SNAPSHOT") // JobsReborn compileOnly("com.github.Zrips:Jobs:v5.2.2.3") // CustomFishing diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index 325159bdc..28d614af0 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -37,6 +37,7 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.condition.AlwaysFalseCondition; import net.momirealms.craftengine.core.plugin.context.event.EventConditions; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; @@ -181,7 +182,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager { } private void logHook(String plugin) { - this.plugin.logger().info("[Compatibility] " + plugin + " hooked"); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.compatibility", plugin)); } @Override @@ -252,8 +253,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager { if (VersionHelper.isOrAbove1_20_3()) { this.plugin.logger().severe(""); if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) { - this.plugin.logger().severe("[Compatibility] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")"); - this.plugin.logger().severe("[Compatibility] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本"); + this.plugin.logger().severe("[兼容性] 插件需要更新 FastAsyncWorldEdit 到 2.13.0 或更高版本,以获得更好的兼容性。(当前版本: " + version + ")"); + this.plugin.logger().severe("[兼容性] 请前往 https://ci.athion.net/job/FastAsyncWorldEdit/ 下载最新版本"); } else { this.plugin.logger().severe("[Compatibility] Update FastAsyncWorldEdit to v2.13.0+ for better compatibility (Current: " + version + ")"); this.plugin.logger().severe("[Compatibility] Download latest version: https://ci.athion.net/job/FastAsyncWorldEdit/"); diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java index a6c936826..0ec8d06c9 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/bettermodel/BetterModelBlockEntityElementConfig.java @@ -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 { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig create(Map arguments) { + public BetterModelBlockEntityElementConfig create(Map arguments) { String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.better_model.missing_model"); - return (BlockEntityElementConfig) new BetterModelBlockEntityElementConfig( + return new BetterModelBlockEntityElementConfig( model, ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java index 0a4c5c41c..c34ef8d12 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/model/modelengine/ModelEngineBlockEntityElementConfig.java @@ -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 { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig create(Map arguments) { + public ModelEngineBlockEntityElementConfig create(Map arguments) { String model = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("model"), "warning.config.block.state.entity_renderer.model_engine.missing_model"); - return (BlockEntityElementConfig) new ModelEngineBlockEntityElementConfig( + return new ModelEngineBlockEntityElementConfig( model, ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/condition/CondIsFurniture.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/condition/CondIsFurniture.java index 96b57c2d5..5be023307 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/condition/CondIsFurniture.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/condition/CondIsFurniture.java @@ -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()); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java index 995f3364a..b65a9bc1b 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java @@ -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(); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomClick.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomClick.java index 86b66d401..05ac9f064 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomClick.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomClick.java @@ -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); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomFurniture.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomFurniture.java index 2afbea401..41128d050 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomFurniture.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/event/EvtCustomFurniture.java @@ -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); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprEntityFurnitureID.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprEntityFurnitureID.java index 0e8826602..fdf2674be 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprEntityFurnitureID.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/expression/ExprEntityFurnitureID.java @@ -16,7 +16,7 @@ public class ExprEntityFurnitureID extends SimplePropertyExpression it.id().toString()) .orElse(null); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java index 9a5f8acc3..5d98d2130 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/advancement/BukkitAdvancementManager.java @@ -119,6 +119,11 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager { return LoadingSequence.ADVANCEMENT; } + @Override + public int count() { + return 0; + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (advancements.containsKey(id)) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java index 4a47cd615..096f42b67 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java @@ -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 loadedFurniture() { + public static Map 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 lootTable = (LootTable) furniture.config().lootTable(); + LootTable lootTable = (LootTable) 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()); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureAttemptPlaceEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureAttemptPlaceEvent.java index 6d0a54209..7e492239e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureAttemptPlaceEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureAttemptPlaceEvent.java @@ -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; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java index 1e1adcf47..2fd4cc6bc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java @@ -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 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 8da86d668..49ffc0057 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder; 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.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.ItemUtils; @@ -38,6 +39,7 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPhysicsEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.world.GenericGameEvent; import org.bukkit.inventory.ItemStack; @@ -233,12 +235,14 @@ public final class BlockEventListener implements Listener { @EventHandler(priority = EventPriority.LOW) public void onStep(GenericGameEvent event) { - if (event.getEvent() != GameEvent.STEP) return; + GameEvent gameEvent = event.getEvent(); + // 只处理落地和走路 + if (gameEvent != GameEvent.STEP) return; Entity entity = event.getEntity(); if (!(entity instanceof Player player)) return; BlockPos pos = EntityUtils.getOnPos(player); Block block = player.getWorld().getBlockAt(pos.x(), pos.y(), pos.z()); - Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()), LocationUtils.toBlockPos(block.getX(), block.getY(), block.getZ())); + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()), LocationUtils.toBlockPos(pos)); Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); if (optionalCustomState.isPresent()) { Location location = player.getLocation(); @@ -253,7 +257,8 @@ public final class BlockEventListener implements Listener { if (cancellable.isCancelled() && !Config.processCancelledStep()) { return; } - player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get()); + SoundData soundData = state.settings().sounds().stepSound(); + player.playSound(location, soundData.id().toString(), SoundCategory.BLOCKS, soundData.volume().get(), soundData.pitch().get()); } else if (Config.enableSoundSystem()) { if (event.isCancelled() && !Config.processCancelledStep()) { return; @@ -267,6 +272,32 @@ public final class BlockEventListener implements Listener { } } + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + public void onFall(EntityDamageEvent event) { + if (event.getCause() != EntityDamageEvent.DamageCause.FALL) + return; + if (!(event.getEntity() instanceof Player player)) return; + BlockPos pos = EntityUtils.getOnPos(player); + Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()), LocationUtils.toBlockPos(pos)); + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(blockState); + if (optionalCustomState.isPresent()) { + Location location = player.getLocation(); + ImmutableBlockState state = optionalCustomState.get(); + SoundData soundData = state.settings().sounds().fallSound(); + player.playSound(location, soundData.id().toString(), SoundCategory.BLOCKS, soundData.volume().get(), soundData.pitch().get()); + } else if (Config.enableSoundSystem()) { + if (event.isCancelled() && !Config.processCancelledStep()) { + return; + } + Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); + Object soundEvent = FastNMS.INSTANCE.field$SoundType$fallSound(soundType); + Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); + if (this.manager.isStepSoundMissing(soundId)) { + player.playSound(player.getLocation(), soundId.toString(), SoundCategory.BLOCKS, 0.15f, 1f); + } + } + } + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onBlockPhysics(BlockPhysicsEvent event) { // for vanilla blocks diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 11133b67d..aae33ecc1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -47,6 +47,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SURFACE_SPREADING_BLOCK = Key.from("craftengine:surface_spreading_block"); public static final Key SNOWY_BLOCK = Key.from("craftengine:snowy_block"); public static final Key HANGABLE_BLOCK = Key.from("craftengine:hangable_block"); + public static final Key DROP_EXPERIENCE_BLOCK = Key.from("craftengine:drop_experience_block"); + public static final Key DROP_EXP_BLOCK = Key.from("craftengine:drop_exp_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -92,5 +94,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY); register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY); register(HANGABLE_BLOCK, HangableBlockBehavior.FACTORY); + register(DROP_EXPERIENCE_BLOCK, DropExperienceBlockBehavior.FACTORY); + register(DROP_EXP_BLOCK, DropExperienceBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DropExperienceBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DropExperienceBlockBehavior.java new file mode 100644 index 000000000..78de11707 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DropExperienceBlockBehavior.java @@ -0,0 +1,104 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.BlockSettings; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.loot.LootContext; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.condition.AllOfCondition; +import net.momirealms.craftengine.core.plugin.context.event.EventConditions; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +public class DropExperienceBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final NumberProvider amount; + private final Condition conditions; + + public DropExperienceBlockBehavior(CustomBlock customBlock, NumberProvider amount, Condition conditions) { + super(customBlock); + this.amount = amount; + this.conditions = conditions; + } + + @Override + public void spawnAfterBreak(Object thisBlock, Object[] args, Callable superMethod) { + boolean dropExperience = (boolean) args[4]; // 通常来说是 false + Item item = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(args[3])); + if (!dropExperience) { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (state == null) { + return; + } + BlockSettings settings = state.settings(); + if (settings.requireCorrectTool()) { + if (item.isEmpty()) { + return; + } + boolean cannotBreak = !settings.isCorrectTool(item.id()) + && (!settings.respectToolComponent() + || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(args[3], state.customBlockState().literalObject())); + if (cannotBreak) { + return; + } + } + } + World world = BukkitWorldManager.instance().wrap(FastNMS.INSTANCE.method$Level$getCraftWorld(args[1])); + BlockPos pos = LocationUtils.fromBlockPos(args[2]); + tryDropExperience(world, pos, item); + } + + private void tryDropExperience(World world, BlockPos pos, Item item) { + Vec3d dropPos = Vec3d.atCenterOf(pos); + ContextHolder holder = ContextHolder.builder() + .withParameter(DirectContextParameters.POSITION, new WorldPosition(world, dropPos)) + .withParameter(DirectContextParameters.ITEM_IN_HAND, item) + .build(); + LootContext context = new LootContext(world, null, 1.0f, holder); + if (this.conditions != null && !this.conditions.test(context)) { + return; + } + int finalAmount = this.amount.getInt(context); + if (finalAmount <= 0) { + return; + } + world.dropExp(dropPos, finalAmount); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + NumberProvider amount = NumberProviders.fromObject(ResourceConfigUtils.get(arguments, "amount", "count")); + Condition conditions = null; + List> conditionList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "conditions", "condition"), EventConditions::fromMap); + if (conditionList.size() == 1) { + conditions = conditionList.getFirst(); + } else if (!conditionList.isEmpty()) { + conditions = new AllOfCondition<>(conditionList); + } + return new DropExperienceBlockBehavior(block, amount, conditions); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java index 625818617..706ebf20d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SimpleStorageBlockEntity.java @@ -172,8 +172,10 @@ public class SimpleStorageBlockEntity extends BlockEntity { public void updateOpenBlockState(boolean open) { ImmutableBlockState state = super.world.getBlockStateAtIfLoaded(this.pos); - if (state == null || state.behavior() != this.behavior) return; - Property property = this.behavior.openProperty(); + if (state == null) return; + SimpleStorageBlockBehavior behavior = state.behavior().getAs(SimpleStorageBlockBehavior.class).orElse(null); + if (behavior == null) return; + Property property = behavior.openProperty(); if (property == null) return; super.world.world().setBlockState(this.pos.x(), this.pos.y(), this.pos.z(), state.with(property, open), UpdateOption.UPDATE_ALL.flags()); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java index 96873d757..fc6ca3df7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemBlockEntityElementConfig.java @@ -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; @@ -29,8 +28,8 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig { List dataValues = new ArrayList<>(); - ItemEntityData.Item.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); - ItemEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, dataValues); + ItemEntityData.Item.addEntityData(item.apply(player).getLiteralObject(), dataValues); + ItemEntityData.NoGravity.addEntityData(true, dataValues); return dataValues; }; } @@ -45,6 +44,14 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig elementClass() { return ItemBlockEntityElement.class; @@ -62,13 +69,18 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig create(Map arguments) { + public ItemBlockEntityElementConfig create(Map arguments) { Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")); - return (BlockEntityElementConfig) new ItemBlockEntityElementConfig( + return new ItemBlockEntityElementConfig( player -> BukkitItemManager.instance().createWrappedItem(itemId, player), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position") ); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java index a8842a81f..a8b02c0b0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ItemDisplayBlockEntityElementConfig.java @@ -1,12 +1,12 @@ 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; @@ -18,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; @@ -61,14 +60,14 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo this.shadowStrength = shadowStrength; this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); - ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); - ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); - ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); - ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); - ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues); - ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues); - ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues); + ItemDisplayEntityData.DisplayedItem.addEntityData(item.apply(player).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; }; } @@ -80,14 +79,6 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo @Override public ItemDisplayBlockEntityElement create(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) { - Quaternionf previousRotation = previous.config.rotation; - if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { - return null; - } - Vector3f translation = previous.config.translation; - if (translation.x != 0 || translation.y != 0 || translation.z != 0) { - return null; - } return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || @@ -95,6 +86,14 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo ); } + @Override + public ItemDisplayBlockEntityElement createExact(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) { + if (!previous.config.equals(this)) { + return null; + } + return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, false); + } + @Override public Class elementClass() { return ItemDisplayBlockEntityElement.class; @@ -148,13 +147,22 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } - public static class Factory implements BlockEntityElementConfigFactory { + @Override + public boolean equals(Object o) { + if (!(o instanceof ItemDisplayBlockEntityElementConfig that)) return false; + return Float.compare(xRot, that.xRot) == 0 && + Float.compare(yRot, that.yRot) == 0 && + Objects.equal(position, that.position) && + Objects.equal(translation, that.translation) && + Objects.equal(rotation, that.rotation); + } + + public static class Factory implements BlockEntityElementConfigFactory { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig create(Map arguments) { + public ItemDisplayBlockEntityElementConfig create(Map arguments) { Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")); - return (BlockEntityElementConfig) 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"), @@ -162,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") ); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java index 43b6af680..5b96a9611 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/TextDisplayBlockEntityElementConfig.java @@ -1,12 +1,12 @@ package net.momirealms.craftengine.bukkit.block.entity.renderer.element; +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; @@ -52,11 +52,11 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo this.billboard = billboard; this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - TextDisplayEntityData.Text.addEntityDataIfNotDefaultValue(ComponentUtils.adventureToMinecraft(text(player)), dataValues); - TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues); - TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues); - TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues); - TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues); + TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(text(player)), dataValues); + TextDisplayEntityData.Scale.addEntityData(this.scale, dataValues); + TextDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues); + TextDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues); + TextDisplayEntityData.Translation.addEntityData(this.translation, dataValues); return dataValues; }; } @@ -68,14 +68,6 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo @Override public TextDisplayBlockEntityElement create(World world, BlockPos pos, TextDisplayBlockEntityElement previous) { - Quaternionf previousRotation = previous.config.rotation; - if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { - return null; - } - Vector3f translation = previous.config.translation; - if (translation.x != 0 || translation.y != 0 || translation.z != 0) { - return null; - } return new TextDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || @@ -83,6 +75,14 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo ); } + @Override + public TextDisplayBlockEntityElement createExact(World world, BlockPos pos, TextDisplayBlockEntityElement previous) { + if (!previous.config.equals(this)) { + return null; + } + return new TextDisplayBlockEntityElement(this, pos, previous.entityId, false); + } + @Override public Class elementClass() { return TextDisplayBlockEntityElement.class; @@ -124,13 +124,22 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } - public static class Factory implements BlockEntityElementConfigFactory { + @Override + public boolean equals(Object o) { + if (!(o instanceof TextDisplayBlockEntityElementConfig that)) return false; + return Float.compare(xRot, that.xRot) == 0 && + Float.compare(yRot, that.yRot) == 0 && + Objects.equal(position, that.position) && + Objects.equal(translation, that.translation) && + Objects.equal(rotation, that.rotation); + } + + public static class Factory implements BlockEntityElementConfigFactory { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig create(Map arguments) { + public TextDisplayBlockEntityElementConfig create(Map arguments) { String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.block.state.entity_renderer.text_display.missing_text"); - return (BlockEntityElementConfig) new TextDisplayBlockEntityElementConfig( + return new TextDisplayBlockEntityElementConfig( text, ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java index 76dfed5d0..3d4780635 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java @@ -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(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitItemEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitItemEntity.java index 449fc5ebb..7ee3115ae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitItemEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitItemEntity.java @@ -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 { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/BukkitEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/BukkitEntityData.java index 2e3ee3eae..814eacd5b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/BukkitEntityData.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/BukkitEntityData.java @@ -39,7 +39,7 @@ public class BukkitEntityData implements EntityData { } @Override - public Object create(Object entityDataAccessor, Object value) { + public Object create(Object entityDataAccessor, T value) { return EntityDataValue.create(entityDataAccessor, value); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCollider.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCollider.java index 69b8c9775..7b330e60a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCollider.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCollider.java @@ -21,7 +21,7 @@ public class BukkitCollider implements Collider { @Override public int entityId() { - return this.collisionEntity.getId(); + return this.collisionEntity.getEntityId(); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java deleted file mode 100644 index dcad38455..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java +++ /dev/null @@ -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 placements, - @NotNull Map>> 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 placements; - private FurnitureSettings settings; - private Map>> 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 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>> events) { - this.events = events; - return this; - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java index 37d64bbf6..d7bbe6f9d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java @@ -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 baseEntity; - private final int baseEntityId; - // colliders - private final Collider[] colliderEntities; - // cache - private final List fakeEntityIds; - private final List entityIds; - private final Map hitBoxes = new Int2ObjectArrayMap<>(); - private final Map 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 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 fakeEntityIds = new IntArrayList(); - List mainEntityIds = new IntArrayList(); - mainEntityIds.add(this.baseEntityId); - - // 绑定外部模型 - Optional 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 packets = new ArrayList<>(); - List minimizedPackets = new ArrayList<>(); - List 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 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 dropOffset = this.placement.dropOffset(); + // 获取掉落物的位置,受到家具变种的影响 + public Location getDropLocation() { + Optional 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 seat : hitBox.seats()) { - seat.destroy(); - } - } - } - - @Override - public UUID uuid() { - return this.baseEntity().getUniqueId(); - } - - @Override - public int baseEntityId() { - return this.baseEntityId; - } - - @NotNull - public List entityIds() { - return Collections.unmodifiableList(this.entityIds); - } - - @NotNull - public List 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(); } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java deleted file mode 100644 index 83ca3fa4d..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java +++ /dev/null @@ -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 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 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 getCachedValues(@Nullable Color color, int @Nullable [] colors) { - List cachedValues = new ArrayList<>(this.commonValues); - Item 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); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index f946e4888..5b0f5cacc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -1,22 +1,24 @@ 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.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; @@ -34,13 +36,17 @@ 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 furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f); - private final Map furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f); + + private final Map byMetaEntityId = new ConcurrentHashMap<>(256, 0.5f); + private final Map byVirtualEntityId = new ConcurrentHashMap<>(512, 0.5f); + private final Map byColliderEntityId = new ConcurrentHashMap<>(512, 0.5f); // Event listeners private final FurnitureEventListener furnitureEventListener; @@ -52,53 +58,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 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 taskExecutor = (entity, runnable) -> entity.getScheduler().run(this.plugin.javaPlugin(), (t) -> runnable.run(), () -> {}); for (World world : Bukkit.getWorlds()) { List 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 +114,11 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { List 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 +132,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 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 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 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 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 +309,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; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBox.java deleted file mode 100644 index 8f725a94b..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitHitBox.java +++ /dev/null @@ -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[] 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[] createSeats(HitBoxConfig config) { - SeatConfig[] seatConfigs = config.seats(); - Seat[] 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[] seats() { - return this.seats; - } - - @Override - public Optional clip(Vec3d min, Vec3d max) { - for (HitBoxPart hbe : this.parts) { - Optional 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()); - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java index ce4864b90..534a3b841 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/FurnitureEventListener.java @@ -2,6 +2,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 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 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 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); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/BukkitFurnitureElementConfigs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/BukkitFurnitureElementConfigs.java new file mode 100644 index 000000000..4ce2d57f2 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/BukkitFurnitureElementConfigs.java @@ -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() { + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElement.java new file mode 100644 index 000000000..ae884d0c7 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElement.java @@ -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 collector) { + collector.accept(this.entityId); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElementConfig.java new file mode 100644 index 000000000..caa972e9b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElementConfig.java @@ -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 { + public static final Factory FACTORY = new Factory(); + public final BiFunction> 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> itemFunction = (player, colorSource) -> { + Item 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 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 { + + @Override + public ItemDisplayFurnitureElementConfig create(Map 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 + + '}'; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/AbstractFurnitureHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/AbstractFurnitureHitBox.java new file mode 100644 index 000000000..f40ead283 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/AbstractFurnitureHitBox.java @@ -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[] seats; + + public AbstractFurnitureHitBox(Furniture furniture, FurnitureHitBoxConfig config) { + this.furniture = furniture; + this.seats = createSeats(config); + } + + @SuppressWarnings("unchecked") + private Seat[] createSeats(FurnitureHitBoxConfig config) { + SeatConfig[] seatConfigs = config.seats(); + Seat[] 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[] 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); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitFurnitureHitboxTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitFurnitureHitboxTypes.java new file mode 100644 index 000000000..f7fa5e2e1 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitFurnitureHitboxTypes.java @@ -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); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java deleted file mode 100644 index 4d92bd7c8..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java +++ /dev/null @@ -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); - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitbox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitbox.java new file mode 100644 index 000000000..900909794 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitbox.java @@ -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 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 colliders() { + return List.of(this.collider); + } + + @Override + public List 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 collector) { + collector.accept(this.entityId); + } + + @Override + public CustomFurnitureHitboxConfig config() { + return this.config; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitboxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitboxConfig.java new file mode 100644 index 000000000..d7147708c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitboxConfig.java @@ -0,0 +1,114 @@ +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 { + public static final Factory FACTORY = new Factory(); + private final float scale; + private final Object entityType; + private final List 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 cachedValues() { + return this.cachedValues; + } + + public float width() { + return width; + } + + public float height() { + return height; + } + + @Override + public void prepareForPlacement(WorldPosition targetPos, Consumer aabbConsumer) { + if (this.blocksBuilding) { + Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position); + aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, this.width, this.height)); + } + } + + @Override + public CustomFurnitureHitbox create(Furniture furniture) { + return new CustomFurnitureHitbox(furniture, this); + } + + public static class Factory implements FurnitureHitBoxConfigFactory { + + @Override + public CustomFurnitureHitboxConfig create(Map 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); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBoxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBoxConfig.java deleted file mode 100644 index fa8e974a8..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBoxConfig.java +++ /dev/null @@ -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 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 packets, Consumer collider, Consumer 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 aabbs) { - } - - @Override - public int[] acquireEntityIds(Supplier entityIdSupplier) { - return new int[] {entityIdSupplier.get()}; - } - - public static class Factory implements HitBoxConfigFactory { - - @Override - public HitBoxConfig create(Map 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); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitbox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitbox.java new file mode 100644 index 000000000..e5306701a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitbox.java @@ -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 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 colliders() { + return List.of(this.collider); + } + + @Override + public List parts() { + return List.of(this.part); + } + + @Override + public void show(Player player) { + List 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 collector) { + collector.accept(this.entityId); + } + + @Override + public HappyGhastFurnitureHitboxConfig config() { + return this.config; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitboxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitboxConfig.java new file mode 100644 index 000000000..6264b3a5f --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitboxConfig.java @@ -0,0 +1,83 @@ +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 { + public static final Factory FACTORY = new Factory(); + private final double scale; + private final boolean hardCollision; + private final List 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 cachedValues() { + return cachedValues; + } + + @Override + public HappyGhastFurnitureHitbox create(Furniture furniture) { + return new HappyGhastFurnitureHitbox(furniture, this); + } + + @Override + public void prepareForPlacement(WorldPosition targetPos, Consumer aabbConsumer) { + if (this.blocksBuilding) { + Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position); + aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, 4 * this.scale, 4 * this.scale)); + } + } + + public static class Factory implements FurnitureHitBoxConfigFactory { + + @Override + public FurnitureHitBoxConfig create(Map 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 + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBoxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBoxConfig.java deleted file mode 100644 index 5663b9f87..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBoxConfig.java +++ /dev/null @@ -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 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 packets, - Consumer collider, - Consumer 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 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 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 entityIdSupplier) { - return new int[] {entityIdSupplier.get()}; - } - - public static class Factory implements HitBoxConfigFactory { - - @Override - public HitBoxConfig create(Map 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 - ); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitbox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitbox.java new file mode 100644 index 000000000..4b4a34110 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitbox.java @@ -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 colliders() { + return List.of(this.collider); + } + + @Override + public List parts() { + return List.of(this.part); + } + + @Override + public InteractionFurnitureHitboxConfig config() { + return this.config; + } + + @Override + public void collectVirtualEntityId(Consumer 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); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitboxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitboxConfig.java new file mode 100644 index 000000000..909335b0a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitboxConfig.java @@ -0,0 +1,113 @@ +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 { + 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 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 cachedValues() { + return cachedValues; + } + + @Override + public void prepareForPlacement(WorldPosition targetPos, Consumer aabbConsumer) { + if (this.blocksBuilding) { + Vec3d relativePosition = Furniture.getRelativePosition(targetPos, this.position); + aabbConsumer.accept(AABB.makeBoundingBox(relativePosition, size.x, size.y)); + } + } + + @Override + public InteractionFurnitureHitbox create(Furniture furniture) { + return new InteractionFurnitureHitbox(furniture, this); + } + + public static class Factory implements FurnitureHitBoxConfigFactory { + + @Override + public InteractionFurnitureHitboxConfig create(Map 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 + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBoxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBoxConfig.java deleted file mode 100644 index 60bec26d4..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBoxConfig.java +++ /dev/null @@ -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 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 packets, - Consumer collider, - Consumer 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 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 entityIdSupplier) { - return new int[] {entityIdSupplier.get()}; - } - - public static class Factory implements HitBoxConfigFactory { - - @Override - public HitBoxConfig create(Map 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 - ); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitbox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitbox.java new file mode 100644 index 000000000..ac88d288d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitbox.java @@ -0,0 +1,139 @@ +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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class ShulkerFurnitureHitbox extends AbstractFurnitureHitBox { + private final ShulkerFurnitureHitboxConfig config; + private final List parts; + private final List 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 packets = new ArrayList<>(); + List colliders = new ArrayList<>(); + List 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 colliders() { + return this.colliders; + } + + @Override + public List parts() { + return this.parts; + } + + @Override + public void collectVirtualEntityId(Consumer 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 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()}; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBoxConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitboxConfig.java similarity index 60% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBoxConfig.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitboxConfig.java index 57034cc9a..2b309b806 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBoxConfig.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitboxConfig.java @@ -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 { 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 cachedShulkerValues = new ArrayList<>(); private final DirectionalShulkerSpawner spawner; + private final List 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 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,86 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig { } } - public Collider createCollider(Direction direction, World world, Vector3f offset, double x, double y, double z, int entityId, Consumer 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 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)); + } + } + + 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 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 packets, + Consumer collider, + Consumer aabb); + } + + public Collider createCollider(Direction direction, World world, + Vector3f offset, double x, double y, double z, + int entityId, + Consumer 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 +245,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 packets, - Consumer collider, - Consumer 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 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 packets, - Consumer collider, - Consumer aabb); - } - - @FunctionalInterface - interface AABBCreator { - - AABB create(double x, double y, double z, float yaw, Vector3f offset); - } - - @Override - public int[] acquireEntityIds(Supplier 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 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 +295,26 @@ public class ShulkerHitBoxConfig extends AbstractHitBoxConfig { default -> throw new IllegalStateException("Unexpected value: " + newDirection); } } + + public static class Factory implements FurnitureHitBoxConfigFactory { + + @Override + public ShulkerFurnitureHitboxConfig create(Map 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 + ); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java index 81df0accb..3c3d34027 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java @@ -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 ALLOWED_ANCHOR_TYPES = Set.of("wall", "ceiling", "ground"); private final Key id; + private final Map rules; - public FurnitureItemBehavior(Key id) { + public FurnitureItemBehavior(Key id, Map 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 optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id); + Optional 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,90 @@ 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 xz = placement.alignmentRule().apply(Pair.of(clickedPosition.y(), clickedPosition.z())); + Pair xz = rule.alignmentRule().apply(Pair.of(clickedPosition.y(), clickedPosition.z())); finalPlacePosition = new Vec3d(clickedPosition.x(), xz.left(), xz.right()); } else { - Pair xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.y())); + Pair 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 xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z())); + furnitureYaw = rule.rotationRule().apply(180 + (player != null ? player.yRot() : 0)); + Pair 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 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())) { + if (player != null && player.enableFurnitureDebug() && VersionHelper.isPaper()) { + player.playSound(Key.of("minecraft:entity.villager.no")); + Key flame = Key.of("flame"); + for (AABB aabb : aabbs) { + List edgePoints = aabb.getEdgePoints(0.125); + for (Vec3d edgePoint : edgePoints) { + player.playParticle(flame, edgePoint.x(), edgePoint.y(), edgePoint.z()); + } + } + } 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 +161,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 +174,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 +193,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 rulesMap = ResourceConfigUtils.getAsMapOrNull(arguments.get("rules"), "rules"); + Key furnitureId; if (id instanceof Map map) { + Map 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 placementSection = ResourceConfigUtils.getAsMapOrNull(furnitureSection.get("placement"), "placement"); + if (placementSection != null) { + rulesMap = new HashMap<>(); + for (Map.Entry entry : placementSection.entrySet()) { + if (entry.getValue() instanceof Map innerMap) { + if (innerMap.containsKey("rules")) { + Map 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 rules = new EnumMap<>(AnchorType.class); + if (rulesMap != null) { + for (Map.Entry entry : rulesMap.entrySet()) { + try { + AnchorType type = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ROOT)); + Map 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); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index 88f5173de..4d7e6e374 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -457,9 +457,11 @@ public class ItemEventListener implements Listener { } } else { // fixme 如何取消堆叠数量>1的物品的默认replacement - Item replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer); - if (replacementItem != null) { - PlayerUtils.giveItem(serverPlayer, 1, replacementItem); + if (replacement != null) { + Item replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer); + if (replacementItem != null) { + PlayerUtils.giveItem(serverPlayer, 1, replacementItem); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java index d38e58a24..7836a0812 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/loot/BukkitVanillaLootManager.java @@ -93,6 +93,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme public class VanillaLootParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot"}; + private int count; @Override public int loadingSequence() { @@ -104,6 +105,16 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme return CONFIG_SECTION_NAME; } + @Override + public int count() { + return this.count; + } + + @Override + public void preProcess() { + this.count = 0; + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("type"), "warning.config.vanilla_loot.missing_type"); @@ -147,6 +158,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme } } } + this.count++; } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 4b7aa72cc..e074577b1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.obfuscation.ObfA; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.Base64Utils; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; @@ -94,7 +95,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { return; } if (!Config.sendPackOnUpload()) return; - CraftEngine.instance().logger().info("Completed uploading resource pack"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.resource_pack.upload")); for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { sendResourcePack(player); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index ce298bf65..e141aa000 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -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) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java index 9a6729b0a..21bd8791a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java @@ -31,34 +31,11 @@ public class BukkitPlatform implements Platform { Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); } - @SuppressWarnings("unchecked") - @Override - public Object snbtToJava(String nbt) { - try { - Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}"); - Map map = (Map) MRegistryOps.NBT.convertTo(MRegistryOps.JAVA, tag); - return map.get("root"); - } catch (CommandSyntaxException e) { - throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt); - } - } - @Override public Tag jsonToSparrowNBT(JsonElement json) { return MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, json); } - @Override - public Tag snbtToSparrowNBT(String nbt) { - try { - Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}"); - CompoundTag map = (CompoundTag) MRegistryOps.NBT.convertTo(MRegistryOps.SPARROW_NBT, tag); - return map.get("root"); - } catch (CommandSyntaxException e) { - throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt); - } - } - @Override public Tag javaToSparrowNBT(Object object) { return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 6ce68c38d..533032261 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -42,12 +42,15 @@ public class BukkitCommandManager extends AbstractCommandManager new SearchUsageAdminCommand(this, plugin), 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), new DebugAppearanceStateUsageCommand(this, plugin), new DebugClearCooldownCommand(this, plugin), new DebugEntityIdCommand(this, plugin), + new DebugFurnitureCommand(this, plugin), new DebugRealStateUsageCommand(this, plugin), new DebugItemDataCommand(this, plugin), new DebugSetBlockCommand(this, plugin), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugFurnitureCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugFurnitureCommand.java new file mode 100644 index 000000000..ee2ac39d5 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugFurnitureCommand.java @@ -0,0 +1,36 @@ +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 org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; + +public class DebugFurnitureCommand extends BukkitCommandFeature { + + public DebugFurnitureCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .handler(context -> { + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(context.sender()); + boolean b = !serverPlayer.enableFurnitureDebug(); + serverPlayer.setEnableFurnitureDebug(b); + serverPlayer.sendMessage(Component.text("Furniture Debug Mode: ").append(Component.text(b ? "ON" : "OFF").color(b ? NamedTextColor.GREEN : NamedTextColor.RED)), false); + }); + } + + @Override + public String getFeatureID() { + return "debug_furniture"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSpawnFurnitureCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSpawnFurnitureCommand.java index a6333f2d5..832a1c40e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSpawnFurnitureCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSpawnFurnitureCommand.java @@ -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() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + NamespacedKey namespacedKey = context.get("id"); + Key id = KeyUtils.namespacedKey2Key(namespacedKey); + BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance(); + Optional optionalCustomFurniture = furnitureManager.furnitureById(id); + return optionalCustomFurniture.>>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 optionalCustomFurniture = furnitureManager.furnitureById(id); + Optional 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); }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java new file mode 100644 index 000000000..d8a9d0d9a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityViewDistanceScaleCommand.java @@ -0,0 +1,52 @@ +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.DoubleParser; + +public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature { + + public SetEntityViewDistanceScaleCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .flag(FlagKeys.SILENT_FLAG) + .required("player", PlayerParser.playerParser()) + .required("scale", DoubleParser.doubleParser(0.125, 8)) + .handler(context -> { + if (!Config.enableEntityCulling()) { + plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Entity culling is not enabled on this server").color(NamedTextColor.RED)); + return; + } + if (Config.entityCullingViewDistance() <= 0) { + plugin().senderFactory().wrap(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); + serverPlayer.setEntityCullingViewDistanceScale(scale); + handleFeedback(context, MessageConstants.COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName())); + }); + } + + @Override + public String getFeatureID() { + return "set_entity_view_distance_scale"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ToggleEntityCullingCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ToggleEntityCullingCommand.java new file mode 100644 index 000000000..1d8786ec9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ToggleEntityCullingCommand.java @@ -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 { + + public ToggleEntityCullingCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .flag(FlagKeys.SILENT_FLAG) + .required("player", PlayerParser.playerParser()) + .optional("state", BooleanParser.booleanParser()) + .handler(context -> { + if (!Config.enableEntityCulling()) { + plugin().senderFactory().wrap(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 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"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index c49b423e6..7728e1641 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -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") @@ -2035,7 +1996,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes boolean hasGlobalPalette = false; // 创建客户端侧世界(只在开启实体情况下创建) - ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null; + ClientSection[] clientSections = Config.entityCullingRayTracing() ? new ClientSection[count] : null; for (int i = 0; i < count; i++) { MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); @@ -2214,10 +2175,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // 获取客户端侧区域 ClientSection clientSection = null; - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { SectionPos sectionPos = SectionPos.of(sPos); ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey); - clientSection = trackedChunk.sectionById(sectionPos.y); + if (trackedChunk != null) { + clientSection = trackedChunk.sectionById(sectionPos.y); + } } for (int i = 0; i < blocks; i++) { @@ -2260,7 +2223,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FriendlyByteBuf buf = event.getBuffer(); BlockPos pos = buf.readBlockPos(); int before = buf.readVarInt(); - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); if (trackedChunk != null) { trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(before)); @@ -2440,7 +2403,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes BlockPos blockPos = buf.readBlockPos(); int state = buf.readInt(); // 移除不透明设置 - if (Config.enableEntityCulling()) { + if (Config.entityCullingRayTracing()) { ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4)); if (trackedChunk != null) { trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, false); @@ -3350,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; } } @@ -3699,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); @@ -3765,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); @@ -3782,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 result = hitBoxPart.aabb().clip(LocationUtils.toVec3d(eyeLocation), LocationUtils.toVec3d(endLocation)); + Optional 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; } @@ -3831,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> optionalCustomItem = itemInHand.getCustomItem(); if (optionalCustomItem.isPresent() && !optionalCustomItem.get().behaviors().isEmpty()) { for (ItemBehavior behavior : optionalCustomItem.get().behaviors()) { @@ -3851,9 +3825,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes ); } else { if (!serverPlayer.isSecondaryUseActive()) { - for (Seat seat : hitbox.seats()) { + for (Seat seat : hitBox.seats()) { if (!seat.isOccupied()) { if (seat.spawnSeat(serverPlayer, furniture.position())) { + if (!part.interactive()) { + serverPlayer.swingHand(InteractionHand.MAIN_HAND); + } break; } } @@ -3864,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); @@ -3976,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); } @@ -3992,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); @@ -4003,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); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java index 0a60cfc3a..a73ebc3e2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/FurniturePacketHandler.java @@ -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 fakeEntities; + private final int metaEntityId; + private final int[] virtualHitboxEntities; - public FurniturePacketHandler(List 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; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index c433a7f84..db48a52fb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -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) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index a08fea4dc..2bc3a8635 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -7,7 +7,9 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; +import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.block.entity.BlockEntityHolder; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; @@ -25,6 +27,9 @@ 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.furniture.FurnitureVariant; +import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.Player; @@ -32,6 +37,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.network.ConnectionState; @@ -51,6 +57,7 @@ import org.bukkit.block.Block; import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EquipmentSlot; @@ -71,6 +78,9 @@ 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"); + public static final Key ENABLE_FURNITURE_DEBUG = Key.of("craftengine:enable_furniture_debug"); private final BukkitCraftEngine plugin; // connection state @@ -114,8 +124,6 @@ public class BukkitServerPlayer extends Player { private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaBlockStateCount()); // cache if player can break blocks private boolean clientSideCanBreak = true; - // prevent AFK players from consuming too much CPU resource on predicting - private Location previousEyeLocation; // a cooldown for better breaking experience private int lastSuccessfulBreak; // player's game tick @@ -142,8 +150,20 @@ public class BukkitServerPlayer extends Player { private int lastStopMiningTick; // 跟踪到的方块实体渲染器 private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); - + private final Map trackedFurniture = new ConcurrentHashMap<>(); private final EntityCulling culling; + private Vec3d firstPersonCameraVec3; + private Vec3d thirdPersonCameraVec3; + // 是否启用实体剔除 + private boolean enableEntityCulling; + // 玩家眼睛所在位置 + private Location eyeLocation; + // 是否启用家具调试 + private boolean enableFurnitureDebug; + // 上一次对准的家具 + private BukkitFurniture lastHitFurniture; + // 缓存的tick + private int lastHitFurnitureTick; public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -157,7 +177,7 @@ public class BukkitServerPlayer extends Player { } } } - this.culling = new EntityCulling(this, 64, 0.5); + this.culling = new EntityCulling(this); } public void setPlayer(org.bukkit.entity.Player player) { @@ -169,6 +189,10 @@ public class BukkitServerPlayer extends Player { this.isNameVerified = true; 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.enableFurnitureDebug = Optional.ofNullable(player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENABLE_FURNITURE_DEBUG), PersistentDataType.BOOLEAN)).orElse(false); + this.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0)); this.selectedLocale = TranslationManager.parseLocale(locale); this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f); this.entityTypeView = new ConcurrentHashMap<>(256); @@ -509,10 +533,11 @@ public class BukkitServerPlayer extends Player { @Override public void tick() { // not fully online - if (serverPlayer() == null) return; + Object serverPlayer = serverPlayer(); + if (serverPlayer == null) return; + org.bukkit.entity.Player bukkitPlayer = platformPlayer(); if (VersionHelper.isFolia()) { try { - Object serverPlayer = serverPlayer(); Object gameMode = FastNMS.INSTANCE.field$ServerPlayer$gameMode(serverPlayer); this.gameTicks = (int) CoreReflections.field$ServerPlayerGameMode$gameTicks.get(gameMode); } catch (ReflectiveOperationException e) { @@ -524,12 +549,57 @@ public class BukkitServerPlayer extends Player { if (this.gameTicks % 20 == 0) { this.updateGUI(); } + + if (this.enableFurnitureDebug) { + BukkitFurniture furniture = CraftEngineFurniture.rayTrace(platformPlayer()); + boolean forceShow = furniture != this.lastHitFurniture; + if (forceShow) { + this.lastHitFurnitureTick = 0; + } else { + this.lastHitFurnitureTick++; + if (this.lastHitFurnitureTick % 30 == 0) { + forceShow = true; + } + } + this.lastHitFurniture = furniture; + if (furniture != null && forceShow) { + FurnitureVariant currentVariant = furniture.getCurrentVariant(); + List aabbs = new ArrayList<>(); + for (FurnitureHitBoxConfig config : currentVariant.hitBoxConfigs()) { + config.prepareForPlacement(furniture.position(), aabbs::add); + } + Key endRod = Key.of("soul_fire_flame"); + for (AABB aabb : aabbs) { + for (Vec3d point : aabb.getEdgePoints(0.125)) { + this.playParticle(endRod, point.x, point.y, point.z); + } + } + } + } + + // 更新眼睛位置 + { + Location unsureEyeLocation = bukkitPlayer.getEyeLocation(); + Entity vehicle = bukkitPlayer.getVehicle(); + if (vehicle != null) { + 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.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(); + } + } + this.eyeLocation = unsureEyeLocation; + } + if (hasSwingHand()) { if (this.isDestroyingBlock) { this.tickBlockDestroy(); } else if (this.lastStopMiningPos != null && this.gameTicks - this.lastStopMiningTick <= 5) { double range = getCachedInteractionRange(); - RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result != null) { Block hitBlock = result.getHitBlock(); if (hitBlock != null) { @@ -552,25 +622,79 @@ public class BukkitServerPlayer extends Player { this.isHackedBreak = false; } } - if (Config.predictBreaking() && !this.isDestroyingCustomBlock) { - // if it's not destroying blocks, we do predict - if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) { - Location eyeLocation = platformPlayer().getEyeLocation(); - if (eyeLocation.equals(this.previousEyeLocation)) { - return; + + if (Config.entityCullingRayTracing()) { + org.bukkit.entity.Player player = platformPlayer(); + Location eyeLocation = this.eyeLocation.clone(); + this.firstPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation); + int distance = 4; + if (VersionHelper.isOrAbove1_21_6()) { + Entity vehicle = player.getVehicle(); + if (vehicle != null && vehicle.getType() == EntityType.HAPPY_GHAST) { + distance = 8; } - this.previousEyeLocation = eyeLocation; - this.predictNextBlockToMine(); + } + this.thirdPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation.subtract(eyeLocation.getDirection().multiply(distance))); + } + } + + @Override + public void entityCullingTick() { + this.culling.restoreTokenOnTick(); + if (this.firstPersonCameraVec3 == null || this.thirdPersonCameraVec3 == null) { + return; + } + boolean useRayTracing = Config.entityCullingRayTracing(); + 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); } } - if (Config.enableEntityCulling()) { - long nano1 = System.nanoTime(); - for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { - boolean visible = this.culling.isVisible(cullableObject.cullable.aabb(), LocationUtils.toVec3d(platformPlayer().getEyeLocation())); - cullableObject.setShown(this, visible); + } + + 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); + } } - long nano2 = System.nanoTime(); - //CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms"); + // 之前不可见 + 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); } } @@ -590,17 +714,12 @@ public class BukkitServerPlayer extends Player { public boolean canInteractWithBlock(BlockPos pos, double distance) { double d = this.getCachedInteractionRange() + distance; - return (new AABB(pos)).distanceToSqr(this.getEyePosition()) < d * d; + return (new AABB(pos)).distanceToSqr(this.getEyePos()) < d * d; } public boolean canInteractPoint(Vec3d pos, double distance) { double d = this.getCachedInteractionRange() + distance; - return Vec3d.distanceToSqr(this.getEyePosition(), pos) < d * d; - } - - public final Vec3d getEyePosition() { - Location eyeLocation = this.platformPlayer().getEyeLocation(); - return new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ()); + return Vec3d.distanceToSqr(this.getEyePos(), pos) < d * d; } @Override @@ -620,7 +739,7 @@ public class BukkitServerPlayer extends Player { private void predictNextBlockToMine() { double range = getCachedInteractionRange() + Config.extendedInteractionRange(); - RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result == null) { if (!this.clientSideCanBreak) { setClientSideCanBreakBlock(true); @@ -760,7 +879,7 @@ public class BukkitServerPlayer extends Player { try { org.bukkit.entity.Player player = platformPlayer(); double range = getCachedInteractionRange(); - RayTraceResult result = player.rayTraceBlocks(range, FluidCollisionMode.NEVER); + RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER); if (result == null) return; Block hitBlock = result.getHitBlock(); if (hitBlock == null) return; @@ -1200,6 +1319,9 @@ public class BukkitServerPlayer extends Player { @Override public void removeTrackedChunk(long chunkPos) { this.trackedChunks.remove(chunkPos); + if (Config.entityCullingRayTracing()) { + this.culling.removeLastVisitChunkIfMatches((int) chunkPos, (int) (chunkPos >> 32)); + } } @Override @@ -1276,6 +1398,35 @@ public class BukkitServerPlayer extends Player { } } + @Override + public void setEntityCullingViewDistanceScale(double value) { + value = Math.min(Math.max(0.125, value), 8); + this.culling.setDistanceScale(value); + 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 this.enableEntityCulling; + } + + @Override + public void setEnableFurnitureDebug(boolean enable) { + this.enableFurnitureDebug = enable; + platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENABLE_FURNITURE_DEBUG), PersistentDataType.BOOLEAN, enable); + } + + @Override + public boolean enableFurnitureDebug() { + return enableFurnitureDebug; + } + @Override public void giveExperiencePoints(int xpPoints) { platformPlayer().giveExp(xpPoints); @@ -1340,4 +1491,62 @@ public class BukkitServerPlayer extends Player { public void clearTrackedBlockEntities() { 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()); + } + + @Override + public void playParticle(Key particleId, double x, double y, double z) { + Particle particle = Registry.PARTICLE_TYPE.get(KeyUtils.toNamespacedKey(particleId)); + if (particle != null) { + platformPlayer().getWorld().spawnParticle(particle, List.of(platformPlayer()), null, x, y, z, 1, 0, 0,0, 0, null, false); + } + } + + public Location getEyeLocation() { + org.bukkit.entity.Player player = platformPlayer(); + Location eyeLocation = player.getEyeLocation(); + Entity vehicle = player.getVehicle(); + if (vehicle != null) { + Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer()); + eyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos)); + } + return eyeLocation; + } + + public Vec3d getEyePos() { + org.bukkit.entity.Player player = platformPlayer(); + Entity vehicle = player.getVehicle(); + if (vehicle != null) { + Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer()); + return new Vec3d(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos)); + } else { + Location location = player.getLocation(); + return new Vec3d(location.getX(), location.getY() + player.getEyeHeight(), location.getZ()); + } + } + + private RayTraceResult rayTrace(Location start, double range, FluidCollisionMode mode) { + return start.getWorld().rayTraceBlocks(start, start.getDirection(), range, mode); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index f9cc52975..81396c16e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -26,13 +26,9 @@ public final class EntityUtils { } public static BlockPos getOnPos(Player player) { - try { - Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); - Object blockPos = CoreReflections.method$Entity$getOnPos.invoke(serverPlayer, 1.0E-5F); - return LocationUtils.fromBlockPos(blockPos); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); + Object blockPos = FastNMS.INSTANCE.method$Entity$getOnPos(serverPlayer); + return LocationUtils.fromBlockPos(blockPos); } public static Entity spawnEntity(World world, Location loc, EntityType type, Consumer function) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index c54cb0d55..98ef141a8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -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 { diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index a2bfdbeef..24e0bcccf 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -129,6 +129,18 @@ unset_locale: usage: - /ce feature locale unset +set_entity_view_distance_scale: + enable: true + permission: ce.command.admin.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 @@ -258,6 +270,13 @@ debug_generate_internal_assets: - /craftengine debug generate-internal-assets - /ce debug generate-internal-assets +debug_furniture: + enable: true + permission: ce.command.debug.furniture + usage: + - /craftengine debug furniture + - /ce debug furniture + debug_test: enable: true permission: ce.command.debug.test diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 0d6efafb4..64b7eef86 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -197,6 +197,8 @@ resource-pack: deny-non-minecraft-request: true # Generates a single-use, time-limited download link for each player. one-time-token: true + # Improved validation for deny-non-minecraft-request and one-time-token; do not enable on offline servers or validation will fail. + strict-validation: false rate-limiting: # Maximum bandwidth per second to prevent server instability for other players during resource pack downloads max-bandwidth-per-second: 5_000_000 # 5MB/s @@ -552,10 +554,23 @@ chunk-system: remove: [] convert: {} +# [Premium Exclusive] client-optimization: - # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. + # Requires a restart to fully apply. entity-culling: 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-distance: 64 # -1 = no limit + # Determining the number of threads to execute these raytrace operations + threads: 1 + # Limit the maximum number of entities with visibility changes per tick for one player + # This helps mitigate client-side performance impacts and server-side bandwidth spikes caused by a large number of entities appearing. + rate-limiting: + enable: true + bucket-size: 1000 + restore-per-tick: 25 # Enables or disables debug mode debug: @@ -563,4 +578,6 @@ debug: packet: false furniture: false item: false - resource-pack: false \ No newline at end of file + resource-pack: false + block: false + entity-culling: false \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml index 5aba431e7..120c02811 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/sofa.yml @@ -93,7 +93,7 @@ items: state: barrier entity-renderer: item: default:sofa - yaw: 90 + rotation: 90 facing=north,shape=straight: state: barrier entity-renderer: @@ -102,12 +102,12 @@ items: state: barrier entity-renderer: item: default:sofa - yaw: 180 + rotation: 180 facing=west,shape=straight: state: barrier entity-renderer: item: default:sofa - yaw: 270 + rotation: 270 facing=east,shape=inner_left: state: barrier entity-renderer: @@ -116,22 +116,22 @@ items: state: barrier entity-renderer: item: default:sofa_inner - yaw: 270 + rotation: 270 facing=south,shape=inner_left: state: barrier entity-renderer: item: default:sofa_inner - yaw: 90 + rotation: 90 facing=west,shape=inner_left: state: barrier entity-renderer: item: default:sofa_inner - yaw: 180 + rotation: 180 facing=east,shape=inner_right: state: barrier entity-renderer: item: default:sofa_inner - yaw: 90 + rotation: 90 facing=north,shape=inner_right: state: barrier entity-renderer: @@ -140,12 +140,12 @@ items: state: barrier entity-renderer: item: default:sofa_inner - yaw: 180 + rotation: 180 facing=west,shape=inner_right: state: barrier entity-renderer: item: default:sofa_inner - yaw: 270 + rotation: 270 variants: facing=east,shape=inner_left: appearance: facing=east,shape=inner_left diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 1a5a03297..2435ae04e 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -27,13 +27,17 @@ items: texture: minecraft:item/custom/topaz blocks: default:topaz_ore: + behavior: + type: drop_experience_block + amount: 3~7 + condition: + type: enchantment + predicate: minecraft:silk_touch<=0 loot: template: default:loot_table/ore arguments: ore_drop: default:topaz ore_block: default:topaz_ore - min_exp: 3 - max_exp: 7 settings: template: default:settings/ore arguments: @@ -45,13 +49,17 @@ blocks: arguments: path: minecraft:block/custom/topaz_ore default:deepslate_topaz_ore: + behavior: + type: drop_experience_block + amount: 3~7 + condition: + type: enchantment + predicate: minecraft:silk_touch<=0 loot: template: default:loot_table/ore arguments: ore_drop: default:topaz ore_block: default:deepslate_topaz_ore - min_exp: 3 - max_exp: 7 settings: template: default:settings/deepslate_ore arguments: diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml index be22a435c..66e36f644 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/bench.yml @@ -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 diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml index 15194da75..4fdd08219 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/flower_basket.yml @@ -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: default:flower_basket_wall: material: nether_brick model: minecraft:item/custom/flower_basket_wall + item-name: default:flower_basket_ceiling: material: nether_brick model: minecraft:item/custom/flower_basket_ceiling + item-name: 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 @@ -49,13 +59,12 @@ furniture: width: 0.7 height: 0.5 interactive: true + invisible: 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: @@ -67,6 +76,7 @@ furniture: width: 0.46 height: 0.75 interactive: true + invisible: true - type: interaction can-use-item-on: true can-be-hit-by-projectile: true @@ -75,14 +85,12 @@ furniture: width: 0.46 height: 0.75 interactive: true + invisible: 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: @@ -93,4 +101,5 @@ furniture: position: 0,-0.7,0 width: 0.7 height: 0.7 - interactive: true \ No newline at end of file + interactive: true + invisible: true \ No newline at end of file diff --git a/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml index 9ea08846d..a399a75d3 100644 --- a/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml +++ b/common-files/src/main/resources/resources/default/configuration/furniture/wooden_chair.yml @@ -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: diff --git a/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml b/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml index 3550304a2..c254f682e 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates/block_states.yml @@ -1834,354 +1834,354 @@ templates: state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 east=true,north=false,south=false,waterlogged=false,west=false: state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=false,waterlogged=false,west=false: state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=false,west=false: state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 east=false,north=false,south=false,waterlogged=false,west=true: state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=false,waterlogged=false,west=false: state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=false,west=false: state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=false,waterlogged=false,west=true: state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=false,west=false: state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=true,south=false,waterlogged=false,west=true: state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=false,west=true: state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=true,waterlogged=false,west=false: state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=true,south=false,waterlogged=false,west=true: state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=false,west=true: state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=false,west=true: state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=true,north=true,south=true,waterlogged=false,west=true: state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=false,south=false,waterlogged=true,west=false: state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 east=true,north=false,south=false,waterlogged=true,west=false: state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=false,waterlogged=true,west=false: state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=true,west=false: state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 east=false,north=false,south=false,waterlogged=true,west=true: state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=false,waterlogged=true,west=false: state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=true,west=false: state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=false,waterlogged=true,west=true: state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=true,west=false: state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=true,south=false,waterlogged=true,west=true: state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=false,north=false,south=true,waterlogged=true,west=true: state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 east=true,north=true,south=true,waterlogged=true,west=false: state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=false] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=true,south=false,waterlogged=true,west=true: state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=true,north=false,south=true,waterlogged=true,west=true: state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 270 + rotation: 270 east=false,north=true,south=true,waterlogged=true,west=true: state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 east=true,north=true,south=true,waterlogged=true,west=true: state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=true] entity-renderer: - item: ${fence_post_item} - yaw: 180 + rotation: 180 scale: 1.0003 translation: 0,0.0001,0 - item: ${fence_side_item} - yaw: 0 + rotation: 0 - item: ${fence_side_item} - yaw: 90 + rotation: 90 - item: ${fence_side_item} - yaw: 180 + rotation: 180 - item: ${fence_side_item} - yaw: 270 + rotation: 270 variants: waterlogged=true: settings: diff --git a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml index 24d3c1878..c089ddca6 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml @@ -180,7 +180,6 @@ templates: # ore_block: the ore block # ore_drop: the drops of the ore material # ore_drop_count: the amount of the ore materials - # exp: the exp to drop default:loot_table/ore: pools: - rolls: 1 @@ -203,8 +202,6 @@ templates: formula: type: ore_drops - type: explosion_decay - - type: drop_exp - count: ${exp:-2~4} # Using Silk Touch or shears will cause the leaves block itself to drop. # Using Fortune, however, increases the drop rates of sticks and saplings. diff --git a/common-files/src/main/resources/translations/de.yml b/common-files/src/main/resources/translations/de.yml index 5ce8d8ef8..1d523d817 100644 --- a/common-files/src/main/resources/translations/de.yml +++ b/common-files/src/main/resources/translations/de.yml @@ -160,8 +160,8 @@ warning.config.sound.missing_name: "Problem in Datei gefunden - warning.config.jukebox_song.duplicate: "Problem in Datei gefunden - Doppelter Jukebox-Song ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." warning.config.jukebox_song.missing_sound: "Problem in Datei gefunden - Beim Jukebox-Song '' fehlt das erforderliche 'sound'-Argument." warning.config.furniture.duplicate: "Problem in Datei gefunden - Doppeltes Furniture ''. Bitte prüfe, ob dieselbe Konfiguration in anderen Dateien vorhanden ist." -warning.config.furniture.missing_placement: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'placement'-Argument." -warning.config.furniture.element.missing_item: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'item'-Argument für eines seiner Elemente." +warning.config.furniture.missing_variants: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'variants'-Argument." +warning.config.furniture.element.item_display.missing_item: "Problem in Datei gefunden - Beim Furniture '' fehlt das erforderliche 'item'-Argument für eines seiner Elemente." warning.config.furniture.settings.unknown: "Problem in Datei gefunden - Das Furniture '' verwendet einen unbekannten Einstellungs-Typ ''." warning.config.furniture.hitbox.invalid_type: "Problem in Datei gefunden - Das Furniture '' verwendet einen ungültigen Hitbox-Typ ''." warning.config.furniture.hitbox.custom.invalid_entity: "Problem in Datei gefunden - Das Furniture '' verwendet eine benutzerdefinierte Hitbox mit ungültigem Entity-Typ ''." diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index cd0751c7b..b4b992ed4 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -38,6 +38,20 @@ argument.parse.failure.aggregate.missing: "Missing component ''Invalid component '': " argument.parse.failure.either: "Could not resolve or from ''" argument.parse.failure.namedtextcolor: "'' is not a named text color" +info.pack.load: "Loaded pack: . Default namespace: " +info.resource.load: "Loaded in ms ()" +info.resource_pack.start: "Generating resource pack..." +info.resource_pack.generate: "Generated resource pack in ms" +info.resource_pack.validate: "Validated resource pack in ms" +info.resource_pack.optimize: "Optimized resource pack in ms" +info.resource_pack.optimize.json: "> Optimizing json files..." +info.resource_pack.optimize.texture: "> Optimizing textures..." +info.resource_pack.optimize.result: "□ Before/After/Ratio: KB/ KB/%" +info.resource_pack.create: "Created resource pack zip in ms" +info.resource_pack.upload: "Completed uploading resource pack" +info.host.self.netty_server: "Netty HTTP server started on port: " +info.host.cache.load: "[] Loaded cached resource pack metadata" +info.compatibility: "[Compatibility] hooked" command.reload.config.success: "Configs reloaded in ms. (Async: ms | Sync: ms)" command.reload.config.failure: "Config reload failed. Check console logs." command.reload.pack.success: "Resource pack reloaded in ms." @@ -72,8 +86,10 @@ command.upload.on_progress: "Started uploading progress. Check the consol command.send_resource_pack.success.single: "Sent resource pack to ." command.send_resource_pack.success.multiple: "Send resource packs to players." command.locale.set.failure: "Invalid locale format: " -command.locale.set.success: "Updated selected locale to for " +command.locale.set.success: "Selected locale has been set to for " command.locale.unset.success: "Cleared selected locale for " +command.entity_view_distance_scale.set.success: "Entity view distance scale updated to for " +command.entity_culling.toggle.success: "Entity culling status updated to for " warning.network.resource_pack.unverified_uuid: "Player is attempting to request a resource pack using a UUID () that is not authenticated by the server." warning.config.pack.duplicated_files: "Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section." warning.config.yaml.duplicated_key: "Issue found in file - Found duplicated key '' at line , this might cause unexpected results." @@ -89,6 +105,32 @@ warning.config.type.vec3d: "Issue found in file - Failed to load warning.config.type.map: "Issue found in file - Failed to load '': Cannot cast '' to Map type for option ''." warning.config.type.aabb: "Issue found in file - Failed to load '': Cannot cast '' to AABB type for option ''." warning.config.type.snbt.invalid_syntax: "Issue found in file - Failed to load '': Invalid snbt syntax ''." +warning.config.type.snbt.invalid_syntax.parse_error: " at position : " +warning.config.type.snbt.invalid_syntax.here: "<--[HERE]" +warning.config.type.snbt.parser.expected_string_uuid: "Expected a string representing a valid UUID" +warning.config.type.snbt.parser.expected_number_or_boolean: "Expected a number or a boolean" +warning.config.type.snbt.parser.trailing: "Unexpected trailing data" +warning.config.type.snbt.parser.expected.compound: "Expected compound tag" +warning.config.type.snbt.parser.number_parse_failure: "Failed to parse number: " +warning.config.type.snbt.parser.expected_hex_escape: "Expected a character literal of length " +warning.config.type.snbt.parser.invalid_codepoint: "Invalid Unicode character value: " +warning.config.type.snbt.parser.no_such_operation: "No such operation: " +warning.config.type.snbt.parser.expected_integer_type: "Expected an integer number" +warning.config.type.snbt.parser.expected_float_type: "Expected a floating point number" +warning.config.type.snbt.parser.expected_non_negative_number: "Expected a non-negative number" +warning.config.type.snbt.parser.invalid_character_name: "Invalid Unicode character name" +warning.config.type.snbt.parser.invalid_array_element_type: "Invalid array element type" +warning.config.type.snbt.parser.invalid_unquoted_start: "Unquoted strings can't start with digits 0-9, + or -" +warning.config.type.snbt.parser.expected_unquoted_string: "Expected a valid unquoted string" +warning.config.type.snbt.parser.invalid_string_contents: "Invalid string contents" +warning.config.type.snbt.parser.expected_binary_numeral: "Expected a binary number" +warning.config.type.snbt.parser.underscore_not_allowed: "Underscore is not allowed in binary numerals" +warning.config.type.snbt.parser.expected_decimal_numeral: "Expected a decimal number" +warning.config.type.snbt.parser.expected_hex_numeral: "Expected a hexadecimal number" +warning.config.type.snbt.parser.empty_key: "Key cannot be empty" +warning.config.type.snbt.parser.leading_zero_not_allowed: "Decimal numbers can't start with 0" +warning.config.type.snbt.parser.infinity_not_allowed: "Non-finite numbers are not allowed" +warning.config.type.snbt.parser.incorrect: "Expected literal " warning.config.number.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for number argument." warning.config.number.invalid_type: "Issue found in file - The config '' is using an invalid number argument type ''." warning.config.number.missing_argument: "Issue found in file - The config '' is missing the argument for 'number'." @@ -160,6 +202,7 @@ warning.config.recipe.smithing_transform.post_processor.missing_type: "I warning.config.recipe.smithing_transform.post_processor.invalid_type: "Issue found in file - The smithing transform recipe '' is using an invalid post processor type ''." warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "Issue found in file - The smithing transform recipe '' is missing the required argument 'components' for post-processors 'keep_components'." warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "Issue found in file - The smithing transform recipe '' is missing the required argument 'tags' for post-processors 'keep_tags'." +warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths: "Issue found in file - The smithing transform recipe '' is missing the required argument 'paths' for post-processors 'keep_custom_data'." warning.config.recipe.smithing_transform.missing_base: "Issue found in file - The smithing transform recipe '' is missing the required 'base' argument." warning.config.recipe.smithing_trim.missing_base: "Issue found in file - The smithing trim recipe '' is missing the required 'base' argument." warning.config.recipe.smithing_trim.missing_template_type: "Issue found in file - The smithing trim recipe '' is missing the required 'template-type' argument." @@ -188,8 +231,9 @@ warning.config.sound.missing_name: "Issue found in file - The so warning.config.jukebox_song.duplicate: "Issue found in file - Duplicated jukebox song ''. Please check if there is the same configuration in other files." warning.config.jukebox_song.missing_sound: "Issue found in file - The jukebox song '' is missing the required 'sound' argument." warning.config.furniture.duplicate: "Issue found in file - Duplicated furniture ''. Please check if there is the same configuration in other files." -warning.config.furniture.missing_placement: "Issue found in file - The furniture '' is missing the required 'placement' argument." -warning.config.furniture.element.missing_item: "Issue found in file - The furniture '' is missing the required 'item' argument for one of its elements." +warning.config.furniture.missing_variants: "Issue found in file - The furniture '' is missing the required 'variants' argument." +warning.config.furniture.element.invalid_type: "Issue found in file - The furniture '' is using an invalid element type ''." +warning.config.furniture.element.item_display.missing_item: "Issue found in file - The furniture '' is missing the required 'item' argument for 'item_display' element." warning.config.furniture.settings.unknown: "Issue found in file - The furniture '' is using an unknown setting type ''." warning.config.furniture.hitbox.invalid_type: "Issue found in file - The furniture '' is using an invalid hitbox type ''." warning.config.furniture.hitbox.custom.invalid_entity: "Issue found in file - The furniture '' is using a custom hitbox with invalid entity type ''." diff --git a/common-files/src/main/resources/translations/es.yml b/common-files/src/main/resources/translations/es.yml index 0a262c0f3..c52e59568 100644 --- a/common-files/src/main/resources/translations/es.yml +++ b/common-files/src/main/resources/translations/es.yml @@ -111,8 +111,8 @@ warning.config.sound.missing_name: "Problema encontrado en el archivo Problema encontrado en el archivo - Canción de tocadiscos duplicada ''. Verifica si hay la misma configuración en otros archivos." warning.config.jukebox_song.missing_sound: "Problema encontrado en el archivo - La canción de tocadiscos '' carece del argumento requerido 'sound'." warning.config.furniture.duplicate: "Problema encontrado en el archivo - Mueble duplicado ''. Verifica si hay la misma configuración en otros archivos." -warning.config.furniture.missing_placement: "Problema encontrado en el archivo - El mueble '' carece del argumento requerido 'placement'." -warning.config.furniture.element.missing_item: "Problema encontrado en el archivo - El mueble '' carece del argumento requerido 'item' para uno de sus elementos." +warning.config.furniture.missing_variants: "Problema encontrado en el archivo - El mueble '' carece del argumento requerido 'variants'." +warning.config.furniture.element.item_display.missing_item: "Problema encontrado en el archivo - El mueble '' carece del argumento requerido 'item' para uno de sus elementos." warning.config.furniture.settings.unknown: "Problema encontrado en el archivo - El mueble '' está usando un tipo de configuración desconocido ''." warning.config.furniture.hitbox.invalid_type: "Problema encontrado en el archivo - El mueble '' está usando un tipo de hitbox inválido ''." warning.config.furniture.hitbox.custom.invalid_entity: "Problema encontrado en el archivo - El mueble '' está usando un hitbox personalizado con un tipo de entidad inválido ''." diff --git a/common-files/src/main/resources/translations/fr_fr.yml b/common-files/src/main/resources/translations/fr_fr.yml index 49bad7ee0..cf519f058 100644 --- a/common-files/src/main/resources/translations/fr_fr.yml +++ b/common-files/src/main/resources/translations/fr_fr.yml @@ -175,8 +175,8 @@ warning.config.sound.missing_name: "Problème trouvé dans le fichier Problème trouvé dans le fichier - Chanson de jukebox dupliquée ''. Vérifiez s’il existe la même configuration dans d’autres fichiers." warning.config.jukebox_song.missing_sound: "Problème trouvé dans le fichier - La chanson de jukebox '' manque l’argument obligatoire 'sound'." warning.config.furniture.duplicate: "Problème trouvé dans le fichier - Meuble dupliqué ''. Vérifiez s’il existe la même configuration dans d’autres fichiers." -warning.config.furniture.missing_placement: "Problème trouvé dans le fichier - Le meuble '' manque l’argument obligatoire 'placement'." -warning.config.furniture.element.missing_item: "Problème trouvé dans le fichier - Le meuble '' manque l’argument obligatoire 'item' pour un de ses éléments." +warning.config.furniture.missing_variants: "Problème trouvé dans le fichier - Le meuble '' manque l’argument obligatoire 'variants'." +warning.config.furniture.element.item_display.missing_item: "Problème trouvé dans le fichier - Le meuble '' manque l’argument obligatoire 'item' pour un de ses éléments." warning.config.furniture.settings.unknown: "Problème trouvé dans le fichier - Le meuble '' utilise un type de paramètre inconnu ''." warning.config.furniture.hitbox.invalid_type: "Problème trouvé dans le fichier - Le meuble '' utilise un type de hitbox invalide ''." warning.config.furniture.hitbox.custom.invalid_entity: "Problème trouvé dans le fichier - Le meuble '' utilise un hitbox personnalisé avec un type d’entité invalide ''." diff --git a/common-files/src/main/resources/translations/ru_ru.yml b/common-files/src/main/resources/translations/ru_ru.yml index af586317c..6e55efa40 100644 --- a/common-files/src/main/resources/translations/ru_ru.yml +++ b/common-files/src/main/resources/translations/ru_ru.yml @@ -148,8 +148,8 @@ warning.config.sound.missing_name: "Проблема найдена в warning.config.jukebox_song.duplicate: "Проблема найдена в файле - Дублированная песня музыкального автомата ''. Проверьте, есть ли такая же конфигурация в других файлах." warning.config.jukebox_song.missing_sound: "Проблема найдена в файле - В песне музыкального автомата '' отсутствует необходимый 'sound' аргумент." warning.config.furniture.duplicate: "Проблема найдена в файле - Дублированная мебель ''. Проверьте, есть ли такая же конфигурация в других файлах." -warning.config.furniture.missing_placement: "Проблема найдена в файле - В мебели '' отсутствует необходимый 'placement' аргумент." -warning.config.furniture.element.missing_item: "Проблема найдена в файле - В мебели '' отсутствует необходимый 'item' аргумент для одного из его элементов." +warning.config.furniture.missing_variants: "Проблема найдена в файле - В мебели '' отсутствует необходимый 'variants' аргумент." +warning.config.furniture.element.item_display.missing_item: "Проблема найдена в файле - В мебели '' отсутствует необходимый 'item' аргумент для одного из его элементов." warning.config.furniture.settings.unknown: "Проблема найдена в файле - Мебель '' использует неизвестный тип настройки ''." warning.config.furniture.hitbox.invalid_type: "Проблема найдена в файле - Мебель '' использует недопустимый тип хитбокса ''." warning.config.furniture.hitbox.custom.invalid_entity: "Проблема найдена в файле - Мебель '' использует пользовательский хитбокс с недопустимым типом сущности ''." diff --git a/common-files/src/main/resources/translations/tr.yml b/common-files/src/main/resources/translations/tr.yml index 94a56309f..f826f3cc7 100644 --- a/common-files/src/main/resources/translations/tr.yml +++ b/common-files/src/main/resources/translations/tr.yml @@ -110,8 +110,8 @@ warning.config.sound.missing_name: " dosyasında sorun bulundu - warning.config.jukebox_song.duplicate: " dosyasında sorun bulundu - Yinelenen müzik çalar şarkısı ''. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin." warning.config.jukebox_song.missing_sound: " dosyasında sorun bulundu - '' müzik çalar şarkısı gerekli 'sound' argümanı eksik." warning.config.furniture.duplicate: " dosyasında sorun bulundu - Yinelenen mobilya ''. Diğer dosyalarda aynı yapılandırmanın olup olmadığını kontrol edin." -warning.config.furniture.missing_placement: " dosyasında sorun bulundu - '' mobilyası gerekli 'placement' argümanı eksik." -warning.config.furniture.element.missing_item: " dosyasında sorun bulundu - '' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik." +warning.config.furniture.missing_variants: " dosyasında sorun bulundu - '' mobilyası gerekli 'variants' argümanı eksik." +warning.config.furniture.element.item_display.missing_item: " dosyasında sorun bulundu - '' mobilyası, elementlerinden biri için gerekli 'item' argümanı eksik." warning.config.furniture.settings.unknown: " dosyasında sorun bulundu - '' mobilyası bilinmeyen bir ayar türü '' kullanıyor." warning.config.furniture.hitbox.invalid_type: " dosyasında sorun bulundu - '' mobilyası geçersiz bir hitbox türü '' kullanıyor." warning.config.furniture.hitbox.custom.invalid_entity: " dosyasında sorun bulundu - '' mobilyası, geçersiz varlık türü '' olan özel bir hitbox kullanıyor." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index da06eeff2..71fbf6f5e 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -38,6 +38,20 @@ argument.parse.failure.aggregate.missing: "缺少组件 ''" argument.parse.failure.aggregate.failure: "无效的组件 '': " argument.parse.failure.either: "无法从 '' 解析 " argument.parse.failure.namedtextcolor: "'' 不是颜色代码" +info.pack.load: "已加载包: . 默认命名空间: " +info.resource.load: "加载 耗时 ms ()" +info.resource_pack.start: "正在开始生成资源包..." +info.resource_pack.generate: "生成资源包耗时 ms" +info.resource_pack.validate: "验证资源包耗时 ms" +info.resource_pack.optimize: "优化资源包耗时 ms" +info.resource_pack.optimize.json: "> 正在优化json文件..." +info.resource_pack.optimize.texture: "> 正在优化贴图文件..." +info.resource_pack.optimize.result: "□ 优化前/优化后/比例: KB/ KB/%" +info.resource_pack.create: "创建资源包文件耗时 ms" +info.resource_pack.upload: "资源包上传完成" +info.host.self.netty_server: "Netty HTTP 服务已在端口 开启" +info.host.cache.load: "[] 已加载缓存的资源包元数据" +info.compatibility: "[兼容性] 已挂钩 " command.reload.config.success: "重新加载配置完成. 耗时 毫秒 (异步: ms | 同步: ms)" command.reload.config.failure: "重新加载配置失败, 请检查控制台日志" command.reload.pack.success: "资源包重新加载完成. 耗时 毫秒" @@ -88,6 +102,32 @@ warning.config.type.vector3f: "在文件 发现问题 - 无法 warning.config.type.vec3d: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度浮点数三维向量类型 (选项 '')" warning.config.type.map: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为映射类型 (选项 '')" warning.config.type.snbt.invalid_syntax: "在文件 发现问题 - 无法加载 '': 无效的 SNBT 语法 ''" +warning.config.type.snbt.invalid_syntax.parse_error: ",位于第个字符:" +warning.config.type.snbt.invalid_syntax.here: "<--[此处]" +warning.config.type.snbt.parser.expected_string_uuid: "应为表示有效UUID的字符串" +warning.config.type.snbt.parser.expected_number_or_boolean: "应为数字或布尔型" +warning.config.type.snbt.parser.trailing: "多余的尾随数据" +warning.config.type.snbt.parser.expected.compound: "应为复合标签" +warning.config.type.snbt.parser.number_parse_failure: "解析数字失败:" +warning.config.type.snbt.parser.expected_hex_escape: "字符字面量长度应为" +warning.config.type.snbt.parser.invalid_codepoint: "无效的Unicode字符码位:" +warning.config.type.snbt.parser.no_such_operation: "不存在的操作: " +warning.config.type.snbt.parser.expected_integer_type: "应为整数" +warning.config.type.snbt.parser.expected_float_type: "应为浮点数" +warning.config.type.snbt.parser.expected_non_negative_number: "应为非负数" +warning.config.type.snbt.parser.invalid_character_name: "无效的Unicode字符名称" +warning.config.type.snbt.parser.invalid_array_element_type: "无效的数组元素类型" +warning.config.type.snbt.parser.invalid_unquoted_start: "无引号字符串不能以数字0-9、+或-开头" +warning.config.type.snbt.parser.expected_unquoted_string: "应为有效的无引号字符串" +warning.config.type.snbt.parser.invalid_string_contents: "无效的字符串内容" +warning.config.type.snbt.parser.expected_binary_numeral: "应为二进制数" +warning.config.type.snbt.parser.underscore_not_allowed: "数字的开头和结尾不允许使用下划线字符" +warning.config.type.snbt.parser.expected_decimal_numeral: "应为十进制数" +warning.config.type.snbt.parser.expected_hex_numeral: "应为十六进制数" +warning.config.type.snbt.parser.empty_key: "键不能为空" +warning.config.type.snbt.parser.leading_zero_not_allowed: "十进制数不能以0开头" +warning.config.type.snbt.parser.infinity_not_allowed: "不允许使用非有限数的数值" +warning.config.type.snbt.parser.incorrect: "应为字面量" warning.config.number.missing_type: "在文件 发现问题 - 配置项 '' 缺少数字类型所需的 'type' 参数" warning.config.number.invalid_type: "在文件 发现问题 - 配置项 '' 使用了无效的数字类型 ''" warning.config.number.missing_argument: "在文件 发现问题 - 配置项 '' 缺少数字参数" @@ -159,6 +199,7 @@ warning.config.recipe.smithing_transform.post_processor.missing_type: " warning.config.recipe.smithing_transform.post_processor.invalid_type: "在文件 发现问题 - 锻造升级配方 '' 使用了无效的后处理器类型 ''" warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components: "在文件 发现问题 - 锻造升级配方 '' 的 'keep_components' 后处理器缺少必需的 'components' 参数" warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "在文件 发现问题 - 锻造升级配方 '' 的 'keep_tags' 后处理器缺少必需的 'tags' 参数" +warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths: "在文件 发现问题 - 锻造升级配方 '' 的 'keep_custom_data' 后处理器缺少必需的 'paths' 参数" warning.config.recipe.smithing_transform.missing_base: "在文件 发现问题 - 锻造升级配方 '' 缺少必需的 'base' 参数" warning.config.recipe.smithing_trim.missing_base: "在文件 发现问题 - 锻造纹饰配方 '' 缺少必需的 'base' 参数" warning.config.recipe.smithing_trim.missing_template_type: "在文件 发现问题 - 锻造纹饰配方 '' 缺少必需的 'template-type' 参数" @@ -183,8 +224,8 @@ warning.config.sound.missing_name: "在文件 发现问题 - 音 warning.config.jukebox_song.duplicate: "在文件 发现问题 - 重复的唱片机歌曲 '' 请检查其他文件中是否存在相同配置" warning.config.jukebox_song.missing_sound: "在文件 发现问题 - 唱片机歌曲 '' 缺少必需的 'sound' 参数" warning.config.furniture.duplicate: "在文件 发现问题 - 重复的家具 '' 请检查其他文件中是否存在相同配置" -warning.config.furniture.missing_placement: "在文件 发现问题 - 家具 '' 缺少必需的 'placement' 参数" -warning.config.furniture.element.missing_item: "在文件 发现问题 - 家具 '' 的某个元素缺少必需的 'item' 参数" +warning.config.furniture.missing_variants: "在文件 发现问题 - 家具 '' 缺少必需的 'variants' 参数" +warning.config.furniture.element.item_display.missing_item: "在文件 发现问题 - 家具 '' 的 'item_display' 元素缺少必需的 'item' 参数" warning.config.furniture.settings.unknown: "在文件 发现问题 - 家具 '' 使用了未知的设置类型 ''" warning.config.furniture.hitbox.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的碰撞箱类型 ''" warning.config.furniture.hitbox.custom.invalid_entity: "在文件 发现问题 - 家具 '' 的自定义碰撞箱使用了无效的实体类型 ''" diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 1b0b09185..9f620cd82 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -31,6 +31,7 @@ import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.logger.Debugger; @@ -38,6 +39,7 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; @@ -52,6 +54,7 @@ import java.util.concurrent.ExecutionException; public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager { private static final JsonElement EMPTY_VARIANT_MODEL = MiscUtils.init(new JsonObject(), o -> o.addProperty("model", "minecraft:block/empty")); + private static final AABB DEFAULT_BLOCK_ENTITY_AABB = new AABB(-.5, -.5, -.5, .5, .5, .5); protected final BlockParser blockParser; protected final BlockStateMappingParser blockStateMappingParser; // 根据id获取自定义方块 @@ -258,6 +261,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem public class BlockStateMappingParser extends SectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"}; + private int count; @Override public String[] sectionId() { @@ -269,6 +273,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return LoadingSequence.BLOCK_STATE_MAPPING; } + @Override + public int count() { + return this.count; + } + + @Override + public void preProcess() { + this.count = 0; + } + @Override public void parseSection(Pack pack, Path path, Map section) throws LocalizedException { ExceptionCollector exceptionCollector = new ExceptionCollector<>(); @@ -299,6 +313,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>()); blockStateWrappers.add(beforeState); AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState); + this.count++; } exceptionCollector.throwIfPresent(); } @@ -328,6 +343,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState); } + @Override + public int count() { + return AbstractBlockManager.this.byId.size(); + } + public void addPendingConfigSection(PendingConfigSection section) { this.pendingConfigSections.add(section); } @@ -603,7 +623,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem BlockStateAppearance blockStateAppearance = new BlockStateAppearance( visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer")), - ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb") + parseCullingData(appearanceSection.get("entity-culling")) ); appearances.put(appearanceName, blockStateAppearance); if (anyAppearance == null) { @@ -643,7 +663,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } for (ImmutableBlockState possibleState : possibleStates) { possibleState.setVisualBlockState(appearance.blockState()); - possibleState.setEstimatedBoundingBox(appearance.estimateAABB()); + possibleState.setCullingData(appearance.cullingData()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -667,7 +687,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (visualState == null) { visualState = anyAppearance.blockState(); state.setVisualBlockState(visualState); - state.setEstimatedBoundingBox(anyAppearance.estimateAABB()); + state.setCullingData(anyAppearance.cullingData()); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); @@ -707,6 +727,18 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem }, () -> GsonHelper.get().toJson(section))); } + 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); + Map argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling"); + return new CullingData( + ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "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") + ); + } + @SuppressWarnings("unchecked") private Optional[]> parseBlockEntityRender(Object arguments) { if (arguments == null) return Optional.empty(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java index e2e78a9a4..50a18c120 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java @@ -41,6 +41,7 @@ public class BlockSettings { float friction = 0.6f; float speedFactor = 1f; float jumpFactor = 1f; + Map, 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 getCustomData(CustomDataType type) { + return (T) this.customData.get(type); + } + + public void clearCustomData() { + this.customData.clear(); + } + + @Nullable + @SuppressWarnings("unchecked") + public T removeCustomData(CustomDataType type) { + return (T) this.customData.remove(type); + } + + public void addCustomData(CustomDataType key, T value) { + this.customData.put(key, value); + } + public Set 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); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index d2b8d0189..d3e7f8c85 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -2,11 +2,12 @@ package net.momirealms.craftengine.core.block; 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.world.collision.AABB; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; import java.util.Optional; public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer, - AABB estimateAABB) { + @Nullable CullingData cullingData) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 9a4010ef8..a112628f5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -13,11 +13,11 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; @@ -43,7 +43,8 @@ public final class ImmutableBlockState { private BlockEntityType blockEntityType; @Nullable private BlockEntityElementConfig[] renderers; - private AABB estimatedBoundingBox; + @Nullable + private CullingData cullingData; ImmutableBlockState( Holder.Reference owner, @@ -89,12 +90,13 @@ public final class ImmutableBlockState { this.renderers = renderers; } - public void setEstimatedBoundingBox(AABB aabb) { - this.estimatedBoundingBox = aabb; + @Nullable + public CullingData cullingData() { + return cullingData; } - public AABB estimatedBoundingBox() { - return estimatedBoundingBox; + public void setCullingData(@Nullable CullingData cullingData) { + this.cullingData = cullingData; } public boolean hasBlockEntity() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java index 78dc7210a..fffeaaa89 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntity.java @@ -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); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index d7ca645e6..9c5f1d2e3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -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"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java index c4b725b06..46083cc4b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypes.java @@ -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 INACTIVE = register(BlockEntityTypeKeys.INACTIVE); public static BlockEntityType register(Key id) { BlockEntityType type = new BlockEntityType<>(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/InactiveBlockEntity.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/InactiveBlockEntity.java new file mode 100644 index 000000000..3aa928f83 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/InactiveBlockEntity.java @@ -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; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 4551ed667..461331e96 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -2,18 +2,19 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.world.Cullable; -import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental public class ConstantBlockEntityRenderer implements Cullable { private final BlockEntityElement[] elements; - public final AABB aabb; + public final CullingData cullingData; - public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) { + public ConstantBlockEntityRenderer(BlockEntityElement[] elements, @Nullable CullingData cullingData) { this.elements = elements; - this.aabb = aabb; + this.cullingData = cullingData; } @Override @@ -55,7 +56,11 @@ public class ConstantBlockEntityRenderer implements Cullable { } @Override - public AABB aabb() { - return this.aabb; + public CullingData cullingData() { + return this.cullingData; + } + + public boolean canCull() { + return this.cullingData != null; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java index 0d3c320ad..99346e5bc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfig.java @@ -11,5 +11,9 @@ public interface BlockEntityElementConfig { return null; } + default E createExact(World world, BlockPos pos, E previous) { + return null; + } + Class elementClass(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java index 2aba16476..c4959f0aa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigFactory.java @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.block.entity.render.element; import java.util.Map; @FunctionalInterface -public interface BlockEntityElementConfigFactory { +public interface BlockEntityElementConfigFactory { - BlockEntityElementConfig create(Map args); + BlockEntityElementConfig create(Map args); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java index 1dabfa3be..c355c4eb2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/element/BlockEntityElementConfigs.java @@ -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) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE) + public static void register(Key key, BlockEntityElementConfigFactory type) { + ((WritableRegistry>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE) .register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type); } public static BlockEntityElementConfig fromMap(Map 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 factory = (BlockEntityElementConfigFactory) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type); if (factory == null) { throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java index 12d09ee8c..85d4a1aba 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/tick/TickingBlockEntityImpl.java @@ -32,7 +32,7 @@ public class TickingBlockEntityImpl implements TickingBlo // 不是合法方块 if (!this.blockEntity.isValidBlockState(state)) { this.chunk.removeBlockEntity(pos); - Debugger.BLOCK_ENTITY.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null); + Debugger.BLOCK.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null); return; } try { diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java b/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java index 5c52db4e2..81ab02cf7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java @@ -11,6 +11,8 @@ import java.util.UUID; public interface Entity { Key type(); + boolean isValid(); + double x(); double y(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/data/EntityData.java b/core/src/main/java/net/momirealms/craftengine/core/entity/data/EntityData.java index 1351831e6..896473f0f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/data/EntityData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/data/EntityData.java @@ -12,14 +12,14 @@ public interface EntityData { 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); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/Billboard.java b/core/src/main/java/net/momirealms/craftengine/core/entity/display/Billboard.java similarity index 80% rename from core/src/main/java/net/momirealms/craftengine/core/entity/Billboard.java rename to core/src/main/java/net/momirealms/craftengine/core/entity/display/Billboard.java index 7533b658b..a415a8e35 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/Billboard.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/display/Billboard.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.entity; +package net.momirealms.craftengine.core.entity.display; public enum Billboard { FIXED(0), diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/ItemDisplayContext.java b/core/src/main/java/net/momirealms/craftengine/core/entity/display/ItemDisplayContext.java similarity index 87% rename from core/src/main/java/net/momirealms/craftengine/core/entity/ItemDisplayContext.java rename to core/src/main/java/net/momirealms/craftengine/core/entity/display/ItemDisplayContext.java index 239488019..fdbe09050 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/ItemDisplayContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/display/ItemDisplayContext.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.entity; +package net.momirealms.craftengine.core.entity.display; public enum ItemDisplayContext { NONE(0), diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java deleted file mode 100644 index 3f4d4bae4..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java +++ /dev/null @@ -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 placements; - private final Map>> events; - @Nullable - private final LootTable lootTable; - - private final AnchorType anyType; - - protected AbstractCustomFurniture(@NotNull Key id, - @NotNull FurnitureSettings settings, - @NotNull Map placements, - @NotNull Map>> 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 function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { - function.run(context); - } - } - - @Override - public Key id() { - return this.id; - } - - @Override - public Map 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; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java deleted file mode 100644 index 9e470cb6d..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java +++ /dev/null @@ -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; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 52ea8252d..23b3227ba 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -1,14 +1,18 @@ 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.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; @@ -21,7 +25,7 @@ import java.nio.file.Path; import java.util.*; public abstract class AbstractFurnitureManager implements FurnitureManager { - protected final Map byId = new HashMap<>(); + protected final Map byId = new HashMap<>(); private final CraftEngine plugin; private final FurnitureParser furnitureParser; // Cached command suggestions @@ -56,12 +60,12 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { } @Override - public Optional furnitureById(Key id) { + public Optional furnitureById(Key id) { return Optional.ofNullable(this.byId.get(id)); } @Override - public Map loadedFurniture() { + public Map loadedFurniture() { return Collections.unmodifiableMap(this.byId); } @@ -70,11 +74,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 +107,76 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { return LoadingSequence.FURNITURE; } - @SuppressWarnings("unchecked") + @Override + public int count() { + return AbstractFurnitureManager.this.byId.size(); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractFurnitureManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.furniture.duplicate"); } - EnumMap placements = new EnumMap<>(AnchorType.class); - Object placementObj = section.get("placement"); - Map 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 entry : placementMap.entrySet()) { - // anchor type - AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)); - Map placementArguments = MiscUtils.castToMap(entry.getValue(), false); - Optional optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset")); - // furniture display elements - List elements = new ArrayList<>(); - List> elementConfigs = (List>) placementArguments.getOrDefault("elements", List.of()); - for (Map 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 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 variants = new HashMap<>(); + for (Map.Entry e0 : variantsMap.entrySet()) { + String variantName = e0.getKey(); + Map variantArguments = ResourceConfigUtils.getAsMap(e0.getValue(), variantName); + Optional optionalLootSpawnOffset = Optional.ofNullable(variantArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset")); + List> elements = ResourceConfigUtils.parseConfigAsList(variantArguments.get("elements"), FurnitureElementConfigs::fromMap); + + // fixme 外部模型不应该在这 Optional 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 hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap); + List> hitboxes = ResourceConfigUtils.parseConfigAsList(variantArguments.get("hitboxes"), FurnitureHitBoxTypes::fromMap); if (hitboxes.isEmpty() && externalModel.isEmpty()) { hitboxes = List.of(defaultHitBox()); } - // rules - Map 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 - )); - } + variants.put(variantName, new FurnitureVariant( + variantName, + parseCullingData(section.get("entity-culling")), + 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) { + if (arguments instanceof Boolean b && !b) + return null; + if (!(arguments instanceof Map)) + return new CullingData(null, Config.entityCullingViewDistance(), 0.25, true); + Map argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling"); + return new CullingData( + ResourceConfigUtils.getOrDefault(argumentsMap.get("aabb"), it -> ResourceConfigUtils.getAsAABB(it, "aabb"), null), + ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"), + ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.25), "aabb-expansion"), + ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing") + ); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AnchorType.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AnchorType.java index d3a099b6f..8ec7b6749 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AnchorType.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AnchorType.java @@ -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]; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java deleted file mode 100644 index 19afdf32f..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java +++ /dev/null @@ -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 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 placements); - - Builder settings(FurnitureSettings settings); - - Builder lootTable(LootTable lootTable); - - Builder events(Map>> events); - - CustomFurniture build(); - } - - record Placement(AnchorType anchorType, - FurnitureElement[] elements, - HitBoxConfig[] hitBoxConfigs, - RotationRule rotationRule, - AlignmentRule alignmentRule, - Optional externalModel, - Optional dropOffset) { - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java index e5a2bcd59..9210c649e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java @@ -1,48 +1,274 @@ 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 net.momirealms.craftengine.core.world.collision.AABB; 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.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 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 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 = 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(); + if (aabb == null) { + List aabbs = new ArrayList<>(); + for (FurnitureHitBoxConfig hitBoxConfig : this.currentVariant.hitBoxConfigs()) { + hitBoxConfig.prepareForPlacement(position, aabbs::add); + } + return new CullingData(getMaxAABB(aabbs), parent.maxDistance, parent.aabbExpansion, parent.rayTracing); + } else { + Vector3f[] vertices = new Vector3f[] { + // 底面两个对角点 + new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), + new Vector3f((float) aabb.maxX, (float) aabb.minY, (float) aabb.maxZ), + // 顶面两个对角点 + new Vector3f((float) aabb.minX, (float) aabb.maxY, (float) aabb.minZ), + new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ) + }; + double minX = Double.MAX_VALUE, minY = aabb.minY; // Y方向不变 + double maxX = -Double.MAX_VALUE, maxY = aabb.maxY; // Y方向不变 + double minZ = Double.MAX_VALUE, maxZ = -Double.MAX_VALUE; + for (Vector3f vertex : vertices) { + Vec3d rotatedPos = getRelativePosition(position, vertex); + minX = Math.min(minX, rotatedPos.x); + minZ = Math.min(minZ, rotatedPos.z); + maxX = Math.max(maxX, rotatedPos.x); + maxZ = Math.max(maxZ, rotatedPos.z); + } + return new CullingData(new AABB(minX, minY, minZ, maxX, maxY, maxZ), + parent.maxDistance, parent.aabbExpansion, parent.rayTracing); + } + } + + private static @NotNull AABB getMaxAABB(List aabbs) { + double minX = 0; + double minY = 0; + double minZ = 0; + double maxX = 0; + double maxY = 0; + double maxZ = 0; + for (int i = 0; i < aabbs.size(); i++) { + AABB aabb = aabbs.get(i); + if (i == 0) { + minX = aabb.minX; + minY = aabb.minY; + minZ = aabb.minZ; + maxX = aabb.maxX; + maxY = aabb.maxY; + maxZ = aabb.maxZ; + } else { + 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); + } + } + return new AABB(minX, minY, minZ, maxX, maxY, maxZ); + } @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 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(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureColorSource.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureColorSource.java new file mode 100644 index 000000000..2a6427080 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureColorSource.java @@ -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) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfig.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfig.java new file mode 100644 index 000000000..cd5634893 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfig.java @@ -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 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 optionalVariant = accessor.variant(); + String variantName = null; + if (optionalVariant.isPresent()) { + variantName = optionalVariant.get(); + } else { + @SuppressWarnings("deprecation") + Optional 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 variants); + + Builder settings(FurnitureSettings settings); + + Builder lootTable(LootTable lootTable); + + Builder events(Map>> events); + + Builder behavior(FurnitureBehavior behavior); + + FurnitureConfig build(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfigImpl.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfigImpl.java new file mode 100644 index 000000000..a68613fef --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureConfigImpl.java @@ -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 variants; + private final Map>> events; + private final FurnitureBehavior behavior; + @Nullable + private final LootTable lootTable; + + private FurnitureConfigImpl(@NotNull Key id, + @NotNull FurnitureSettings settings, + @NotNull Map variants, + @NotNull Map>> 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 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 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 variants; + private FurnitureSettings settings; + private Map>> 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 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>> events) { + this.events = events; + return this; + } + + @Override + public Builder behavior(FurnitureBehavior behavior) { + this.behavior = behavior; + return this; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureDataAccessor.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureDataAccessor.java new file mode 100644 index 000000000..fb4785432 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureDataAccessor.java @@ -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() { + 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 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 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 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() { + 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); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java deleted file mode 100644 index 785b24c68..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java +++ /dev/null @@ -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 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(); - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureExtraData.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureExtraData.java deleted file mode 100644 index 72587f2a1..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureExtraData.java +++ /dev/null @@ -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() { - 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 fireworkExplosionColors() { - if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS)); - return Optional.empty(); - } - - public Optional dyedColor() { - if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR))); - return Optional.empty(); - } - - public Optional 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); - } - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java index e57baec7d..99d4ee3a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java @@ -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 cachedSuggestions(); - Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound); + Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor extraData, boolean playSound); - Optional furnitureById(Key id); + Optional furnitureById(Key id); - Map loadedFurniture(); + Map 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); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java index 298dbc877..f6bf10314 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java @@ -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, 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 getCustomData(CustomDataType type) { + return (T) this.customData.get(type); + } + + public void clearCustomData() { + this.customData.clear(); + } + + @Nullable + @SuppressWarnings("unchecked") + public T removeCustomData(CustomDataType type) { + return (T) this.customData.remove(type); + } + + public void addCustomData(CustomDataType 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); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureVariant.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureVariant.java new file mode 100644 index 000000000..206a5ddf7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureVariant.java @@ -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, + Optional dropOffset) { +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java deleted file mode 100644 index 961ea7420..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java +++ /dev/null @@ -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[] seats(); - - Optional clip(Vec3d min, Vec3d max); - - HitBoxPart[] parts(); - - HitBoxConfig config(); -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfig.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfig.java deleted file mode 100644 index fdc574861..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfig.java +++ /dev/null @@ -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 packets, Consumer collider, Consumer aabb); - - void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer aabbs); - - int[] acquireEntityIds(Supplier entityIdSupplier); - - SeatConfig[] seats(); - - Vector3f position(); - - boolean blocksBuilding(); - - boolean canBeHitByProjectile(); - - boolean canUseItemOn(); -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfigFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfigFactory.java deleted file mode 100644 index 32fb5f355..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxConfigFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.momirealms.craftengine.core.entity.furniture; - -import java.util.Map; - -public interface HitBoxConfigFactory { - - HitBoxConfig create(Map arguments); -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxPart.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxPart.java deleted file mode 100644 index cfa336889..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxPart.java +++ /dev/null @@ -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) { -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/EmptyFurnitureBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/EmptyFurnitureBehavior.java new file mode 100644 index 000000000..439348957 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/EmptyFurnitureBehavior.java @@ -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(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehavior.java new file mode 100644 index 000000000..655168a5a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehavior.java @@ -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 FurnitureTicker createSyncFurnitureTicker(T furniture) { + return null; + } + + default FurnitureTicker createAsyncBlockEntityTicker(T furniture) { + return null; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElement.java new file mode 100644 index 000000000..6a625f941 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElement.java @@ -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 collector); + + void show(Player player); + + void hide(Player player); + + default void deactivate() {} + + default void activate() {} +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfig.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfig.java new file mode 100644 index 000000000..31ab640f8 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfig.java @@ -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 create(@NotNull Furniture furniture); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigFactory.java new file mode 100644 index 000000000..5172948f3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigFactory.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.entity.furniture.element; + +import java.util.Map; + +public interface FurnitureElementConfigFactory { + + FurnitureElementConfig create(Map args); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigs.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigs.java new file mode 100644 index 000000000..7247cdf0e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigs.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.entity.furniture.element; + +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.registry.Registries; +import net.momirealms.craftengine.core.registry.WritableRegistry; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + +import java.util.Map; +import java.util.Optional; + +public class FurnitureElementConfigs { + public static final Key ITEM_DISPLAY = Key.of("craftengine:item_display"); + 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, FurnitureElementConfigFactory type) { + ((WritableRegistry>) BuiltInRegistries.FURNITURE_ELEMENT_TYPE) + .register(ResourceKey.create(Registries.FURNITURE_ELEMENT_TYPE.location(), key), type); + } + + public static FurnitureElementConfig fromMap(Map arguments) { + Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY); + @SuppressWarnings("unchecked") + FurnitureElementConfigFactory factory = (FurnitureElementConfigFactory) BuiltInRegistries.FURNITURE_ELEMENT_TYPE.getValue(type); + if (factory == null) { + throw new LocalizedResourceConfigException("warning.config.furniture.element.invalid_type", type.toString()); + } + return factory.create(arguments); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractHitBoxConfig.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/AbstractFurnitureHitBoxConfig.java similarity index 58% rename from core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractHitBoxConfig.java rename to core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/AbstractFurnitureHitBoxConfig.java index d7f8e3021..b121fbe02 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractHitBoxConfig.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/AbstractFurnitureHitBoxConfig.java @@ -1,16 +1,20 @@ -package net.momirealms.craftengine.core.entity.furniture; +package net.momirealms.craftengine.core.entity.furniture.hitbox; import net.momirealms.craftengine.core.entity.seat.SeatConfig; import org.joml.Vector3f; -public abstract class AbstractHitBoxConfig implements HitBoxConfig { +public abstract class AbstractFurnitureHitBoxConfig implements FurnitureHitBoxConfig { protected final SeatConfig[] seats; protected final Vector3f position; protected final boolean canUseItemOn; protected final boolean blocksBuilding; protected final boolean canBeHitByProjectile; - public AbstractHitBoxConfig(SeatConfig[] seats, Vector3f position, boolean canUseItemOn, boolean blocksBuilding, boolean canBeHitByProjectile) { + public AbstractFurnitureHitBoxConfig(SeatConfig[] seats, + Vector3f position, + boolean canUseItemOn, + boolean blocksBuilding, + boolean canBeHitByProjectile) { this.seats = seats; this.position = position; this.canUseItemOn = canUseItemOn; @@ -30,16 +34,16 @@ public abstract class AbstractHitBoxConfig implements HitBoxConfig { @Override public boolean blocksBuilding() { - return blocksBuilding; + return this.blocksBuilding; } @Override public boolean canBeHitByProjectile() { - return canBeHitByProjectile; + return this.canBeHitByProjectile; } @Override public boolean canUseItemOn() { - return canUseItemOn; + return this.canUseItemOn; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBox.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBox.java new file mode 100644 index 000000000..95f7b652a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBox.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.core.entity.furniture.hitbox; + +import net.momirealms.craftengine.core.entity.furniture.Collider; +import net.momirealms.craftengine.core.entity.player.Player; +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.List; +import java.util.Optional; +import java.util.function.Consumer; + +public interface FurnitureHitBox extends SeatOwner { + + Seat[] seats(); + + List colliders(); + + List parts(); + + void show(Player player); + + void hide(Player player); + + FurnitureHitBoxConfig config(); + + void collectVirtualEntityId(Consumer collector); + + default Optional clip(Vec3d min, Vec3d max) { + for (FurnitureHitboxPart value : parts()) { + Optional clip = value.aabb().clip(min, max); + if (clip.isPresent()) { + return clip; + } + } + return Optional.empty(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfig.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfig.java new file mode 100644 index 000000000..7c8325e00 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfig.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.entity.furniture.hitbox; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import net.momirealms.craftengine.core.entity.seat.SeatConfig; +import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.collision.AABB; +import org.joml.Vector3f; + +import java.util.function.Consumer; + +public interface FurnitureHitBoxConfig { + + H create(Furniture furniture); + + SeatConfig[] seats(); + + Vector3f position(); + + boolean blocksBuilding(); + + boolean canBeHitByProjectile(); + + boolean canUseItemOn(); + + void prepareForPlacement(WorldPosition targetPos, Consumer aabbConsumer); + +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfigFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfigFactory.java new file mode 100644 index 000000000..4d867d1ec --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxConfigFactory.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.entity.furniture.hitbox; + +import java.util.Map; + +public interface FurnitureHitBoxConfigFactory { + + FurnitureHitBoxConfig create(Map arguments); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxTypes.java similarity index 54% rename from core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java rename to core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxTypes.java index c005ee619..61fc5b871 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitBoxTypes.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.entity.furniture; +package net.momirealms.craftengine.core.entity.furniture.hitbox; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; @@ -10,20 +10,22 @@ import net.momirealms.craftengine.core.util.ResourceKey; import java.util.Map; import java.util.Optional; -public class HitBoxTypes { +public class FurnitureHitBoxTypes { public static final Key INTERACTION = Key.of("minecraft:interaction"); public static final Key SHULKER = Key.of("minecraft:shulker"); public static final Key HAPPY_GHAST = Key.of("minecraft:happy_ghast"); + public static final Key VIRTUAL = Key.of("minecraft:virtual"); public static final Key CUSTOM = Key.of("minecraft:custom"); - public static void register(Key key, HitBoxConfigFactory factory) { - ((WritableRegistry) BuiltInRegistries.HITBOX_FACTORY) - .register(ResourceKey.create(Registries.HITBOX_FACTORY.location(), key), factory); + public static void register(Key key, FurnitureHitBoxConfigFactory factory) { + ((WritableRegistry>) BuiltInRegistries.FURNITURE_HITBOX_TYPE) + .register(ResourceKey.create(Registries.FURNITURE_HITBOX_TYPE.location(), key), factory); } - public static HitBoxConfig fromMap(Map arguments) { - Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(HitBoxTypes.INTERACTION); - HitBoxConfigFactory factory = BuiltInRegistries.HITBOX_FACTORY.getValue(type); + public static FurnitureHitBoxConfig fromMap(Map arguments) { + Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(FurnitureHitBoxTypes.INTERACTION); + @SuppressWarnings("unchecked") + FurnitureHitBoxConfigFactory factory = (FurnitureHitBoxConfigFactory) BuiltInRegistries.FURNITURE_HITBOX_TYPE.getValue(type); if (factory == null) { throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.invalid_type", type.toString()); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitboxPart.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitboxPart.java new file mode 100644 index 000000000..acf13acd3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/hitbox/FurnitureHitboxPart.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.entity.furniture.hitbox; + +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.collision.AABB; + +public record FurnitureHitboxPart(int entityId, AABB aabb, Vec3d pos, boolean interactive) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/FurnitureTicker.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/FurnitureTicker.java new file mode 100644 index 000000000..1f034cbd9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/FurnitureTicker.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.entity.furniture.tick; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; + +public interface FurnitureTicker { + + void tick(T furniture); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/ItemEntity.java b/core/src/main/java/net/momirealms/craftengine/core/entity/item/ItemEntity.java similarity index 67% rename from core/src/main/java/net/momirealms/craftengine/core/entity/ItemEntity.java rename to core/src/main/java/net/momirealms/craftengine/core/entity/item/ItemEntity.java index 77483b9c1..120c168f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/ItemEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/item/ItemEntity.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.entity; +package net.momirealms.craftengine.core.entity.item; import net.momirealms.craftengine.core.item.Item; diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 36e3fcb76..6fd240dbb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.entity.AbstractEntity; +import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; @@ -38,6 +39,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void setClientSideWorld(World world); + public abstract void entityCullingTick(); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); @@ -189,6 +192,16 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void setSelectedLocale(@Nullable Locale locale); + public abstract void setEntityCullingViewDistanceScale(double value); + + public abstract void setEnableEntityCulling(boolean enable); + + public abstract boolean enableEntityCulling(); + + public abstract boolean enableFurnitureDebug(); + + public abstract void setEnableFurnitureDebug(boolean enableFurnitureDebug); + public abstract void giveExperiencePoints(int xpPoints); public abstract void giveExperienceLevels(int levels); @@ -209,9 +222,24 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void removeTrackedBlockEntities(Collection renders); + public abstract void addTrackedFurniture(int entityId, Furniture furniture); + public abstract void clearTrackedBlockEntities(); @Override public void remove() { } + + public abstract void playParticle(Key particleId, double x, double y, double z); + + public abstract void removeTrackedFurniture(int entityId); + + public abstract void clearTrackedFurniture(); + + public abstract WorldPosition eyePosition(); + + @Override + public boolean isValid() { + return this.isOnline(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java b/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java index 7801b8f9d..ca0c73679 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/projectile/ProjectileMeta.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.entity.projectile; -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.util.Key; import org.joml.Quaternionf; import org.joml.Vector3f; diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index df9e4fdff..cfa52da0b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -442,9 +442,14 @@ public abstract class AbstractFontManager implements FontManager { return LoadingSequence.EMOJI; } + @Override + public int count() { + return AbstractFontManager.this.emojis.size(); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { - if (emojis.containsKey(id)) { + if (AbstractFontManager.this.emojis.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.emoji.duplicate"); } String permission = (String) section.get("permission"); @@ -510,6 +515,11 @@ public abstract class AbstractFontManager implements FontManager { return LoadingSequence.IMAGE; } + @Override + public int count() { + return AbstractFontManager.this.images.size(); + } + @Override public void postProcess() { for (Map.Entry entry : this.idAllocators.entrySet()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index d6defc9b4..3104b4e53 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -351,6 +351,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl .toList(); registerArmorTrimPattern(trims); } + + @Override + public int count() { + return AbstractItemManager.this.equipments.size(); + } } public void addOrMergeEquipment(ComponentBasedEquipment equipment) { @@ -368,6 +373,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; private final Map idAllocators = new HashMap<>(); + @Override + public int count() { + return AbstractItemManager.this.customItemsById.size(); + } + private boolean isModernFormatRequired() { return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index 186588e32..64a67c8dd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.item; -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.projectile.ProjectileMeta; import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; import net.momirealms.craftengine.core.item.equipment.Equipment; @@ -134,6 +134,12 @@ public class ItemSettings { this.customData.clear(); } + @Nullable + @SuppressWarnings("unchecked") + public T removeCustomData(CustomDataType type) { + return (T) this.customData.remove(type); + } + public void addCustomData(CustomDataType key, T value) { this.customData.put(key, value); } @@ -440,8 +446,8 @@ public class ItemSettings { Key customTridentItemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("item"), "warning.config.item.settings.projectile.missing_item")); ItemDisplayContext displayType = ItemDisplayContext.valueOf(args.getOrDefault("display-transform", "NONE").toString().toUpperCase(Locale.ENGLISH)); Billboard billboard = Billboard.valueOf(args.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH)); - Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation"); - Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale"); + Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", 0), "translation"); + Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", 1), "scale"); Quaternionf rotation = ResourceConfigUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation"), "rotation"); double range = ResourceConfigUtils.getAsDouble(args.getOrDefault("range", 1), "range"); return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentsModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentsModifier.java index 143e4572f..e5f551690 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentsModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentsModifier.java @@ -1,12 +1,15 @@ package net.momirealms.craftengine.core.item.modifier; import com.google.gson.JsonElement; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.momirealms.craftengine.core.item.*; import net.momirealms.craftengine.core.plugin.CraftEngine; +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.Pair; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.snbt.TagParser; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; @@ -41,7 +44,12 @@ public class ComponentsModifier implements ItemDataModifier { if (string.startsWith("(json) ")) { return CraftEngine.instance().platform().jsonToSparrowNBT(GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonElement.class)); } else if (string.startsWith("(snbt) ")) { - return CraftEngine.instance().platform().snbtToSparrowNBT(string.substring("(snbt) ".length())); + String snbt = string.substring("(snbt) ".length()); + try { + return TagParser.parseTagFully(snbt); + } catch (CommandSyntaxException e) { + throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage()); + } } } return CraftEngine.instance().platform().javaToSparrowNBT(value); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java index 93d1ea157..657906f95 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeManager.java @@ -134,6 +134,11 @@ public abstract class AbstractRecipeManager implements RecipeManager { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return Math.max(0, AbstractRecipeManager.this.byId.size() - AbstractRecipeManager.this.dataPackRecipes.size()); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (!Config.enableRecipeSystem()) return; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java index 190ee8004..965772071 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java @@ -232,10 +232,12 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip public static final Key KEEP_COMPONENTS = Key.of("craftengine:keep_components"); public static final Key KEEP_TAGS = Key.of("craftengine:keep_tags"); public static final Key MERGE_ENCHANTMENTS = Key.of("craftengine:merge_enchantments"); + public static final Key KEEP_CUSTOM_DATA = Key.of("craftengine:keep_custom_data"); static { if (VersionHelper.isOrAbove1_20_5()) { register(KEEP_COMPONENTS, KeepComponents.FACTORY); + register(KEEP_CUSTOM_DATA, KeepCustomData.FACTORY); } else { register(KEEP_TAGS, KeepTags.FACTORY); } @@ -315,6 +317,42 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip } } + public static class KeepCustomData implements ItemDataProcessor { + public static final Factory FACTORY = new Factory(); + private final List paths; + + public KeepCustomData(List data) { + this.paths = data; + } + + @Override + public void accept(Item item1, Item item2, Item item3) { + for (String[] path : this.paths) { + Object dataObj = item1.getJavaTag((Object[]) path); + if (dataObj != null) { + item3.setTag(dataObj, (Object[]) path); + } + } + } + + @Override + public Key type() { + return ItemDataProcessors.KEEP_CUSTOM_DATA; + } + + public static class Factory implements ProcessorFactory { + + @Override + public ItemDataProcessor create(Map arguments) { + List paths = MiscUtils.getAsStringList(ResourceConfigUtils.requireNonNullOrThrow( + arguments.get("paths"), + "warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths") + ); + return new KeepCustomData(paths.stream().map(it -> it.split("\\.")).toList()); + } + } + } + public static class KeepComponents implements ItemDataProcessor { public static final Factory FACTORY = new Factory(); private final List components; @@ -339,6 +377,7 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip } public static class Factory implements ProcessorFactory { + private static final Key CUSTOM_DATA = Key.of("minecraft", "custom_data"); @Override public ItemDataProcessor create(Map arguments) { @@ -347,7 +386,7 @@ public class CustomSmithingTransformRecipe extends AbstractedFixedResultRecip throw new LocalizedResourceConfigException("warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components"); } List components = MiscUtils.getAsStringList(componentsObj); - return new KeepComponents(components.stream().map(Key::of).toList()); + return new KeepComponents(components.stream().map(Key::of).filter(it -> !CUSTOM_DATA.equals(it)).toList()); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 1aa740527..8c66bbff7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -407,7 +407,7 @@ public abstract class AbstractPackManager implements PackManager { } Pack pack = new Pack(path, new PackMeta(author, description, version, namespace), enable); this.loadedPacks.put(path.getFileName().toString(), pack); - this.plugin.logger().info("Loaded pack: " + pack.folder().getFileName() + ". Default namespace: " + namespace); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.pack.load", pack.folder().getFileName().toString(), namespace)); } } } catch (IOException e) { @@ -695,7 +695,12 @@ public abstract class AbstractPackManager implements PackManager { parser.loadAll(); parser.postProcess(); long t2 = System.nanoTime(); - this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms"); + int count = parser.count(); + if (parser.silentIfNotExists() && count == 0) { + continue; + } + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource.load", + parser.sectionId()[0], String.format("%.2f", ((t2 - t1) / 1_000_000.0)), String.valueOf(count))); } } @@ -720,7 +725,7 @@ public abstract class AbstractPackManager implements PackManager { @Override public void generateResourcePack() throws IOException { - this.plugin.logger().info("Generating resource pack..."); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.start")); long time1 = System.currentTimeMillis(); // Create cache data @@ -768,17 +773,17 @@ public abstract class AbstractPackManager implements PackManager { this.removeAllShaders(generatedPackPath); } long time2 = System.currentTimeMillis(); - this.plugin.logger().info("Generated resource pack in " + (time2 - time1) + "ms"); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.generate", String.valueOf(time2 - time1))); if (Config.validateResourcePack()) { this.validateResourcePack(generatedPackPath); } long time3 = System.currentTimeMillis(); - this.plugin.logger().info("Validated resource pack in " + (time3 - time2) + "ms"); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.validate", String.valueOf(time3 - time2))); if (Config.optimizeResourcePack()) { this.optimizeResourcePack(generatedPackPath); } long time4 = System.currentTimeMillis(); - this.plugin.logger().info("Optimized resource pack in " + (time4 - time3) + "ms"); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize", String.valueOf(time4 - time3))); Path finalPath = resourcePackPath(); Files.createDirectories(finalPath.getParent()); try { @@ -787,7 +792,7 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().severe("Error zipping resource pack", e); } long time5 = System.currentTimeMillis(); - this.plugin.logger().info("Created resource pack zip file in " + (time5 - time4) + "ms"); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.create", String.valueOf(time5 - time4))); this.generationEventDispatcher.accept(generatedPackPath, finalPath); } } @@ -1042,7 +1047,7 @@ public abstract class AbstractPackManager implements PackManager { } if (Config.optimizeJson()) { - this.plugin.logger().info("> Optimizing json files..."); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.json")); AtomicLong previousBytes = new AtomicLong(0L); AtomicLong afterBytes = new AtomicLong(0L); List> futures = new ArrayList<>(); @@ -1109,11 +1114,11 @@ public abstract class AbstractPackManager implements PackManager { long originalSize = previousBytes.get(); long optimizedSize = afterBytes.get(); double compressionRatio = ((double) optimizedSize / originalSize) * 100; - this.plugin.logger().info("□ Before/After/Ratio: " + formatSize(originalSize) + "/" + formatSize(optimizedSize) + "/" + String.format("%.2f%%", compressionRatio)); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.result", formatSize(originalSize), formatSize(optimizedSize), String.format("%.2f%%", compressionRatio))); } if (Config.optimizeTexture()) { - this.plugin.logger().info("> Optimizing textures..."); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.texture")); AtomicLong previousBytes = new AtomicLong(0L); AtomicLong afterBytes = new AtomicLong(0L); List> futures = new ArrayList<>(); @@ -1155,7 +1160,7 @@ public abstract class AbstractPackManager implements PackManager { long originalSize = previousBytes.get(); long optimizedSize = afterBytes.get(); double compressionRatio = ((double) optimizedSize / originalSize) * 100; - this.plugin.logger().info("□ Before/After/Ratio: " + formatSize(originalSize) + "/" + formatSize(optimizedSize) + "/" + String.format("%.2f%%", compressionRatio)); + this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.result", formatSize(originalSize), formatSize(optimizedSize), String.format("%.2f%%", compressionRatio))); } } @@ -1170,7 +1175,7 @@ public abstract class AbstractPackManager implements PackManager { " ".repeat(Math.max(0, emptyLength)) + "]"; return String.format( - "%s %d/%d (%.1f%%) | Time: %ss", + "%s %d/%d (%.1f%%) | %ss", progressBar, current, total, @@ -2854,6 +2859,11 @@ public abstract class AbstractPackManager implements PackManager { this.excludeJson.clear(); } + @Override + public int count() { + return this.excludeJson.size() + this.excludeTexture.size(); + } + public Set excludeTexture() { return excludeTexture; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 674fbf908..8b36730a6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; import org.jetbrains.annotations.Nullable; @@ -85,7 +86,7 @@ public class AlistHost implements ResourcePackHost { new TypeToken>(){}.getType() ); this.cachedSha1 = cache.get("sha1"); - CraftEngine.instance().logger().info("[Alist] Loaded cached resource pack metadata"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "Alist")); } catch (Exception e) { CraftEngine.instance().logger().warn("[Alist] Failed to load cache " + cachePath, e); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index beab2350a..98b65751b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; import java.io.IOException; @@ -58,7 +59,7 @@ public class DropboxHost implements ResourcePackHost { this.refreshToken = getString(cache, "refresh_token"); this.accessToken = getString(cache, "access_token"); this.expiresAt = getLong(cache, "expires_at"); - CraftEngine.instance().logger().info("[Dropbox] Loaded cached resource pack info"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "Dropbox")); } catch (Exception e) { CraftEngine.instance().logger().warn("[Dropbox] Failed to load cache " + cachePath, e); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java index b94adf1f1..d2e4fd58b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/GitLabHost.java @@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; import java.io.IOException; @@ -58,7 +59,7 @@ public class GitLabHost implements ResourcePackHost { if (uuidString != null && !uuidString.isEmpty()) { this.uuid = UUID.fromString(uuidString); } - CraftEngine.instance().logger().info("[GitLab] Loaded cached resource pack info"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "GitLab")); } catch (Exception e) { CraftEngine.instance().logger().warn( "[GitLab] Failed to read cache file: " + cachePath, e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 22a0dfc37..89db7002d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -70,7 +71,7 @@ public class LobFileHost implements ResourcePackHost { if (uuidString != null && !uuidString.isEmpty()) { this.uuid = UUID.fromString(uuidString); } - CraftEngine.instance().logger().info("[LobFile] Loaded cached resource pack info"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "LobFile")); } catch (Exception e) { CraftEngine.instance().logger().warn( "[LobFile] Failed to read cache file: " + e.getMessage()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index a15fa632f..3fb95aa92 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.util.*; import java.io.FileNotFoundException; @@ -76,7 +77,7 @@ public class OneDriveHost implements ResourcePackHost { this.sha1 = cache.get("sha1"); this.fileId = cache.get("file-id"); - CraftEngine.instance().logger().info("[OneDrive] Loaded cached resource pack info"); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "OneDrive")); } catch (Exception e) { CraftEngine.instance().logger().warn( "[OneDrive] Failed to load cache" + cachePath, e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 7d8937de1..9d08e7c78 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -28,7 +28,7 @@ public class SelfHost implements ResourcePackHost { @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl(); + ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl(player); if (data == null) return CompletableFuture.completedFuture(List.of()); return CompletableFuture.completedFuture(List.of(data)); } @@ -77,7 +77,7 @@ public class SelfHost implements ResourcePackHost { boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token"); String protocol = arguments.getOrDefault("protocol", "http").toString(); boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request"); - + boolean strictValidation = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("strict-validation", false), "strict-validation"); Bandwidth limit = null; Map rateLimitingSection = ResourceConfigUtils.getAsMapOrNull(arguments.get("rate-limiting"), "rate-limiting"); @@ -98,7 +98,7 @@ public class SelfHost implements ResourcePackHost { maxBandwidthUsage = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("max-bandwidth-per-second", 0), "max-bandwidth"); minDownloadSpeed = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("min-download-speed-per-player", 50_000), "min-download-speed-per-player"); } - selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed); + selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed, strictValidation); return INSTANCE; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 9a88c719c..29563b49c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -21,6 +21,7 @@ import io.netty.util.CharsetUtil; import io.netty.util.concurrent.GlobalEventExecutor; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; @@ -33,6 +34,8 @@ import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; +import java.util.Collections; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -41,7 +44,7 @@ import java.util.concurrent.atomic.AtomicLong; public class SelfHostHttpServer { private static SelfHostHttpServer instance; - private final Cache oneTimePackUrls = Caffeine.newBuilder() + private final Cache oneTimePackUrls = Caffeine.newBuilder() .maximumSize(1024) .scheduler(Scheduler.systemScheduler()) .expireAfterWrite(1, TimeUnit.MINUTES) @@ -67,6 +70,7 @@ public class SelfHostHttpServer { private String url; private boolean denyNonMinecraft = true; private boolean useToken; + private boolean strictValidation = false; private long globalUploadRateLimit = 0; private long minDownloadSpeed = 50_000; @@ -97,13 +101,15 @@ public class SelfHostHttpServer { Bandwidth limitPerIp, boolean token, long globalUploadRateLimit, - long minDownloadSpeed) { + long minDownloadSpeed, + boolean strictValidation) { this.ip = ip; this.url = url; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; this.limitPerIp = limitPerIp; this.useToken = token; + this.strictValidation = strictValidation; if (this.globalUploadRateLimit != globalUploadRateLimit || this.minDownloadSpeed != minDownloadSpeed) { this.globalUploadRateLimit = globalUploadRateLimit; this.minDownloadSpeed = minDownloadSpeed; @@ -161,7 +167,7 @@ public class SelfHostHttpServer { }); try { serverChannel = b.bind(port).sync().channel(); - CraftEngine.instance().logger().info("Netty HTTP server started on port: " + port); + CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.self.netty_server", String.valueOf(port))); } catch (InterruptedException e) { CraftEngine.instance().logger().warn("Failed to start Netty server", e); Thread.currentThread().interrupt(); @@ -214,8 +220,9 @@ public class SelfHostHttpServer { private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) { // 使用一次性token if (useToken) { - String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null); - if (!validateToken(token)) { + String token = queryDecoder.parameters().getOrDefault("token", Collections.emptyList()).stream().findFirst().orElse(null); + String clientUUID = strictValidation ? request.headers().get("X-Minecraft-UUID") : null; + if (!validateToken(token, clientUUID)) { sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid token"); blockedRequests.incrementAndGet(); return; @@ -225,7 +232,12 @@ public class SelfHostHttpServer { // 不是Minecraft客户端 if (denyNonMinecraft) { String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT); - if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { + boolean nonMinecraftClient = userAgent == null || !userAgent.startsWith("Minecraft Java/"); + if (strictValidation && !nonMinecraftClient) { + String clientVersion = request.headers().get("X-Minecraft-Version"); + nonMinecraftClient = !Objects.equals(clientVersion, userAgent.substring("Minecraft Java/".length())); + } + if (nonMinecraftClient) { sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid client"); blockedRequests.incrementAndGet(); return; @@ -300,10 +312,11 @@ public class SelfHostHttpServer { return rateLimiter.tryConsume(1); } - private boolean validateToken(String token) { + private boolean validateToken(String token, String clientUUID) { if (token == null || token.length() != 36) return false; - Boolean valid = oneTimePackUrls.getIfPresent(token); - if (valid != null) { + String valid = oneTimePackUrls.getIfPresent(token); + boolean isValid = strictValidation ? Objects.equals(valid, clientUUID) : valid != null; + if (isValid) { oneTimePackUrls.invalidate(token); return true; } @@ -348,7 +361,7 @@ public class SelfHostHttpServer { } @Nullable - public ResourcePackDownloadData generateOneTimeUrl() { + public ResourcePackDownloadData generateOneTimeUrl(UUID user) { if (this.resourcePackBytes == null) return null; if (!this.useToken) { @@ -356,7 +369,7 @@ public class SelfHostHttpServer { } String token = UUID.randomUUID().toString(); - oneTimePackUrls.put(token, true); + oneTimePackUrls.put(token, strictValidation ? user.toString().replace("-", "") : ""); return new ResourcePackDownloadData( url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), packUUID, diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 2c32adc8b..3075ea81e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -26,6 +26,8 @@ import net.momirealms.craftengine.core.plugin.dependency.Dependencies; import net.momirealms.craftengine.core.plugin.dependency.Dependency; import net.momirealms.craftengine.core.plugin.dependency.DependencyManager; import net.momirealms.craftengine.core.plugin.dependency.DependencyManagerImpl; +import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManager; +import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManagerImpl; import net.momirealms.craftengine.core.plugin.gui.GuiManager; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManager; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl; @@ -79,6 +81,7 @@ public abstract class CraftEngine implements Plugin { protected GlobalVariableManager globalVariableManager; protected ProjectileManager projectileManager; protected SeatManager seatManager; + protected EntityCullingManager entityCullingManager; private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry(); private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry(); @@ -118,6 +121,8 @@ public abstract class CraftEngine implements Plugin { this.globalVariableManager = new GlobalVariableManager(); // 初始化物品浏览器 this.itemBrowserManager = new ItemBrowserManagerImpl(this); + // 初始化实体剔除器 + this.entityCullingManager = new EntityCullingManagerImpl(); } public void setUpConfigAndLocale() { @@ -158,6 +163,7 @@ public abstract class CraftEngine implements Plugin { this.advancementManager.reload(); this.projectileManager.reload(); this.seatManager.reload(); + this.entityCullingManager.reload(); } private void runDelayTasks(boolean reloadRecipe) { @@ -349,6 +355,7 @@ public abstract class CraftEngine implements Plugin { if (this.translationManager != null) this.translationManager.disable(); if (this.globalVariableManager != null) this.globalVariableManager.disable(); if (this.projectileManager != null) this.projectileManager.disable(); + if (this.entityCullingManager != null) this.entityCullingManager.disable(); if (this.scheduler != null) this.scheduler.shutdownScheduler(); if (this.scheduler != null) this.scheduler.shutdownExecutor(); if (this.commandManager != null) this.commandManager.unregisterFeatures(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java index 6a8ec531b..1195d6904 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java @@ -10,12 +10,8 @@ public interface Platform { void dispatchCommand(String command); - Object snbtToJava(String nbt); - Tag jsonToSparrowNBT(JsonElement json); - Tag snbtToSparrowNBT(String nbt); - Tag javaToSparrowNBT(Object object); World getWorld(String name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 3720a4268..df09a8b08 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -54,6 +54,8 @@ public class Config { protected boolean debug$item; protected boolean debug$furniture; protected boolean debug$resource_pack; + protected boolean debug$block; + protected boolean debug$entity_culling; protected boolean resource_pack$remove_tinted_leaves_particle; protected boolean resource_pack$generate_mod_assets; @@ -204,6 +206,12 @@ public class Config { protected int emoji$max_emojis_per_parse; protected boolean client_optimization$entity_culling$enable; + protected int client_optimization$entity_culling$view_distance; + protected int client_optimization$entity_culling$threads; + protected boolean client_optimization$entity_culling$ray_tracing; + protected boolean client_optimization$entity_culling$rate_limiting$enable; + protected int client_optimization$entity_culling$rate_limiting$bucket_size; + protected int client_optimization$entity_culling$rate_limiting$restore_per_tick; public Config(CraftEngine plugin) { this.plugin = plugin; @@ -307,6 +315,8 @@ public class Config { debug$item = config.getBoolean("debug.item", false); debug$furniture = config.getBoolean("debug.furniture", false); debug$resource_pack = config.getBoolean("debug.resource-pack", false); + debug$block = config.getBoolean("debug.block", false); + debug$entity_culling = config.getBoolean("debug.entity-culling", false); // resource pack resource_pack$path = resolvePath(config.getString("resource-pack.path", "./generated/resource_pack.zip")); @@ -565,7 +575,15 @@ public class Config { emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); // client optimization - client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false); + if (firstTime) { + client_optimization$entity_culling$enable = VersionHelper.PREMIUM && config.getBoolean("client-optimization.entity-culling.enable", false); + } + client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64); + client_optimization$entity_culling$threads = config.getInt("client-optimization.entity-culling.threads", 1); + client_optimization$entity_culling$ray_tracing = client_optimization$entity_culling$enable && config.getBoolean("client-optimization.entity-culling.ray-tracing", true); + client_optimization$entity_culling$rate_limiting$enable = config.getBoolean("client-optimization.entity-culling.rate-limiting.enable", true); + client_optimization$entity_culling$rate_limiting$bucket_size = config.getInt("client-optimization.entity-culling.rate-limiting.bucket-size", 300); + client_optimization$entity_culling$rate_limiting$restore_per_tick = config.getInt("client-optimization.entity-culling.rate-limiting.restore-per-tick", 5); firstTime = false; } @@ -604,12 +622,12 @@ public class Config { return instance.debug$item; } - public static boolean debugBlockEntity() { - return false; + public static boolean debugBlock() { + return instance.debug$block; } - public static boolean debugBlock() { - return false; + public static boolean debugEntityCulling() { + return instance.debug$entity_culling; } public static boolean debugFurniture() { @@ -1161,6 +1179,30 @@ public class Config { return instance.client_optimization$entity_culling$enable; } + public static int entityCullingViewDistance() { + return instance.client_optimization$entity_culling$view_distance; + } + + public static int entityCullingThreads() { + return instance.client_optimization$entity_culling$threads; + } + + public static boolean enableEntityCullingRateLimiting() { + return instance.client_optimization$entity_culling$rate_limiting$enable; + } + + public static int entityCullingRateLimitingBucketSize() { + return instance.client_optimization$entity_culling$rate_limiting$bucket_size; + } + + public static int entityCullingRateLimitingRestorePerTick() { + return instance.client_optimization$entity_culling$rate_limiting$restore_per_tick; + } + + public static boolean entityCullingRayTracing() { + return instance.client_optimization$entity_culling$ray_tracing; + } + public YamlDocument loadOrCreateYamlData(String fileName) { Path path = this.plugin.dataFolderPath().resolve(fileName); if (!Files.exists(path)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java index 5fc06c91e..915fb7cc7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java @@ -25,4 +25,12 @@ public interface ConfigParser extends Comparable { void loadAll(); void clear(); + + default int count() { + return -1; + } + + default boolean silentIfNotExists() { + return true; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ArgumentString.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ArgumentString.java index f3846da86..15f821b76 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ArgumentString.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ArgumentString.java @@ -1,8 +1,9 @@ package net.momirealms.craftengine.core.plugin.config.template; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.SNBTReader; +import net.momirealms.craftengine.core.util.snbt.TagParser; import java.util.ArrayList; import java.util.List; @@ -67,8 +68,14 @@ public interface ArgumentString { } else { this.placeholder = placeholderContent.substring(0, separatorIndex); String defaultValueString = placeholderContent.substring(separatorIndex + 2); + Object parsed; try { - this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(new SNBTReader(defaultValueString).deserializeAsJava()); + parsed = TagParser.parseObjectFully(defaultValueString); + } catch (CommandSyntaxException e) { + throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage()); + } + try { + this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(parsed); } catch (LocalizedResourceConfigException e) { e.appendTailArgument(this.placeholder); throw e; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java index 3b0502db5..cfe45b6ca 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java @@ -51,6 +51,11 @@ public class TemplateManagerImpl implements TemplateManager { return LoadingSequence.TEMPLATE; } + @Override + public int count() { + return TemplateManagerImpl.this.templates.size(); + } + @Override public void parseObject(Pack pack, Path path, String node, Key id, Object obj) { if (TemplateManagerImpl.this.templates.containsKey(id)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java index 078a8c600..42af0daa5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/GlobalVariableManager.java @@ -49,6 +49,11 @@ public class GlobalVariableManager implements Manageable { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return GlobalVariableManager.this.globalVariables.size(); + } + @Override public void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException { if (object != null) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java index 2a29e7fd9..0faee9842 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java @@ -11,7 +11,8 @@ public enum EventTrigger { BREAK("break", "dig"), PLACE("place", "build"), PICK_UP("pick_up", "pick"), - STEP("step"),; + STEP("step"), + FALL("fall"),; public static final Map BY_NAME = new HashMap<>(); private final String[] names; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveFurnitureFunction.java index 88a2eb2ff..518b061cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveFurnitureFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveFurnitureFunction.java @@ -41,12 +41,12 @@ public class RemoveFurnitureFunction extends AbstractCondit WorldPosition position = furniture.position(); World world = position.world(); furniture.destroy(); - LootTable lootTable = furniture.config().lootTable(); + LootTable lootTable = furniture.config.lootTable(); 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)); Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); Player player = optionalPlayer.orElse(null); if (player != null) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ReplaceFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ReplaceFurnitureFunction.java index 520863010..2c4577909 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ReplaceFurnitureFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ReplaceFurnitureFunction.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.context.function; -import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; @@ -22,7 +21,7 @@ public class ReplaceFurnitureFunction extends AbstractCondi private final NumberProvider z; private final NumberProvider pitch; private final NumberProvider yaw; - private final AnchorType anchorType; + private final String variant; private final boolean dropLoot; private final boolean playSound; @@ -33,7 +32,7 @@ public class ReplaceFurnitureFunction extends AbstractCondi NumberProvider z, NumberProvider pitch, NumberProvider yaw, - AnchorType anchorType, + String variant, boolean dropLoot, boolean playSound, List> predicates @@ -45,7 +44,7 @@ public class ReplaceFurnitureFunction extends AbstractCondi this.z = z; this.pitch = pitch; this.yaw = yaw; - this.anchorType = anchorType; + this.variant = variant; this.dropLoot = dropLoot; this.playSound = playSound; } @@ -71,7 +70,7 @@ public class ReplaceFurnitureFunction extends AbstractCondi RemoveFurnitureFunction.removeFurniture(ctx, oldFurniture, dropLoot, playSound); // Place the new furniture - SpawnFurnitureFunction.spawnFurniture(this.newFurnitureId, newPosition, this.anchorType, this.playSound); + SpawnFurnitureFunction.spawnFurniture(this.newFurnitureId, newPosition, this.variant, this.playSound); } } @@ -94,10 +93,10 @@ public class ReplaceFurnitureFunction extends AbstractCondi NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); - AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null); + String variant = ResourceConfigUtils.getAsStringOrNull(ResourceConfigUtils.get(arguments, "variant", "anchor-type")); boolean dropLoot = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("drop-loot", true), "drop-loot"); boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound"); - return new ReplaceFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, dropLoot, playSound, getPredicates(arguments)); + return new ReplaceFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, variant, dropLoot, playSound, getPredicates(arguments)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java index 39a879c3e..e297cf2f8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SpawnFurnitureFunction.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.plugin.context.function; -import net.momirealms.craftengine.core.entity.furniture.AnchorType; -import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData; +import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.Context; @@ -15,7 +14,6 @@ import net.momirealms.craftengine.core.world.WorldPosition; import java.util.List; import java.util.Map; -import java.util.Optional; public class SpawnFurnitureFunction extends AbstractConditionalFunction { private final Key furnitureId; @@ -24,7 +22,7 @@ public class SpawnFurnitureFunction extends AbstractConditi private final NumberProvider z; private final NumberProvider pitch; private final NumberProvider yaw; - private final AnchorType anchorType; + private final String variant; private final boolean playSound; public SpawnFurnitureFunction( @@ -34,7 +32,7 @@ public class SpawnFurnitureFunction extends AbstractConditi NumberProvider z, NumberProvider pitch, NumberProvider yaw, - AnchorType anchorType, + String variant, boolean playSound, List> predicates ) { @@ -45,7 +43,7 @@ public class SpawnFurnitureFunction extends AbstractConditi this.z = z; this.pitch = pitch; this.yaw = yaw; - this.anchorType = anchorType; + this.variant = variant; this.playSound = playSound; } @@ -59,16 +57,12 @@ public class SpawnFurnitureFunction extends AbstractConditi float pitchValue = this.pitch.getFloat(ctx); float yawValue = this.yaw.getFloat(ctx); WorldPosition position = new WorldPosition(world, xPos, yPos, zPos, pitchValue, yawValue); - spawnFurniture(this.furnitureId, position, this.anchorType, this.playSound); + spawnFurniture(this.furnitureId, position, this.variant, this.playSound); }); } - public static void spawnFurniture(Key furnitureId, WorldPosition position, AnchorType anchorType, boolean playSound) { - CraftEngine.instance().furnitureManager().furnitureById(furnitureId).ifPresent(furniture -> { - AnchorType anchor = Optional.ofNullable(anchorType).orElse(furniture.getAnyAnchorType()); - FurnitureExtraData extraData = FurnitureExtraData.builder().anchorType(anchor).build(); - CraftEngine.instance().furnitureManager().place(position, furniture, extraData, playSound); - }); + public static void spawnFurniture(Key furnitureId, WorldPosition position, String variant, boolean playSound) { + CraftEngine.instance().furnitureManager().furnitureById(furnitureId).ifPresent(furniture -> CraftEngine.instance().furnitureManager().place(position, furniture, FurnitureDataAccessor.ofVariant(variant), playSound)); } @Override @@ -90,9 +84,9 @@ public class SpawnFurnitureFunction extends AbstractConditi NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "")); NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "")); - AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null); + String variant = ResourceConfigUtils.getAsStringOrNull(ResourceConfigUtils.get(arguments, "variant", "anchor-type")); boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound"); - return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, playSound, getPredicates(arguments)); + return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, variant, playSound, getPredicates(arguments)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java index 2191c4da9..376550f22 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/DirectContextParameters.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.context.parameter; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.Entity; -import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; @@ -55,7 +54,7 @@ public final class DirectContextParameters { public static final ContextKey ID = ContextKey.direct("id"); public static final ContextKey CUSTOM_MODEL_DATA = ContextKey.direct("custom_model_data"); public static final ContextKey FURNITURE = ContextKey.direct("furniture"); - public static final ContextKey ANCHOR_TYPE = ContextKey.direct("anchor_type"); + public static final ContextKey VARIANT = ContextKey.direct("variant"); public static final ContextKey HAND = ContextKey.direct("hand"); public static final ContextKey EVENT = ContextKey.direct("event"); public static final ContextKey IS_SNEAKING = ContextKey.direct("is_sneaking"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java index 0fd065ee2..273e16b2b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.plugin.context.parameter; import net.momirealms.craftengine.core.entity.Entity; -import net.momirealms.craftengine.core.entity.ItemEntity; +import net.momirealms.craftengine.core.entity.item.ItemEntity; import net.momirealms.craftengine.core.plugin.context.ChainParameterProvider; import net.momirealms.craftengine.core.plugin.context.ContextKey; import net.momirealms.craftengine.core.util.MiscUtils; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/FurnitureParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/FurnitureParameterProvider.java index dc4774853..c7dd3f082 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/FurnitureParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/FurnitureParameterProvider.java @@ -12,9 +12,9 @@ import java.util.function.Function; public class FurnitureParameterProvider implements ChainParameterProvider { private static final Map, Function> CONTEXT_FUNCTIONS = new HashMap<>(); static { - CONTEXT_FUNCTIONS.put(DirectContextParameters.ID, Furniture::id); + CONTEXT_FUNCTIONS.put(DirectContextParameters.ID, f -> f.config().id()); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Furniture::uuid); - CONTEXT_FUNCTIONS.put(DirectContextParameters.ANCHOR_TYPE, Furniture::anchorType); + CONTEXT_FUNCTIONS.put(DirectContextParameters.VARIANT, f -> f.getCurrentVariant().name()); CONTEXT_FUNCTIONS.put(DirectContextParameters.X, furniture -> furniture.position().x()); CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, furniture -> furniture.position().y()); CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, furniture -> furniture.position().z()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java new file mode 100644 index 000000000..67c7131f0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/CullingData.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.world.collision.AABB; + +public final class CullingData { + public final AABB aabb; + public final int maxDistance; + public final double aabbExpansion; + public final boolean rayTracing; + + public CullingData(AABB aabb, int maxDistance, double aabbExpansion, boolean rayTracing) { + this.aabb = aabb; + this.maxDistance = maxDistance; + this.aabbExpansion = aabbExpansion; + this.rayTracing = rayTracing; + } + + public AABB aabb() { + return this.aabb; + } + + public int maxDistance() { + return this.maxDistance; + } + + public double aabbExpansion() { + return this.aabbExpansion; + } + + public boolean rayTracing() { + return this.rayTracing; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java index 2b16695bf..79dac8655 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin.entityculling; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.MutableVec3d; @@ -13,38 +14,57 @@ import java.util.Arrays; public final class EntityCulling { public static final int MAX_SAMPLES = 14; private final Player player; - private final int maxDistance; - private final double aabbExpansion; private final boolean[] dotSelectors = new boolean[MAX_SAMPLES]; private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; - private final int[] lastHitBlock = new int[MAX_SAMPLES * 3]; - private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES]; + private final int[] lastHitBlock = new int[3]; + private boolean canCheckLastHitBlock = false; private int hitBlockCount = 0; private int lastVisitChunkX = Integer.MAX_VALUE; private int lastVisitChunkZ = Integer.MAX_VALUE; private ClientChunk lastVisitChunk = null; + private int currentTokens = Config.entityCullingRateLimitingBucketSize(); + private double distanceScale = 1d; - public EntityCulling(Player player, int maxDistance, double aabbExpansion) { + public EntityCulling(Player player) { this.player = player; - this.maxDistance = maxDistance; - this.aabbExpansion = aabbExpansion; for (int i = 0; i < MAX_SAMPLES; i++) { this.targetPoints[i] = new MutableVec3d(0,0,0); } } - public boolean isVisible(AABB aabb, Vec3d cameraPos) { - // 情空标志位 - Arrays.fill(this.canCheckLastHitBlock, false); - this.hitBlockCount = 0; + public void setDistanceScale(double distanceScale) { + this.distanceScale = distanceScale; + } - // 根据AABB获取能包裹此AABB的最小长方体 - int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion); - int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion); - int minZ = MiscUtils.floor(aabb.minZ - this.aabbExpansion); - int maxX = MiscUtils.ceil(aabb.maxX + this.aabbExpansion); - int maxY = MiscUtils.ceil(aabb.maxY + this.aabbExpansion); - int maxZ = MiscUtils.ceil(aabb.maxZ + this.aabbExpansion); + public double distanceScale() { + return distanceScale; + } + + public void restoreTokenOnTick() { + this.currentTokens = Math.min(Config.entityCullingRateLimitingBucketSize(), this.currentTokens + Config.entityCullingRateLimitingRestorePerTick()); + } + + public boolean takeToken() { + if (this.currentTokens > 0) { + this.currentTokens--; + return true; + } + return false; + } + + public boolean isVisible(CullingData cullable, Vec3d cameraPos, boolean rayTracing) { + // 情空标志位 + this.canCheckLastHitBlock = false; + this.hitBlockCount = 0; + AABB aabb = cullable.aabb; + double aabbExpansion = cullable.aabbExpansion; + + double minX = aabb.minX - aabbExpansion; + double minY = aabb.minY - aabbExpansion; + double minZ = aabb.minZ - aabbExpansion; + double maxX = aabb.maxX + aabbExpansion; + double maxY = aabb.maxY + aabbExpansion; + double maxZ = aabb.maxZ + aabbExpansion; double cameraX = cameraPos.x; double cameraY = cameraPos.y; @@ -60,7 +80,8 @@ public final class EntityCulling { } // 如果设置了最大距离 - if (this.maxDistance > 0) { + double maxDistance = cullable.maxDistance * this.distanceScale; + if (maxDistance > 0) { // 计算AABB到相机的最小距离 double distanceSq = 0.0; // 计算XYZ轴方向的距离 @@ -68,13 +89,17 @@ public final class EntityCulling { distanceSq += distanceSq(minY, maxY, cameraY, relY); distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ); // 检查距离是否超过最大值 - double maxDistanceSq = this.maxDistance * this.maxDistance; + double maxDistanceSq = maxDistance * maxDistance; // 超过最大距离,剔除 if (distanceSq > maxDistanceSq) { return false; } } + if (!rayTracing || !cullable.rayTracing) { + return true; + } + // 清空之前的缓存 Arrays.fill(this.dotSelectors, false); if (relX == Relative.POSITIVE) { @@ -111,8 +136,14 @@ public final class EntityCulling { if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ); if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ); if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ); - if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ); + if (this.dotSelectors[13]) targetPoints[size++].set(averageX, maxY, averageZ); +// if (Config.debugEntityCulling()) { +// for (int i = 0; i < size; i++) { +// MutableVec3d targetPoint = this.targetPoints[i]; +// this.player.playParticle(Key.of("flame"), targetPoint.x, targetPoint.y, targetPoint.z); +// } +// } return isVisible(cameraPos, this.targetPoints, size); } @@ -168,7 +199,7 @@ public final class EntityCulling { int startBlockZ = MiscUtils.floor(start.z); // 遍历所有目标点进行视线检测 - outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { MutableVec3d currentTarget = targets[targetIndex]; // 计算起点到目标的相对向量(世界坐标差) @@ -177,14 +208,9 @@ public final class EntityCulling { double deltaZ = start.z - currentTarget.z; // 检查之前命中的方块,大概率还是命中 - for (int i = 0; i < MAX_SAMPLES; i++) { - if (this.canCheckLastHitBlock[i]) { - int offset = i * 3; - if (rayIntersection(this.lastHitBlock[offset], this.lastHitBlock[offset + 1], this.lastHitBlock[offset + 2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { - continue outer; - } - } else { - break; + if (this.canCheckLastHitBlock) { + if (rayIntersection(this.lastHitBlock[0], this.lastHitBlock[1], this.lastHitBlock[2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { + continue; } } @@ -195,9 +221,9 @@ public final class EntityCulling { // 预计算每单位距离在各方块边界上的步进增量 // 这些值表示射线穿过一个方块所需的时间分数 - double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0 - double stepIncrementY = 1.0 / (absDeltaY + 1e-10); - double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10); + double stepIncrementX = 1.0 / absDeltaX; + double stepIncrementY = 1.0 / absDeltaY; + double stepIncrementZ = 1.0 / absDeltaZ; // 射线将穿过的总方块数量(包括起点和终点) int totalBlocksToCheck = 1; @@ -270,21 +296,25 @@ public final class EntityCulling { if (isLineOfSightClear) { return true; } else { - this.canCheckLastHitBlock[this.hitBlockCount++] = true; + this.canCheckLastHitBlock = true; } } return false; } - private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ, + private boolean stepRay(int startingX, int startingY, int startingZ, double stepSizeX, double stepSizeY, double stepSizeZ, - int remainingSteps, int stepDirectionX, int stepDirectionY, - int stepDirectionZ, double nextStepTimeY, double nextStepTimeX, - double nextStepTimeZ) { + int remainingSteps, + int stepDirectionX, int stepDirectionY, int stepDirectionZ, + double nextStepTimeY, double nextStepTimeX, double nextStepTimeZ) { - // 遍历射线路径上的所有方块(跳过最后一个目标方块) - for (; remainingSteps > 1; remainingSteps--) { + int currentBlockX = startingX; + int currentBlockY = startingY; + int currentBlockZ = startingZ; + + // 遍历射线路径上的所有方块 + for (; remainingSteps > 0; remainingSteps--) { // 检查当前方块是否遮挡视线 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { @@ -315,7 +345,23 @@ public final class EntityCulling { return true; } - private double distanceSq(int min, int max, double camera, Relative rel) { + private int getCacheIndex(int x, int y, int z, int startX, int startY, int startZ) { + int deltaX = startX + 16 - x; + if (deltaX < 0 || deltaX >= 32) { + return -1; + } + int deltaY = startY + 16 - y; + if (deltaY < 0 || deltaY >= 32) { + return -1; + } + int deltaZ = startZ + 16 - z; + if (deltaZ < 0 || deltaZ >= 32) { + return -1; + } + return deltaX + 32 * deltaY + 32 * 32 * deltaZ; + } + + private double distanceSq(double min, double max, double camera, Relative rel) { if (rel == Relative.NEGATIVE) { double dx = camera - max; return dx * dx; @@ -353,7 +399,7 @@ public final class EntityCulling { private enum Relative { INSIDE, POSITIVE, NEGATIVE; - public static Relative from(int min, int max, double pos) { + public static Relative from(double min, double max, double pos) { if (min > pos) return POSITIVE; else if (max < pos) return NEGATIVE; return INSIDE; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java new file mode 100644 index 000000000..123cfbdee --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManager.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.plugin.Manageable; + +public interface EntityCullingManager extends Manageable { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java new file mode 100644 index 000000000..e01602e81 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingManagerImpl.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.plugin.config.Config; + +import java.util.ArrayList; +import java.util.List; + +public class EntityCullingManagerImpl implements EntityCullingManager { + private final List threads = new ArrayList<>(); + + @Override + public void load() { + if (Config.enableEntityCulling()) { + int threads = Math.min(64, Math.max(Config.entityCullingThreads(), 1)); + for (int i = 0; i < threads; i++) { + EntityCullingThread thread = new EntityCullingThread(i, threads); + this.threads.add(thread); + thread.start(); + } + } + } + + @Override + public void unload() { + if (!this.threads.isEmpty()) { + for (EntityCullingThread thread : this.threads) { + thread.stop(); + } + this.threads.clear(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java new file mode 100644 index 000000000..7eb6f1e69 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCullingThread.java @@ -0,0 +1,67 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.logger.Debugger; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class EntityCullingThread { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final int id; + private final int threads; + private int timer; + + public EntityCullingThread(int id, int threads) { + this.id = id; + this.threads = threads; + } + + public void start() { + // 错开线程启动时间,避免所有线程同时执行 + long initialDelay = this.id * (50L / this.threads); + this.scheduler.scheduleAtFixedRate(this::scheduleTask, initialDelay, 50, TimeUnit.MILLISECONDS); + } + + private void scheduleTask() { + // 使用CAS操作,更安全 + if (!this.isRunning.compareAndSet(false, true)) { + return; + } + + this.scheduler.execute(() -> { + try { + int processed = 0; + long startTime = System.nanoTime(); + + for (Player player : CraftEngine.instance().networkManager().onlineUsers()) { + // 使用绝对值确保非负,使用 threads 而不是 threads-1 确保均匀分布 + if (Math.abs(player.uuid().hashCode()) % this.threads == this.id) { + player.entityCullingTick(); + processed++; + } + } + + long duration = System.nanoTime() - startTime; + if (Config.debugEntityCulling() && this.timer++ % 20 == 0) { + String value = String.format("EntityCullingThread-%d processed %d players in %sms", + this.id, processed, String.format("%.2f", duration / 1_000_000.0)); + Debugger.ENTITY_CULLING.debug(() -> value); + } + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run entity culling tick: " + e.getMessage()); + } finally { + this.isRunning.set(false); + } + }); + } + + public void stop() { + this.scheduler.shutdown(); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java deleted file mode 100644 index a77160fa1..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java +++ /dev/null @@ -1,216 +0,0 @@ -package net.momirealms.craftengine.core.plugin.entityculling; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -// Amanatides, J., & Woo, A. A Fast Voxel Traversal Algorithm for Ray Tracing. http://www.cse.yorku.ca/~amana/research/grid.pdf. -public final class VoxelIterator implements Iterator { - private int x; - private int y; - private int z; - private int stepX; - private int stepY; - private int stepZ; - private double tMax; - private double tMaxX; - private double tMaxY; - private double tMaxZ; - private double tDeltaX; - private double tDeltaY; - private double tDeltaZ; - private int[] ref = new int[3]; // This implementation always returns ref or refSwap to avoid garbage. Can easily be changed if needed. - private int[] refSwap = new int[3]; - private int[] next; - - public VoxelIterator(double startX, double startY, double startZ, double endX, double endY, double endZ) { - initialize(startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { - initialize(x, y, z, startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { - if (normalized) { - initializeNormalized(startX, startY, startZ, directionX, directionY, directionZ, distance); - } else { - initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); - } - } - - public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { - if (normalized) { - initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } else { - initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - } - - public VoxelIterator initialize(double startX, double startY, double startZ, double endX, double endY, double endZ) { - return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, endX, endY, endZ); - } - - public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { - double directionX = endX - startX; - double directionY = endY - startY; - double directionZ = endZ - startZ; - double distance = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); - double fixedDistance = distance == 0. ? Double.NaN : distance; - directionX /= fixedDistance; - directionY /= fixedDistance; - directionZ /= fixedDistance; - return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator initialize(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, distance); - } - - public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - double signum = Math.signum(distance); - directionX *= signum; - directionY *= signum; - directionZ *= signum; - double length = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); - - if (length == 0.) { - length = Double.NaN; - } - - directionX /= length; - directionY /= length; - directionZ /= length; - return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); - } - - public VoxelIterator initializeNormalized(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - return initializeNormalized(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); - } - - public VoxelIterator initializeNormalized(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { - this.x = x; - this.y = y; - this.z = z; - tMax = distance; - stepX = directionX < 0. ? -1 : 1; - stepY = directionY < 0. ? -1 : 1; - stepZ = directionZ < 0. ? -1 : 1; - tMaxX = directionX == 0. ? Double.POSITIVE_INFINITY : (x + (stepX + 1) / 2 - startX) / directionX; - tMaxY = directionY == 0. ? Double.POSITIVE_INFINITY : (y + (stepY + 1) / 2 - startY) / directionY; - tMaxZ = directionZ == 0. ? Double.POSITIVE_INFINITY : (z + (stepZ + 1) / 2 - startZ) / directionZ; - tDeltaX = 1. / Math.abs(directionX); - tDeltaY = 1. / Math.abs(directionY); - tDeltaZ = 1. / Math.abs(directionZ); - next = ref; - ref[0] = x; - ref[1] = y; - ref[2] = z; - return this; - } - - public int[] calculateNext() { - if (tMaxX < tMaxY) { - if (tMaxZ < tMaxX) { - if (tMaxZ <= tMax) { - z += stepZ; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxZ += tDeltaZ; - } else { - next = null; - } - } else { - if (tMaxX <= tMax) { - if (tMaxZ == tMaxX) { - z += stepZ; - tMaxZ += tDeltaZ; - } - - x += stepX; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxX += tDeltaX; - } else { - next = null; - } - } - } else if (tMaxY < tMaxZ) { - if (tMaxY <= tMax) { - if (tMaxX == tMaxY) { - x += stepX; - tMaxX += tDeltaX; - } - - y += stepY; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxY += tDeltaY; - } else { - next = null; - } - } else { - if (tMaxZ <= tMax) { - if (tMaxX == tMaxZ) { - x += stepX; - tMaxX += tDeltaX; - } - - if (tMaxY == tMaxZ) { - y += stepY; - tMaxY += tDeltaY; - } - - z += stepZ; - // next = new int[] { x, y, z }; - ref[0] = x; - ref[1] = y; - ref[2] = z; - tMaxZ += tDeltaZ; - } else { - next = null; - } - } - - return next; - } - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public int[] next() { - int[] next = this.next; - - if (next == null) { - throw new NoSuchElementException(); - } - - int[] temp = ref; - ref = refSwap; - refSwap = temp; - this.next = ref; - calculateNext(); - return next; - } - - private static int floor(double value) { - int i = (int) value; - return value < (double) i ? i - 1 : i; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index b4d6d30f4..27baae5e7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -109,6 +109,11 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { return LoadingSequence.CATEGORY; } + @Override + public int count() { + return ItemBrowserManagerImpl.this.byId.size(); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { String name = section.getOrDefault("name", id).toString(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index b488bde08..f8cfbd946 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -41,4 +41,6 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_MULTIPLE = Component.translatable().key("command.item.clear.failed.multiple"); TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_SINGLE = Component.translatable().key("command.item.clear.test.single"); TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_MULTIPLE = Component.translatable().key("command.item.clear.test.multiple"); + TranslatableComponent.Builder COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS = Component.translatable().key("command.entity_view_distance_scale.set.success"); + TranslatableComponent.Builder COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS = Component.translatable().key("command.entity_culling.toggle.success"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java index 89fa4edea..8f9909250 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManager.java @@ -4,13 +4,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.translation.Translator; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag; +import net.momirealms.craftengine.core.util.AdventureHelper; import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public interface TranslationManager extends Manageable { @@ -74,6 +73,15 @@ public interface TranslationManager extends Manageable { } } + default String translateLog(String id, String... arguments) { + String translation = miniMessageTranslation(id); + if (translation == null) { + return id; + } + Component deserialize = AdventureHelper.customMiniMessage().deserialize(translation, new IndexedArgumentTag(Arrays.stream(arguments).map(Component::text).toList())); + return AdventureHelper.plainTextContent(deserialize); + } + Set translationKeys(); void log(String id, String... args); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index 44cd01d8c..e193d837b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -277,6 +277,7 @@ public class TranslationManagerImpl implements TranslationManager { public class TranslationParser extends IdSectionConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"translations", "translation", "l10n", "localization", "i18n", "internationalization"}; + private int count; @Override public int loadingSequence() { @@ -288,6 +289,16 @@ public class TranslationManagerImpl implements TranslationManager { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return this.count; + } + + @Override + public void preProcess() { + this.count = 0; + } + @Override public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map section) { Locale locale = TranslationManager.parseLocale(id.value()); @@ -300,6 +311,7 @@ public class TranslationManagerImpl implements TranslationManager { String key = entry.getKey(); bundle.put(key, entry.getValue().toString()); TranslationManagerImpl.this.translationKeys.add(key); + this.count++; } TranslationManagerImpl.this.registry.registerAll(locale, bundle); @@ -313,6 +325,7 @@ public class TranslationManagerImpl implements TranslationManager { Component deserialize = AdventureHelper.miniMessage().deserialize(AdventureHelper.legacyToMiniMessage(s), ShiftTag.INSTANCE, ImageTag.INSTANCE); return AdventureHelper.getLegacy().serialize(deserialize); }; + private int count; @Override public int loadingSequence() { @@ -324,6 +337,16 @@ public class TranslationManagerImpl implements TranslationManager { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return this.count; + } + + @Override + public void preProcess() { + this.count = 0; + } + @Override public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map section) { String langId = id.value().toLowerCase(Locale.ENGLISH); @@ -333,6 +356,7 @@ public class TranslationManagerImpl implements TranslationManager { entry -> this.langProcessor.apply(String.valueOf(entry.getValue())) )); TranslationManagerImpl.this.addClientTranslation(langId, sectionData); + this.count += sectionData.size(); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java index 49dcb3ef7..3dd1cf3f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/logger/Debugger.java @@ -12,7 +12,7 @@ public enum Debugger { RESOURCE_PACK(Config::debugResourcePack), ITEM(Config::debugItem), BLOCK(Config::debugBlock), - BLOCK_ENTITY(Config::debugBlockEntity); + ENTITY_CULLING(Config::debugEntityCulling); private final Supplier condition; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java index 57fb85de9..233b09cf5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/EntityPacketHandler.java @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.entity.player.Player; public interface EntityPacketHandler { - default boolean handleEntitiesRemove(IntList entityIds) { + default boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) { return false; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java index c728459ac..8e2ab4818 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/AbstractJavaScheduler.java @@ -6,6 +6,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.util.Arrays; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -46,6 +47,13 @@ public abstract class AbstractJavaScheduler implements SchedulerAdapter { return new AsyncTask(future); } + @Override + public SchedulerTask asyncRepeating(Consumer task, long delay, long interval, TimeUnit unit) { + LazyAsyncTask asyncTask = new LazyAsyncTask(); + asyncTask.future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(() -> task.accept(asyncTask)), delay, interval, unit); + return asyncTask; + } + @Override public void shutdownScheduler() { this.scheduler.shutdown(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java new file mode 100644 index 000000000..84ad86199 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/LazyAsyncTask.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.plugin.scheduler; + +import java.util.concurrent.ScheduledFuture; + +public class LazyAsyncTask implements SchedulerTask { + + public ScheduledFuture future; + + @Override + public void cancel() { + if (future != null) { + future.cancel(false); + } + } + + @Override + public boolean cancelled() { + if (future == null) return false; + return future.isCancelled(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java index fd7af51e3..786ea491c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/SchedulerAdapter.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.plugin.scheduler; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; public interface SchedulerAdapter { @@ -25,6 +26,8 @@ public interface SchedulerAdapter { SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit); + SchedulerTask asyncRepeating(Consumer task, long delay, long interval, TimeUnit unit); + void shutdownScheduler(); void shutdownExecutor(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index f09e3a159..287ca2752 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.entity.BlockEntityType; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.properties.PropertyFactory; -import net.momirealms.craftengine.core.entity.furniture.HitBoxConfigFactory; +import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory; +import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.item.equipment.EquipmentFactory; @@ -76,7 +77,6 @@ public class BuiltInRegistries { public static final Registry> PATH_MATCHER_FACTORY = createConstantBoundRegistry(Registries.PATH_MATCHER_FACTORY, 16); public static final Registry RESOLUTION_FACTORY = createConstantBoundRegistry(Registries.RESOLUTION_FACTORY, 16); public static final Registry SMITHING_RESULT_PROCESSOR_FACTORY = createConstantBoundRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY, 16); - public static final Registry HITBOX_FACTORY = createConstantBoundRegistry(Registries.HITBOX_FACTORY, 16); public static final Registry RESOURCE_PACK_HOST_FACTORY = createConstantBoundRegistry(Registries.RESOURCE_PACK_HOST_FACTORY, 16); public static final Registry> EVENT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.EVENT_FUNCTION_FACTORY, 128); public static final Registry> EVENT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.EVENT_CONDITION_FACTORY, 128); @@ -88,9 +88,11 @@ public class BuiltInRegistries { public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE, 16); public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE, 16); public static final Registry> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET, 16); - public static final Registry> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 128); - public static final Registry BLOCK_ENTITY_ELEMENT_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_ELEMENT_TYPE, 16); + public static final Registry> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 64); + public static final Registry> BLOCK_ENTITY_ELEMENT_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_ELEMENT_TYPE, 16); public static final Registry CRAFT_REMAINDER_FACTORY = createConstantBoundRegistry(Registries.CRAFT_REMAINDER_FACTORY, 16); + public static final Registry> FURNITURE_ELEMENT_TYPE = createConstantBoundRegistry(Registries.FURNITURE_ELEMENT_TYPE, 16); + public static final Registry> FURNITURE_HITBOX_TYPE = createConstantBoundRegistry(Registries.FURNITURE_HITBOX_TYPE, 16); private static Registry createConstantBoundRegistry(ResourceKey> key, int expectedSize) { return new ConstantBoundRegistry<>(key, expectedSize); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index 33a34a9d9..4afecdcd7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.entity.BlockEntityType; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory; import net.momirealms.craftengine.core.block.properties.PropertyFactory; -import net.momirealms.craftengine.core.entity.furniture.HitBoxConfigFactory; +import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory; +import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory; import net.momirealms.craftengine.core.item.ItemDataModifierFactory; import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; import net.momirealms.craftengine.core.item.equipment.EquipmentFactory; @@ -78,7 +79,6 @@ public class Registries { public static final ResourceKey>> PATH_MATCHER_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("path_matcher_factory")); public static final ResourceKey> RESOLUTION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("resolution_factory")); public static final ResourceKey> SMITHING_RESULT_PROCESSOR_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("smithing_result_processor_factory")); - public static final ResourceKey> HITBOX_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("hitbox_factory")); public static final ResourceKey> RESOURCE_PACK_HOST_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("resource_pack_host_factory")); public static final ResourceKey>> EVENT_FUNCTION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("event_function_factory")); public static final ResourceKey>> EVENT_CONDITION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("event_condition_factory")); @@ -91,6 +91,8 @@ public class Registries { public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); public static final ResourceKey>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet_type")); public static final ResourceKey>> BLOCK_ENTITY_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_type")); - public static final ResourceKey> BLOCK_ENTITY_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_element_type")); + public static final ResourceKey>> BLOCK_ENTITY_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_element_type")); public static final ResourceKey> CRAFT_REMAINDER_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("craft_remainder_factory")); + public static final ResourceKey>> FURNITURE_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("furniture_element_type")); + public static final ResourceKey>> FURNITURE_HITBOX_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("furniture_hitbox_type")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java index 0972a6eed..1a31d0140 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java @@ -97,6 +97,11 @@ public abstract class AbstractSoundManager implements SoundManager { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return AbstractSoundManager.this.songs.size(); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractSoundManager.this.songs.containsKey(id)) { @@ -124,6 +129,11 @@ public abstract class AbstractSoundManager implements SoundManager { return CONFIG_SECTION_NAME; } + @Override + public int count() { + return AbstractSoundManager.this.byId.size(); + } + @Override public void parseSection(Pack pack, Path path, String node, Key id, Map section) { if (AbstractSoundManager.this.byId.containsKey(id)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 523bbc364..6f327817a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -415,4 +415,8 @@ public class MiscUtils { } return false; } + + public static int growByHalf(int value, int minValue) { + return (int) Math.max(Math.min((long) value + (value >> 1), 2147483639L), minValue); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 1a98323d1..e3be821e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -36,7 +36,7 @@ public final class ResourceConfigUtils { return defaultValue; } try { - return Enum.valueOf(clazz, o.toString().toUpperCase(Locale.ENGLISH)); + return Enum.valueOf(clazz, o.toString().toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { return defaultValue; } @@ -263,18 +263,26 @@ public final class ResourceConfigUtils { } public static Vector3f getAsVector3f(Object o, String option) { - if (o == null) return new Vector3f(); - if (o instanceof List list && list.size() == 3) { - return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString())); - } else { - String stringFormat = o.toString(); - String[] split = stringFormat.split(","); - if (split.length == 3) { - return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2])); - } else if (split.length == 1) { - return new Vector3f(Float.parseFloat(split[0])); - } else { - throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option); + switch (o) { + case null -> { + return new Vector3f(); + } + case List list when list.size() == 3 -> { + return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString())); + } + case Number number -> { + return new Vector3f(number.floatValue()); + } + default -> { + String stringFormat = o.toString(); + String[] split = stringFormat.split(","); + if (split.length == 3) { + return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2])); + } else if (split.length == 1) { + return new Vector3f(Float.parseFloat(split[0])); + } else { + throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option); + } } } } @@ -344,50 +352,54 @@ public final class ResourceConfigUtils { } public static AABB getAsAABB(Object o, String option) { - if (o == null) { - throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option); - } - if (o instanceof Number number) { - double min = -(number.doubleValue() / 2); - double max = number.doubleValue() / 2; - return new AABB(min, min, min, max, max, max); - } else { - double[] args; - if (o instanceof List list) { - args = new double[list.size()]; - for (int i = 0; i < args.length; i++) { - if (list.get(i) instanceof Number number) { - args[i] = number.doubleValue(); - } else { + switch (o) { + case null -> throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option); + case AABB aabb -> { + return aabb; + } + case Number number -> { + double min = -(number.doubleValue() / 2); + double max = number.doubleValue() / 2; + return new AABB(min, min, min, max, max, max); + } + default -> { + double[] args; + if (o instanceof List list) { + args = new double[list.size()]; + for (int i = 0; i < args.length; i++) { + if (list.get(i) instanceof Number number) { + args[i] = number.doubleValue(); + } else { + try { + args[i] = Double.parseDouble(list.get(i).toString()); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } + } else { + String[] split = o.toString().split(","); + args = new double[split.length]; + for (int i = 0; i < args.length; i++) { try { - args[i] = Double.parseDouble(list.get(i).toString()); + args[i] = Double.parseDouble(split[i]); } catch (NumberFormatException e) { throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); } } } - } else { - String[] split = o.toString().split(","); - args = new double[split.length]; - for (int i = 0; i < args.length; i++) { - try { - args[i] = Double.parseDouble(split[i]); - } catch (NumberFormatException e) { - throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); - } + if (args.length == 1) { + return new AABB(-args[0] / 2, -args[0] / 2, -args[0] / 2, args[0] / 2, args[0] / 2, args[0] / 2); + } else if (args.length == 2) { + return new AABB(-args[0] / 2, -args[1] / 2, -args[0] / 2, args[0] / 2, args[1] / 2, args[0] / 2); + } else if (args.length == 3) { + return new AABB(-args[0] / 2, -args[1] / 2, -args[2] / 2, args[0] / 2, args[1] / 2, args[2] / 2); + } else if (args.length == 6) { + return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]); + } else { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); } } - if (args.length == 1) { - return new AABB(-args[0]/2, -args[0]/2, -args[0]/2, args[0]/2, args[0]/2, args[0]/2); - } else if (args.length == 2) { - return new AABB(-args[0]/2, -args[1]/2, -args[0]/2, args[0]/2, args[1]/2, args[0]/2); - } else if (args.length == 3) { - return new AABB(-args[0]/2, -args[1]/2, -args[2]/2, args[0]/2, args[1]/2, args[2]/2); - } else if (args.length == 6) { - return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]); - } else { - throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); - } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java deleted file mode 100644 index 0be81388d..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java +++ /dev/null @@ -1,289 +0,0 @@ -package net.momirealms.craftengine.core.util; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -public final class SNBTReader extends DefaultStringReader { - private static final char COMPOUND_START = '{'; - private static final char COMPOUND_END = '}'; - private static final char LIST_START = '['; - private static final char LIST_END = ']'; - private static final char STRING_DELIMITER = '"'; - private static final char SINGLE_QUOTES = '\''; - private static final char DOUBLE_QUOTES = '"'; - private static final char KEY_VALUE_SEPARATOR = ':'; - private static final char ELEMENT_SEPARATOR = ','; - - private static final char ARRAY_DELIMITER = ';'; - private static final char BYTE_ARRAY = 'b'; - private static final char INT_ARRAY = 'i'; - private static final char LONG_ARRAY = 'l'; - - public SNBTReader(String content) { - super(content); - } - - public Object deserializeAsJava() { - Object result = this.parseValue(); - this.skipWhitespace(); - if (getCursor() != getTotalLength()) - throw new IllegalArgumentException("Extra content at end: " + substring(getCursor(), getTotalLength())); - return result; - } - - // 开始解析, 步进字符. - private Object parseValue() { - skipWhitespace(); - return switch (peek()) { - case COMPOUND_START -> parseCompound(); - case LIST_START -> parseList(); - case DOUBLE_QUOTES -> { - skip(); - yield readStringUntil(DOUBLE_QUOTES); - } - case SINGLE_QUOTES -> { - skip(); - yield readStringUntil(SINGLE_QUOTES); - } - default -> parsePrimitive(); - }; - } - - // 解析包小肠 {} - private Map parseCompound() { - skip(); // 跳过 '{' - skipWhitespace(); - - Map compoundMap = new LinkedHashMap<>(); - - if (canRead() && peek() != COMPOUND_END) { - do { - String key = parseKey(); - if (!canRead() || peek() != KEY_VALUE_SEPARATOR) { - throw new IllegalArgumentException("Expected ':' at position " + getCursor()); - } - skip(); // 跳过 ':' - Object value = parseValue(); - compoundMap.put(key, value); - skipWhitespace(); - } while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */); - } - - if (!canRead() || peek() != COMPOUND_END) { - throw new IllegalArgumentException("Expected '}' at position " + getCursor()); - } - skip(); // 跳过 '}' - return compoundMap; - } - - // 解析列表值 [1, 2, 3] - private Object parseList() { - skip(); // 跳过 '[' - skipWhitespace(); - - // 检查接下来的2个非空格字符, 确认是否要走数组解析. - if (canRead()) { - setMarker(cursor); // 记录指针, 尝试解析数组. - char typeChar = Character.toLowerCase(peek()); - if (typeChar == BYTE_ARRAY || typeChar == INT_ARRAY || typeChar == LONG_ARRAY) { - skip(); - skipWhitespace(); - if (canRead() && peek() == ARRAY_DELIMITER) { // 下一个必须是 ';' - skip(); - switch (typeChar) { // 解析并返回数组喵 - case BYTE_ARRAY -> { - return parseArray(list -> { - byte[] bytes = new byte[list.size()]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = list.get(i).byteValue(); - } - return bytes; - }); - } - case INT_ARRAY -> { - return parseArray(list -> { - int[] ints = new int[list.size()]; - for (int i = 0; i < ints.length; i++) { - ints[i] = list.get(i).intValue(); - } - return ints; - }); - } - case LONG_ARRAY -> { - return parseArray(list -> { - long[] longs = new long[list.size()]; - for (int i = 0; i < longs.length; i++) { - longs[i] = list.get(i).longValue(); - } - return longs; - }); - } - } - } - } - restore(); // 复原指针. - } - - List elementList = new ArrayList<>(); - - if (canRead() && peek() != LIST_END) { - do { - elementList.add(parseValue()); - skipWhitespace(); - } while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */); - } - - if (!canRead() || peek() != LIST_END) { - throw new IllegalArgumentException("Expected ']' at position " + getCursor()); - } - skip(); // 跳过 ']' - return elementList; - } - - // 解析数组 [I; 11, 41, 54] - // ArrayType -> B, I, L. - private Object parseArray(Function, Object> convertor) { - skipWhitespace(); - // 用来暂存解析出的数字 - List elements = new ArrayList<>(); - if (canRead() && peek() != LIST_END) { - do { - Object element = parseValue(); - - // 1.21.6的SNBT原版是支持 {key:[B;1,2b,0xFF]} 这种奇葩写法的, 越界部分会被自动舍弃, 如0xff的byte值为-1. - // 如果需要和原版对齐, 那么只需要判断是否是数字就行了. - // if (!(element instanceof Number number)) - // throw new IllegalArgumentException("Error element type at pos " + getCursor()); - if (!(element instanceof Number number)) - throw new IllegalArgumentException("Error parsing number at pos " + getCursor()); - - elements.add(number); // 校验通过后加入 - skipWhitespace(); - } while (canRead() && peek() == ELEMENT_SEPARATOR && ++cursor > 0 /* 跳过 ',' */); - } - - if (!canRead() || peek() != LIST_END) - throw new IllegalArgumentException("Expected ']' at position " + getCursor()); - skip(); // 跳过 ']' - return convertor.apply(elements); - } - - // 解析Key值 - private String parseKey() { - skipWhitespace(); - if (!canRead()) { - throw new IllegalArgumentException("Unterminated key at " + getCursor()); - } - - // 如果有双引号就委托给string解析处理. - char peek = peek(); - if (peek == STRING_DELIMITER) { - skip(); - return readStringUntil(STRING_DELIMITER); - } else if (peek == SINGLE_QUOTES) { - skip(); - return readStringUntil(SINGLE_QUOTES); - } - - int start = getCursor(); - while (canRead()) { - char c = peek(); - if (c == ' ') break; // 忽略 key 后面的空格, { a :1} 应当解析成 {a:1} - if (Character.isJavaIdentifierPart(c)) skip(); else break; - } - - String key = substring(start, getCursor()); - skipWhitespace(); // 跳过 key 后面的空格. - return key; - } - - // 解析原生值 - private Object parsePrimitive() { - // 先解析获取值的长度 - int tokenStart = getCursor(); - int lastWhitespace = 0; // 记录值末尾的空格数量,{a:炒鸡 大保健} 和 {a: 炒鸡 大保健 } 都应解析成 "炒鸡 大保健". - boolean contentHasWhitespace = false; // 记录值中有没有空格. - while (canRead()) { - char c = peek(); - if (c == ',' || c == ']' || c == '}') break; - skip(); - if (c == ' ') { - lastWhitespace++; // 遇到空格先增加值, 代表值尾部空格数量. - continue; - } - if (lastWhitespace > 0) { - lastWhitespace = 0; // 遇到正常字符时清空记录的尾部空格数. - contentHasWhitespace = true; - } - } - int tokenLength = getCursor() - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格. - if (tokenLength == 0) return null; // 如果值长度为0则返回null. - if (contentHasWhitespace) return substring(tokenStart, tokenStart + tokenLength); // 如果值的中间有空格, 一定是字符串, 可直接返回. - - // 布尔值检查 - if (tokenLength == 4) { - if (matchesAt(tokenStart, "true")) return Boolean.TRUE; - if (matchesAt(tokenStart, "null")) return null; // 支持 {key:null}. - } else if (tokenLength == 5) { - if (matchesAt(tokenStart, "false")) return Boolean.FALSE; - } - if (tokenLength > 1) { - // 至少有1个字符,给了后缀的可能性 - char lastChar = charAt(tokenStart + tokenLength - 1); - try { - switch (lastChar) { - case 'b', 'B' -> { - return Byte.parseByte(substring(tokenStart, tokenStart + tokenLength - 1)); - } - case 's', 'S' -> { - return Short.parseShort(substring(tokenStart, tokenStart + tokenLength - 1)); - } - case 'l', 'L' -> { - return Long.parseLong(substring(tokenStart, tokenStart + tokenLength - 1)); - } - case 'f', 'F' -> { - return Float.parseFloat(substring(tokenStart, tokenStart + tokenLength)); - } - case 'd', 'D' -> { - return Double.parseDouble(substring(tokenStart, tokenStart + tokenLength)); - } - default -> { - String fullString = substring(tokenStart, tokenStart + tokenLength); - try { - double d = Double.parseDouble(fullString); - if (d % 1 != 0 || fullString.contains(".") || fullString.contains("e")) { - return d; - } else { - return (int) d; - } - } catch (NumberFormatException e) { - return fullString; - } - } - } - } catch (NumberFormatException e) { - return substring(tokenStart, tokenStart + tokenLength); - } - } else { - char onlyChar = charAt(tokenStart); - if (isNumber(onlyChar)) { - return onlyChar - '0'; - } else { - return String.valueOf(onlyChar); - } - } - } - - // 工具函数: 快速检查布尔值字符串匹配, 忽略大小写. - private boolean matchesAt(int start, String target) { - for (int i = 0; i < target.length(); i++) { - char c1 = charAt(start + i); - char c2 = target.charAt(i); - if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较 - } - return true; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtGrammar.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtGrammar.java new file mode 100644 index 000000000..0ff4982f2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtGrammar.java @@ -0,0 +1,1015 @@ +package net.momirealms.craftengine.core.util.snbt; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JavaOps; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.bytes.ByteList; +import it.unimi.dsi.fastutil.chars.CharList; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.util.snbt.parse.*; +import net.momirealms.craftengine.core.util.snbt.parse.Dictionary; +import net.momirealms.craftengine.core.util.snbt.parse.grammar.*; +import net.momirealms.sparrow.nbt.codec.LegacyJavaOps; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + +public class SnbtGrammar { + private static final DynamicCommandExceptionType ERROR_NUMBER_PARSE_FAILURE = new LocalizedDynamicCommandExceptionType( + message -> new LocalizedMessage("warning.config.type.snbt.parser.number_parse_failure", String.valueOf(message)) + ); + static final DynamicCommandExceptionType ERROR_EXPECTED_HEX_ESCAPE = new LocalizedDynamicCommandExceptionType( + length -> new LocalizedMessage("warning.config.type.snbt.parser.expected_hex_escape", String.valueOf(length)) + ); + private static final DynamicCommandExceptionType ERROR_INVALID_CODEPOINT = new LocalizedDynamicCommandExceptionType( + codepoint -> new LocalizedMessage("warning.config.type.snbt.parser.invalid_codepoint", String.valueOf(codepoint)) + ); + private static final DynamicCommandExceptionType ERROR_NO_SUCH_OPERATION = new LocalizedDynamicCommandExceptionType( + operation -> new LocalizedMessage("warning.config.type.snbt.parser.no_such_operation", String.valueOf(operation)) + ); + static final DelayedException ERROR_EXPECTED_INTEGER_TYPE = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_integer_type")) + ); + private static final DelayedException ERROR_EXPECTED_FLOAT_TYPE = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_float_type")) + ); + static final DelayedException ERROR_EXPECTED_NON_NEGATIVE_NUMBER = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_non_negative_number")) + ); + private static final DelayedException ERROR_INVALID_CHARACTER_NAME = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_character_name")) + ); + static final DelayedException ERROR_INVALID_ARRAY_ELEMENT_TYPE = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_array_element_type")) + ); + private static final DelayedException ERROR_INVALID_UNQUOTED_START = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_unquoted_start")) + ); + private static final DelayedException ERROR_EXPECTED_UNQUOTED_STRING = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_unquoted_string")) + ); + private static final DelayedException ERROR_INVALID_STRING_CONTENTS = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_string_contents")) + ); + private static final DelayedException ERROR_EXPECTED_BINARY_NUMERAL = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_binary_numeral")) + ); + private static final DelayedException ERROR_UNDERSCORE_NOT_ALLOWED = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.underscore_not_allowed")) + ); + private static final DelayedException ERROR_EXPECTED_DECIMAL_NUMERAL = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_decimal_numeral")) + ); + private static final DelayedException ERROR_EXPECTED_HEX_NUMERAL = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_hex_numeral")) + ); + private static final DelayedException ERROR_EMPTY_KEY = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.empty_key")) + ); + private static final DelayedException ERROR_LEADING_ZERO_NOT_ALLOWED = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.leading_zero_not_allowed")) + ); + private static final DelayedException ERROR_INFINITY_NOT_ALLOWED = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.infinity_not_allowed")) + ); + private static final NumberRunParseRule BINARY_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_BINARY_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) { + @Override + protected boolean isAccepted(char c) { + return switch (c) { + case '0', '1', '_' -> true; + default -> false; + }; + } + }; + private static final NumberRunParseRule DECIMAL_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_DECIMAL_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) { + @Override + protected boolean isAccepted(char c) { + return switch (c) { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_' -> true; + default -> false; + }; + } + }; + private static final NumberRunParseRule HEX_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_HEX_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) { + @Override + protected boolean isAccepted(char c) { + return switch (c) { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', '_', 'a', 'b', 'c', 'd', 'e', 'f' -> true; + default -> false; + }; + } + }; + private static final GreedyPredicateParseRule PLAIN_STRING_CHUNK = new GreedyPredicateParseRule(1, ERROR_INVALID_STRING_CONTENTS) { + @Override + protected boolean isAccepted(char c) { + return switch (c) { + case '"', '\'', '\\' -> false; + default -> true; + }; + } + }; + private static final StringReaderTerms.TerminalCharacters NUMBER_LOOKEAHEAD = new StringReaderTerms.TerminalCharacters(CharList.of()) { + @Override + protected boolean isAccepted(char c) { + return canStartNumber(c); + } + }; + private static final Pattern UNICODE_NAME = Pattern.compile("[-a-zA-Z0-9 ]+"); + + static DelayedException createNumberParseError(NumberFormatException ex) { + return DelayedException.create(ERROR_NUMBER_PARSE_FAILURE, ex.getMessage()); + } + + private static boolean isAllowedToStartUnquotedString(char c) { + return !canStartNumber(c); + } + + static boolean canStartNumber(char c) { + return switch (c) { + case '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> true; + default -> false; + }; + } + + static boolean needsUnderscoreRemoval(String contents) { + return contents.indexOf(95) != -1; + } + + private static void cleanAndAppend(StringBuilder output, String contents) { + cleanAndAppend(output, contents, needsUnderscoreRemoval(contents)); + } + + static void cleanAndAppend(StringBuilder output, String contents, boolean needsUnderscoreRemoval) { + if (needsUnderscoreRemoval) { + for (char c : contents.toCharArray()) { + if (c != '_') { + output.append(c); + } + } + return; + } + output.append(contents); + } + + static short parseUnsignedShort(String string, int radix) { + int parse = Integer.parseInt(string, radix); + if (parse >> 16 == 0) { + return (short) parse; + } + throw new NumberFormatException("out of range: " + parse); + } + + @Nullable + private static T createFloat( + DynamicOps ops, + Sign sign, + @Nullable String whole, + @Nullable String fraction, + @Nullable Signed exponent, + @Nullable TypeSuffix typeSuffix, + ParseState state + ) { + StringBuilder result = new StringBuilder(); + sign.append(result); + if (whole != null) { + cleanAndAppend(result, whole); + } + + if (fraction != null) { + result.append('.'); + cleanAndAppend(result, fraction); + } + + if (exponent != null) { + result.append('e'); + exponent.sign().append(result); + cleanAndAppend(result, exponent.value); + } + + try { + String string = result.toString(); + + return switch (typeSuffix) { + case null -> convertDouble(ops, state, string); + case FLOAT -> convertFloat(ops, state, string); + case DOUBLE -> convertDouble(ops, state, string); + default -> { + state.errorCollector().store(state.mark(), ERROR_EXPECTED_FLOAT_TYPE); + yield null; + } + }; + } catch (NumberFormatException e) { + state.errorCollector().store(state.mark(), createNumberParseError(e)); + return null; + } + } + + @Nullable + private static T convertFloat(DynamicOps ops, ParseState state, String contents) { + float value = Float.parseFloat(contents); + if (!Float.isFinite(value)) { + state.errorCollector().store(state.mark(), ERROR_INFINITY_NOT_ALLOWED); + return null; + } + return ops.createFloat(value); + } + + @Nullable + private static T convertDouble(DynamicOps ops, ParseState state, String contents) { + double value = Double.parseDouble(contents); + if (!Double.isFinite(value)) { + state.errorCollector().store(state.mark(), ERROR_INFINITY_NOT_ALLOWED); + return null; + } + return ops.createDouble(value); + } + + private static String joinList(List list) { + return switch (list.size()) { + case 0 -> ""; + case 1 -> list.getFirst(); + default -> String.join("", list); + }; + } + + @SuppressWarnings("unchecked") + public static Grammar createParser(DynamicOps ops) { + T trueValue = ops.createBoolean(true); + T falseValue = ops.createBoolean(false); + T emptyMapValue = ops.emptyMap(); + T emptyList = ops.emptyList(); + T nullString = ops.createString(SnbtOperations.BUILTIN_NULL); + boolean isJavaType = SnbtOperations.BUILTIN_NULL.equals(nullString); // 确定是 Java 类型的 + + Dictionary rules = new Dictionary<>(); + + // 符号解析规则 + Atom sign = Atom.of("sign"); + rules.put( + sign, + Term.alternative( + Term.sequence(StringReaderTerms.character('+'), Term.marker(sign, Sign.PLUS)), + Term.sequence(StringReaderTerms.character('-'), Term.marker(sign, Sign.MINUS)) + ), + scope -> scope.getOrThrow(sign) + ); + + // 整数后缀解析规则 + Atom integerSuffix = Atom.of("integer_suffix"); + rules.put( + integerSuffix, + Term.alternative( + Term.sequence( + StringReaderTerms.characters('u', 'U'), + Term.alternative( + Term.sequence(StringReaderTerms.characters('b', 'B'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.BYTE))), + Term.sequence(StringReaderTerms.characters('s', 'S'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.SHORT))), + Term.sequence(StringReaderTerms.characters('i', 'I'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.INT))), + Term.sequence(StringReaderTerms.characters('l', 'L'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.LONG))) + ) + ), + Term.sequence( + StringReaderTerms.characters('s', 'S'), + Term.alternative( + Term.sequence(StringReaderTerms.characters('b', 'B'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.BYTE))), + Term.sequence(StringReaderTerms.characters('s', 'S'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.SHORT))), + Term.sequence(StringReaderTerms.characters('i', 'I'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.INT))), + Term.sequence(StringReaderTerms.characters('l', 'L'), Term.marker(integerSuffix, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.LONG))) + ) + ), + Term.sequence(StringReaderTerms.characters('b', 'B'), Term.marker(integerSuffix, new IntegerSuffix(null, TypeSuffix.BYTE))), + Term.sequence(StringReaderTerms.characters('s', 'S'), Term.marker(integerSuffix, new IntegerSuffix(null, TypeSuffix.SHORT))), + Term.sequence(StringReaderTerms.characters('i', 'I'), Term.marker(integerSuffix, new IntegerSuffix(null, TypeSuffix.INT))), + Term.sequence(StringReaderTerms.characters('l', 'L'), Term.marker(integerSuffix, new IntegerSuffix(null, TypeSuffix.LONG))) + ), + scope -> scope.getOrThrow(integerSuffix) + ); + + // 二进制解析规则 + Atom binaryNumeral = Atom.of("binary_numeral"); + rules.put(binaryNumeral, BINARY_NUMERAL); + + // 十进制解析规则 + Atom decimalNumeral = Atom.of("decimal_numeral"); + rules.put(decimalNumeral, DECIMAL_NUMERAL); + + // 十六进制解析规则 + Atom hexNumeral = Atom.of("hex_numeral"); + rules.put(hexNumeral, HEX_NUMERAL); + + // 整数常量解析规则 + Atom integerLiteral = Atom.of("integer_literal"); + NamedRule integerLiteralRule = rules.put( + integerLiteral, + Term.sequence( + Term.optional(rules.named(sign)), + Term.alternative( + Term.sequence( + StringReaderTerms.character('0'), + Term.cut(), + Term.alternative( + Term.sequence(StringReaderTerms.characters('x', 'X'), Term.cut(), rules.named(hexNumeral)), + Term.sequence(StringReaderTerms.characters('b', 'B'), rules.named(binaryNumeral)), + Term.sequence(rules.named(decimalNumeral), Term.cut(), Term.fail(ERROR_LEADING_ZERO_NOT_ALLOWED)), + Term.marker(decimalNumeral, "0") + ) + ), + rules.named(decimalNumeral) + ), + Term.optional(rules.named(integerSuffix)) + ), + scope -> { + IntegerSuffix suffix = scope.getOrDefault(integerSuffix, IntegerSuffix.EMPTY); + Sign signValue = scope.getOrDefault(sign, Sign.PLUS); + String decimalContents = scope.get(decimalNumeral); + if (decimalContents != null) { + return new IntegerLiteral(signValue, Base.DECIMAL, decimalContents, suffix); + } + String hexContents = scope.get(hexNumeral); + if (hexContents != null) { + return new IntegerLiteral(signValue, Base.HEX, hexContents, suffix); + } + String binaryContents = scope.getOrThrow(binaryNumeral); + return new IntegerLiteral(signValue, Base.BINARY, binaryContents, suffix); + } + ); + + // 浮点型后缀解析规则 + Atom floatTypeSuffix = Atom.of("float_type_suffix"); + rules.put( + floatTypeSuffix, + Term.alternative( + Term.sequence(StringReaderTerms.characters('f', 'F'), Term.marker(floatTypeSuffix, TypeSuffix.FLOAT)), + Term.sequence(StringReaderTerms.characters('d', 'D'), Term.marker(floatTypeSuffix, TypeSuffix.DOUBLE)) + ), + scope -> scope.getOrThrow(floatTypeSuffix) + ); + + // 浮点数指数部分解析规则 + Atom> floatExponentPart = Atom.of("float_exponent_part"); + rules.put( + floatExponentPart, + Term.sequence( + StringReaderTerms.characters('e', 'E'), + Term.optional(rules.named(sign)), + rules.named(decimalNumeral) + ), + scope -> new Signed<>(scope.getOrDefault(sign, Sign.PLUS), scope.getOrThrow(decimalNumeral)) + ); + + // 浮点数常量解析规则 + Atom floatWholePart = Atom.of("float_whole_part"); // 整数部分 + Atom floatFractionPart = Atom.of("float_fraction_part"); // 小数部分 + Atom floatLiteral = Atom.of("float_literal"); + rules.putComplex( + floatLiteral, + Term.sequence( + Term.optional(rules.named(sign)), + Term.alternative( + Term.sequence( + rules.namedWithAlias(decimalNumeral, floatWholePart), + StringReaderTerms.character('.'), + Term.cut(), + Term.optional(rules.namedWithAlias(decimalNumeral, floatFractionPart)), + Term.optional(rules.named(floatExponentPart)), + Term.optional(rules.named(floatTypeSuffix)) + ), + Term.sequence( + StringReaderTerms.character('.'), + Term.cut(), + rules.namedWithAlias(decimalNumeral, floatFractionPart), + Term.optional(rules.named(floatExponentPart)), + Term.optional(rules.named(floatTypeSuffix)) + ), + Term.sequence( + rules.namedWithAlias(decimalNumeral, floatWholePart), + rules.named(floatExponentPart), + Term.cut(), + Term.optional(rules.named(floatTypeSuffix)) + ), + Term.sequence( + rules.namedWithAlias(decimalNumeral, floatWholePart), + Term.optional(rules.named(floatExponentPart)), + rules.named(floatTypeSuffix) + ) + ) + ), + state -> { + Scope scope = state.scope(); + Sign wholeSign = scope.getOrDefault(sign, Sign.PLUS); + String whole = scope.get(floatWholePart); + String fraction = scope.get(floatFractionPart); + Signed exponent = scope.get(floatExponentPart); + TypeSuffix typeSuffix = scope.get(floatTypeSuffix); + return createFloat(ops, wholeSign, whole, fraction, exponent, typeSuffix, state); + } + ); + + // 二位十六进制字符串解析规则 + Atom stringHex2 = Atom.of("string_hex_2"); + rules.put(stringHex2, new SimpleHexLiteralParseRule(2)); + + // 四位十六进制字符串解析规则 + Atom stringHex4 = Atom.of("string_hex_4"); + rules.put(stringHex4, new SimpleHexLiteralParseRule(4)); + + // 八位十六进制字符串解析规则 + Atom stringHex8 = Atom.of("string_hex_8"); + rules.put(stringHex8, new SimpleHexLiteralParseRule(8)); + + // Unicode名称字符串解析规则 + Atom stringUnicodeName = Atom.of("string_unicode_name"); + rules.put(stringUnicodeName, new GreedyPatternParseRule(UNICODE_NAME, ERROR_INVALID_CHARACTER_NAME)); + + // 字符串转义序列解析规则 + Atom stringEscapeSequence = Atom.of("string_escape_sequence"); + rules.putComplex( + stringEscapeSequence, + Term.alternative( + Term.sequence(StringReaderTerms.character('b'), Term.marker(stringEscapeSequence, "\b")), + Term.sequence(StringReaderTerms.character('s'), Term.marker(stringEscapeSequence, " ")), + Term.sequence(StringReaderTerms.character('t'), Term.marker(stringEscapeSequence, "\t")), + Term.sequence(StringReaderTerms.character('n'), Term.marker(stringEscapeSequence, "\n")), + Term.sequence(StringReaderTerms.character('f'), Term.marker(stringEscapeSequence, "\f")), + Term.sequence(StringReaderTerms.character('r'), Term.marker(stringEscapeSequence, "\r")), + Term.sequence(StringReaderTerms.character('\\'), Term.marker(stringEscapeSequence, "\\")), + Term.sequence(StringReaderTerms.character('\''), Term.marker(stringEscapeSequence, "'")), + Term.sequence(StringReaderTerms.character('"'), Term.marker(stringEscapeSequence, "\"")), + Term.sequence(StringReaderTerms.character('x'), rules.named(stringHex2)), + Term.sequence(StringReaderTerms.character('u'), rules.named(stringHex4)), + Term.sequence(StringReaderTerms.character('U'), rules.named(stringHex8)), + Term.sequence( + StringReaderTerms.character('N'), + StringReaderTerms.character('{'), + rules.named(stringUnicodeName), + StringReaderTerms.character('}') + ) + ), + state -> { + Scope scope = state.scope(); + String plainEscape = scope.getAny(stringEscapeSequence); + if (plainEscape != null) { + return plainEscape; + } + String hexEscape = scope.getAny(stringHex2, stringHex4, stringHex8); + if (hexEscape != null) { + int codePoint = HexFormat.fromHexDigits(hexEscape); + if (!Character.isValidCodePoint(codePoint)) { + state.errorCollector().store(state.mark(), DelayedException.create(ERROR_INVALID_CODEPOINT, String.format(Locale.ROOT, "U+%08X", codePoint))); + return null; + } + return Character.toString(codePoint); + } + String character = scope.getOrThrow(stringUnicodeName); + + int codePoint; + try { + codePoint = Character.codePointOf(character); + } catch (IllegalArgumentException var12x) { + state.errorCollector().store(state.mark(), ERROR_INVALID_CHARACTER_NAME); + return null; + } + + return Character.toString(codePoint); + } + ); + + // 纯文本字符串解析规则 + Atom stringPlainContents = Atom.of("string_plain_contents"); + rules.put(stringPlainContents, PLAIN_STRING_CHUNK); + + // 字符串解析规则 + Atom> stringChunks = Atom.of("string_chunks"); // 字符串块 + Atom stringContents = Atom.of("string_contents"); // 字符串内容 + + // 单引号字符串块解析规则 + Atom singleQuotedStringChunk = Atom.of("single_quoted_string_chunk"); + NamedRule singleQuotedStringChunkRule = rules.put( + singleQuotedStringChunk, + Term.alternative( + rules.namedWithAlias(stringPlainContents, stringContents), + Term.sequence(StringReaderTerms.character('\\'), rules.namedWithAlias(stringEscapeSequence, stringContents)), + Term.sequence(StringReaderTerms.character('"'), Term.marker(stringContents, "\"")) + ), + scope -> scope.getOrThrow(stringContents) + ); + + // 单引号字符串内容解析规则 + Atom singleQuotedStringContents = Atom.of("single_quoted_string_contents"); + rules.put( + singleQuotedStringContents, + Term.repeated(singleQuotedStringChunkRule, stringChunks), + scope -> joinList(scope.getOrThrow(stringChunks)) + ); + + // 双引号字符串块解析规则 + Atom doubleQuotedStringChunk = Atom.of("double_quoted_string_chunk"); + NamedRule doubleQuotedStringChunkRule = rules.put( + doubleQuotedStringChunk, + Term.alternative( + rules.namedWithAlias(stringPlainContents, stringContents), + Term.sequence(StringReaderTerms.character('\\'), rules.namedWithAlias(stringEscapeSequence, stringContents)), + Term.sequence(StringReaderTerms.character('\''), Term.marker(stringContents, "'")) + ), + scope -> scope.getOrThrow(stringContents) + ); + + // 双引号字符串内容解析规则 + Atom doubleQuotedStringContents = Atom.of("double_quoted_string_contents"); + rules.put( + doubleQuotedStringContents, + Term.repeated(doubleQuotedStringChunkRule, stringChunks), + scope -> joinList(scope.getOrThrow(stringChunks)) + ); + + // 带引号的字符串字面量解析规则 + Atom quotedStringLiteral = Atom.of("quoted_string_literal"); + rules.put( + quotedStringLiteral, + Term.alternative( + Term.sequence( + StringReaderTerms.character('"'), + Term.cut(), + Term.optional(rules.namedWithAlias(doubleQuotedStringContents, stringContents)), + StringReaderTerms.character('"') + ), + Term.sequence( + StringReaderTerms.character('\''), + Term.optional(rules.namedWithAlias(singleQuotedStringContents, stringContents)), + StringReaderTerms.character('\'') + ) + ), + scope -> scope.getOrThrow(stringContents) + ); + + // 不带引号的字符串解析规则 + Atom unquotedString = Atom.of("unquoted_string"); + rules.put( + unquotedString, + new UnquotedStringParseRule(1, ERROR_EXPECTED_UNQUOTED_STRING) + ); + + // 列表解析规则 + Atom literal = Atom.of("literal"); // 字面量 + Atom> argumentList = Atom.of("arguments"); // 参数 + rules.put( + argumentList, + Term.repeatedWithTrailingSeparator( + rules.forward(literal), + argumentList, + StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR) + ), + scope -> scope.getOrThrow(argumentList) + ); + + // 不带引号的字符串或内置表达式解析规则 + Atom unquotedStringOrBuiltIn = Atom.of("unquoted_string_or_builtin"); + rules.putComplex( + unquotedStringOrBuiltIn, + Term.sequence( + rules.named(unquotedString), + Term.optional(Term.sequence( + StringReaderTerms.character('('), + rules.named(argumentList), + StringReaderTerms.character(')') + )) + ), + state -> { + Scope scope = state.scope(); + String contents = scope.getOrThrow(unquotedString); + if (!contents.isEmpty() && isAllowedToStartUnquotedString(contents.charAt(0))) { // 非空且是合法开头字符 + List arguments = scope.get(argumentList); + if (arguments != null) { // 带参数尝试解析内置表达式 + SnbtOperations.BuiltinKey key = new SnbtOperations.BuiltinKey(contents, arguments.size()); + SnbtOperations.BuiltinOperation operation = SnbtOperations.BUILTIN_OPERATIONS.get(key); + if (operation != null) { + return operation.run(ops, arguments, state); + } + state.errorCollector().store(state.mark(), DelayedException.create(ERROR_NO_SUCH_OPERATION, key.toString())); + return null; + } else if (contents.equalsIgnoreCase(SnbtOperations.BUILTIN_TRUE)) { // 解析不带引号的 true 为布尔值 + return trueValue; + } else if (contents.equalsIgnoreCase(SnbtOperations.BUILTIN_FALSE)) { // 解析不带引号的 false 为布尔值 + return falseValue; + } else if (contents.equalsIgnoreCase(SnbtOperations.BUILTIN_NULL)) { // 解析不带引号的 null 为空值,该功能并非标准 SNBT 语法 + return Objects.requireNonNullElseGet(ops.empty() /*一般来说这里都不会是null*/, () -> { + if (isJavaType) { + // 如果是 null 一般就是使用 Object 对象的 Java 类型,可以安全的强行转换 + return (T) CachedParseState.JAVA_NULL_VALUE_MARKER; + } + return nullString; // 意外情况保持 SNBT 默认行为 + }); + } + return ops.createString(contents); + } + state.errorCollector().store(state.mark(), SnbtOperations.BUILTIN_IDS, ERROR_INVALID_UNQUOTED_START); + return null; + } + ); + + // 映射键解析规则 + Atom mapKey = Atom.of("map_key"); + rules.put( + mapKey, + Term.alternative( + rules.named(quotedStringLiteral), + rules.named(unquotedString) + ), + scope -> scope.getAnyOrThrow(quotedStringLiteral, unquotedString) + ); + + // 映射条目解析规则 + Atom> mapEntry = Atom.of("map_entry"); + NamedRule> mapEntryRule = rules.putComplex( + mapEntry, + Term.sequence( + rules.named(mapKey), + StringReaderTerms.character(TagParser.NAME_VALUE_SEPARATOR), + rules.named(literal) + ), + state -> { + Scope scope = state.scope(); + String key = scope.getOrThrow(mapKey); + if (key.isEmpty()) { + state.errorCollector().store(state.mark(), ERROR_EMPTY_KEY); + return null; + } + T value = scope.getOrThrow(literal); + return Map.entry(key, value); + } + ); + + // 映射条目集合解析规则 + Atom>> mapEntries = Atom.of("map_entries"); + rules.put( + mapEntries, + Term.repeatedWithTrailingSeparator( + mapEntryRule, + mapEntries, + StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR) + ), + scope -> scope.getOrThrow(mapEntries) + ); + + // 映射字面量解析规则 + Atom mapLiteral = Atom.of("map_literal"); + rules.put( + mapLiteral, + Term.sequence( + StringReaderTerms.character('{'), + Scope.increaseDepth(), + rules.named(mapEntries), + Scope.decreaseDepth(), + StringReaderTerms.character('}') + ), + scope -> { + List> entries = scope.getOrThrow(mapEntries); + if (entries.isEmpty()) { + return emptyMapValue; + } + Builder builder = ImmutableMap.builderWithExpectedSize(entries.size()); + for (Entry e : entries) { + builder.put(ops.createString(e.getKey()), e.getValue()); + } + return ops.createMap(builder.buildKeepingLast()); + } + ); + + // 列表条目集合解析规则 + Atom> listEntries = Atom.of("list_entries"); + rules.put( + listEntries, + Term.repeatedWithTrailingSeparator( + rules.forward(literal), + listEntries, + StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR) + ), + scope -> scope.getOrThrow(listEntries) + ); + + // 数组前缀解析规则 + Atom arrayPrefix = Atom.of("array_prefix"); + rules.put( + arrayPrefix, + Term.alternative( + Term.sequence(StringReaderTerms.character('B'), Term.marker(arrayPrefix, ArrayPrefix.BYTE)), + Term.sequence(StringReaderTerms.character('L'), Term.marker(arrayPrefix, ArrayPrefix.LONG)), + Term.sequence(StringReaderTerms.character('I'), Term.marker(arrayPrefix, ArrayPrefix.INT)) + ), + scope -> scope.getOrThrow(arrayPrefix) + ); + + // 整数数组条目集合解析规则 + Atom> intArrayEntries = Atom.of("int_array_entries"); + rules.put( + intArrayEntries, + Term.repeatedWithTrailingSeparator( + integerLiteralRule, + intArrayEntries, + StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR) + ), + scope -> scope.getOrThrow(intArrayEntries) + ); + + // 列表字面量解析规则 + Atom listLiteral = Atom.of("list_literal"); + rules.putComplex( + listLiteral, + Term.sequence( + StringReaderTerms.character('['), + Scope.increaseDepth(), + Term.alternative( + Term.sequence( + rules.named(arrayPrefix), + StringReaderTerms.character(';'), + rules.named(intArrayEntries) + ), + rules.named(listEntries) + ), + Scope.decreaseDepth(), + StringReaderTerms.character(']') + ), + state -> { + Scope scope = state.scope(); + ArrayPrefix arrayType = scope.get(arrayPrefix); + if (arrayType != null) { + List entries = scope.getOrThrow(intArrayEntries); + return entries.isEmpty() ? arrayType.create(ops) : arrayType.create(ops, entries, state); + } + List entries = scope.getOrThrow(listEntries); + return entries.isEmpty() ? emptyList : ops.createList(entries.stream()); + } + ); + + // 基本规则解析规则 + NamedRule literalRule = rules.putComplex( + literal, + Term.alternative( + Term.sequence( + Term.positiveLookahead(NUMBER_LOOKEAHEAD), + Term.alternative( + rules.namedWithAlias(floatLiteral, literal), + rules.named(integerLiteral) + ) + ), + Term.sequence( + Term.positiveLookahead(StringReaderTerms.characters('"', '\'')), + Term.cut(), + rules.named(quotedStringLiteral) + ), + Term.sequence( + Term.positiveLookahead(StringReaderTerms.character('{')), + Term.cut(), + rules.namedWithAlias(mapLiteral, literal) + ), + Term.sequence( + Term.positiveLookahead(StringReaderTerms.character('[')), + Term.cut(), + rules.namedWithAlias(listLiteral, literal) + ), + rules.namedWithAlias(unquotedStringOrBuiltIn, literal) + ), + state -> { + Scope scope = state.scope(); + String quotedString = scope.get(quotedStringLiteral); + if (quotedString != null) { + return ops.createString(quotedString); + } + IntegerLiteral integer = scope.get(integerLiteral); + return integer != null ? integer.create(ops, state) : scope.getOrThrow(literal); + } + ); + + return new Grammar<>(rules, literalRule); + } + + enum ArrayPrefix { + BYTE(TypeSuffix.BYTE) { + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]); + + @Override + public T create(DynamicOps ops) { + return ops.createByteList(EMPTY_BUFFER); + } + + @Nullable + @Override + public T create(DynamicOps ops, List entries, ParseState state) { + ByteList result = new ByteArrayList(); + for (IntegerLiteral entry : entries) { + Number number = this.buildNumber(entry, state); + if (number == null) { + return null; + } + result.add(number.byteValue()); + } + return ops.createByteList(ByteBuffer.wrap(result.toByteArray())); + } + }, + INT(TypeSuffix.INT, TypeSuffix.BYTE, TypeSuffix.SHORT) { + @Override + public T create(DynamicOps ops) { + return ops.createIntList(IntStream.empty()); + } + + @Nullable + @Override + public T create(DynamicOps ops, List entries, ParseState state) { + IntStream.Builder result = IntStream.builder(); + for (IntegerLiteral entry : entries) { + Number parsedNumber = this.buildNumber(entry, state); + if (parsedNumber == null) { + return null; + } + result.add(parsedNumber.intValue()); + } + return ops.createIntList(result.build()); + } + }, + LONG(TypeSuffix.LONG, TypeSuffix.BYTE, TypeSuffix.SHORT, TypeSuffix.INT) { + @Override + public T create(DynamicOps ops) { + return ops.createLongList(LongStream.empty()); + } + + @Nullable + @Override + public T create(DynamicOps ops, List entries, ParseState state) { + LongStream.Builder result = LongStream.builder(); + for (IntegerLiteral entry : entries) { + Number parsedNumber = this.buildNumber(entry, state); + if (parsedNumber == null) { + return null; + } + result.add(parsedNumber.longValue()); + } + return ops.createLongList(result.build()); + } + }; + + private final TypeSuffix defaultType; + private final Set additionalTypes; + + ArrayPrefix(final TypeSuffix defaultType, final TypeSuffix... additionalTypes) { + this.additionalTypes = Set.of(additionalTypes); + this.defaultType = defaultType; + } + + public boolean isAllowed(TypeSuffix type) { + return type == this.defaultType || this.additionalTypes.contains(type); + } + public abstract T create(DynamicOps ops); + + @Nullable + public abstract T create(DynamicOps ops, List entries, ParseState state); + + @Nullable + protected Number buildNumber(IntegerLiteral entry, ParseState state) { + TypeSuffix actualType = this.computeType(entry.suffix); + if (actualType == null) { + state.errorCollector().store(state.mark(), ERROR_INVALID_ARRAY_ELEMENT_TYPE); + return null; + } + return (Number) entry.create(VersionHelper.isOrAbove1_20_5() ? JavaOps.INSTANCE : LegacyJavaOps.INSTANCE, actualType, state); + } + + @Nullable + private TypeSuffix computeType(IntegerSuffix value) { + TypeSuffix type = value.type(); + if (type == null) { + return this.defaultType; + } + return !this.isAllowed(type) ? null : type; + } + } + enum Base { + BINARY, + DECIMAL, + HEX + } + + record IntegerLiteral(Sign sign, Base base, String digits, IntegerSuffix suffix) { + private SignedPrefix signedOrDefault() { + return Objects.requireNonNullElseGet(this.suffix.signed, () -> switch (this.base) { + case BINARY, HEX -> SignedPrefix.UNSIGNED; + case DECIMAL -> SignedPrefix.SIGNED; + }); + } + + private String cleanupDigits(Sign sign) { + boolean needsUnderscoreRemoval = needsUnderscoreRemoval(this.digits); + if (sign != Sign.MINUS && !needsUnderscoreRemoval) { + return this.digits; + } + StringBuilder result = new StringBuilder(); + sign.append(result); + cleanAndAppend(result, this.digits, needsUnderscoreRemoval); + return result.toString(); + } + + @Nullable + public T create(DynamicOps ops, ParseState state) { + return this.create(ops, Objects.requireNonNullElse(this.suffix.type, TypeSuffix.INT), state); + } + + @Nullable + public T create(DynamicOps ops, TypeSuffix type, ParseState state) { + boolean isSigned = this.signedOrDefault() == SignedPrefix.SIGNED; + if (!isSigned && this.sign == Sign.MINUS) { + state.errorCollector().store(state.mark(), ERROR_EXPECTED_NON_NEGATIVE_NUMBER); + return null; + } + String fixedDigits = this.cleanupDigits(this.sign); + + int radix = switch (this.base) { + case BINARY -> 2; + case DECIMAL -> 10; + case HEX -> 16; + }; + + try { + if (isSigned) { + return switch (type) { + case BYTE -> ops.createByte(Byte.parseByte(fixedDigits, radix)); + case SHORT -> ops.createShort(Short.parseShort(fixedDigits, radix)); + case INT -> ops.createInt(Integer.parseInt(fixedDigits, radix)); + case LONG -> ops.createLong(Long.parseLong(fixedDigits, radix)); + default -> { + state.errorCollector().store(state.mark(), ERROR_EXPECTED_INTEGER_TYPE); + yield null; + } + }; + } + return switch (type) { + case BYTE -> ops.createByte(com.google.common.primitives.UnsignedBytes.parseUnsignedByte(fixedDigits, radix)); + case SHORT -> ops.createShort(parseUnsignedShort(fixedDigits, radix)); + case INT -> ops.createInt(Integer.parseUnsignedInt(fixedDigits, radix)); + case LONG -> ops.createLong(Long.parseUnsignedLong(fixedDigits, radix)); + default -> { + state.errorCollector().store(state.mark(), ERROR_EXPECTED_INTEGER_TYPE); + yield null; + } + }; + } catch (NumberFormatException var8) { + state.errorCollector().store(state.mark(), createNumberParseError(var8)); + return null; + } + } + } + + record IntegerSuffix(@Nullable SignedPrefix signed, @Nullable TypeSuffix type) { + public static final IntegerSuffix EMPTY = new IntegerSuffix(null, null); + } + + enum Sign { + PLUS, + MINUS; + + public void append(StringBuilder output) { + if (this == MINUS) { + output.append("-"); + } + } + } + + record Signed(Sign sign, T value) { + } + + enum SignedPrefix { + SIGNED, + UNSIGNED + } + + static class SimpleHexLiteralParseRule extends GreedyPredicateParseRule { + public SimpleHexLiteralParseRule(int size) { + super(size, size, DelayedException.create(ERROR_EXPECTED_HEX_ESCAPE, String.valueOf(size))); + } + + @Override + protected boolean isAccepted(char c) { + return switch (c) { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' -> true; + default -> false; + }; + } + } + + enum TypeSuffix { + FLOAT, + DOUBLE, + BYTE, + SHORT, + INT, + LONG + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtOperations.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtOperations.java new file mode 100644 index 000000000..53a6fa7f0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/SnbtOperations.java @@ -0,0 +1,92 @@ +package net.momirealms.craftengine.core.util.snbt; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.serialization.DynamicOps; +import net.momirealms.craftengine.core.util.snbt.parse.*; +import net.momirealms.sparrow.nbt.util.UUIDUtil; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class SnbtOperations { + static final DelayedException ERROR_EXPECTED_STRING_UUID = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_string_uuid")) + ); + static final DelayedException ERROR_EXPECTED_NUMBER_OR_BOOLEAN = DelayedException.create( + new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_number_or_boolean")) + ); + public static final String BUILTIN_TRUE = "true"; + public static final String BUILTIN_FALSE = "false"; + public static final String BUILTIN_NULL = "null"; + public static final Map BUILTIN_OPERATIONS = Map.of( + new BuiltinKey("bool", 1), new BuiltinOperation() { + @Override + public T run(DynamicOps ops, List arguments, ParseState state) { + Boolean result = convert(ops, arguments.getFirst()); + if (result == null) { + state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_NUMBER_OR_BOOLEAN); + return null; + } + return ops.createBoolean(result); + } + + @Nullable + private static Boolean convert(DynamicOps ops, T arg) { + Optional asBoolean = ops.getBooleanValue(arg).result(); + if (asBoolean.isPresent()) { + return asBoolean.get(); + } else { + Optional asNumber = ops.getNumberValue(arg).result(); + return asNumber.isPresent() ? asNumber.get().doubleValue() != 0.0 : null; + } + } + }, new BuiltinKey("uuid", 1), new BuiltinOperation() { + @Override + public T run(DynamicOps ops, List arguments, ParseState state) { + Optional arg = ops.getStringValue(arguments.getFirst()).result(); + if (arg.isEmpty()) { + state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID); + return null; + } + UUID uuid; + try { + uuid = UUID.fromString(arg.get()); + } catch (IllegalArgumentException var7) { + state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID); + return null; + } + + return ops.createIntList(IntStream.of(UUIDUtil.uuidToIntArray(uuid))); + } + } + ); + public static final SuggestionSupplier BUILTIN_IDS = new SuggestionSupplier<>() { + private final Set keys = Stream.concat( + Stream.of(BUILTIN_FALSE, BUILTIN_TRUE, BUILTIN_NULL), SnbtOperations.BUILTIN_OPERATIONS.keySet().stream().map(BuiltinKey::id) + ) + .collect(Collectors.toSet()); + + @Override + public Stream possibleValues(ParseState state) { + return this.keys.stream(); + } + }; + + public record BuiltinKey(String id, int argCount) { + @Override + public @NotNull String toString() { + return this.id + "/" + this.argCount; + } + } + + public interface BuiltinOperation { + @Nullable + T run(DynamicOps ops, List arguments, ParseState state); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/TagParser.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/TagParser.java new file mode 100644 index 000000000..f963c635a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/TagParser.java @@ -0,0 +1,97 @@ +package net.momirealms.craftengine.core.util.snbt; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JavaOps; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.util.snbt.parse.LocalizedMessage; +import net.momirealms.craftengine.core.util.snbt.parse.LocalizedSimpleCommandExceptionType; +import net.momirealms.craftengine.core.util.snbt.parse.grammar.Grammar; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; +import net.momirealms.sparrow.nbt.codec.LegacyJavaOps; +import net.momirealms.sparrow.nbt.codec.LegacyNBTOps; +import net.momirealms.sparrow.nbt.codec.NBTOps; + +@SuppressWarnings("unused") +public class TagParser { + public static final SimpleCommandExceptionType ERROR_TRAILING_DATA = new LocalizedSimpleCommandExceptionType( + new LocalizedMessage("warning.config.type.snbt.parser.trailing") + ); + public static final SimpleCommandExceptionType ERROR_EXPECTED_COMPOUND = new LocalizedSimpleCommandExceptionType( + new LocalizedMessage("warning.config.type.snbt.parser.expected.compound") + ); + public static final char ELEMENT_SEPARATOR = ','; + public static final char NAME_VALUE_SEPARATOR = ':'; + public static final TagParser NBT_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? NBTOps.INSTANCE : LegacyNBTOps.INSTANCE); + public static final TagParser JAVA_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? JavaOps.INSTANCE : LegacyJavaOps.INSTANCE); + private final DynamicOps ops; + private final Grammar grammar; + + private TagParser(DynamicOps ops, Grammar grammar) { + this.ops = ops; + this.grammar = grammar; + } + + public DynamicOps ops() { + return this.ops; + } + + public static TagParser create(DynamicOps ops) { + return new TagParser<>(ops, SnbtGrammar.createParser(ops)); + } + + private static CompoundTag castToCompoundOrThrow(StringReader reader, Tag result) throws CommandSyntaxException { + if (result instanceof CompoundTag compoundTag) { + return compoundTag; + } + throw ERROR_EXPECTED_COMPOUND.createWithContext(reader); + } + public static CompoundTag parseCompoundFully(String input) throws CommandSyntaxException { + StringReader reader = new StringReader(input); + Tag result = NBT_OPS_PARSER.parseFully(reader); + return castToCompoundOrThrow(reader, result); + } + + public static Tag parseTagFully(String input) throws CommandSyntaxException { + StringReader reader = new StringReader(input); + return NBT_OPS_PARSER.parseFully(reader); + } + + public static Object parseObjectFully(String input) throws CommandSyntaxException { + StringReader reader = new StringReader(input); + return JAVA_OPS_PARSER.parseFully(reader); + } + + public T parseFully(String input) throws CommandSyntaxException { + return this.parseFully(new StringReader(input)); + } + + public T parseFully(StringReader reader) throws CommandSyntaxException { + T result = this.grammar.parse(reader); + reader.skipWhitespace(); + if (reader.canRead()) { + throw ERROR_TRAILING_DATA.createWithContext(reader); + } + return result; + } + + public T parseAsArgument(StringReader reader) throws CommandSyntaxException { + return this.grammar.parse(reader); + } + + public static CompoundTag parseCompoundAsArgument(StringReader reader) throws CommandSyntaxException { + Tag result = parseTagAsArgument(reader); + return castToCompoundOrThrow(reader, result); + } + + public static Tag parseTagAsArgument(StringReader reader) throws CommandSyntaxException { + return NBT_OPS_PARSER.parseAsArgument(reader); + } + + public static Object parseObjectAsArgument(StringReader reader) throws CommandSyntaxException { + return JAVA_OPS_PARSER.parseAsArgument(reader); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Atom.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Atom.java new file mode 100644 index 000000000..1601e947b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Atom.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public record Atom(String name) { + @Override + public @NotNull String toString() { + return "<" + this.name + ">"; + } + + public static Atom of(String name) { + return new Atom<>(name); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/CachedParseState.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/CachedParseState.java new file mode 100644 index 000000000..b71d66c24 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/CachedParseState.java @@ -0,0 +1,235 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import net.momirealms.craftengine.core.util.MiscUtils; + +import javax.annotation.Nullable; + +@SuppressWarnings("unchecked") +public abstract class CachedParseState implements ParseState { + private PositionCache[] positionCache = new PositionCache[256]; + private final ErrorCollector errorCollector; + private final Scope scope = new Scope(); + private SimpleControl[] controlCache = new SimpleControl[16]; + private int nextControlToReturn; + private final Silent silent = new Silent(); + public static final Object JAVA_NULL_VALUE_MARKER = new Object() { + @Override + public String toString() { + return "null"; + } + }; + + protected CachedParseState(ErrorCollector errorCollector) { + this.errorCollector = errorCollector; + } + + @Override + public Scope scope() { + return this.scope; + } + + @Override + public ErrorCollector errorCollector() { + return this.errorCollector; + } + + @Nullable + @Override + public T parse(NamedRule rule) { + int markBeforeParse = this.mark(); + PositionCache positionCache = this.getCacheForPosition(markBeforeParse); + int entryIndex = positionCache.findKeyIndex(rule.name()); + if (entryIndex != -1) { + CacheEntry value = positionCache.getValue(entryIndex); + if (value != null) { + if (value == CachedParseState.CacheEntry.NEGATIVE) { + return null; + } + this.restore(value.markAfterParse); + return value.value; + } + } else { + entryIndex = positionCache.allocateNewEntry(rule.name()); + } + + T result = rule.value().parse(this); + CacheEntry entry; + if (result == null) { + entry = CacheEntry.negativeEntry(); + } else { + int markAfterParse = this.mark(); + entry = new CacheEntry<>(result, markAfterParse); + } + + positionCache.setValue(entryIndex, entry); + return result; + } + + private PositionCache getCacheForPosition(int index) { + int currentSize = this.positionCache.length; + if (index >= currentSize) { + int newSize = MiscUtils.growByHalf(currentSize, index + 1); + PositionCache[] newCache = new PositionCache[newSize]; + System.arraycopy(this.positionCache, 0, newCache, 0, currentSize); + this.positionCache = newCache; + } + + PositionCache result = this.positionCache[index]; + if (result == null) { + result = new PositionCache(); + this.positionCache[index] = result; + } + + return result; + } + + @Override + public Control acquireControl() { + int currentSize = this.controlCache.length; + if (this.nextControlToReturn >= currentSize) { + int newSize = MiscUtils.growByHalf(currentSize, this.nextControlToReturn + 1); + SimpleControl[] newControlCache = new SimpleControl[newSize]; + System.arraycopy(this.controlCache, 0, newControlCache, 0, currentSize); + this.controlCache = newControlCache; + } + + int controlIndex = this.nextControlToReturn++; + SimpleControl entry = this.controlCache[controlIndex]; + if (entry == null) { + entry = new SimpleControl(); + this.controlCache[controlIndex] = entry; + } else { + entry.reset(); + } + + return entry; + } + + @Override + public void releaseControl() { + this.nextControlToReturn--; + } + + @Override + public ParseState silent() { + return this.silent; + } + + record CacheEntry(@Nullable T value, int markAfterParse) { + public static final CacheEntry NEGATIVE = new CacheEntry<>(null, -1); + + public static CacheEntry negativeEntry() { + return (CacheEntry) NEGATIVE; + } + } + + static class PositionCache { + public static final int ENTRY_STRIDE = 2; + private static final int NOT_FOUND = -1; + private Object[] atomCache = new Object[16]; + private int nextKey; + + public int findKeyIndex(Atom key) { + for (int i = 0; i < this.nextKey; i += ENTRY_STRIDE) { + if (this.atomCache[i] == key) { + return i; + } + } + + return NOT_FOUND; + } + + public int allocateNewEntry(Atom key) { + int newKeyIndex = this.nextKey; + this.nextKey += ENTRY_STRIDE; + int newValueIndex = newKeyIndex + 1; + int currentSize = this.atomCache.length; + if (newValueIndex >= currentSize) { + int newSize = MiscUtils.growByHalf(currentSize, newValueIndex + 1); + Object[] newCache = new Object[newSize]; + System.arraycopy(this.atomCache, 0, newCache, 0, currentSize); + this.atomCache = newCache; + } + + this.atomCache[newKeyIndex] = key; + return newKeyIndex; + } + + @Nullable + public CacheEntry getValue(int keyIndex) { + return (CacheEntry) this.atomCache[keyIndex + 1]; + } + + public void setValue(int keyIndex, CacheEntry entry) { + this.atomCache[keyIndex + 1] = entry; + } + } + + class Silent implements ParseState { + private final ErrorCollector silentCollector = new ErrorCollector.Nop<>(); + + @Override + public ErrorCollector errorCollector() { + return this.silentCollector; + } + + @Override + public Scope scope() { + return CachedParseState.this.scope(); + } + + @Nullable + @Override + public T parse(NamedRule rule) { + return CachedParseState.this.parse(rule); + } + + @Override + public S input() { + return CachedParseState.this.input(); + } + + @Override + public int mark() { + return CachedParseState.this.mark(); + } + + @Override + public void restore(int mark) { + CachedParseState.this.restore(mark); + } + + @Override + public Control acquireControl() { + return CachedParseState.this.acquireControl(); + } + + @Override + public void releaseControl() { + CachedParseState.this.releaseControl(); + } + + @Override + public ParseState silent() { + return this; + } + } + + static class SimpleControl implements Control { + private boolean hasCut; + + @Override + public void cut() { + this.hasCut = true; + } + + @Override + public boolean hasCut() { + return this.hasCut; + } + + public void reset() { + this.hasCut = false; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Control.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Control.java new file mode 100644 index 000000000..b17ce13ec --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Control.java @@ -0,0 +1,18 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +public interface Control { + Control UNBOUND = new Control() { + @Override + public void cut() { + } + + @Override + public boolean hasCut() { + return false; + } + }; + + void cut(); + + boolean hasCut(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/DelayedException.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/DelayedException.java new file mode 100644 index 000000000..173b85840 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/DelayedException.java @@ -0,0 +1,19 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.momirealms.craftengine.core.util.snbt.parse.grammar.StringReaderTerms; + +@FunctionalInterface +public interface DelayedException { + T create(String contents, int position); + + static DelayedException create(SimpleCommandExceptionType type) { + return (contents, position) -> type.createWithContext(StringReaderTerms.createReader(contents, position)); + } + + static DelayedException create(DynamicCommandExceptionType type, String argument) { + return (contents, position) -> type.createWithContext(StringReaderTerms.createReader(contents, position), argument); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Dictionary.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Dictionary.java new file mode 100644 index 000000000..66ea4e088 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Dictionary.java @@ -0,0 +1,93 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import javax.annotation.Nullable; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public class Dictionary { + private final Map, Entry> terms = new IdentityHashMap<>(); + + public NamedRule put(Atom name, Rule entry) { + Entry holder = (Entry)this.terms.computeIfAbsent(name, Entry::new); + if (holder.value != null) { + throw new IllegalArgumentException("Trying to override rule: " + name); + } + holder.value = entry; + return holder; + } + + public NamedRule putComplex(Atom name, Term term, Rule.RuleAction action) { + return this.put(name, Rule.fromTerm(term, action)); + } + + public NamedRule put(Atom name, Term term, Rule.SimpleRuleAction action) { + return this.put(name, Rule.fromTerm(term, action)); + } + + public void checkAllBound() { + List> unboundNames = this.terms.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .toList(); + if (!unboundNames.isEmpty()) { + throw new IllegalStateException("Unbound names: " + unboundNames); + } + } + + public NamedRule forward(Atom name) { + return this.getOrCreateEntry(name); + } + + private Entry getOrCreateEntry(Atom name) { + return (Entry)this.terms.computeIfAbsent(name, Entry::new); + } + + public Term named(Atom name) { + return new Reference<>(this.getOrCreateEntry(name), name); + } + + public Term namedWithAlias(Atom nameToParse, Atom nameToStore) { + return new Reference<>(this.getOrCreateEntry(nameToParse), nameToStore); + } + + static class Entry implements NamedRule, Supplier { + private final Atom name; + @Nullable + Rule value; + + private Entry(Atom name) { + this.name = name; + } + + @Override + public Atom name() { + return this.name; + } + + @Override + public Rule value() { + return Objects.requireNonNull(this.value, this); + } + + @Override + public String get() { + return "Unbound rule " + this.name; + } + } + + record Reference(Entry ruleToParse, Atom nameToStore) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + T result = state.parse(this.ruleToParse); + if (result == null) { + return false; + } + scope.put(this.nameToStore, result); + return true; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorCollector.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorCollector.java new file mode 100644 index 000000000..1bb43a0ae --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorCollector.java @@ -0,0 +1,97 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import net.momirealms.craftengine.core.util.MiscUtils; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("unchecked") +public interface ErrorCollector { + void store(int cursor, SuggestionSupplier suggestions, Object reason); + + default void store(int cursor, Object reason) { + this.store(cursor, SuggestionSupplier.empty(), reason); + } + + void finish(int finalCursor); + + class LongestOnly implements ErrorCollector { + private MutableErrorEntry[] entries = new MutableErrorEntry[16]; + private int nextErrorEntry; + private int lastCursor = -1; + + private void discardErrorsFromShorterParse(int cursor) { + if (cursor > this.lastCursor) { + this.lastCursor = cursor; + this.nextErrorEntry = 0; + } + } + + @Override + public void finish(int finalCursor) { + this.discardErrorsFromShorterParse(finalCursor); + } + + @Override + public void store(int cursor, SuggestionSupplier suggestions, Object reason) { + this.discardErrorsFromShorterParse(cursor); + if (cursor == this.lastCursor) { + this.addErrorEntry(suggestions, reason); + } + } + + private void addErrorEntry(SuggestionSupplier suggestions, Object reason) { + int currentSize = this.entries.length; + if (this.nextErrorEntry >= currentSize) { + int newSize = MiscUtils.growByHalf(currentSize, this.nextErrorEntry + 1); + MutableErrorEntry[] newEntries = new MutableErrorEntry[newSize]; + System.arraycopy(this.entries, 0, newEntries, 0, currentSize); + this.entries = newEntries; + } + + int entryIndex = this.nextErrorEntry++; + MutableErrorEntry entry = this.entries[entryIndex]; + if (entry == null) { + entry = new MutableErrorEntry<>(); + this.entries[entryIndex] = entry; + } + + entry.suggestions = suggestions; + entry.reason = reason; + } + + public List> entries() { + int errorCount = this.nextErrorEntry; + if (errorCount == 0) { + return List.of(); + } + List> result = new ArrayList<>(errorCount); + + for (int i = 0; i < errorCount; i++) { + MutableErrorEntry mutableErrorEntry = this.entries[i]; + result.add(new ErrorEntry<>(this.lastCursor, mutableErrorEntry.suggestions, mutableErrorEntry.reason)); + } + + return result; + } + + public int cursor() { + return this.lastCursor; + } + + static class MutableErrorEntry { + SuggestionSupplier suggestions = SuggestionSupplier.empty(); + Object reason = "empty"; + } + } + + class Nop implements ErrorCollector { + @Override + public void store(int cursor, SuggestionSupplier suggestions, Object reason) { + } + + @Override + public void finish(int finalCursor) { + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorEntry.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorEntry.java new file mode 100644 index 000000000..dff487beb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ErrorEntry.java @@ -0,0 +1,4 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +public record ErrorEntry(int cursor, SuggestionSupplier suggestions, Object reason) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedCommandSyntaxException.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedCommandSyntaxException.java new file mode 100644 index 000000000..21204ec40 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedCommandSyntaxException.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandExceptionType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.AdventureHelper; + +import java.util.Optional; +import java.util.function.Supplier; + +public class LocalizedCommandSyntaxException extends CommandSyntaxException { + public static final int CONTEXT_AMOUNT = 50; + public static final String PARSE_ERROR_NODE = "warning.config.type.snbt.invalid_syntax.parse_error"; + public static final String HERE_NODE = "warning.config.type.snbt.invalid_syntax.here"; + private final Message message; + private final String input; + private final int cursor; + + public LocalizedCommandSyntaxException(CommandExceptionType type, Message message) { + super(type, message); + this.message = message; + this.input = null; + this.cursor = -1; + } + + public LocalizedCommandSyntaxException(CommandExceptionType type, Message message, String input, int cursor) { + super(type, message, input, cursor); + this.message = message; + this.input = input; + this.cursor = cursor; + } + + @Override + public String getMessage() { + String message = this.message.getString(); + final String context = getContext(); + if (context == null) { + return message; + } + return generateLocalizedMessage( + PARSE_ERROR_NODE, + () -> message + " at position " + this.cursor + ": " + context, + message, String.valueOf(this.cursor), context + ); + } + + @Override + public String getContext() { + if (this.input == null || this.cursor < 0) { + return null; + } + final StringBuilder builder = new StringBuilder(); + final int cursor = Math.min(this.input.length(), this.cursor); + + if (cursor > CONTEXT_AMOUNT) { + builder.append("..."); + } + + builder.append(this.input, Math.max(0, cursor - CONTEXT_AMOUNT), cursor); + builder.append(generateLocalizedMessage(HERE_NODE, () -> "<--[HERE]")); + + return builder.toString(); + } + + + private String generateLocalizedMessage(String node, Supplier fallback, String... arguments) { + try { + String rawMessage = Optional.ofNullable(TranslationManager.instance() + .miniMessageTranslation(node)).orElse(fallback.get()); + String cleanMessage = AdventureHelper.miniMessage() + .stripTags(rawMessage); + for (int i = 0; i < arguments.length; i++) { + cleanMessage = cleanMessage.replace( + "", + arguments[i] != null ? arguments[i] : "null" + ); + } + return cleanMessage; + } catch (Exception e) { + return fallback.get(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedDynamicCommandExceptionType.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedDynamicCommandExceptionType.java new file mode 100644 index 000000000..ee80a3bee --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedDynamicCommandExceptionType.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.mojang.brigadier.ImmutableStringReader; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + +import java.util.function.Function; + +public class LocalizedDynamicCommandExceptionType extends DynamicCommandExceptionType { + private final Function function; + + public LocalizedDynamicCommandExceptionType(Function function) { + super(function); + this.function = function; + } + + @Override + public CommandSyntaxException create(final Object arg) { + return new LocalizedCommandSyntaxException(this, function.apply(arg)); + } + + @Override + public CommandSyntaxException createWithContext(final ImmutableStringReader reader, final Object arg) { + return new LocalizedCommandSyntaxException(this, function.apply(arg), reader.getString(), reader.getCursor()); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedMessage.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedMessage.java new file mode 100644 index 000000000..c8da1cab7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedMessage.java @@ -0,0 +1,53 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.mojang.brigadier.Message; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.AdventureHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Optional; + +public class LocalizedMessage implements Message { + private final String node; + private final String[] arguments; + + public LocalizedMessage( + @NotNull String node, + @Nullable String... arguments + ) { + this.node = node; + this.arguments = arguments != null + ? Arrays.copyOf(arguments, arguments.length) + : new String[0]; + } + + @Override + public String getString() { + return generateLocalizedMessage(); + } + + private String generateLocalizedMessage() { + try { + String rawMessage = Optional.ofNullable(TranslationManager.instance() + .miniMessageTranslation(this.node)).orElse(this.node); + String cleanMessage = AdventureHelper.miniMessage() + .stripTags(rawMessage); + for (int i = 0; i < arguments.length; i++) { + cleanMessage = cleanMessage.replace( + "", + arguments[i] != null ? arguments[i] : "null" + ); + } + return cleanMessage; + } catch (Exception e) { + return String.format( + "Failed to translate. Node: %s, Arguments: %s. Cause: %s", + node, + Arrays.toString(arguments), + e.getMessage() + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedSimpleCommandExceptionType.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedSimpleCommandExceptionType.java new file mode 100644 index 000000000..b046fab0c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/LocalizedSimpleCommandExceptionType.java @@ -0,0 +1,25 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.mojang.brigadier.ImmutableStringReader; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + +public class LocalizedSimpleCommandExceptionType extends SimpleCommandExceptionType { + private final Message message; + + public LocalizedSimpleCommandExceptionType(Message message) { + super(message); + this.message = message; + } + + @Override + public CommandSyntaxException create() { + return new LocalizedCommandSyntaxException(this, message); + } + + @Override + public CommandSyntaxException createWithContext(final ImmutableStringReader reader) { + return new LocalizedCommandSyntaxException(this, message, reader.getString(), reader.getCursor()); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/NamedRule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/NamedRule.java new file mode 100644 index 000000000..d4f634e1d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/NamedRule.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +public interface NamedRule { + Atom name(); + + Rule value(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ParseState.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ParseState.java new file mode 100644 index 000000000..2ab971364 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/ParseState.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import javax.annotation.Nullable; +import java.util.Optional; + +public interface ParseState { + Scope scope(); + + ErrorCollector errorCollector(); + + default Optional parseTopRule(NamedRule rule) { + T result = this.parse(rule); + if (result != null) { + this.errorCollector().finish(this.mark()); + } + + if (!this.scope().hasOnlySingleFrame()) { + throw new IllegalStateException("Malformed scope: " + this.scope()); + } + return Optional.ofNullable(result); + } + + @Nullable + T parse(NamedRule rule); + + S input(); + + int mark(); + + void restore(int mark); + + Control acquireControl(); + + void releaseControl(); + + ParseState silent(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Rule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Rule.java new file mode 100644 index 000000000..cfb7403c4 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Rule.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import javax.annotation.Nullable; + +public interface Rule { + @Nullable + T parse(ParseState state); + + static Rule fromTerm(Term child, RuleAction action) { + return new WrappedTerm<>(action, child); + } + + static Rule fromTerm(Term child, SimpleRuleAction action) { + return new WrappedTerm<>(action, child); + } + + @FunctionalInterface + interface RuleAction { + @Nullable + T run(ParseState state); + } + + @FunctionalInterface + interface SimpleRuleAction extends RuleAction { + T run(Scope ruleScope); + + @Override + default T run(ParseState state) { + return this.run(state.scope()); + } + } + + record WrappedTerm(RuleAction action, Term child) implements Rule { + @Nullable + @Override + public T parse(ParseState state) { + Scope scope = state.scope(); + scope.pushFrame(); + + try { + if (!this.child.parse(state, scope, Control.UNBOUND)) { + return null; + } + + return this.action.run(state); + } finally { + scope.popFrame(); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Scope.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Scope.java new file mode 100644 index 000000000..d152ecc89 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Scope.java @@ -0,0 +1,315 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import com.google.common.annotations.VisibleForTesting; +import net.momirealms.craftengine.core.util.MiscUtils; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unchecked") +public final class Scope { + private static final int NOT_FOUND = -1; + private static final Object FRAME_START_MARKER = new Object() { + @Override + public String toString() { + return "frame"; + } + }; + private static final int ENTRY_STRIDE = 2; + private Object[] stack = new Object[128]; + private int topEntryKeyIndex = 0; + private int topMarkerKeyIndex = 0; + private int depth; + + public Scope() { + this.stack[0] = FRAME_START_MARKER; + this.stack[1] = null; + } + + private int valueIndex(Atom atom) { + for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) { + Object key = this.stack[i]; + + assert key instanceof Atom; + + if (key == atom) { + return i + 1; + } + } + + return NOT_FOUND; + } + + public int valueIndexForAny(Atom... atoms) { + for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) { + Object key = this.stack[i]; + + assert key instanceof Atom; + + for (Atom atom : atoms) { + if (atom == key) { + return i + 1; + } + } + } + + return NOT_FOUND; + } + + private void ensureCapacity(int additionalEntryCount) { + int currentSize = this.stack.length; + int currentLastValueIndex = this.topEntryKeyIndex + 1; + int newLastValueIndex = currentLastValueIndex + additionalEntryCount * 2; + if (newLastValueIndex >= currentSize) { + int newSize = MiscUtils.growByHalf(currentSize, newLastValueIndex + 1); + Object[] newStack = new Object[newSize]; + System.arraycopy(this.stack, 0, newStack, 0, currentSize); + this.stack = newStack; + } + + assert this.validateStructure(); + } + + private void setupNewFrame() { + this.topEntryKeyIndex += ENTRY_STRIDE; + this.stack[this.topEntryKeyIndex] = FRAME_START_MARKER; + this.stack[this.topEntryKeyIndex + 1] = this.topMarkerKeyIndex; + this.topMarkerKeyIndex = this.topEntryKeyIndex; + } + + public void pushFrame() { + this.ensureCapacity(1); + this.setupNewFrame(); + + assert this.validateStructure(); + } + + private int getPreviousMarkerIndex(int markerKeyIndex) { + return (Integer) this.stack[markerKeyIndex + 1]; + } + + public void popFrame() { + assert this.topMarkerKeyIndex != 0; + + this.topEntryKeyIndex = this.topMarkerKeyIndex - ENTRY_STRIDE; + this.topMarkerKeyIndex = this.getPreviousMarkerIndex(this.topMarkerKeyIndex); + + assert this.validateStructure(); + } + + public void splitFrame() { + int currentFrameMarkerIndex = this.topMarkerKeyIndex; + int nonMarkerEntriesInFrame = (this.topEntryKeyIndex - this.topMarkerKeyIndex) / ENTRY_STRIDE; + this.ensureCapacity(nonMarkerEntriesInFrame + 1); + this.setupNewFrame(); + int sourceCursor = currentFrameMarkerIndex + ENTRY_STRIDE; + int targetCursor = this.topEntryKeyIndex; + + for (int i = 0; i < nonMarkerEntriesInFrame; i++) { + targetCursor += ENTRY_STRIDE; + Object key = this.stack[sourceCursor]; + + assert key != null; + + this.stack[targetCursor] = key; + this.stack[targetCursor + 1] = null; + sourceCursor += ENTRY_STRIDE; + } + + this.topEntryKeyIndex = targetCursor; + + assert this.validateStructure(); + } + + public void clearFrameValues() { + for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) { + assert this.stack[i] instanceof Atom; + + this.stack[i + 1] = null; + } + + assert this.validateStructure(); + } + + public void mergeFrame() { + int previousMarkerIndex = this.getPreviousMarkerIndex(this.topMarkerKeyIndex); + int previousFrameCursor = previousMarkerIndex; + int currentFrameCursor = this.topMarkerKeyIndex; + + while (currentFrameCursor < this.topEntryKeyIndex) { + previousFrameCursor += ENTRY_STRIDE; + currentFrameCursor += ENTRY_STRIDE; + Object newKey = this.stack[currentFrameCursor]; + + assert newKey instanceof Atom; + + Object newValue = this.stack[currentFrameCursor + 1]; + Object oldKey = this.stack[previousFrameCursor]; + if (oldKey != newKey) { + this.stack[previousFrameCursor] = newKey; + this.stack[previousFrameCursor + 1] = newValue; + } else if (newValue != null) { + this.stack[previousFrameCursor + 1] = newValue; + } + } + + this.topEntryKeyIndex = previousFrameCursor; + this.topMarkerKeyIndex = previousMarkerIndex; + + assert this.validateStructure(); + } + + public void put(Atom name, @Nullable T value) { + int valueIndex = this.valueIndex(name); + if (valueIndex != NOT_FOUND) { + this.stack[valueIndex] = value; + } else { + this.ensureCapacity(1); + this.topEntryKeyIndex += ENTRY_STRIDE; + this.stack[this.topEntryKeyIndex] = name; + this.stack[this.topEntryKeyIndex + 1] = value; + } + + assert this.validateStructure(); + } + + @Nullable + public T get(Atom name) { + int valueIndex = this.valueIndex(name); + return (T) (valueIndex != NOT_FOUND ? this.stack[valueIndex] : null); + } + + public T getOrThrow(Atom name) { + int valueIndex = this.valueIndex(name); + if (valueIndex == NOT_FOUND) { + throw new IllegalArgumentException("No value for atom " + name); + } + return (T) this.stack[valueIndex]; + } + + public T getOrDefault(Atom name, T fallback) { + int valueIndex = this.valueIndex(name); + return (T) (valueIndex != NOT_FOUND ? this.stack[valueIndex] : fallback); + } + + @Nullable + @SafeVarargs + public final T getAny(Atom... names) { + int valueIndex = this.valueIndexForAny(names); + return (T) (valueIndex != NOT_FOUND ? this.stack[valueIndex] : null); + } + + @SafeVarargs + public final T getAnyOrThrow(Atom... names) { + int valueIndex = this.valueIndexForAny(names); + if (valueIndex == NOT_FOUND) { + throw new IllegalArgumentException("No value for atoms " + Arrays.toString(names)); + } + return (T) this.stack[valueIndex]; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + boolean afterFrame = true; + + for (int i = 0; i <= this.topEntryKeyIndex; i += ENTRY_STRIDE) { + Object key = this.stack[i]; + Object value = this.stack[i + 1]; + if (key == FRAME_START_MARKER) { + result.append('|'); + afterFrame = true; + } else { + if (!afterFrame) { + result.append(','); + } + + afterFrame = false; + result.append(key).append(':').append(value); + } + } + + return result.toString(); + } + + @VisibleForTesting + public Map, ?> lastFrame() { + HashMap, Object> result = new HashMap<>(); + + for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) { + Object key = this.stack[i]; + Object value = this.stack[i + 1]; + result.put((Atom) key, value); + } + + return result; + } + + public boolean hasOnlySingleFrame() { + for (int i = this.topEntryKeyIndex; i > 0; i--) { + if (this.stack[i] == FRAME_START_MARKER) { + return false; + } + } + + if (this.stack[0] != FRAME_START_MARKER) { + throw new IllegalStateException("Corrupted stack"); + } + return true; + } + + private boolean validateStructure() { + assert this.topMarkerKeyIndex >= 0; + + assert this.topEntryKeyIndex >= this.topMarkerKeyIndex; + + for (int i = 0; i <= this.topEntryKeyIndex; i += ENTRY_STRIDE) { + Object object = this.stack[i]; + if (object != FRAME_START_MARKER && !(object instanceof Atom)) { + return false; + } + } + + for (int ix = this.topMarkerKeyIndex; ix != 0; ix = this.getPreviousMarkerIndex(ix)) { + Object object = this.stack[ix]; + if (object != FRAME_START_MARKER) { + return false; + } + } + + return true; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Term increaseDepth() { + class IncreasingDepthTerm implements Term { + public static final IncreasingDepthTerm INSTANCE = new IncreasingDepthTerm(); + + @Override + public boolean parse(final ParseState state, final Scope scope, final Control control) { + if (++scope.depth > 512) { + state.errorCollector().store(state.mark(), new IllegalStateException("Too deep")); + return false; + } + return true; + } + } + return (Term) IncreasingDepthTerm.INSTANCE; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Term decreaseDepth() { + class DecreasingDepthTerm implements Term { + public static final DecreasingDepthTerm INSTANCE = new DecreasingDepthTerm(); + + @Override + public boolean parse(final ParseState state, final Scope scope, final Control control) { + scope.depth--; + return true; + } + } + return (Term) DecreasingDepthTerm.INSTANCE; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/SuggestionSupplier.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/SuggestionSupplier.java new file mode 100644 index 000000000..30c32ac7c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/SuggestionSupplier.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import java.util.stream.Stream; + +public interface SuggestionSupplier { + Stream possibleValues(ParseState state); + + static SuggestionSupplier empty() { + return state -> Stream.empty(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Term.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Term.java new file mode 100644 index 000000000..383602b0e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/Term.java @@ -0,0 +1,235 @@ +package net.momirealms.craftengine.core.util.snbt.parse; + +import java.util.ArrayList; +import java.util.List; + +public interface Term { + boolean parse(ParseState state, Scope scope, Control control); + + static Term marker(Atom name, T value) { + return new Marker<>(name, value); + } + + @SafeVarargs + static Term sequence(Term... terms) { + return new Sequence<>(terms); + } + + @SafeVarargs + static Term alternative(Term... terms) { + return new Alternative<>(terms); + } + + static Term optional(Term term) { + return new Maybe<>(term); + } + + static Term repeated(NamedRule element, Atom> listName) { + return repeated(element, listName, 0); + } + + static Term repeated(NamedRule element, Atom> listName, int minRepetitions) { + return new Repeated<>(element, listName, minRepetitions); + } + + static Term repeatedWithTrailingSeparator(NamedRule element, Atom> listName, Term separator) { + return repeatedWithTrailingSeparator(element, listName, separator, 0); + } + + static Term repeatedWithTrailingSeparator(NamedRule element, Atom> listName, Term seperator, int minRepetitions) { + return new RepeatedWithSeparator<>(element, listName, seperator, minRepetitions, true); + } + + static Term positiveLookahead(Term term) { + return new LookAhead<>(term, true); + } + + static Term cut() { + return new Term<>() { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + control.cut(); + return true; + } + + @Override + public String toString() { + return "↑"; + } + }; + } + + static Term empty() { + return new Term<>() { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + return true; + } + + @Override + public String toString() { + return "ε"; + } + }; + } + + static Term fail(final Object message) { + return new Term<>() { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + state.errorCollector().store(state.mark(), message); + return false; + } + + @Override + public String toString() { + return "fail"; + } + }; + } + + record Alternative(Term[] elements) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + Control controlForThis = state.acquireControl(); + + try { + int mark = state.mark(); + scope.splitFrame(); + + for (Term element : this.elements) { + if (element.parse(state, scope, controlForThis)) { + scope.mergeFrame(); + return true; + } + + scope.clearFrameValues(); + state.restore(mark); + if (controlForThis.hasCut()) { + break; + } + } + + scope.popFrame(); + return false; + } finally { + state.releaseControl(); + } + } + } + + record LookAhead(Term term, boolean positive) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + int mark = state.mark(); + boolean result = this.term.parse(state.silent(), scope, control); + state.restore(mark); + return this.positive == result; + } + } + + record Marker(Atom name, T value) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + scope.put(this.name, this.value); + return true; + } + } + + record Maybe(Term term) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + int mark = state.mark(); + if (!this.term.parse(state, scope, control)) { + state.restore(mark); + } + + return true; + } + } + + record Repeated(NamedRule element, Atom> listName, int minRepetitions) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + int mark = state.mark(); + List elements = new ArrayList<>(this.minRepetitions); + + while (true) { + int entryMark = state.mark(); + T parsedElement = state.parse(this.element); + if (parsedElement == null) { + state.restore(entryMark); + if (elements.size() < this.minRepetitions) { + state.restore(mark); + return false; + } + scope.put(this.listName, elements); + return true; + } + + elements.add(parsedElement); + } + } + } + + record RepeatedWithSeparator( + NamedRule element, Atom> listName, Term separator, int minRepetitions, boolean allowTrailingSeparator + ) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + int listMark = state.mark(); + List elements = new ArrayList<>(this.minRepetitions); + boolean first = true; + + while (true) { + int markBeforeSeparator = state.mark(); + if (!first && !this.separator.parse(state, scope, control)) { + state.restore(markBeforeSeparator); + break; + } + + int markAfterSeparator = state.mark(); + T parsedElement = state.parse(this.element); + if (parsedElement == null) { + if (first) { + state.restore(markAfterSeparator); + } else { + if (!this.allowTrailingSeparator) { + state.restore(listMark); + return false; + } + + state.restore(markAfterSeparator); + } + break; + } + + elements.add(parsedElement); + first = false; + } + + if (elements.size() < this.minRepetitions) { + state.restore(listMark); + return false; + } + scope.put(this.listName, elements); + return true; + } + } + + record Sequence(Term[] elements) implements Term { + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + int mark = state.mark(); + + for (Term element : this.elements) { + if (!element.parse(state, scope, control)) { + state.restore(mark); + return false; + } + } + + return true; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/Grammar.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/Grammar.java new file mode 100644 index 000000000..e93117570 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/Grammar.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.util.snbt.parse.*; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public record Grammar(Dictionary rules, NamedRule top) { + public Grammar { + rules.checkAllBound(); + } + + public Optional parse(ParseState state) { + return state.parseTopRule(this.top); + } + + public T parse(StringReader reader) throws CommandSyntaxException { + ErrorCollector.LongestOnly errorCollector = new ErrorCollector.LongestOnly<>(); + StringReaderParserState stringReaderParserState = new StringReaderParserState(errorCollector, reader); + Optional optionalResult = this.parse(stringReaderParserState); + if (optionalResult.isPresent()) { + T result = optionalResult.get(); + if (CachedParseState.JAVA_NULL_VALUE_MARKER.equals(result)) { + result = null; + } + return result; + } + List> errorEntries = errorCollector.entries(); + List exceptions = errorEntries.stream().mapMulti((entry, output) -> { + if (entry.reason() instanceof DelayedException delayedException) { + output.accept(delayedException.create(reader.getString(), entry.cursor())); + } else if (entry.reason() instanceof Exception exception1) { + output.accept(exception1); + } + }).toList(); + + for (Exception exception : exceptions) { + if (exception instanceof CommandSyntaxException cse) { + throw cse; + } + } + + if (exceptions.size() == 1 && exceptions.getFirst() instanceof RuntimeException re) { + throw re; + } + throw new IllegalStateException("Failed to parse: " + errorEntries.stream().map(ErrorEntry::toString).collect(Collectors.joining(", "))); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPatternParseRule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPatternParseRule.java new file mode 100644 index 000000000..58bf9d797 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPatternParseRule.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.util.snbt.parse.DelayedException; +import net.momirealms.craftengine.core.util.snbt.parse.ParseState; +import net.momirealms.craftengine.core.util.snbt.parse.Rule; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class GreedyPatternParseRule implements Rule { + private final Pattern pattern; + private final DelayedException error; + + public GreedyPatternParseRule(Pattern pattern, DelayedException error) { + this.pattern = pattern; + this.error = error; + } + + @Override + public String parse(ParseState state) { + StringReader input = state.input(); + String fullString = input.getString(); + Matcher matcher = this.pattern.matcher(fullString).region(input.getCursor(), fullString.length()); + if (!matcher.lookingAt()) { + state.errorCollector().store(state.mark(), this.error); + return null; + } + input.setCursor(matcher.end()); + return matcher.group(0); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPredicateParseRule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPredicateParseRule.java new file mode 100644 index 000000000..3144dcc27 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/GreedyPredicateParseRule.java @@ -0,0 +1,48 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.util.snbt.parse.DelayedException; +import net.momirealms.craftengine.core.util.snbt.parse.ParseState; +import net.momirealms.craftengine.core.util.snbt.parse.Rule; + +import javax.annotation.Nullable; + +public abstract class GreedyPredicateParseRule implements Rule { + private final int minSize; + private final int maxSize; + private final DelayedException error; + + public GreedyPredicateParseRule(int minSize, DelayedException error) { + this(minSize, Integer.MAX_VALUE, error); + } + + public GreedyPredicateParseRule(int minSize, int maxSize, DelayedException error) { + this.minSize = minSize; + this.maxSize = maxSize; + this.error = error; + } + + @Nullable + @Override + public String parse(ParseState state) { + StringReader input = state.input(); + String fullString = input.getString(); + int start = input.getCursor(); + int pos = start; + + while (pos < fullString.length() && this.isAccepted(fullString.charAt(pos)) && pos - start < this.maxSize) { + pos++; + } + + int length = pos - start; + if (length < this.minSize) { + state.errorCollector().store(state.mark(), this.error); + return null; + } + input.setCursor(pos); + return fullString.substring(start, pos); + } + + protected abstract boolean isAccepted(char c); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/NumberRunParseRule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/NumberRunParseRule.java new file mode 100644 index 000000000..dd4e6249b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/NumberRunParseRule.java @@ -0,0 +1,46 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.util.snbt.parse.DelayedException; +import net.momirealms.craftengine.core.util.snbt.parse.ParseState; +import net.momirealms.craftengine.core.util.snbt.parse.Rule; + +import javax.annotation.Nullable; + +public abstract class NumberRunParseRule implements Rule { + private final DelayedException noValueError; + private final DelayedException underscoreNotAllowedError; + + public NumberRunParseRule(DelayedException noValueError, DelayedException underscoreNotAllowedError) { + this.noValueError = noValueError; + this.underscoreNotAllowedError = underscoreNotAllowedError; + } + + @Nullable + @Override + public String parse(ParseState state) { + StringReader input = state.input(); + input.skipWhitespace(); + String fullString = input.getString(); + int start = input.getCursor(); + int pos = start; + + while (pos < fullString.length() && this.isAccepted(fullString.charAt(pos))) { + pos++; + } + + int length = pos - start; + if (length == 0) { + state.errorCollector().store(state.mark(), this.noValueError); + return null; + } else if (fullString.charAt(start) != '_' && fullString.charAt(pos - 1) != '_') { + input.setCursor(pos); + return fullString.substring(start, pos); + } + state.errorCollector().store(state.mark(), this.underscoreNotAllowedError); + return null; + } + + protected abstract boolean isAccepted(char c); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderParserState.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderParserState.java new file mode 100644 index 000000000..d076fafd4 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderParserState.java @@ -0,0 +1,29 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import net.momirealms.craftengine.core.util.snbt.parse.CachedParseState; +import net.momirealms.craftengine.core.util.snbt.parse.ErrorCollector; + +public class StringReaderParserState extends CachedParseState { + private final StringReader input; + + public StringReaderParserState(ErrorCollector errorCollector, StringReader input) { + super(errorCollector); + this.input = input; + } + + @Override + public StringReader input() { + return this.input; + } + + @Override + public int mark() { + return this.input.getCursor(); + } + + @Override + public void restore(int mark) { + this.input.setCursor(mark); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderTerms.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderTerms.java new file mode 100644 index 000000000..14fe3f3aa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/StringReaderTerms.java @@ -0,0 +1,64 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import it.unimi.dsi.fastutil.chars.CharList; +import net.momirealms.craftengine.core.util.snbt.parse.*; + +import java.util.stream.Collectors; + +public interface StringReaderTerms { + DynamicCommandExceptionType LITERAL_INCORRECT = new LocalizedDynamicCommandExceptionType( + expected -> new LocalizedMessage("warning.config.type.snbt.parser.incorrect", String.valueOf(expected)) + ); + + static Term character(final char value) { + return new TerminalCharacters(CharList.of(value)) { + @Override + protected boolean isAccepted(char v) { + return value == v; + } + }; + } + + static Term characters(final char v1, final char v2) { + return new TerminalCharacters(CharList.of(v1, v2)) { + @Override + protected boolean isAccepted(char v) { + return v == v1 || v == v2; + } + }; + } + + static StringReader createReader(String contents, int cursor) { + StringReader reader = new StringReader(contents); + reader.setCursor(cursor); + return reader; + } + + abstract class TerminalCharacters implements Term { + private final DelayedException error; + private final SuggestionSupplier suggestions; + + public TerminalCharacters(CharList values) { + String joinedValues = values.intStream().mapToObj(Character::toString).collect(Collectors.joining("|")); + this.error = DelayedException.create(LITERAL_INCORRECT, joinedValues); + this.suggestions = s -> values.intStream().mapToObj(Character::toString); + } + + @Override + public boolean parse(ParseState state, Scope scope, Control control) { + state.input().skipWhitespace(); + int cursor = state.mark(); + if (state.input().canRead() && this.isAccepted(state.input().read())) { + return true; + } + state.errorCollector().store(cursor, this.suggestions, this.error); + return false; + } + + protected abstract boolean isAccepted(char value); + } + +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/UnquotedStringParseRule.java b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/UnquotedStringParseRule.java new file mode 100644 index 000000000..2d46092b7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/snbt/parse/grammar/UnquotedStringParseRule.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.util.snbt.parse.grammar; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.momirealms.craftengine.core.util.snbt.parse.DelayedException; +import net.momirealms.craftengine.core.util.snbt.parse.ParseState; +import net.momirealms.craftengine.core.util.snbt.parse.Rule; + +import javax.annotation.Nullable; + +public class UnquotedStringParseRule implements Rule { + private final int minSize; + private final DelayedException error; + + public UnquotedStringParseRule(int minSize, DelayedException error) { + this.minSize = minSize; + this.error = error; + } + + @Nullable + @Override + public String parse(ParseState state) { + state.input().skipWhitespace(); + int cursor = state.mark(); + String value = state.input().readUnquotedString(); + if (value.length() < this.minSize) { + state.errorCollector().store(cursor, this.error); + return null; + } + return value; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java index 700442fb3..404594503 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java @@ -1,13 +1,15 @@ package net.momirealms.craftengine.core.world; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.world.collision.AABB; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; public interface Cullable { - AABB aabb(); - void show(Player player); void hide(Player player); + + @Nullable + CullingData cullingData(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/WorldPosition.java b/core/src/main/java/net/momirealms/craftengine/core/world/WorldPosition.java index 868538ea6..fb0b9dd02 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/WorldPosition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/WorldPosition.java @@ -1,12 +1,14 @@ package net.momirealms.craftengine.core.world; +import java.util.Objects; + public class WorldPosition implements Position { - private final World world; - private final double x; - private final double y; - private final double z; - private final float xRot; - private final float yRot; + public final World world; + public final double x; + public final double y; + public final double z; + public final float xRot; + public final float yRot; public WorldPosition(World world, Position position) { this.x = position.x(); @@ -70,4 +72,28 @@ public class WorldPosition implements Position { public float yRot() { return yRot; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WorldPosition that = (WorldPosition) o; + return Double.compare(that.x, this.x) == 0 && + Double.compare(that.y, this.y) == 0 && + Double.compare(that.z, this.z) == 0 && + Float.compare(that.xRot, this.xRot) == 0 && + Float.compare(that.yRot, this.yRot) == 0 && + Objects.equals(this.world, that.world); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(this.world); + result = 31 * result + Double.hashCode(this.x); + result = 31 * result + Double.hashCode(this.y); + result = 31 * result + Double.hashCode(this.z); + result = 31 * result + Float.floatToIntBits(this.xRot); + result = 31 * result + Float.floatToIntBits(this.yRot); + return result; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index 4ca2fe5df..d278ed0cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; @@ -25,40 +26,42 @@ import java.util.*; import java.util.concurrent.locks.ReentrantReadWriteLock; public class CEChunk { + private static final int DEFAULT_MAP_SIZE = 8; public final CEWorld world; public final ChunkPos chunkPos; public final CESection[] sections; public final WorldHeight worldHeightAccessor; - public final Map blockEntities; // 从区域线程上访问,安全 - public final Map tickingSyncBlockEntitiesByPos; // 从区域线程上访问,安全 - public final Map tickingAsyncBlockEntitiesByPos; // 从区域线程上访问,安全 - public final Map constantBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 - public final Map dynamicBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 + private final Map blockEntities; // 从区域线程上访问,安全 + private final Map tickingSyncBlockEntitiesByPos; // 从区域线程上访问,安全 + private final Map tickingAsyncBlockEntitiesByPos; // 从区域线程上访问,安全 + private final Map constantBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 + private final Map dynamicBlockEntityRenderers; // 会从区域线程上读写,netty线程上读取 private final ReentrantReadWriteLock renderLock = new ReentrantReadWriteLock(); private volatile boolean dirty; private volatile boolean loaded; private volatile boolean activated; + private boolean isEntitiesLoaded; public CEChunk(CEWorld world, ChunkPos chunkPos) { this.world = world; this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); this.sections = new CESection[this.worldHeightAccessor.getSectionsCount()]; - this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.blockEntities = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); this.fillEmptySection(); } - public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, @Nullable ListTag blockEntitiesTag, @Nullable ListTag itemDisplayBlockRenders) { + public CEChunk(CEWorld world, ChunkPos chunkPos, CESection[] sections, @Nullable ListTag blockEntitiesTag, @Nullable ListTag blockEntityRenders, @Nullable ListTag entities) { this.world = world; this.chunkPos = chunkPos; this.worldHeightAccessor = world.worldHeight(); - this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); - this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.dynamicBlockEntityRenderers = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.tickingSyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); + this.tickingAsyncBlockEntitiesByPos = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); int sectionCount = this.worldHeightAccessor.getSectionsCount(); this.sections = new CESection[sectionCount]; if (sections != null) { @@ -71,22 +74,22 @@ public class CEChunk { } this.fillEmptySection(); if (blockEntitiesTag != null) { - this.blockEntities = new Object2ObjectOpenHashMap<>(Math.max(blockEntitiesTag.size(), 10), 0.5f); + this.blockEntities = new Object2ObjectOpenHashMap<>(Math.max(blockEntitiesTag.size(), DEFAULT_MAP_SIZE), 0.5f); List blockEntities = DefaultBlockEntitySerializer.deserialize(this, blockEntitiesTag); for (BlockEntity blockEntity : blockEntities) { this.setBlockEntity(blockEntity); } } else { - this.blockEntities = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.blockEntities = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); } - if (itemDisplayBlockRenders != null) { - this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(itemDisplayBlockRenders.size(), 10), 0.5f); - List blockEntityRendererPoses = DefaultBlockEntityRendererSerializer.deserialize(this.chunkPos, itemDisplayBlockRenders); + if (blockEntityRenders != null) { + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(Math.max(blockEntityRenders.size(), DEFAULT_MAP_SIZE), 0.5f); + List blockEntityRendererPoses = DefaultBlockEntityRendererSerializer.deserialize(this.chunkPos, blockEntityRenders); for (BlockPos pos : blockEntityRendererPoses) { this.addConstantBlockEntityRenderer(pos); } } else { - this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(10, 0.5f); + this.constantBlockEntityRenderers = new Object2ObjectOpenHashMap<>(DEFAULT_MAP_SIZE, 0.5f); } } @@ -139,7 +142,12 @@ public class CEChunk { BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; - ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements, state.estimatedBoundingBox().move(pos)); + ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer( + elements, + Optional.ofNullable(state.cullingData()) + .map(data -> new CullingData(data.aabb.move(pos), data.maxDistance, data.aabbExpansion, data.rayTracing)) + .orElse(null) + ); World wrappedWorld = this.world.world(); List trackedBy = getTrackedBy(); boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty(); @@ -147,6 +155,12 @@ public class CEChunk { if (previous != null) { // 由于entity-render的体量基本都很小,所以考虑一个特殊情况,即前后都是1个renderer,对此情况进行简化和优化 BlockEntityElement[] previousElements = previous.elements().clone(); + + /* + * + * 1 对 1,命中率最高 + * + */ if (previousElements.length == 1 && renderers.length == 1) { BlockEntityElement previousElement = previousElements[0]; BlockEntityElementConfig config = renderers[0]; @@ -156,18 +170,19 @@ public class CEChunk { if (element != null) { elements[0] = element; if (hasTrackedBy) { - // 如果启用实体剔除,那么只对已经渲染的进行变换 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { + for (Player player : trackedBy) { + // 如果启用剔除,则暂时保留原先可见度,因为大概率可见度不发生变化 + if (Config.enableEntityCulling()) { VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); - if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + if (trackedBlockEntity == null || trackedBlockEntity.isShown) { element.transform(player); } - } - } - // 否则直接变换 - else { - for (Player player : trackedBy) { + if (trackedBlockEntity != null) { + trackedBlockEntity.setCullable(renderer); + } else { + player.addTrackedBlockEntity(pos, renderer); + } + } else { element.transform(player); } } @@ -178,44 +193,56 @@ public class CEChunk { BlockEntityElement element = config.create(wrappedWorld, pos); elements[0] = element; if (hasTrackedBy) { - // 如果启用实体剔除,那么只添加记录 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - player.addTrackedBlockEntity(pos, renderer); - } - } - // 否则直接显示 - else { - for (Player player : trackedBy) { + for (Player player : trackedBy) { + if (Config.enableEntityCulling()) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity != null) { + if (trackedBlockEntity.isShown) { + trackedBlockEntity.setShown(player, false); + } + trackedBlockEntity.setCullable(renderer); + } else { + player.addTrackedBlockEntity(pos, renderer); + } + } else { previousElement.hide(player); element.show(player); } } } } - } else { + } + /* + * + * 1 对 多, 多 对 多 + * + */ + else { + + VirtualCullableObject[] previousObjects = hasTrackedBy ? new VirtualCullableObject[trackedBy.size()] : null; + if (hasTrackedBy) { + for (int j = 0; j < previousObjects.length; j++) { + previousObjects[j] = trackedBy.get(j).getTrackedBlockEntity(pos); + } + } + outer: for (int i = 0; i < elements.length; i++) { BlockEntityElementConfig config = renderers[i]; + /* + * 严格可变换部分 + */ for (int j = 0; j < previousElements.length; j++) { BlockEntityElement previousElement = previousElements[j]; if (previousElement != null && config.elementClass().isInstance(previousElement)) { - BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement); + BlockEntityElement newElement = ((BlockEntityElementConfig) config).createExact(wrappedWorld, pos, previousElement); if (newElement != null) { previousElements[j] = null; elements[i] = newElement; if (hasTrackedBy) { - // 如果启用实体剔除,那么只对已经渲染的进行变换 - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); - if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { - newElement.transform(player); - } - } - } - // 否则直接变换 - else { - for (Player player : trackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { newElement.transform(player); } } @@ -224,21 +251,48 @@ public class CEChunk { } } } + /* + * 可变换部分 + */ + for (int j = 0; j < previousElements.length; j++) { + BlockEntityElement previousElement = previousElements[j]; + if (previousElement != null && config.elementClass().isInstance(previousElement)) { + BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement); + if (newElement != null) { + previousElements[j] = null; + elements[i] = newElement; + if (hasTrackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { + newElement.transform(player); + } + } + } + continue outer; + } + } + } + /* + * 不可变换的直接生成 + */ BlockEntityElement newElement = config.create(wrappedWorld, pos); elements[i] = newElement; if (hasTrackedBy) { - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { - player.addTrackedBlockEntity(pos, renderer); - } - } else { - for (Player player : trackedBy) { + for (int k = 0; k < trackedBy.size(); k++) { + Player player = trackedBy.get(k); + VirtualCullableObject cullableObject = previousObjects[k]; + if (cullableObject == null || cullableObject.isShown) { newElement.show(player); } } } } if (hasTrackedBy) { + /* + * 未能完成变化的,需要直接删除 + */ for (int i = 0; i < previousElements.length; i++) { BlockEntityElement previousElement = previousElements[i]; if (previousElement != null) { @@ -247,19 +301,31 @@ public class CEChunk { } } } + // 添加 track + for (int i = 0; i < previousObjects.length; i++) { + VirtualCullableObject previousObject = previousObjects[i]; + if (previousObject != null) { + previousObject.setCullable(renderer); + } else { + trackedBy.get(i).addTrackedBlockEntity(pos, renderer); + } + } } } } else { + /* + * + * 全新方块实体 + * + */ for (int i = 0; i < elements.length; i++) { elements[i] = renderers[i].create(wrappedWorld, pos); } if (hasTrackedBy) { - if (Config.enableEntityCulling()) { - for (Player player : trackedBy) { + for (Player player : trackedBy) { + if (Config.enableEntityCulling()) { player.addTrackedBlockEntity(pos, renderer); - } - } else { - for (Player player : trackedBy) { + } else { renderer.show(player); } } @@ -456,7 +522,7 @@ public class CEChunk { BlockPos pos = blockEntity.pos(); ImmutableBlockState blockState = this.getBlockState(pos); if (!blockState.hasBlockEntity()) { - Debugger.BLOCK_ENTITY.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos); + Debugger.BLOCK.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos); return; } // 设置方块实体所在世界 @@ -600,6 +666,14 @@ public class CEChunk { return this.sections; } + public boolean isEntitiesLoaded() { + return this.isEntitiesLoaded; + } + + public void setEntitiesLoaded(boolean entitiesLoaded) { + this.isEntitiesLoaded = entitiesLoaded; + } + public boolean isLoaded() { return this.loaded; } @@ -614,5 +688,6 @@ public class CEChunk { if (!this.loaded) return; this.world.removeLoadedChunk(this); this.loaded = false; + this.isEntitiesLoaded = false; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java index 80b6f9ef2..fef5e547d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -4,14 +4,18 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.world.Cullable; public class VirtualCullableObject { - public final Cullable cullable; - private boolean isShown; + public Cullable cullable; + public boolean isShown; public VirtualCullableObject(Cullable cullable) { this.cullable = cullable; this.isShown = false; } + public void setCullable(Cullable cullable) { + this.cullable = cullable; + } + public Cullable cullable() { return cullable; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java index 5c2a49712..46d340b54 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultBlockEntitySerializer.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.InactiveBlockEntity; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.util.Key; @@ -37,11 +38,13 @@ public final class DefaultBlockEntitySerializer { CompoundTag data = tag.getCompound(i); Key id = Key.of(data.getString("id")); BlockEntityType type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id); + BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); + ImmutableBlockState blockState = chunk.getBlockState(pos); if (type == null) { - Debugger.BLOCK_ENTITY.debug(() -> "Unknown block entity type: " + id); + Debugger.BLOCK.debug(() -> "Unknown block entity type: " + id); + BlockEntity blockEntity = new InactiveBlockEntity(pos, blockState, data); + blockEntities.add(blockEntity); } else { - BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos()); - ImmutableBlockState blockState = chunk.getBlockState(pos); if (blockState.blockEntityType() == type) { Optional entityBlockBehavior = blockState.behavior().getAs(EntityBlockBehavior.class); if (entityBlockBehavior.isPresent()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index 2f851fe49..030d8fc3e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -54,7 +54,7 @@ public final class DefaultChunkSerializer { } } ListTag blockEntities = chunkNbt.getList("block_entities"); - ListTag itemDisplayBlockRenders = chunkNbt.getList("block_entity_renderers"); - return new CEChunk(world, pos, sectionArray, blockEntities, itemDisplayBlockRenders); + ListTag blockEntityRenders = chunkNbt.getList("block_entity_renderers"); + return new CEChunk(world, pos, sectionArray, blockEntities, blockEntityRenders, null); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index c5a725f59..1f759dada 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -5,7 +5,10 @@ import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.EntityHitResult; import net.momirealms.craftengine.core.world.Vec3d; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; public class AABB { @@ -57,7 +60,7 @@ public class AABB { return x * x + y * y + z * z; } - public static AABB fromInteraction(Vec3d pos, double width, double height) { + public static AABB makeBoundingBox(Vec3d pos, double width, double height) { return new AABB( pos.x - width / 2, pos.y, @@ -68,6 +71,17 @@ public class AABB { ); } + public static AABB makeBoundingBox(Vector3f pos, double width, double height) { + return new AABB( + pos.x - width / 2, + pos.y, + pos.z - width / 2, + pos.x + width / 2, + pos.y + height, + pos.z + width / 2 + ); + } + public Optional clip(Vec3d min, Vec3d max) { double[] traceDistance = {1.0}; double deltaX = max.x - min.x; @@ -147,6 +161,78 @@ public class AABB { return (value >= min - EPSILON) && (value <= max + EPSILON); } + public List getEdgePoints(double interval) { + List points = new ArrayList<>(); + + // AABB的8个顶点 + Vec3d[] vertices = { + new Vec3d(minX, minY, minZ), // 0 + new Vec3d(maxX, minY, minZ), // 1 + new Vec3d(minX, maxY, minZ), // 2 + new Vec3d(maxX, maxY, minZ), // 3 + new Vec3d(minX, minY, maxZ), // 4 + new Vec3d(maxX, minY, maxZ), // 5 + new Vec3d(minX, maxY, maxZ), // 6 + new Vec3d(maxX, maxY, maxZ) // 7 + }; + + // 12条边的定义(连接哪两个顶点) + int[][] edges = { + {0, 1}, // 底部X边(前) + {1, 3}, // 底部Y边(右) + {3, 2}, // 底部X边(后) + {2, 0}, // 底部Y边(左) + + {4, 5}, // 顶部X边(前) + {5, 7}, // 顶部Y边(右) + {7, 6}, // 顶部X边(后) + {6, 4}, // 顶部Y边(左) + + {0, 4}, // Z边(左下前) + {1, 5}, // Z边(右下前) + {2, 6}, // Z边(左后上) + {3, 7} // Z边(右后上) + }; + + for (int[] edge : edges) { + Vec3d start = vertices[edge[0]]; + Vec3d end = vertices[edge[1]]; + points.addAll(sampleLine(start, end, interval)); + } + + return points; + } + + private List sampleLine(Vec3d start, Vec3d end, double interval) { + List points = new ArrayList<>(); + + // 计算线段长度 + double dx = end.x - start.x; + double dy = end.y - start.y; + double dz = end.z - start.z; + double length = Math.sqrt(dx * dx + dy * dy + dz * dz); + + // 计算采样点数(去掉终点避免重复) + int numPoints = (int) Math.floor(length / interval); + + // 如果线段太短,至少返回起点 + if (numPoints <= 0) { + points.add(start); + return points; + } + + // 按间隔采样 + for (int i = 0; i <= numPoints; i++) { + double t = (double) i / numPoints; + double x = start.x + dx * t; + double y = start.y + dy * t; + double z = start.z + dz * t; + points.add(new Vec3d(x, y, z)); + } + + return points; + } + @Override public String toString() { return "AABB{" + diff --git a/gradle.properties b/gradle.properties index e594984a9..255046cbe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings -project_version=0.0.65.13.1 -config_version=58 -lang_version=40 +project_version=0.0.65.16 +config_version=60 +lang_version=43 project_group=net.momirealms latest_supported_version=1.21.10 @@ -48,7 +48,7 @@ byte_buddy_version=1.18.1 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=1.0.5 -nms_helper_version=1.0.138 +nms_helper_version=1.0.141 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.38.7