diff --git a/README.md b/README.md index 2f45c4583..1780802cf 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ repositories { ``` ```kotlin dependencies { - compileOnly("net.momirealms:craft-engine-core:0.0.65") - compileOnly("net.momirealms:craft-engine-bukkit:0.0.65") + compileOnly("net.momirealms:craft-engine-core:0.0.66") + compileOnly("net.momirealms:craft-engine-bukkit:0.0.66") } ``` \ No newline at end of file diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 949b79fe4..af75e84e9 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -18,6 +18,7 @@ repositories { maven("https://jitpack.io") // sxitem slimefun maven("https://repo.codemc.io/repository/maven-public/") // quickshop maven("https://repo.nexomc.com/releases/") // nexo + maven("https://repo.opencollab.dev/main/") // geyser } dependencies { @@ -41,8 +42,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 +63,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 @@ -88,6 +89,10 @@ dependencies { compileOnly("io.github.Slimefun:Slimefun4:RC-32") // QuickShop compileOnly("com.ghostchu:quickshop-api:6.2.0.10") + // Geyser + compileOnly("org.geysermc.geyser:api:2.9.0-SNAPSHOT") + // Floodgate + compileOnly("org.geysermc.floodgate:api:2.2.4-SNAPSHOT") } java { 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..35e2717f0 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 @@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.compatibility; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.block.entity.renderer.element.BukkitBlockEntityElementConfigs; +import net.momirealms.craftengine.bukkit.compatibility.bedrock.FloodgateUtils; +import net.momirealms.craftengine.bukkit.compatibility.bedrock.GeyserUtils; import net.momirealms.craftengine.bukkit.compatibility.item.*; import net.momirealms.craftengine.bukkit.compatibility.legacy.slimeworld.LegacySlimeFormatStorageAdaptor; import net.momirealms.craftengine.bukkit.compatibility.leveler.*; @@ -19,6 +21,7 @@ import net.momirealms.craftengine.bukkit.compatibility.quickshop.QuickShopItemEx import net.momirealms.craftengine.bukkit.compatibility.region.WorldGuardRegionCondition; import net.momirealms.craftengine.bukkit.compatibility.skript.SkriptHook; import net.momirealms.craftengine.bukkit.compatibility.slimeworld.SlimeFormatStorageAdaptor; +import net.momirealms.craftengine.bukkit.compatibility.tag.CustomNameplateHatSettings; import net.momirealms.craftengine.bukkit.compatibility.tag.CustomNameplateProviders; import net.momirealms.craftengine.bukkit.compatibility.viaversion.ViaVersionUtils; import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockRegister; @@ -37,6 +40,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; @@ -53,6 +57,8 @@ public class BukkitCompatibilityManager implements CompatibilityManager { private final Map tagResolverProviders; private TagResolverProvider[] tagResolverProviderArray = null; private boolean hasPlaceholderAPI; + private boolean hasGeyser; + private boolean hasFloodgate; public BukkitCompatibilityManager(BukkitCraftEngine plugin) { this.plugin = plugin; @@ -96,6 +102,7 @@ public class BukkitCompatibilityManager implements CompatibilityManager { registerTagResolverProvider(new CustomNameplateProviders.Background()); registerTagResolverProvider(new CustomNameplateProviders.Nameplate()); registerTagResolverProvider(new CustomNameplateProviders.Bubble()); + new CustomNameplateHatSettings().register(); logHook("CustomNameplates"); } Key worldGuardRegion = Key.of("worldguard:region"); @@ -107,6 +114,12 @@ public class BukkitCompatibilityManager implements CompatibilityManager { EventConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); LootConditions.register(worldGuardRegion, new AlwaysFalseCondition.FactoryImpl<>()); } + if (this.isPluginEnabled("Geyser-Spigot")) { + this.hasGeyser = true; + } + if (this.isPluginEnabled("floodgate")) { + this.hasFloodgate = true; + } } @Override @@ -181,7 +194,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 +265,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/"); @@ -365,4 +378,16 @@ public class BukkitCompatibilityManager implements CompatibilityManager { } return resolvers; } + + @Override + public boolean isBedrockPlayer(Player player) { + UUID uuid = player.uuid(); + if (this.hasFloodgate) { + return FloodgateUtils.isFloodgatePlayer(uuid); + } + if (this.hasGeyser) { + return GeyserUtils.isGeyserPlayer(uuid); + } + return uuid.version() == 0; + } } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/FloodgateUtils.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/FloodgateUtils.java new file mode 100644 index 000000000..57969e68d --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/FloodgateUtils.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.bukkit.compatibility.bedrock; + +import org.geysermc.floodgate.api.FloodgateApi; + +import java.util.UUID; + +public final class FloodgateUtils { + + private FloodgateUtils() {} + + public static boolean isFloodgatePlayer(UUID uuid) { + return FloodgateApi.getInstance().isFloodgatePlayer(uuid); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/GeyserUtils.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/GeyserUtils.java new file mode 100644 index 000000000..f62bfc243 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/bedrock/GeyserUtils.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.bukkit.compatibility.bedrock; + +import org.geysermc.api.Geyser; + +import java.util.UUID; + +public final class GeyserUtils { + + private GeyserUtils() {} + + public static boolean isGeyserPlayer(UUID uuid) { + return Geyser.api().isBedrockPlayer(uuid); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MMOItemsSource.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MMOItemsSource.java index b22e5595a..68f5831af 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MMOItemsSource.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/item/MMOItemsSource.java @@ -25,7 +25,9 @@ public class MMOItemsSource implements ExternalItemSource { split = split[0].split("_", 2); } if (split.length == 1) return new ItemStack(Material.AIR); - MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), split[1].toUpperCase()); + // 这里与使用和mmoitems相同的转换id方法 + String mmoItemId = split[1].toUpperCase().replace("-", "_").replace(" ", "_"); + MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), mmoItemId); return mmoItem == null ? new ItemStack(Material.AIR) : requireNonNull(mmoItem.newBuilder().build()); } 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/mythicmobs/MythicItemDrop.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java index 9258e4214..cb3ba791a 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java @@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop { context = ItemBuildContext.of(player); } } - int amountInt = MiscUtils.fastFloor(amount + 0.5F); + int amountInt = MiscUtils.floor(amount + 0.5F); ItemStack itemStack = this.customItem.buildItemStack(context, amountInt); return adapt(itemStack).amount(amountInt); } diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/CheckItemExpansion.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/CheckItemExpansion.java index a208e2901..2a163b32d 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/CheckItemExpansion.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/CheckItemExpansion.java @@ -1,10 +1,8 @@ package net.momirealms.craftengine.bukkit.compatibility.papi; import me.clip.placeholderapi.expansion.PlaceholderExpansion; -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.util.ItemStackUtils; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -13,8 +11,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.function.Predicate; - public class CheckItemExpansion extends PlaceholderExpansion { private final CraftEngine plugin; @@ -108,11 +104,6 @@ public class CheckItemExpansion extends PlaceholderExpansion { } private int getItemCount(BukkitServerPlayer player, String[] param) { - Key itemId = Key.of(param[0], param[1]); - Predicate predicate = nmsStack -> this.plugin.itemManager().wrap(ItemStackUtils.asCraftMirror(nmsStack)).id().equals(itemId); - Object inventory = FastNMS.INSTANCE.method$Player$getInventory(player.serverPlayer()); - Object inventoryMenu = FastNMS.INSTANCE.field$Player$inventoryMenu(player.serverPlayer()); - Object craftSlots = FastNMS.INSTANCE.method$InventoryMenu$getCraftSlots(inventoryMenu); - return FastNMS.INSTANCE.method$Inventory$clearOrCountMatchingItems(inventory, predicate, 0, craftSlots); + return player.clearOrCountMatchingInventoryItems(Key.of(param[0], param[1]), 0); } } 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/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/tag/CustomNameplateHatSettings.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/tag/CustomNameplateHatSettings.java new file mode 100644 index 000000000..232161645 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/tag/CustomNameplateHatSettings.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.bukkit.compatibility.tag; + +import io.papermc.paper.event.entity.EntityEquipmentChangedEvent; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemSettings; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.CustomDataType; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.customnameplates.api.CNPlayer; +import net.momirealms.customnameplates.api.CustomNameplates; +import net.momirealms.customnameplates.api.CustomNameplatesAPI; +import net.momirealms.customnameplates.api.feature.tag.TagRenderer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public final class CustomNameplateHatSettings implements Listener { + public static final CustomDataType HAT_HEIGHT = new CustomDataType<>(); + + public void register() { + ItemSettings.Modifiers.registerFactory("hat-height", height -> { + double heightD = ResourceConfigUtils.getAsDouble(height, "hat-height"); + return settings -> settings.addCustomData(HAT_HEIGHT, heightD); + }); + Bukkit.getPluginManager().registerEvents(this, BukkitCraftEngine.instance().javaPlugin()); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onEquipmentChange(EntityEquipmentChangedEvent event) { + if (!(event.getEntity() instanceof Player player)) return; + Map equipmentChanges = event.getEquipmentChanges(); + EntityEquipmentChangedEvent.EquipmentChange equipmentChange = equipmentChanges.get(EquipmentSlot.HEAD); + if (equipmentChange == null) return; + ItemStack newItem = equipmentChange.newItem(); + updateHatHeight(player, newItem); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + // 稍微延迟一下,可以等待背包同步插件的处理 + if (VersionHelper.isFolia()) { + player.getScheduler().runDelayed(BukkitCraftEngine.instance().javaPlugin(), t1 -> { + if (player.isOnline()) { + updateHatHeight(player, player.getInventory().getItem(EquipmentSlot.HEAD)); + } + }, null, 10); + } else { + CraftEngine.instance().scheduler().sync().runLater(() -> { + if (player.isOnline()) { + updateHatHeight(player, player.getInventory().getItem(EquipmentSlot.HEAD)); + } + }, 10); + } + } + + public void updateHatHeight(Player player, ItemStack newItem) { + CNPlayer cnPlayer = CustomNameplatesAPI.getInstance().getPlayer(player.getUniqueId()); + if (cnPlayer == null) return; + TagRenderer tagRender = CustomNameplates.getInstance().getUnlimitedTagManager().getTagRender(cnPlayer); + if (tagRender == null) return; + Item wrapped = BukkitItemManager.instance().wrap(newItem); + Optional> optionalCustomItem = wrapped.getCustomItem(); + if (optionalCustomItem.isEmpty()) { + tagRender.hatOffset(0d); + return; + } + Double customHeight = optionalCustomItem.get().settings().getCustomData(HAT_HEIGHT); + tagRender.hatOffset(Objects.requireNonNullElse(customHeight, 0d)); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index be5b1f2ed..fdbfbeb8f 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Material; import java.lang.reflect.Field; +import java.util.Locale; import java.util.Set; import java.util.stream.Stream; @@ -63,7 +64,7 @@ public class WorldEditBlockRegister { } if (!input.contains(":")) { - String lowerSearch = input.toLowerCase(); + String lowerSearch = input.toLowerCase(Locale.ROOT); return Stream.concat( namespacesInUse.stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":"), BlockStateParser.fillSuggestions(input).stream() diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 0e0f45f54..c7b4e9d09 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -77,6 +77,10 @@ paper { register("ViaVersion") { required = false } register("QuickShop-Hikari") { required = false } + // Geyser + register("Geyser-Spigot") { required = false } + register("floodgate") { required = false } + // AdvancedSlimePaper register("SlimeWorldPlugin") { required = false } register("SlimeWorldManager") { required = false } 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..9421777da 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 @@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.VersionHelper; import java.nio.file.Path; @@ -98,8 +99,8 @@ public final class BukkitAdvancementManager extends AbstractAdvancementManager { NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant, true) : NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, Arrays.asList(advancement), new HashSet<>(), advancementsToGrant); Object removePacket = VersionHelper.isOrAbove1_21_5() ? - NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>(), true) : - NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), new HashSet<>() {{add(resourceLocation);}}, new HashMap<>()); + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), MiscUtils.init(new HashSet<>(), s -> s.add(resourceLocation)), new HashMap<>(), true) : + NetworkReflections.constructor$ClientboundUpdateAdvancementsPacket.newInstance(false, new ArrayList<>(), MiscUtils.init(new HashSet<>(), s -> s.add(resourceLocation)), new HashMap<>()); player.sendPackets(List.of(grantPacket, removePacket), false); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to send toast for player " + player.name(), e); @@ -119,6 +120,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/BukkitAdaptors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java index c667f832e..092a25cad 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java @@ -1,14 +1,17 @@ package net.momirealms.craftengine.bukkit.api; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.core.item.Item; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; public final class BukkitAdaptors { @@ -62,4 +65,15 @@ public final class BukkitAdaptors { public static BukkitExistingBlock adapt(@NotNull final Block block) { return new BukkitExistingBlock(block); } + + /** + * Adapts a Bukkit ItemStack to a CraftEngine wrapped item + * + * @param item the Bukkit ItemStack to adapt, must not be null + * @return a non-null Item instance wrapping the provided item + */ + @NotNull + public static Item adapt(@NotNull final ItemStack item) { + return BukkitItemManager.instance().wrap(item); + } } 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..b0bd2dac4 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.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; @@ -60,6 +63,45 @@ public final class CraftEngineFurniture { 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 * @@ -71,7 +113,7 @@ public final class CraftEngineFurniture { public static BukkitFurniture place(Location location, Key furnitureId) { CustomFurniture 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) { + 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) { CustomFurniture 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 + @Deprecated(since = "0.0.66", forRemoval = true) public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) { - return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true); + 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); 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) { + CustomFurniture 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 + @Deprecated(since = "0.0.66", forRemoval = true) public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) { - 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 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, CustomFurniture 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, CustomFurniture 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, CustomFurniture 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..566a516da 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.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; @@ -17,23 +16,20 @@ public final class FurnitureAttemptPlaceEvent extends PlayerEvent implements Can private boolean cancelled; private final CustomFurniture 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 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 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/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 10c77c589..bb379b71f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -42,6 +42,7 @@ import java.util.*; public final class BukkitBlockManager extends AbstractBlockManager { public static final Set CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f); + private static final Object BLOCK_POS$ZERO = LocationUtils.toBlockPos(0,0,0); private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false); private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true); private static BukkitBlockManager instance; @@ -80,12 +81,14 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void init() { + super.init(); this.initMirrorRegistry(); this.initFireBlock(); this.deceiveBukkitRegistry(); this.markVanillaNoteBlocks(); + this.findViewBlockingVanillaBlocks(); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次,预防id超出上限 } public static BukkitBlockManager instance() { @@ -125,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表 super.delayedLoad(); this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create(); for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) { @@ -223,7 +226,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { protected void applyPlatformSettings(ImmutableBlockState state) { DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject(); nmsState.setBlockState(state); - Object nmsVisualState = state.vanillaBlockState().literalObject(); + Object nmsVisualState = state.visualBlockState().literalObject(); BlockSettings settings = state.settings(); try { @@ -240,8 +243,15 @@ public final class BukkitBlockManager extends AbstractBlockManager { boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean(); CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion); CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); + + boolean suffocating = settings.isSuffocating() == Tristate.UNDEFINED ? (canBlockView(state.visualBlockState())) : (settings.isSuffocating().asBoolean()); + CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, suffocating ? ALWAYS_TRUE : ALWAYS_FALSE); CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); - CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)); + CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, + settings.isViewBlocking() == Tristate.UNDEFINED ? + (suffocating ? ALWAYS_TRUE : ALWAYS_FALSE) : + (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE) + ); DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState); ObjectHolder shapeHolder = nmsBlock.shapeDelegate(); @@ -291,9 +301,16 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.burnableBlocks.add(nmsBlock); } - Key vanillaBlockId = state.vanillaBlockState().ownerId(); + Key vanillaBlockId = state.visualBlockState().ownerId(); BlockGenerator.field$CraftEngineBlock$isNoteBlock().set(nmsBlock, vanillaBlockId.equals(BlockKeys.NOTE_BLOCK)); BlockGenerator.field$CraftEngineBlock$isTripwire().set(nmsBlock, vanillaBlockId.equals(BlockKeys.TRIPWIRE)); + if (vanillaBlockId.equals(BlockKeys.BARRIER)) { + state.setRestoreBlockState(createBlockState("minecraft:glass")); + } else { + state.setRestoreBlockState(state.visualBlockState()); + } + // 根据客户端的状态决定其是否阻挡视线 + super.viewBlockingBlocks[state.customBlockState().registryId()] = canBlockView(state.visualBlockState()); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e); } @@ -379,6 +396,23 @@ public final class BukkitBlockManager extends AbstractBlockManager { } } + public boolean canBlockView(BlockStateWrapper wrapper) { + Object blockState = wrapper.literalObject(); + if (!BlockStateUtils.isOcclude(blockState)) { + return false; + } + return FastNMS.INSTANCE.method$BlockStateBase$isCollisionShapeFullBlock(blockState, CoreReflections.instance$EmptyBlockGetter$INSTANCE, BLOCK_POS$ZERO); + } + + private void findViewBlockingVanillaBlocks() { + for (int i = 0; i < this.vanillaBlockStateCount; i++) { + BlockStateWrapper blockState = BlockRegistryMirror.byId(i); + if (canBlockView(blockState)) { + this.viewBlockingBlocks[i] = true; + } + } + } + @Override protected void setVanillaBlockTags(Key id, List tags) { Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java index 53d9cbfb0..907fa26c6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java @@ -20,7 +20,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper imp @Override public BlockStateWrapper visualBlockState() { - return getImmutableBlockState().map(ImmutableBlockState::vanillaBlockState).orElse(null); + return getImmutableBlockState().map(ImmutableBlockState::visualBlockState).orElse(null); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java index ea36861b8..79f31a798 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java @@ -13,6 +13,7 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.util.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; @@ -22,7 +23,7 @@ import java.util.concurrent.Callable; import java.util.function.BiConsumer; public class BukkitBlockBehavior extends AbstractBlockBehavior { - private static final Map>> HARD_CODED_PROPERTY_DATA = new HashMap<>(); + private static final Map>> HARD_CODED_PROPERTY_DATA = new HashMap<>(); static { HARD_CODED_PROPERTY_DATA.put("axis", (behavior, property) -> { @SuppressWarnings("unchecked") @@ -180,7 +181,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]); if (optionalCustomState.isEmpty()) return false; - BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState(); + BlockStateWrapper vanillaState = optionalCustomState.get().visualBlockState(); if (vanillaState == null) return false; return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.literalObject(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]); } 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/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 7ae23af7f..066a13178 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -135,7 +135,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { if (isMaxAge(state)) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); @@ -158,7 +158,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState); 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..d6b6262fb --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DropExperienceBlockBehavior.java @@ -0,0 +1,99 @@ +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.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.MiscUtils; +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; +import java.util.function.Predicate; + +public class DropExperienceBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final NumberProvider amount; + private final Predicate condition; + + public DropExperienceBlockBehavior(CustomBlock customBlock, NumberProvider amount, Predicate condition) { + super(customBlock); + this.amount = amount; + this.condition = condition; + } + + @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.condition.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")); + List> conditionList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "conditions", "condition"), EventConditions::fromMap); + return new DropExperienceBlockBehavior(block, amount, MiscUtils.allOf(conditionList)); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java index 27ffdea31..697b84624 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java @@ -147,7 +147,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPat Player player = context.getPlayer(); if (player == null) return; this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java index a53af5459..359ff4a9c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -60,7 +60,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { } boolean sendParticles = false; ImmutableBlockState customState = optionalCustomState.get(); - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -93,7 +93,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { if (!block.isEmpty()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 9f13df44c..3d53ce579 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -115,7 +115,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -153,7 +153,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java index c297d3289..4339bf882 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java @@ -120,7 +120,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPath Player player = context.getPlayer(); if (player == null) return; this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java index 421e8f94e..00352c68c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.block.entity; +import net.momirealms.craftengine.bukkit.block.behavior.SeatBlockBehavior; import net.momirealms.craftengine.bukkit.entity.seat.BukkitSeat; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; @@ -13,6 +14,8 @@ import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.sparrow.nbt.CompoundTag; +import java.util.Optional; + public class SeatBlockEntity extends BlockEntity implements SeatOwner { private final Seat[] seats; @@ -38,10 +41,12 @@ public class SeatBlockEntity extends BlockEntity implements SeatOwner { } public boolean spawnSeat(Player player) { - Property facing = super.blockState.owner().value().getProperty("facing"); int yRot = 0; - if (facing != null && facing.valueClass() == HorizontalDirection.class) { - HorizontalDirection direction = (HorizontalDirection) super.blockState.get(facing); + Optional behavior = super.blockState.behavior().getAs(SeatBlockBehavior.class); + if (behavior.isEmpty()) return false; + Property facing = behavior.get().directionProperty(); + if (facing != null) { + HorizontalDirection direction = super.blockState.get(facing); yRot = switch (direction) { case NORTH -> 0; case SOUTH -> 180; 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/ArmorStandBlockEntityElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ArmorStandBlockEntityElement.java new file mode 100644 index 000000000..1d6909905 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ArmorStandBlockEntityElement.java @@ -0,0 +1,98 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.IntList; +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.world.score.BukkitTeamManager; +import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import org.joml.Vector3f; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +public class ArmorStandBlockEntityElement implements BlockEntityElement { + public final ArmorStandBlockEntityElementConfig config; + public final Object cachedSpawnPacket; + public final Object cachedDespawnPacket; + public final Object cachedUpdatePosPacket; + public final Object cachedScalePacket; + public final Object cachedTeamPacket; + public final int entityId; + public final UUID uuid = UUID.randomUUID(); + + public ArmorStandBlockEntityElement(ArmorStandBlockEntityElementConfig config, BlockPos pos) { + this(config, pos, CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(), false); + } + + public ArmorStandBlockEntityElement(ArmorStandBlockEntityElementConfig config, BlockPos pos, int entityId, boolean posChanged) { + Vector3f position = config.position(); + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, this.uuid, pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, + config.xRot(), config.yRot(), MEntityTypes.ARMOR_STAND, 0, CoreReflections.instance$Vec3$Zero, config.yRot() + ); + this.config = config; + this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(entityId)); + this.entityId = entityId; + this.cachedUpdatePosPacket = posChanged ? FastNMS.INSTANCE.constructor$ClientboundEntityPositionSyncPacket(this.entityId, pos.x() + position.x, pos.y() + position.y, pos.z() + position.z, config.yRot(), config.xRot(), false) : null; + if (VersionHelper.isOrAbove1_20_5() && config.scale() != 1) { + Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer) (o) -> {}); + FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale()); + this.cachedScalePacket = FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(entityId, Collections.singletonList(attributeIns)); + } else { + this.cachedScalePacket = null; + } + Object teamPacket = null; + if (config.glowColor != null) { + Object teamByColor = BukkitTeamManager.instance().getTeamByColor(config.glowColor); + if (teamByColor != null) { + teamPacket = FastNMS.INSTANCE.method$ClientboundSetPlayerTeamPacket$createMultiplePlayerPacket(teamByColor, List.of(this.uuid.toString()), true); + } + } + this.cachedTeamPacket = teamPacket; + } + + @Override + public void hide(Player player) { + player.sendPacket(this.cachedDespawnPacket, false); + } + + @Override + public void show(Player player) { + player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player))), false); + player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of( + Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject()) + )), false); + if (this.cachedDespawnPacket != null) { + player.sendPacket(this.cachedDespawnPacket, false); + } + if (this.cachedTeamPacket != null) { + player.sendPacket(this.cachedTeamPacket, false); + } + } + + @Override + public void transform(Player player) { + if (this.cachedUpdatePosPacket != null) { + player.sendPackets(List.of( + this.cachedUpdatePosPacket, + FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player)), + FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of( + Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject()) + )) + ), false); + } else { + player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadataValues(player)), false); + player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of( + Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player).getLiteralObject()) + )), false); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ArmorStandBlockEntityElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ArmorStandBlockEntityElementConfig.java new file mode 100644 index 000000000..f0e99b90b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/ArmorStandBlockEntityElementConfig.java @@ -0,0 +1,156 @@ +package net.momirealms.craftengine.bukkit.block.entity.renderer.element; + +import com.google.common.base.Objects; +import net.momirealms.craftengine.bukkit.entity.data.ArmorStandData; +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +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; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.LegacyChatFormatter; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Glowing; +import net.momirealms.craftengine.core.world.World; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class ArmorStandBlockEntityElementConfig implements BlockEntityElementConfig, Glowing { + public static final Factory FACTORY = new Factory(); + public final Function> lazyMetadataPacket; + public final Key itemId; + public final float scale; + public final Vector3f position; + public final float xRot; + public final float yRot; + public final boolean small; + public final LegacyChatFormatter glowColor; + + public ArmorStandBlockEntityElementConfig(Key itemId, + float scale, + Vector3f position, + float xRot, + float yRot, + boolean small, + LegacyChatFormatter glowColor) { + this.itemId = itemId; + this.glowColor = glowColor; + this.scale = scale; + this.position = position; + this.xRot = xRot; + this.yRot = yRot; + this.small = small; + this.lazyMetadataPacket = player -> { + List dataValues = new ArrayList<>(2); + if (glowColor != null) { + BaseEntityData.SharedFlags.addEntityData((byte) 0x60, dataValues); + } else { + BaseEntityData.SharedFlags.addEntityData((byte) 0x20, dataValues); + } + if (small) { + ArmorStandData.ArmorStandFlags.addEntityData((byte) 0x01, dataValues); + } + return dataValues; + }; + } + + @Nullable + @Override + public LegacyChatFormatter glowColor() { + return this.glowColor; + } + + @Override + public ArmorStandBlockEntityElement create(World world, BlockPos pos) { + return new ArmorStandBlockEntityElement(this, pos); + } + + @Override + public ArmorStandBlockEntityElement create(World world, BlockPos pos, ArmorStandBlockEntityElement previous) { + if (previous.config.scale != scale || previous.config.glowColor != glowColor) { + return null; + } + return new ArmorStandBlockEntityElement(this, pos, previous.entityId, + previous.config.yRot != this.yRot || + previous.config.xRot != this.xRot || + !previous.config.position.equals(this.position) + ); + } + + @Override + public ArmorStandBlockEntityElement createExact(World world, BlockPos pos, ArmorStandBlockEntityElement previous) { + if (!previous.config.isSamePosition(this)) { + return null; + } + return new ArmorStandBlockEntityElement(this, pos, previous.entityId, false); + } + + @Override + public Class elementClass() { + return ArmorStandBlockEntityElement.class; + } + + public Item item(Player player) { + Item wrappedItem = BukkitItemManager.instance().createWrappedItem(this.itemId, player); + return wrappedItem == null ? BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player) : wrappedItem ; + } + + public Key itemId() { + return this.itemId; + } + + public float scale() { + return this.scale; + } + + public Vector3f position() { + return this.position; + } + + public float yRot() { + return this.yRot; + } + + public float xRot() { + return this.xRot; + } + + public boolean small() { + return this.small; + } + + public List metadataValues(Player player) { + return this.lazyMetadataPacket.apply(player); + } + + public boolean isSamePosition(ArmorStandBlockEntityElementConfig that) { + return Float.compare(xRot, that.xRot) == 0 && + Float.compare(yRot, that.yRot) == 0 && + Objects.equal(position, that.position); + } + + public static class Factory implements BlockEntityElementConfigFactory { + + @Override + public ArmorStandBlockEntityElementConfig create(Map arguments) { + return new ArmorStandBlockEntityElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.armor_stand.missing_item")), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1f), "scale"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("small", false), "small"), + ResourceConfigUtils.getAsEnum(arguments.get("glow-color"), LegacyChatFormatter.class, null) + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java index ee182e392..7d7ad348a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/renderer/element/BukkitBlockEntityElementConfigs.java @@ -8,6 +8,7 @@ public class BukkitBlockEntityElementConfigs extends BlockEntityElementConfigs { register(ITEM_DISPLAY, ItemDisplayBlockEntityElementConfig.FACTORY); register(TEXT_DISPLAY, TextDisplayBlockEntityElementConfig.FACTORY); register(ITEM, ItemBlockEntityElementConfig.FACTORY); + register(ARMOR_STAND, ArmorStandBlockEntityElementConfig.FACTORY); } private BukkitBlockEntityElementConfigs() {} 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..dadbd2d96 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,35 +2,41 @@ 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; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; +import org.bukkit.inventory.ItemStack; import org.joml.Vector3f; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; public class ItemBlockEntityElementConfig implements BlockEntityElementConfig { public static final Factory FACTORY = new Factory(); - private final Function> lazyMetadataPacket; - private final Function> item; - private final Vector3f position; + public final Function> lazyMetadataPacket; + public final Key itemId; + public final Vector3f position; - public ItemBlockEntityElementConfig(Function> item, Vector3f position) { - this.item = item; + public ItemBlockEntityElementConfig(Key itemId, Vector3f position) { + this.itemId = itemId; this.position = position; this.lazyMetadataPacket = player -> { List dataValues = new ArrayList<>(); - ItemEntityData.Item.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues); - ItemEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, dataValues); + Item wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player); + if (wrappedItem == null) { + wrappedItem = Objects.requireNonNull(BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player)); + } + ItemEntityData.Item.addEntityData(wrappedItem.getLiteralObject(), dataValues); + ItemEntityData.NoGravity.addEntityData(true, dataValues); return dataValues; }; } @@ -45,6 +51,14 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig elementClass() { return ItemBlockEntityElement.class; @@ -54,22 +68,24 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig item(Player player) { - return this.item.apply(player); + public Key itemId() { + return itemId; } public List metadataValues(Player player) { return this.lazyMetadataPacket.apply(player); } - public static class Factory implements BlockEntityElementConfigFactory { + public boolean isSamePosition(ItemBlockEntityElementConfig that) { + return this.position.equals(that.position); + } + + public static class Factory implements BlockEntityElementConfigFactory { - @SuppressWarnings("unchecked") @Override - public BlockEntityElementConfig 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( - player -> BukkitItemManager.instance().createWrappedItem(itemId, player), + public ItemBlockEntityElementConfig create(Map arguments) { + return new ItemBlockEntityElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item.missing_item")), 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..3dbd0ca1a 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,43 +1,51 @@ 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.item.ItemKeys; +import net.momirealms.craftengine.core.util.Color; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.function.Function; public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementConfig { public static final Factory FACTORY = new Factory(); - private final Function> lazyMetadataPacket; - private final Function> item; - private final Vector3f scale; - private final Vector3f position; - private final Vector3f translation; - private final float xRot; - private final float yRot; - private final Quaternionf rotation; - private final ItemDisplayContext displayContext; - private final Billboard billboard; - private final float shadowRadius; - private final float shadowStrength; + public final Function> lazyMetadataPacket; + 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 Color glowColor; + public final int blockLight; + public final int skyLight; + public final float viewRange; - public ItemDisplayBlockEntityElementConfig(Function> item, + public ItemDisplayBlockEntityElementConfig(Key itemId, Vector3f scale, Vector3f position, Vector3f translation, @@ -47,8 +55,12 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo ItemDisplayContext displayContext, Billboard billboard, float shadowRadius, - float shadowStrength) { - this.item = item; + float shadowStrength, + @Nullable Color glowColor, + int blockLight, + int skyLight, + float viewRange) { + this.itemId = itemId; this.scale = scale; this.position = position; this.translation = translation; @@ -59,16 +71,37 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo this.billboard = billboard; this.shadowRadius = shadowRadius; this.shadowStrength = shadowStrength; + this.glowColor = glowColor; + this.blockLight = blockLight; + this.skyLight = skyLight; + this.viewRange = viewRange; 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); + if (glowColor != null) { + ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues); + ItemDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues); + } else { + ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x0, dataValues); + ItemDisplayEntityData.GlowColorOverride.addEntityData(-1, dataValues); + } + Item wrappedItem = BukkitItemManager.instance().createWrappedItem(itemId, player); + if (wrappedItem == null) { + wrappedItem = java.util.Objects.requireNonNull(BukkitItemManager.instance().createWrappedItem(ItemKeys.BARRIER, player)); + } + ItemDisplayEntityData.DisplayedItem.addEntityData(wrappedItem.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); + if (this.blockLight != -1 && this.skyLight != -1) { + ItemDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues); + } else { + ItemDisplayEntityData.BrightnessOverride.addEntityData(-1, dataValues); + } + ItemDisplayEntityData.ViewRange.addEntityData((float) (this.viewRange * player.displayEntityViewDistance()), dataValues); return dataValues; }; } @@ -80,14 +113,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,13 +120,25 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo ); } + @Override + public ItemDisplayBlockEntityElement createExact(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) { + if (!previous.config.isSamePosition(this)) { + return null; + } + return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, false); + } + @Override public Class elementClass() { return ItemDisplayBlockEntityElement.class; } - public Item item(Player player) { - return this.item.apply(player); + public Color glowColor() { + return glowColor; + } + + public Key itemId() { + return itemId; } public Vector3f scale() { @@ -148,24 +185,35 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } - public static class Factory implements BlockEntityElementConfigFactory { + public boolean isSamePosition(ItemDisplayBlockEntityElementConfig that) { + 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) { - Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")); - return (BlockEntityElementConfig) new ItemDisplayBlockEntityElementConfig( - player -> BukkitItemManager.instance().createWrappedItem(itemId, player), + public ItemDisplayBlockEntityElementConfig create(Map arguments) { + Map brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness"); + return new ItemDisplayBlockEntityElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.block.state.entity_renderer.item_display.missing_item")), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "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"), - 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") + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("shadow-strength", 1f), "shadow-strength"), + Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range") ); } } 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..fcdc44fea 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,38 +1,49 @@ 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.display.TextDisplayAlignment; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.Color; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; +import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.function.Function; public class TextDisplayBlockEntityElementConfig implements BlockEntityElementConfig { public static final Factory FACTORY = new Factory(); - private final Function> lazyMetadataPacket; - private final String text; - private final Vector3f scale; - private final Vector3f position; - private final Vector3f translation; - private final float xRot; - private final float yRot; - private final Quaternionf rotation; - private final Billboard billboard; + public final Function> lazyMetadataPacket; + public final String text; + 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 Billboard billboard; + public final Color glowColor; + public final int blockLight; + public final int skyLight; + public final float viewRange; + public final int lineWidth; + public final int backgroundColor; + public final byte opacity; + public final boolean hasShadow; + public final boolean isSeeThrough; + public final boolean useDefaultBackgroundColor; + public final TextDisplayAlignment alignment; public TextDisplayBlockEntityElementConfig(String text, Vector3f scale, @@ -41,7 +52,18 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo float xRot, float yRot, Quaternionf rotation, - Billboard billboard) { + Billboard billboard, + @Nullable Color glowColor, + int blockLight, + int skyLight, + float viewRange, + int lineWidth, + int backgroundColor, + byte opacity, + boolean hasShadow, + boolean isSeeThrough, + boolean useDefaultBackgroundColor, + TextDisplayAlignment alignment) { this.text = text; this.scale = scale; this.position = position; @@ -50,13 +72,41 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo this.yRot = yRot; this.rotation = rotation; this.billboard = billboard; + this.glowColor = glowColor; + this.blockLight = blockLight; + this.skyLight = skyLight; + this.viewRange = viewRange; + this.lineWidth = lineWidth; + this.backgroundColor = backgroundColor; + this.opacity = opacity; + this.hasShadow = hasShadow; + this.useDefaultBackgroundColor = useDefaultBackgroundColor; + this.alignment = alignment; + this.isSeeThrough = isSeeThrough; 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); + if (glowColor != null) { + TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues); + TextDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues); + } else { + TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x0, dataValues); + TextDisplayEntityData.GlowColorOverride.addEntityData(-1, 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); + TextDisplayEntityData.LineWidth.addEntityData(this.lineWidth, dataValues); + TextDisplayEntityData.BackgroundColor.addEntityData(this.backgroundColor, dataValues); + TextDisplayEntityData.TextOpacity.addEntityData(this.opacity, dataValues); + TextDisplayEntityData.TextDisplayMasks.addEntityData(TextDisplayEntityData.encodeMask(this.hasShadow, this.isSeeThrough, this.useDefaultBackgroundColor, this.alignment), dataValues); + if (this.blockLight != -1 && this.skyLight != -1) { + TextDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues); + } else { + TextDisplayEntityData.BrightnessOverride.addEntityData(-1, dataValues); + } + TextDisplayEntityData.ViewRange.addEntityData((float) (this.viewRange * player.displayEntityViewDistance()), dataValues); return dataValues; }; } @@ -68,14 +118,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,13 +125,25 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo ); } + @Override + public TextDisplayBlockEntityElement createExact(World world, BlockPos pos, TextDisplayBlockEntityElement previous) { + if (!previous.config.isSamePosition(this)) { + return null; + } + return new TextDisplayBlockEntityElement(this, pos, previous.entityId, false); + } + @Override public Class elementClass() { return TextDisplayBlockEntityElement.class; } + public String text() { + return text; + } + public Component text(Player player) { - return AdventureHelper.miniMessage().deserialize(this.text, PlayerOptionalContext.of(player).tagResolvers()); + return AdventureHelper.miniMessage().deserialize(this.text, NetworkTextReplaceContext.of(player).tagResolvers()); } public Vector3f scale() { @@ -124,13 +178,21 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo return this.lazyMetadataPacket.apply(player); } - public static class Factory implements BlockEntityElementConfigFactory { + public boolean isSamePosition(TextDisplayBlockEntityElementConfig that) { + 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( + Map brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness"); + return new TextDisplayBlockEntityElementConfig( text, ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("scale", 1f), "scale"), ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0.5f), "position"), @@ -138,7 +200,18 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo ResourceConfigUtils.getAsFloat(arguments.getOrDefault("pitch", 0f), "pitch"), ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"), ResourceConfigUtils.getAsQuaternionf(arguments.getOrDefault("rotation", 0f), "rotation"), - Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)) + Billboard.valueOf(arguments.getOrDefault("billboard", "fixed").toString().toUpperCase(Locale.ROOT)), + Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range"), + ResourceConfigUtils.getAsInt(arguments.getOrDefault("line-width", 200), "line-width"), + ResourceConfigUtils.getOrDefault(arguments.get("background-color"), o -> Color.fromStrings(o.toString().split(",")).color(), 0x40000000), + (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("text-opacity", -1), "text-opacity"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-shadow", false), "has-shadow"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("is-see-through", false), "is-see-through"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("use-default-background-color", false), "use-default-background-color"), + ResourceConfigUtils.getAsEnum(arguments.get("alignment"), TextDisplayAlignment.class, TextDisplayAlignment.CENTER) ); } } 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..efda7a2c5 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; @@ -39,7 +41,12 @@ public class BukkitEntity extends AbstractEntity { } @Override - public int entityID() { + 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/ArmorStandData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/ArmorStandData.java new file mode 100644 index 000000000..b57c49046 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/ArmorStandData.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.bukkit.entity.data; + +public class ArmorStandData extends LivingEntityData { + public static final ArmorStandData ArmorStandFlags = new ArmorStandData<>(ArmorStandData.class, EntityDataValue.Serializers$BYTE, (byte) 0); + // rotations + + public ArmorStandData(Class clazz, Object serializer, T defaultValue) { + super(clazz, serializer, defaultValue); + } +} \ No newline at end of file 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/data/TextDisplayEntityData.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/TextDisplayEntityData.java index 73a801d3b..840dde225 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/TextDisplayEntityData.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/TextDisplayEntityData.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.entity.data; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.core.entity.display.TextDisplayAlignment; public class TextDisplayEntityData extends DisplayEntityData { public static final TextDisplayEntityData Text = new TextDisplayEntityData<>(TextDisplayEntityData.class, EntityDataValue.Serializers$COMPONENT, CoreReflections.instance$Component$empty); @@ -12,4 +13,39 @@ public class TextDisplayEntityData extends DisplayEntityData { public TextDisplayEntityData(Class clazz, Object serializer, T defaultValue) { super(clazz, serializer, defaultValue); } + + public static final int HAS_SHADOW = 0x01; + public static final int IS_SEE_THROUGH = 0x02; + public static final int USE_DEFAULT_BG_COLOR = 0x04; + private static final int LEFT_ALIGNMENT = 0x08; // 8 + private static final int RIGHT_ALIGNMENT = 0x10; // 16 + + public static byte encodeMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, TextDisplayAlignment alignment) { + int bitMask = 0; + + if (hasShadow) { + bitMask |= HAS_SHADOW; + } + if (isSeeThrough) { + bitMask |= IS_SEE_THROUGH; + } + if (useDefaultBackground) { + bitMask |= USE_DEFAULT_BG_COLOR; + } + + switch (alignment) { + case CENTER: // CENTER + break; + case LEFT: // LEFT + bitMask |= LEFT_ALIGNMENT; + break; + case RIGHT: // RIGHT + bitMask |= RIGHT_ALIGNMENT; + break; + default: + throw new IllegalArgumentException("Invalid alignment value"); + } + + return (byte) bitMask; + } } 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..237f8de2a 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,181 @@ 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.api.BukkitAdaptors; 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.plugin.reflection.minecraft.MEntityTypes; 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.hitbox.FurnitureHitBoxConfig; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.QuaternionUtils; import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemDisplay; import org.bukkit.entity.Player; 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.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; -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; +@SuppressWarnings("DuplicatedCode") +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, CustomFurniture 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 boolean setVariant(String variantName) { + FurnitureVariant variant = this.config.getVariant(variantName); + if (variant == null) return false; + if (this.currentVariant == variant) return false; + // 检查新位置是否可用 + List aabbs = new ArrayList<>(); + WorldPosition position = position(); + for (FurnitureHitBoxConfig hitBoxConfig : variant.hitBoxConfigs()) { + hitBoxConfig.prepareBoundingBox(position, aabbs::add, false); + } + if (!aabbs.isEmpty()) { + if (!FastNMS.INSTANCE.checkEntityCollision(position.world.serverWorld(), aabbs.stream().map(it -> FastNMS.INSTANCE.constructor$AABB(it.minX, it.minY, it.minZ, it.maxX, it.maxY, it.maxZ)).toList(), + o -> { + for (Collider collider : super.colliders) { + if (o == collider.handle()) { + return false; + } + } + return true; + })) { + return false; + } + } + // 删除椅子 + super.destroySeats(); + BukkitFurnitureManager.instance().invalidateFurniture(this); + super.clearColliders(); + super.setVariantInternal(variant); + BukkitFurnitureManager.instance().initFurniture(this); + this.addCollidersToWorld(); + this.refresh(); + return true; + } + + @SuppressWarnings("deprecation") + @Override + public CompletableFuture moveTo(WorldPosition position) { + ItemDisplay itemDisplay = this.metaEntity.get(); + if (itemDisplay == null) return CompletableFuture.completedFuture(false); + // 检查新位置是否可用 + List aabbs = new ArrayList<>(); + for (FurnitureHitBoxConfig hitBoxConfig : getCurrentVariant().hitBoxConfigs()) { + hitBoxConfig.prepareBoundingBox(position, aabbs::add, false); + } + if (!aabbs.isEmpty()) { + if (!FastNMS.INSTANCE.checkEntityCollision(position.world.serverWorld(), aabbs.stream().map(it -> FastNMS.INSTANCE.constructor$AABB(it.minX, it.minY, it.minZ, it.maxX, it.maxY, it.maxZ)).toList(), + o -> { + for (Collider collider : super.colliders) { + if (o == collider.handle()) { + return false; + } + } + return true; + })) { + return CompletableFuture.completedFuture(false); + } + } + // 删除椅子 + super.destroySeats(); + // 准备传送 + CompletableFuture future = new CompletableFuture<>(); + BukkitFurnitureManager.instance().invalidateFurniture(this); + super.clearColliders(); + this.location = LocationUtils.toLocation(position); + Object removePacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), l -> l.add(itemDisplay.getEntityId()))); + for (Player player : itemDisplay.getTrackedPlayers()) { + BukkitAdaptors.adapt(player).sendPacket(removePacket, false); + } + itemDisplay.teleportAsync(this.location).thenAccept(result -> { + if (result) { + super.setVariantInternal(getCurrentVariant()); + BukkitFurnitureManager.instance().initFurniture(this); + this.addCollidersToWorld(); + Object addPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(itemDisplay.getEntityId(), itemDisplay.getUniqueId(), + itemDisplay.getX(), itemDisplay.getY(), itemDisplay.getZ(), itemDisplay.getPitch(), itemDisplay.getYaw(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0); + for (Player player : itemDisplay.getTrackedPlayers()) { + BukkitAdaptors.adapt(player).sendPacket(addPacket, false); + } + future.complete(true); + } else { + future.complete(false); + } + }); + return future; + } + + @SuppressWarnings("deprecation") + @Override + protected void refresh() { + ItemDisplay itemDisplay = this.metaEntity.get(); + if (itemDisplay == null) return; + Object removePacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), l -> l.add(itemDisplay.getEntityId()))); + Object addPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(itemDisplay.getEntityId(), itemDisplay.getUniqueId(), + itemDisplay.getX(), itemDisplay.getY(), itemDisplay.getZ(), itemDisplay.getPitch(), itemDisplay.getYaw(), MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0); + for (Player player : itemDisplay.getTrackedPlayers()) { + BukkitAdaptors.adapt(player).sendPacket(removePacket, false); + BukkitAdaptors.adapt(player).sendPacket(addPacket, false); } - return entity; } @Override - public boolean isValid() { - return baseEntity().isValid(); + public void destroy() { + Optional.ofNullable(this.metaEntity.get()).ifPresent(Entity::remove); + for (Collider entity : super.colliders) { + entity.destroy(); + } } - @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..e5c9087f6 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,26 @@ package net.momirealms.craftengine.bukkit.entity.furniture; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionHitBoxConfig; +import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.InteractionFurnitureHitboxConfig; import net.momirealms.craftengine.bukkit.nms.CollisionEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.network.handler.FurniturePacketHandler; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes; -import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.entity.furniture.*; +import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig; +import net.momirealms.craftengine.core.entity.furniture.tick.FurnitureTicker; +import net.momirealms.craftengine.core.entity.furniture.tick.TickingFurnitureImpl; 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 +38,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 +60,56 @@ 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, CustomFurniture 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, CustomFurniture 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() { + super.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 +118,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); } } } @@ -120,155 +131,174 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { @Override public void disable() { + super.disable(); HandlerList.unregisterAll(this.furnitureEventListener); unload(); } @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 + if (optionalFurniture.isEmpty()) return; + + // 只对1.20.2及以上生效,1.20.1比较特殊 + if (!VersionHelper.isOrAbove1_20_2()) { + return; + } + + // 已经在其他事件里加载过了 + CustomFurniture 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; + + // 已经在其他事件里加载过了 + CustomFurniture 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 +314,76 @@ 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); - } - 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); - } + // 创建家具实例,并初始化碰撞实体 + private BukkitFurniture createFurnitureInstance(ItemDisplay display, CustomFurniture furniture) { + BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, getFurnitureDataAccessor(display)); + initFurniture(bukkitFurniture); + Location location = display.getLocation(); + runSafeEntityOperation(location, bukkitFurniture::addCollidersToWorld); return bukkitFurniture; } + protected void initFurniture(BukkitFurniture furniture) { + int entityId = furniture.entityId(); + this.byMetaEntityId.put(entityId, furniture); + for (int id : furniture.virtualEntityIds()) { + this.byVirtualEntityId.put(id, furniture); + } + for (Collider collisionEntity : furniture.colliders()) { + this.byColliderEntityId.put(collisionEntity.entityId(), furniture); + } + if (!this.syncTickers.containsKey(entityId)) { + FurnitureTicker ticker = furniture.config.behavior().createSyncFurnitureTicker(furniture); + if (ticker != null) { + TickingFurnitureImpl tickingFurniture = new TickingFurnitureImpl<>(furniture, ticker); + this.syncTickers.put(entityId, tickingFurniture); + this.addSyncFurnitureTicker(tickingFurniture); + } + } + if (!this.asyncTickers.containsKey(entityId)) { + FurnitureTicker ticker = furniture.config.behavior().createAsyncBlockEntityTicker(furniture); + if (ticker != null) { + TickingFurnitureImpl tickingFurniture = new TickingFurnitureImpl<>(furniture, ticker); + this.asyncTickers.put(entityId, tickingFurniture); + this.addAsyncFurnitureTicker(tickingFurniture); + } + } + } + + protected void invalidateFurniture(BukkitFurniture furniture) { + int entityId = furniture.entityId(); + // 移除entity id映射 + this.byMetaEntityId.remove(entityId); + for (int id : furniture.virtualEntityIds()) { + this.byVirtualEntityId.remove(id); + } + for (Collider collisionEntity : furniture.colliders()) { + this.byColliderEntityId.remove(collisionEntity.entityId()); + } + } + + 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..74d3784b1 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,8 +2,22 @@ package net.momirealms.craftengine.bukkit.entity.furniture; import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.plugin.CraftEngine; +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.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -11,14 +25,18 @@ import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; 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 +47,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 +75,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 +88,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 +100,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,10 +111,50 @@ 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); } } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onInteractFurniture(FurnitureInteractEvent event) { + Player bukkitPlayer = event.getPlayer(); + BukkitServerPlayer player = BukkitAdaptors.adapt(bukkitPlayer); + if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) { + return; + } + Item itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND); + if (!itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) return; + BukkitFurniture furniture = event.furniture(); + List variants = new ArrayList<>(furniture.config.variants().keySet()); + if (variants.size() == 1) { + try { + Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance( + ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(furniture.id().asString()))), true); + player.sendPacket(systemChatPacket, false); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Could not create system chat packet", e); + } + } else { + String variantName = furniture.getCurrentVariant().name(); + int index = variants.indexOf(variantName) + 1; + if (index >= variants.size()) { + index = 0; + } + furniture.setVariant(variants.get(index)); + try { + Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance( + ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.update") + .arguments( + Component.text("variant"), + Component.text(variants.get(index)) + )), true); + player.sendPacket(systemChatPacket, false); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Could not create system chat packet", e); + } + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ArmorStandFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ArmorStandFurnitureElement.java new file mode 100644 index 000000000..e25917dd5 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ArmorStandFurnitureElement.java @@ -0,0 +1,89 @@ +package net.momirealms.craftengine.bukkit.entity.furniture.element; + +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.IntList; +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.world.score.BukkitTeamManager; +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.util.VersionHelper; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +public class ArmorStandFurnitureElement implements FurnitureElement { + private final ArmorStandFurnitureElementConfig config; + private final FurnitureColorSource colorSource; + public final Object cachedSpawnPacket; + public final Object cachedDespawnPacket; + public final Object cachedScalePacket; + public final Object cachedTeamPacket; + public final int entityId; + public final UUID uuid = UUID.randomUUID(); + + public ArmorStandFurnitureElement(Furniture furniture, ArmorStandFurnitureElementConfig config) { + this.config = config; + this.entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + WorldPosition furniturePos = furniture.position(); + Vec3d position = Furniture.getRelativePosition(furniturePos, config.position()); + this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + this.entityId, this.uuid, position.x, position.y, position.z, + furniturePos.xRot, furniturePos.yRot, MEntityTypes.ARMOR_STAND, 0, CoreReflections.instance$Vec3$Zero, furniturePos.yRot + ); + this.colorSource = furniture.dataAccessor.getColorSource(); + this.cachedDespawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(IntList.of(this.entityId)); + if (VersionHelper.isOrAbove1_20_5() && config.scale != 1) { + Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer) (o) -> {}); + FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale()); + this.cachedScalePacket = FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityId, Collections.singletonList(attributeIns)); + } else { + this.cachedScalePacket = null; + } + Object teamPacket = null; + if (config.glowColor != null) { + Object teamByColor = BukkitTeamManager.instance().getTeamByColor(config.glowColor); + if (teamByColor != null) { + teamPacket = FastNMS.INSTANCE.method$ClientboundSetPlayerTeamPacket$createMultiplePlayerPacket(teamByColor, List.of(this.uuid.toString()), true); + } + } + this.cachedTeamPacket = teamPacket; + } + + @Override + public void show(Player player) { + player.sendPackets(List.of(this.cachedSpawnPacket, FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player))), false); + player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundSetEquipmentPacket(this.entityId, List.of( + Pair.of(CoreReflections.instance$EquipmentSlot$HEAD, this.config.item(player, this.colorSource).getLiteralObject()) + )), false); + if (this.cachedScalePacket != null) { + player.sendPacket(this.cachedScalePacket, false); + } + if (this.cachedTeamPacket != null) { + player.sendPacket(this.cachedTeamPacket, false); + } + } + + @Override + public void hide(Player player) { + player.sendPacket(this.cachedDespawnPacket, 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/ArmorStandFurnitureElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ArmorStandFurnitureElementConfig.java new file mode 100644 index 000000000..ab2f09849 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ArmorStandFurnitureElementConfig.java @@ -0,0 +1,127 @@ +package net.momirealms.craftengine.bukkit.entity.furniture.element; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.momirealms.craftengine.bukkit.entity.data.ArmorStandData; +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +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.LegacyChatFormatter; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.Glowing; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +public class ArmorStandFurnitureElementConfig implements FurnitureElementConfig, Glowing { + public static final Factory FACTORY = new Factory(); + public final Function> metadata; + public final Key itemId; + public final float scale; + public final boolean applyDyedColor; + public final Vector3f position; + public final boolean small; + public final LegacyChatFormatter glowColor; + + public ArmorStandFurnitureElementConfig(Key itemId, + float scale, + Vector3f position, + boolean applyDyedColor, + boolean small, + LegacyChatFormatter glowColor) { + this.position = position; + this.applyDyedColor = applyDyedColor; + this.small = small; + this.scale = scale; + this.itemId = itemId; + this.glowColor = glowColor; + this.metadata = (player) -> { + List dataValues = new ArrayList<>(2); + if (glowColor != null) { + BaseEntityData.SharedFlags.addEntityData((byte) 0x60, dataValues); + } else { + BaseEntityData.SharedFlags.addEntityData((byte) 0x20, dataValues); + } + if (small) { + ArmorStandData.ArmorStandFlags.addEntityData((byte) 0x01, dataValues); + } + + return dataValues; + }; + } + + public Item item(Player player, FurnitureColorSource 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)); + } + + @Nullable + @Override + public LegacyChatFormatter glowColor() { + return this.glowColor; + } + + public float scale() { + return scale; + } + + public boolean small() { + return small; + } + + public Vector3f position() { + return this.position; + } + + public boolean applyDyedColor() { + return this.applyDyedColor; + } + + public Key itemId() { + return this.itemId; + } + + @Override + public ArmorStandFurnitureElement create(@NotNull Furniture furniture) { + return new ArmorStandFurnitureElement(furniture, this); + } + + public static class Factory implements FurnitureElementConfigFactory { + + @Override + public ArmorStandFurnitureElementConfig create(Map arguments) { + return new ArmorStandFurnitureElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.armor_stand.missing_item")), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("scale", 1f), "scale"), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("small", false), "small"), + ResourceConfigUtils.getAsEnum(arguments.get("glow-color"), LegacyChatFormatter.class, null) + ); + } + } +} 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..fc328fa1d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/BukkitFurnitureElementConfigs.java @@ -0,0 +1,18 @@ +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); + register(TEXT_DISPLAY, TextDisplayFurnitureElementConfig.FACTORY); + register(ITEM, ItemFurnitureElementConfig.FACTORY); + register(ARMOR_STAND, ArmorStandFurnitureElementConfig.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..b21d957e0 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElement.java @@ -0,0 +1,63 @@ +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.util.MiscUtils; +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; + private final UUID uuid = UUID.randomUUID(); + + 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(MiscUtils.init(new IntArrayList(), a -> a.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, this.uuid, + 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..3a4c9d38e --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemDisplayFurnitureElementConfig.java @@ -0,0 +1,218 @@ +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.Color; +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.jetbrains.annotations.Nullable; +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 final Color glowColor; + public final int blockLight; + public final int skyLight; + public final float viewRange; + + 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, + @Nullable Color glowColor, + int blockLight, + int skyLight, + float viewRange) { + 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; + this.glowColor = glowColor; + this.blockLight = blockLight; + this.skyLight = skyLight; + this.viewRange = viewRange; + 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<>(); + if (glowColor != null) { + ItemDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues); + ItemDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), dataValues); + } + ItemDisplayEntityData.DisplayedItem.addEntityData(itemFunction.apply(player, source).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); + if (this.blockLight != -1 && this.skyLight != -1) { + ItemDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues); + } + ItemDisplayEntityData.ViewRange.addEntityDataIfNotDefaultValue((float) (this.viewRange * player.displayEntityViewDistance()), 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 BiFunction> metadata() { + return this.metadata; + } + + public Key itemId() { + return this.itemId; + } + + @Nullable + public Color glowColor() { + return this.glowColor; + } + + public int blockLight() { + return this.blockLight; + } + + public int skyLight() { + return this.skyLight; + } + + public float viewRange() { + return this.viewRange; + } + + @Override + public ItemDisplayFurnitureElement create(@NotNull Furniture furniture) { + return new ItemDisplayFurnitureElement(furniture, this); + } + + public static class Factory implements FurnitureElementConfigFactory { + + @Override + public ItemDisplayFurnitureElementConfig create(Map arguments) { + Map brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness"); + return new ItemDisplayFurnitureElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item_display.missing_item")), + 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"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color"), + Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range") + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElement.java new file mode 100644 index 000000000..26621dd33 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElement.java @@ -0,0 +1,78 @@ +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.util.MiscUtils; +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 ItemFurnitureElement implements FurnitureElement { + private final ItemFurnitureElementConfig config; + public final int entityId1; + public final int entityId2; + private final Object despawnPacket; + private final FurnitureColorSource colorSource; + public final Object cachedSpawnPacket1; + public final Object cachedSpawnPacket2; + public final Object cachedRidePacket; + + public ItemFurnitureElement(Furniture furniture, ItemFurnitureElementConfig config) { + this.config = config; + this.entityId1 = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + this.entityId2 = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); + WorldPosition furniturePos = furniture.position(); + Vec3d position = Furniture.getRelativePosition(furniturePos, config.position()); + this.cachedSpawnPacket1 = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId1, UUID.randomUUID(), position.x, position.y, position.z, + 0, 0, MEntityTypes.ITEM_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + ); + this.cachedSpawnPacket2 = FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId2, UUID.randomUUID(), position.x, position.y, position.z, + 0, 0, MEntityTypes.ITEM, 0, CoreReflections.instance$Vec3$Zero, 0 + ); + this.cachedRidePacket = FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityId1, entityId2); + this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), + a -> { + a.add(entityId1); + a.add(entityId2); + } + )); + this.colorSource = furniture.dataAccessor.getColorSource(); + } + + @Override + public void show(Player player) { + player.sendPackets(List.of( + this.cachedSpawnPacket1, + this.cachedSpawnPacket2, + this.cachedRidePacket, + FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId2, 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.entityId1, this.entityId2}; + } + + @Override + public void collectVirtualEntityId(Consumer collector) { + collector.accept(this.entityId1); + collector.accept(this.entityId2); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElementConfig.java new file mode 100644 index 000000000..af3808b69 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/ItemFurnitureElementConfig.java @@ -0,0 +1,93 @@ +package net.momirealms.craftengine.bukkit.entity.furniture.element; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +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.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; + +public class ItemFurnitureElementConfig implements FurnitureElementConfig { + public static final Factory FACTORY = new Factory(); + public final BiFunction> metadata; + public final Key itemId; + public final boolean applyDyedColor; + public final Vector3f position; + + public ItemFurnitureElementConfig(Key itemId, + Vector3f position, + boolean applyDyedColor) { + this.position = position; + 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<>(); + ItemEntityData.Item.addEntityData(itemFunction.apply(player, source).getLiteralObject(), dataValues); + ItemEntityData.NoGravity.addEntityData(true, dataValues); + return dataValues; + }; + } + + public Vector3f position() { + return this.position; + } + + public boolean applyDyedColor() { + return this.applyDyedColor; + } + + public BiFunction> metadata() { + return this.metadata; + } + + public Key itemId() { + return this.itemId; + } + + @Override + public ItemFurnitureElement create(@NotNull Furniture furniture) { + return new ItemFurnitureElement(furniture, this); + } + + public static class Factory implements FurnitureElementConfigFactory { + + @Override + public ItemFurnitureElementConfig create(Map arguments) { + return new ItemFurnitureElementConfig( + Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.furniture.element.item.missing_item")), + ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("position", 0f), "position"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("apply-dyed-color", true), "apply-dyed-color") + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/TextDisplayFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/TextDisplayFurnitureElement.java new file mode 100644 index 000000000..a68e1c483 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/TextDisplayFurnitureElement.java @@ -0,0 +1,60 @@ +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.element.FurnitureElement; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.util.MiscUtils; +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 TextDisplayFurnitureElement implements FurnitureElement { + private final TextDisplayFurnitureElementConfig config; + private final WorldPosition position; + private final int entityId; + private final Object despawnPacket; + private final UUID uuid = UUID.randomUUID(); + + public TextDisplayFurnitureElement(Furniture furniture, TextDisplayFurnitureElementConfig 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(MiscUtils.init(new IntArrayList(), a -> a.add(entityId))); + } + + @Override + public void show(Player player) { + player.sendPacket(FastNMS.INSTANCE.constructor$ClientboundBundlePacket(List.of( + FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + this.entityId, this.uuid, + this.position.x, this.position.y, this.position.z, 0, this.position.yRot, + MEntityTypes.TEXT_DISPLAY, 0, CoreReflections.instance$Vec3$Zero, 0 + ), + FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(this.entityId, this.config.metadata.apply(player)) + )), 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/TextDisplayFurnitureElementConfig.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/TextDisplayFurnitureElementConfig.java new file mode 100644 index 000000000..614efbad3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/element/TextDisplayFurnitureElementConfig.java @@ -0,0 +1,251 @@ +package net.momirealms.craftengine.bukkit.entity.furniture.element; + +import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.core.entity.display.Billboard; +import net.momirealms.craftengine.core.entity.display.ItemDisplayContext; +import net.momirealms.craftengine.core.entity.display.TextDisplayAlignment; +import net.momirealms.craftengine.core.entity.furniture.Furniture; +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.plugin.context.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.Color; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +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.Map; +import java.util.Optional; +import java.util.function.Function; + +public class TextDisplayFurnitureElementConfig implements FurnitureElementConfig { + public static final Factory FACTORY = new Factory(); + public final Function> metadata; + public final String text; + 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 Color glowColor; + public final int blockLight; + public final int skyLight; + public final float viewRange; + public final int lineWidth; + public final int backgroundColor; + public final byte opacity; + public final boolean hasShadow; + public final boolean isSeeThrough; + public final boolean useDefaultBackgroundColor; + public final TextDisplayAlignment alignment; + + public TextDisplayFurnitureElementConfig(String text, + Vector3f scale, + Vector3f position, + Vector3f translation, + float xRot, + float yRot, + Quaternionf rotation, + ItemDisplayContext displayContext, + Billboard billboard, + float shadowRadius, + float shadowStrength, + @Nullable Color glowColor, + int blockLight, + int skyLight, + float viewRange, + int lineWidth, + int backgroundColor, + byte opacity, + boolean hasShadow, + boolean isSeeThrough, + boolean useDefaultBackgroundColor, + TextDisplayAlignment alignment) { + this.text = text; + 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.glowColor = glowColor; + this.blockLight = blockLight; + this.skyLight = skyLight; + this.viewRange = viewRange; + this.lineWidth = lineWidth; + this.backgroundColor = backgroundColor; + this.opacity = opacity; + this.hasShadow = hasShadow; + this.useDefaultBackgroundColor = useDefaultBackgroundColor; + this.alignment = alignment; + this.isSeeThrough = isSeeThrough; + this.metadata = (player) -> { + List dataValues = new ArrayList<>(); + if (glowColor != null) { + TextDisplayEntityData.SharedFlags.addEntityData((byte) 0x40, dataValues); + TextDisplayEntityData.GlowColorOverride.addEntityData(glowColor.color(), 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.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues); + TextDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues); + TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(AdventureHelper.miniMessage().deserialize(this.text, NetworkTextReplaceContext.of(player).tagResolvers())), dataValues); + TextDisplayEntityData.LineWidth.addEntityDataIfNotDefaultValue(this.lineWidth, dataValues); + TextDisplayEntityData.BackgroundColor.addEntityDataIfNotDefaultValue(this.backgroundColor, dataValues); + TextDisplayEntityData.TextOpacity.addEntityDataIfNotDefaultValue(this.opacity, dataValues); + TextDisplayEntityData.TextDisplayMasks.addEntityDataIfNotDefaultValue(TextDisplayEntityData.encodeMask(this.hasShadow, this.isSeeThrough, this.useDefaultBackgroundColor, this.alignment), dataValues); + if (this.blockLight != -1 && this.skyLight != -1) { + TextDisplayEntityData.BrightnessOverride.addEntityData(this.blockLight << 4 | this.skyLight << 20, dataValues); + } + TextDisplayEntityData.ViewRange.addEntityDataIfNotDefaultValue((float) (this.viewRange * player.displayEntityViewDistance()), 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 Function> metadata() { + return this.metadata; + } + + @Nullable + public Color glowColor() { + return this.glowColor; + } + + public int blockLight() { + return this.blockLight; + } + + public int skyLight() { + return this.skyLight; + } + + public float viewRange() { + return this.viewRange; + } + + public String text() { + return this.text; + } + + public int lineWidth() { + return this.lineWidth; + } + + public int backgroundColor() { + return this.backgroundColor; + } + + public byte opacity() { + return this.opacity; + } + + public boolean hasShadow() { + return this.hasShadow; + } + + public boolean isSeeThrough() { + return this.isSeeThrough; + } + + public boolean useDefaultBackgroundColor() { + return this.useDefaultBackgroundColor; + } + + public TextDisplayAlignment alignment() { + return this.alignment; + } + + @Override + public TextDisplayFurnitureElement create(@NotNull Furniture furniture) { + return new TextDisplayFurnitureElement(furniture, this); + } + + public static class Factory implements FurnitureElementConfigFactory { + + @Override + public TextDisplayFurnitureElementConfig create(Map arguments) { + Map brightness = ResourceConfigUtils.getAsMap(arguments.getOrDefault("brightness", Map.of()), "brightness"); + return new TextDisplayFurnitureElementConfig( + ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("text"), "warning.config.furniture.element.text_display.missing_text"), + 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"), + Optional.ofNullable(arguments.get("glow-color")).map(it -> Color.fromStrings(it.toString().split(","))).orElse(null), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("block-light", -1), "block-light"), + ResourceConfigUtils.getAsInt(brightness.getOrDefault("sky-light", -1), "sky-light"), + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("view-range", 1f), "view-range"), + ResourceConfigUtils.getAsInt(arguments.getOrDefault("line-width", 200), "line-width"), + ResourceConfigUtils.getOrDefault(arguments.get("background-color"), o -> Color.fromStrings(o.toString().split(",")).color(), 0x40000000), + (byte) ResourceConfigUtils.getAsInt(arguments.getOrDefault("text-opacity", -1), "text-opacity"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("has-shadow", false), "has-shadow"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("is-see-through", false), "is-see-through"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("use-default-background-color", false), "use-default-background-color"), + ResourceConfigUtils.getAsEnum(arguments.get("alignment"), TextDisplayAlignment.class, TextDisplayAlignment.CENTER) + ); + } + } +} 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..7e2caafed --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomFurnitureHitbox.java @@ -0,0 +1,85 @@ +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.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.util.MiscUtils; +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()) { + Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer) (o) -> {}); + FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale()); + packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(entityId, Collections.singletonList(attributeIns))); + } + this.spawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets); + this.part = new FurnitureHitboxPart(entityId, aabb, pos, false); + this.despawnPacket = FastNMS.INSTANCE.constructor$ClientboundRemoveEntitiesPacket(MiscUtils.init(new IntArrayList(), l -> l.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..bd53f0015 --- /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 prepareBoundingBox(WorldPosition targetPos, Consumer aabbConsumer, boolean ignoreBlocksBuilding) { + if (this.blocksBuilding || ignoreBlocksBuilding) { + 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..313e896b9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastFurnitureHitbox.java @@ -0,0 +1,89 @@ +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.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.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) { + Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer) (o) -> {}); + FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale()); + this.packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityId, Collections.singletonList(attributeIns))); + } + 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..3ee687d59 --- /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 prepareBoundingBox(WorldPosition targetPos, Consumer aabbConsumer, boolean ignoreBlocksBuilding) { + if (this.blocksBuilding || ignoreBlocksBuilding) { + 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..454b2eb59 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionFurnitureHitbox.java @@ -0,0 +1,77 @@ +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.util.MiscUtils; +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(MiscUtils.init(new IntArrayList(), l -> l.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..40caaf1ee --- /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 prepareBoundingBox(WorldPosition targetPos, Consumer aabbConsumer, boolean ignoreBlocksBuilding) { + if (this.blocksBuilding || ignoreBlocksBuilding) { + 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..b55a65872 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerFurnitureHitbox.java @@ -0,0 +1,135 @@ +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) { + Object attributeIns = FastNMS.INSTANCE.constructor$AttributeInstance(MAttributeHolders.SCALE, (Consumer) (o) -> {}); + FastNMS.INSTANCE.method$AttributeInstance$setBaseValue(attributeIns, config.scale()); + packets.add(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(this.entityIds[1], Collections.singletonList(attributeIns))); + } + 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..6756948bb 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 prepareBoundingBox(WorldPosition targetPos, Consumer aabbConsumer, boolean ignoreBlocksBuilding) { + if (this.blocksBuilding || ignoreBlocksBuilding) { + 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/entity/seat/BukkitSeatManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java index 2e52d5564..e704276ff 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java @@ -61,6 +61,7 @@ public class BukkitSeatManager implements SeatManager, Listener { @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin()); + Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin()); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java index 699fb7975..619567244 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java @@ -101,7 +101,7 @@ public class AxeItemBehavior extends ItemBehavior { // resend swing if it's not interactable on client side if (!InteractUtils.isInteractable( - bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()), + bukkitPlayer, BlockStateUtils.fromBlockData(customState.visualBlockState().literalObject()), context.getHitResult(), item ) || player.isSecondaryUseActive()) { player.swingHand(context.getHand()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index add1679d3..6d0e7871d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -112,7 +112,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { } else { ImmutableBlockState customState = optionalCustomState.get(); // custom block - if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().literalObject() : againstBlockState)) { + if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.visualBlockState().literalObject() : againstBlockState)) { return InteractionResult.FAIL; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java index 23fde9447..f236c8c29 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java @@ -8,6 +8,7 @@ public class BukkitItemBehaviors extends ItemBehaviors { public static final Key BLOCK_ITEM = Key.from("craftengine:block_item"); public static final Key ON_LIQUID_BLOCK_ITEM = Key.from("craftengine:liquid_collision_block_item"); public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item"); + public static final Key ON_LIQUID_FURNITURE_ITEM = Key.from("craftengine:liquid_collision_furniture_item"); public static final Key FLINT_AND_STEEL_ITEM = Key.from("craftengine:flint_and_steel_item"); public static final Key COMPOSTABLE_ITEM = Key.from("craftengine:compostable_item"); public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); @@ -21,6 +22,7 @@ public class BukkitItemBehaviors extends ItemBehaviors { register(BLOCK_ITEM, BlockItemBehavior.FACTORY); register(ON_LIQUID_BLOCK_ITEM, LiquidCollisionBlockItemBehavior.FACTORY); register(FURNITURE_ITEM, FurnitureItemBehavior.FACTORY); + register(ON_LIQUID_FURNITURE_ITEM, LiquidCollisionFurnitureItemBehavior.FACTORY); register(FLINT_AND_STEEL_ITEM, FlintAndSteelItemBehavior.FACTORY); register(COMPOSTABLE_ITEM, CompostableItemBehavior.FACTORY); register(AXE_ITEM, AxeItemBehavior.FACTORY); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java index b734543f3..70ac737e6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java @@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { // 点击对象为自定义方块 ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); // 原版外观也可燃 - if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) { + if (BlockStateUtils.isBurnable(immutableBlockState.visualBlockState().literalObject())) { return InteractionResult.PASS; } - BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); + BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()); // 点击的是方块上面,则只需要判断shift和可交互 if (direction == Direction.UP) { // 客户端层面必须可交互 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..a40c5a34e 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,48 @@ 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.*; +import java.util.function.Predicate; public class FurnitureItemBehavior extends ItemBehavior { public static final Factory FACTORY = new Factory(); + protected static final Set ALLOWED_ANCHOR_TYPES = Set.of("wall", "ceiling", "ground"); private final Key id; + private final Map rules; + private final boolean ignorePlacer; + private final boolean ignoreEntities; - public FurnitureItemBehavior(Key id) { + public FurnitureItemBehavior(Key id, Map rules, boolean ignorePlacer, boolean ignoreEntities) { this.id = id; + this.rules = rules; + this.ignorePlacer = ignorePlacer; + this.ignoreEntities = ignoreEntities; } public Key furnitureId() { - return id; + return this.id; + } + + public Map rules() { + return this.rules; + } + + public boolean ignorePlacer() { + return this.ignorePlacer; + } + + public boolean ignoreEntities() { + return this.ignoreEntities; } @Override @@ -62,7 +79,6 @@ public class FurnitureItemBehavior extends ItemBehavior { 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 +87,98 @@ public class FurnitureItemBehavior extends ItemBehavior { case DOWN -> AnchorType.CEILING; }; - CustomFurniture.Placement placement = customFurniture.getPlacement(anchorType); - if (placement == null) { + CustomFurniture 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.prepareBoundingBox(furniturePos, aabbs::add, false); } + // 检查方块、实体阻挡 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())) { + Predicate entityPredicate; + if (this.ignoreEntities) { + entityPredicate = (o) -> false; + } else if (this.ignorePlacer) { + entityPredicate = player != null ? (o) -> o != player.serverPlayer() : (o) -> true; + } else { + entityPredicate = (o) -> true; + } + 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(), entityPredicate)) { + 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 +186,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 +199,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 +218,62 @@ 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, + ResourceConfigUtils.getAsBoolean(arguments.get("ignore-placer"), "ignore-placer"), + ResourceConfigUtils.getAsBoolean(arguments.get("ignore-entities"), "ignore-entities") + ); } } + + 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/behavior/LiquidCollisionFurnitureItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionFurnitureItemBehavior.java new file mode 100644 index 000000000..74b31a612 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/LiquidCollisionFurnitureItemBehavior.java @@ -0,0 +1,157 @@ +package net.momirealms.craftengine.bukkit.item.behavior; + +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.entity.furniture.AlignmentRule; +import net.momirealms.craftengine.core.entity.furniture.AnchorType; +import net.momirealms.craftengine.core.entity.furniture.RotationRule; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; +import net.momirealms.craftengine.core.item.context.UseOnContext; +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.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.logger.Debugger; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockHitResult; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.*; + +public class LiquidCollisionFurnitureItemBehavior extends FurnitureItemBehavior { + public static final Factory FACTORY = new Factory(); + private final List liquidTypes; + private final boolean sourceOnly; + + public LiquidCollisionFurnitureItemBehavior(Key id, Map rules, boolean ignorePlacer, boolean ignoreEntities, boolean sourceOnly, List liquidTypes) { + super(id, rules, ignorePlacer, ignoreEntities); + this.liquidTypes = liquidTypes; + this.sourceOnly = sourceOnly; + } + + @Override + public InteractionResult useOnBlock(UseOnContext context) { + return use(context.getLevel(), context.getPlayer(), context.getHand()); + } + + @Override + public InteractionResult use(World world, @Nullable Player player, InteractionHand hand) { + try { + if (player == null) return InteractionResult.FAIL; + Object blockHitResult = CoreReflections.method$Item$getPlayerPOVHitResult.invoke(null, world.serverWorld(), player.serverPlayer(), CoreReflections.instance$ClipContext$Fluid$ANY); + Object blockPos = FastNMS.INSTANCE.field$BlockHitResult$blockPos(blockHitResult); + BlockPos above = new BlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos), FastNMS.INSTANCE.field$Vec3i$z(blockPos)); + Direction direction = DirectionUtils.fromNMSDirection(FastNMS.INSTANCE.field$BlockHitResul$direction(blockHitResult)); + boolean miss = FastNMS.INSTANCE.field$BlockHitResul$miss(blockHitResult); + Vec3d hitPos = LocationUtils.fromVec(CoreReflections.field$HitResult$location.get(blockHitResult)); + Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.method$BlockGetter$getFluidState(world.serverWorld(), blockPos)); + if (fluidType == MFluids.EMPTY) { + return InteractionResult.PASS; + } + String liquid = null; + if (fluidType == MFluids.LAVA) { + liquid = "lava"; + } else if (fluidType == MFluids.WATER) { + liquid = "water"; + } else if (fluidType == MFluids.FLOWING_LAVA) { + if (this.sourceOnly) return InteractionResult.PASS; + liquid = "lava"; + } else if (fluidType == MFluids.FLOWING_WATER) { + if (this.sourceOnly) return InteractionResult.PASS; + liquid = "water"; + } + if (!this.liquidTypes.contains(liquid)) { + return InteractionResult.PASS; + } + if (miss) { + return super.useOnBlock(new UseOnContext(player, hand, BlockHitResult.miss(hitPos, direction, above))); + } else { + boolean inside = CoreReflections.field$BlockHitResult$inside.getBoolean(blockHitResult); + return super.useOnBlock(new UseOnContext(player, hand, new BlockHitResult(hitPos, direction, above, inside))); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Error handling use", e); + return InteractionResult.FAIL; + } + } + + public static class Factory implements ItemBehaviorFactory { + + @Override + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { + Object id = arguments.get("furniture"); + 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())) { + // 防呆 + furnitureSection = MiscUtils.castToMap(map.get(key.toString()), false); + BukkitFurnitureManager.instance().parser().addPendingConfigSection(new PendingConfigSection(pack, path, node, key, furnitureSection)); + } else { + 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); + } + } + } + } + } + } + } else { + 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 LiquidCollisionFurnitureItemBehavior(furnitureId, rules, + ResourceConfigUtils.getAsBoolean(arguments.get("ignore-placer"), "ignore-placer"), + ResourceConfigUtils.getAsBoolean(arguments.get("ignore-entities"), "ignore-entities"), + ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("source-only", true), "source-only"), + MiscUtils.getAsStringList(arguments.get("liquid-type")) + ); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java index b3f763ce1..0a21878f0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java @@ -3,22 +3,21 @@ package net.momirealms.craftengine.bukkit.item.listener; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.ComponentUtils; -import net.momirealms.craftengine.bukkit.util.ItemStackUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.util.MiscUtils; -import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -44,12 +43,10 @@ public class DebugStickListener implements Listener { public void onUseDebugStick(PlayerInteractEvent event) { Block clickedBlock = event.getClickedBlock(); if (clickedBlock == null) return; - ItemStack itemInHand = event.getItem(); - if (ItemStackUtils.isEmpty(itemInHand)) return; - Material material = itemInHand.getType(); - if (material != Material.DEBUG_STICK) return; Player bukkitPlayer = event.getPlayer(); BukkitServerPlayer player = BukkitAdaptors.adapt(bukkitPlayer); + Item itemInHand = player.getItemInHand(event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND); + if (!itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) return; if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) { return; } @@ -73,8 +70,7 @@ public class DebugStickListener implements Listener { ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true); player.sendPacket(systemChatPacket, false); } else { - Item wrapped = BukkitItemManager.instance().wrap(itemInHand); - Object storedData = wrapped.getJavaTag("craftengine:debug_stick_state"); + Object storedData = itemInHand.getJavaTag("craftengine:debug_stick_state"); if (storedData == null) storedData = new HashMap<>(); if (storedData instanceof Map map) { Map data = new HashMap<>(MiscUtils.castToMap(map, false)); @@ -96,7 +92,7 @@ public class DebugStickListener implements Listener { } else { currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive()); data.put(blockId, currentProperty.name()); - wrapped.setTag(data, "craftengine:debug_stick_state"); + itemInHand.setTag(data, "craftengine:debug_stick_state"); Object systemChatPacket = NetworkReflections.constructor$ClientboundSystemChatPacket.newInstance( ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select") .arguments( 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 9f38f16d0..5d3d4afa8 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 @@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.ItemSettings; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; @@ -54,20 +55,16 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.inventory.EnchantingInventory; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; public class ItemEventListener implements Listener { private final BukkitCraftEngine plugin; @@ -167,7 +164,7 @@ public class ItemEventListener implements Listener { // fix client side issues if (action.isRightClick() && hitResult != null && - InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()), hitResult, itemInHand)) { + InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()), hitResult, itemInHand)) { player.updateInventory(); } @@ -272,13 +269,13 @@ public class ItemEventListener implements Listener { if (immutableBlockState != null) { // client won't have sounds if the clientside block is interactable // so we should check and resend sounds on BlockPlaceEvent - BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); + BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()); if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) { if (!serverPlayer.isSecondaryUseActive()) { serverPlayer.setResendSound(); } } else { - if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().literalObject())) { + if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.visualBlockState().literalObject())) { serverPlayer.setResendSwing(); } } @@ -426,7 +423,7 @@ public class ItemEventListener implements Listener { } } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onConsumeItem(PlayerItemConsumeEvent event) { ItemStack consumedItem = event.getItem(); if (ItemStackUtils.isEmpty(consumedItem)) return; @@ -435,9 +432,11 @@ public class ItemEventListener implements Listener { if (optionalCustomItem.isEmpty()) { return; } + Player player = event.getPlayer(); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); CustomItem customItem = optionalCustomItem.get(); - PlayerOptionalContext context = PlayerOptionalContext.of(BukkitAdaptors.adapt(event.getPlayer()), ContextHolder.builder() + PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.ITEM_IN_HAND, wrapped) .withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.HAND, event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND) @@ -448,11 +447,19 @@ public class ItemEventListener implements Listener { } if (event.getPlayer().getGameMode() != GameMode.CREATIVE) { Key replacement = customItem.settings().consumeReplacement(); - if (replacement == null) { - event.setReplacement(null); + if (wrapped.count() == 1) { + if (replacement != null) { + ItemStack replacementItem = this.plugin.itemManager().buildItemStack(replacement, serverPlayer); + event.setReplacement(replacementItem); + } } else { - ItemStack replacementItem = this.plugin.itemManager().buildItemStack(replacement, BukkitAdaptors.adapt(event.getPlayer())); - event.setReplacement(replacementItem); + // fixme 如何取消堆叠数量>1的物品的默认replacement + if (replacement != null) { + Item replacementItem = this.plugin.itemManager().createWrappedItem(replacement, serverPlayer); + if (replacementItem != null) { + PlayerUtils.giveItem(serverPlayer, 1, replacementItem); + } + } } } } @@ -620,4 +627,114 @@ public class ItemEventListener implements Listener { } event.setCurrentItem((ItemStack) result.finalItem().getItem()); } + + @SuppressWarnings("DuplicatedCode") + @EventHandler(ignoreCancelled = true) + public void onPlayerDeath(PlayerDeathEvent event) { + BukkitItemManager instance = BukkitItemManager.instance(); + + // 处理损毁物品 + if (event.getKeepInventory()) { + if (!instance.featureFlag$destroyOnDeathChance()) return; + + Random random = ThreadLocalRandom.current(); + PlayerInventory inventory = event.getPlayer().getInventory(); + for (ItemStack item : inventory.getContents()) { + if (item == null) continue; + + Optional> optional = instance.wrap(item).getCustomItem(); + if (optional.isEmpty()) continue; + + CustomItem customItem = optional.get(); + ItemSettings settings = customItem.settings(); + float destroyChance = settings.destroyOnDeathChance(); + if (destroyChance <= 0f) continue; + + int totalAmount = item.getAmount(); + int destroyCount = 0; + + for (int i = 0; i < totalAmount; i++) { + float rand = random.nextFloat(); + // 判断是否损毁 + if (destroyChance > 0f && rand < destroyChance) { + destroyCount++; + } + } + if (destroyCount != 0) { + item.setAmount(totalAmount - destroyCount); + } + } + } + // 处理保留 + 损毁物品 + else { + if (!instance.featureFlag$keepOnDeathChance() && !instance.featureFlag$destroyOnDeathChance()) return; + Random random = ThreadLocalRandom.current(); + + List itemsToKeep = event.getItemsToKeep(); + List itemsToDrop = event.getDrops(); + + Iterator iterator = itemsToDrop.iterator(); + + while (iterator.hasNext()) { + ItemStack item = iterator.next(); + Optional> optional = instance.wrap(item).getCustomItem(); + if (optional.isEmpty()) continue; + + CustomItem customItem = optional.get(); + ItemSettings settings = customItem.settings(); + + float destroyChance = settings.destroyOnDeathChance(); + float keepChance = settings.keepOnDeathChance(); + + // 如果没有效果,跳过 + if (destroyChance <= 0f && keepChance <= 0f) continue; + + int totalAmount = item.getAmount(); + + int keepCount = 0; + int destroyCount = 0; + int dropCount = 0; + + for (int i = 0; i < totalAmount; i++) { + float rand = random.nextFloat(); + + // 先判断是否损毁 + if (destroyChance > 0f && rand < destroyChance) { + destroyCount++; + } + // 然后判断是否保留(在未损毁的物品中) + else if (keepChance > 0f && rand < (destroyChance + keepChance)) { + keepCount++; + } + // 否则掉落 + else { + dropCount++; + } + } + + // 处理结果 + if (destroyCount == totalAmount) { + iterator.remove(); + continue; + } + + if (keepCount == 0 && dropCount == 0) { + // 实际上不会发生这种情况 + continue; + } + + if (keepCount > 0) { + ItemStack keepItem = item.clone(); + keepItem.setAmount(keepCount); + itemsToKeep.add(keepItem); + } + + if (dropCount > 0) { + item.setAmount(dropCount); + } else { + iterator.remove(); + } + } + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 6e2035f84..82467401f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -407,8 +407,12 @@ public class RecipeEventListener implements Listener { } boolean hasResult = true; - + int realDurabilityPerItem = (int) (repairItem.amount() + repairItem.percent() * maxDamage); + if (realDurabilityPerItem == 0) { + return; + } + int consumeMaxAmount = damage / realDurabilityPerItem + 1; int actualConsumedAmount = Math.min(consumeMaxAmount, wrappedSecond.count()); int actualRepairAmount = actualConsumedAmount * realDurabilityPerItem; 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..df1f0b1d5 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; @@ -26,6 +27,7 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager; import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; +import net.momirealms.craftengine.bukkit.world.score.BukkitTeamManager; import net.momirealms.craftengine.core.item.ItemManager; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; @@ -160,8 +162,9 @@ public class BukkitCraftEngine extends CraftEngine { super.onPluginLoad(); BukkitBlockBehaviors.init(); BukkitItemBehaviors.init(); - BukkitHitBoxTypes.init(); + BukkitFurnitureHitboxTypes.init(); BukkitBlockEntityElementConfigs.init(); + BukkitFurnitureElementConfigs.init(); // 初始化 onload 阶段的兼容性 super.compatibilityManager().onLoad(); // 创建网络管理器 @@ -190,6 +193,8 @@ public class BukkitCraftEngine extends CraftEngine { super.seatManager = new BukkitSeatManager(this); // 初始化家具管理器 super.furnitureManager = new BukkitFurnitureManager(this); + // 初始化队伍管理器 + super.teamManager = new BukkitTeamManager(this); // 注册默认的parser this.registerDefaultParsers(); // 完成加载 @@ -369,6 +374,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..08c658e0b 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 @@ -1,24 +1,18 @@ package net.momirealms.craftengine.bukkit.plugin; import com.google.gson.JsonElement; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; -import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps; import net.momirealms.craftengine.bukkit.util.ParticleUtils; import net.momirealms.craftengine.bukkit.world.particle.BukkitParticleType; import net.momirealms.craftengine.core.plugin.Platform; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.particle.ParticleType; -import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; import org.bukkit.Particle; -import java.util.Map; - public class BukkitPlatform implements Platform { private final BukkitCraftEngine plugin; @@ -31,34 +25,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..5f44c5689 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,16 @@ public class BukkitCommandManager extends AbstractCommandManager new SearchUsageAdminCommand(this, plugin), new TestCommand(this, plugin), new SetLocaleCommand(this, plugin), + new SetDisplayEntityViewDistanceScaleCommand(this, plugin), + new SetEntityCullingDistanceScaleCommand(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/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index 126e06e3e..910ecf5c2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -78,7 +78,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature Set ids = new HashSet<>(); for (CustomBlock customBlock : instance.loadedBlocks().values()) { for (ImmutableBlockState state : customBlock.variantProvider().states()) { - ids.add(state.vanillaBlockState()); + ids.add(state.visualBlockState()); } } VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); 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/DebugGenerateInternalAssetsCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGenerateInternalAssetsCommand.java index c92fcea03..70201e12f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGenerateInternalAssetsCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugGenerateInternalAssetsCommand.java @@ -13,6 +13,7 @@ import org.incendo.cloud.parser.standard.StringParser; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -139,7 +140,7 @@ public class DebugGenerateInternalAssetsCommand extends BukkitCommandFeature callback) { try (InputStream inputStream = Files.newInputStream(folder.resolve("_list.json"))) { String s = prefix.isEmpty() ? "" : (prefix + "/"); - JsonObject listJson = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject(); + JsonObject listJson = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject(); JsonArray fileList = listJson.getAsJsonArray("files"); for (JsonElement element : fileList) { if (element instanceof JsonPrimitive primitive) { 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..016169435 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,7 +4,6 @@ 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.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; @@ -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,7 +42,16 @@ 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"); @@ -54,9 +63,9 @@ public class DebugSpawnFurnitureCommand extends BukkitCommandFeature { + + public SetDisplayEntityViewDistanceScaleCommand(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 -> { + Player player = context.get("player"); + double scale = context.get("scale"); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + serverPlayer.setDisplayEntityViewDistanceScale(scale); + handleFeedback(context, MessageConstants.COMMAND_DISPLAY_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName())); + }); + } + + @Override + public String getFeatureID() { + return "set_display_entity_view_distance_scale"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityCullingDistanceScaleCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityCullingDistanceScaleCommand.java new file mode 100644 index 000000000..89968985e --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SetEntityCullingDistanceScaleCommand.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +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.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 SetEntityCullingDistanceScaleCommand extends BukkitCommandFeature { + + public SetEntityCullingDistanceScaleCommand(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 -> { + Player player = context.get("player"); + double scale = context.get("scale"); + BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); + serverPlayer.setEntityCullingDistanceScale(scale); + handleFeedback(context, MessageConstants.COMMAND_ENTITY_CULLING_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName())); + }); + } + + @Override + public String getFeatureID() { + return "set_entity_culling_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/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 0d90bceef..ed69ec3a4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -283,10 +283,10 @@ public final class WorldStorageInjector { if (Config.enableLightSystem()) { if (previousImmutableBlockState.isEmpty()) { // 原版块到自定义块,只需要判断新块是否和客户端视觉一致 - updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); + updateLight(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, x, y, z); } else { // 自定义块到自定义块 - updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); + updateLight$complex(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, previousState, x, y, z); } } } else { @@ -311,7 +311,7 @@ public final class WorldStorageInjector { } if (Config.enableLightSystem()) { // 自定义块到原版块,只需要判断旧块是否和客户端一直 - BlockStateWrapper wrapper = previous.vanillaBlockState(); + BlockStateWrapper wrapper = previous.visualBlockState(); if (wrapper != null) { updateLight(holder, wrapper.literalObject(), previousState, x, y, z); } 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 4d7761189..5546b0734 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 @@ -22,6 +22,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.DataComponentValue; import net.kyori.adventure.text.event.HoverEvent; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; @@ -58,14 +59,15 @@ 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; import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipeHolder; @@ -90,9 +92,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.CEChunk; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.ClientSection; +import net.momirealms.craftengine.core.world.chunk.client.PackedOcclusionStorage; +import net.momirealms.craftengine.core.world.chunk.client.SingularOcclusionStorage; import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; import net.momirealms.craftengine.core.world.chunk.packet.MCSection; import net.momirealms.sparrow.nbt.CompoundTag; @@ -125,6 +130,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import java.util.function.Predicate; public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; @@ -288,7 +294,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - public void registerBlockStatePacketListeners(int[] blockStateMappings) { + public void registerBlockStatePacketListeners(int[] blockStateMappings, Predicate occlusionPredicate) { int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount(); int[] newMappings = new int[blockStateMappings.length]; @@ -318,10 +324,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes newMappings, newMappingsMOD, newMappings.length, - RegistryUtils.currentBiomeRegistrySize() + RegistryUtils.currentBiomeRegistrySize(), + occlusionPredicate ), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); - registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); - registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); + registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); + registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); registerS2CGamePacketListener( VersionHelper.isOrAbove1_21_4() ? new LevelParticleListener1_21_4(newMappings, newMappingsMOD) : @@ -353,11 +360,10 @@ 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); + registerNMSPacketConsumer(new ClientInformationListener(), VersionHelper.isOrAbove1_20_2() ? NetworkReflections.clazz$ServerboundClientInformationPacket1 : NetworkReflections.clazz$ServerboundClientInformationPacket0); registerNMSPacketConsumer(new ContainerClickListener1_21_5(), VersionHelper.isOrAbove1_21_5() ? NetworkReflections.clazz$ServerboundContainerClickPacket : null); registerS2CGamePacketListener(new ForgetLevelChunkListener(), this.packetIds.clientboundForgetLevelChunkPacket(), "ClientboundForgetLevelChunkPacket"); registerS2CGamePacketListener(new SetScoreListener1_20_3(), VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1, "ClientboundSetScorePacket"); @@ -1095,7 +1101,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (player.isAdventureMode()) { if (Config.simplifyAdventureBreakCheck()) { ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); - if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { + if (!player.canBreak(pos, state.visualBlockState().literalObject())) { player.preventMiningBlock(); return; } @@ -1241,7 +1247,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; @@ -1267,7 +1273,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())); } } @@ -1288,6 +1294,27 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } + public static class ClientInformationListener implements NMSPacketListener { + + @Override + public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { + try { + if (VersionHelper.isOrAbove1_20_2()) { + Object clientInfo = NetworkReflections.field$ServerboundClientInformationPacket$information.get(packet); + if (clientInfo == null) return; + String locale = (String) CoreReflections.field$ClientInformation$language.get(clientInfo); + if (locale == null) return; + ((BukkitServerPlayer) user).setClientLocale(TranslationManager.parseLocale(locale)); + } else { + String locale = (String) NetworkReflections.field$ServerboundClientInformationPacket$language.get(packet); + if (locale == null) return; + ((BukkitServerPlayer) user).setClientLocale(TranslationManager.parseLocale(locale)); + } + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundClientInformationPacket", e); + } + } + } public static class SetCreativeSlotListener implements NMSPacketListener { @@ -1336,7 +1363,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Key itemId = state.settings().itemId(); // no item available if (itemId == null) return; - Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().literalObject()); + Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.visualBlockState().literalObject()); Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock); if (vanillaBlockItem == null) return; Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey()); @@ -1411,9 +1438,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + player.setClientSideWorld(BukkitAdaptors.adapt(world)); } else { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); } @@ -1441,10 +1466,10 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + 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"); } @@ -1721,9 +1746,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); @@ -1743,43 +1765,17 @@ 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 { + private void returnToWorld(NetWorkUser user, Queue configurationTasks, Object packetListener) { + configurationTasks.add(CoreReflections.instance$JoinWorldTask); + try { + CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$startNextTask.invokeExact(packetListener); + } catch (Throwable e) { + CraftEngine.instance().logger().warn("Cannot return to world for " + user.name(), e); + } + } + @SuppressWarnings("unchecked") @Override public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { @@ -1830,15 +1826,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // 请求资源包 ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost(); host.requestResourcePackDownloadLink(user.uuid()).whenComplete((dataList, t) -> { - if (t != null) { - CraftEngine.instance().logger().warn("Failed to get pack data for player " + user.name(), t); - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - return; - } - if (dataList.isEmpty()) { - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); - return; - } Queue configurationTasks; try { configurationTasks = (Queue) CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$configurationTasksGetter.invokeExact(packetListener); @@ -1847,13 +1834,22 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); return; } + if (t != null) { + CraftEngine.instance().logger().warn("Failed to get pack data for player " + user.name(), t); + returnToWorld(user, configurationTasks, packetListener); + return; + } + if (dataList.isEmpty()) { + returnToWorld(user, configurationTasks, packetListener); + return; + } // 向配置阶段连接的任务重加入资源包的任务 for (ResourcePackDownloadData data : dataList) { configurationTasks.add(FastNMS.INSTANCE.constructor$ServerResourcePackConfigurationTask(ResourcePackUtils.createServerResourcePackInfo(data.uuid(), data.url(), data.sha1()))); user.addResourcePackUUID(data.uuid()); } // 最后再加入一个 JoinWorldTask 并开始资源包任务 - FastNMS.INSTANCE.method$ServerConfigurationPacketListenerImpl$returnToWorld(packetListener); + returnToWorld(user, configurationTasks, packetListener); }); } } @@ -1957,17 +1953,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private final IntIdentityList biomeList; private final IntIdentityList blockList; private final boolean needsDowngrade; + private final Predicate occlusionPredicate; - public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) { + public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; this.biomeList = new IntIdentityList(biomeRegistrySize); this.blockList = new IntIdentityList(blockRegistrySize); this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize); - } - - public int remapBlockState(int stateId, boolean enableMod) { - return enableMod ? this.modBlockStateMapper[stateId] : this.blockStateMapper[stateId]; + this.occlusionPredicate = occlusionPredicate; } @Override @@ -1979,6 +1973,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); boolean named = !VersionHelper.isOrAbove1_20_2(); + int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper; + // 读取区块数据 int heightmapsCount = 0; Map heightmapsMap = null; @@ -2000,36 +1996,94 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.readBytes(chunkDataBytes); // 客户端侧section数量很重要,不能读取此时玩家所在的真实世界,包具有滞后性 - int count = player.clientSideSectionCount(); + net.momirealms.craftengine.core.world.World clientSideWorld = player.clientSideWorld(); + WorldHeight worldHeight = clientSideWorld.worldHeight(); + int count = worldHeight.getSectionsCount(); MCSection[] sections = new MCSection[count]; FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes)); boolean hasChangedAnyBlock = false; boolean hasGlobalPalette = false; + // 创建客户端侧世界(只在开启实体情况下创建) + 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); mcSection.readPacket(chunkDataByteBuf); + PalettedContainer container = mcSection.blockStateContainer(); + // 重定向生物群系 if (remapBiomes(user, mcSection.biomeContainer())) { hasChangedAnyBlock = true; } + Palette palette = container.data().palette(); if (palette.canRemap()) { - if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) { + + // 重定向方块 + if (palette.remapAndCheck(s -> remapper[s])) { hasChangedAnyBlock = true; } + + // 处理客户端侧哪些方块有阻挡 + if (clientSections != null) { + int size = palette.getSize(); + // 单个元素的情况下,使用优化的存储方案 + if (size == 1) { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(this.occlusionPredicate.test(palette.get(0)))); + } else { + boolean hasOcclusions = false; + boolean hasNoOcclusions = false; + for (int h = 0; h < size; h++) { + int entry = palette.get(h); + if (this.occlusionPredicate.test(entry)) { + hasOcclusions = true; + } else { + hasNoOcclusions = true; + } + } + // 两种情况都有,那么需要一个个遍历处理视线遮挡数据 + if (hasOcclusions && hasNoOcclusions) { + PackedOcclusionStorage storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); + for (int j = 0; j < 4096; j++) { + int state = container.get(j); + storage.set(j, this.occlusionPredicate.test(state)); + } + } + // 全遮蔽或全透视则使用优化存储方案 + else { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(hasOcclusions)); + } + } + } } else { hasGlobalPalette = true; + + PackedOcclusionStorage storage = null; + if (clientSections != null) { + storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); + } + for (int j = 0; j < 4096; j++) { int state = container.get(j); - int newState = remapBlockState(state, user.clientModEnabled()); + + // 重定向方块 + int newState = remapper[state]; if (newState != state) { container.set(j, newState); hasChangedAnyBlock = true; } + + // 写入视线遮挡数据 + if (storage != null) { + storage.set(j, this.occlusionPredicate.test(state)); + } } } + sections[i] = mcSection; } @@ -2094,13 +2148,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } // 记录加载的区块 - player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); + player.addTrackedChunk(chunkPos.longKey, new ClientChunk(clientSections, worldHeight)); // 生成方块实体 - CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); - if (ceChunk != null) { - ceChunk.spawnBlockEntities(player); + CEWorld ceWorld = clientSideWorld.storageWorld(); + // 世界可能被卸载,因为包滞后 + if (ceWorld != null) { + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); + if (ceChunk != null) { + // 生成方块实体 + ceChunk.spawnBlockEntities(player); + } } } } @@ -2108,63 +2166,66 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class SectionBlockUpdateListener implements ByteBufferPacketListener { private final int[] blockStateMapper; private final int[] modBlockStateMapper; + private final Predicate occlusionPredicate; - public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; + this.occlusionPredicate = occlusionPredicate; } @Override public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { - if (user.clientModEnabled()) { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = modBlockStateMapper[((int) (k >>> 12))]; + int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper; + FriendlyByteBuf buf = event.getBuffer(); + long sPos = buf.readLong(); + int blocks = buf.readVarInt(); + short[] positions = new short[blocks]; + int[] states = new int[blocks]; + + // 获取客户端侧区域 + ClientSection clientSection = null; + if (Config.entityCullingRayTracing()) { + SectionPos sectionPos = SectionPos.of(sPos); + ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey); + if (trackedChunk != null) { + clientSection = trackedChunk.sectionById(sectionPos.y); } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); - } else { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = blockStateMapper[((int) (k >>> 12))]; - } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); } + + for (int i = 0; i < blocks; i++) { + long k = buf.readVarLong(); + short posIndex = (short) ((int) (k & 4095L)); + positions[i] = posIndex; + int beforeState = ((int) (k >>> 12)); + states[i] = remapper[beforeState]; + if (clientSection != null) { + // 设置遮蔽状态 + BlockPos pos = SectionPos.unpackSectionRelativePos(posIndex); + clientSection.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(beforeState)); + } + } + + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeLong(sPos); + buf.writeVarInt(blocks); + for (int i = 0; i < blocks; i++) { + buf.writeVarLong((long) states[i] << 12 | positions[i]); + } + event.setChanged(true); } } public static class BlockUpdateListener implements ByteBufferPacketListener { private final int[] blockStateMapper; private final int[] modBlockStateMapper; + private final Predicate occlusionPredicate; - public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; + this.occlusionPredicate = occlusionPredicate; } @Override @@ -2172,6 +2233,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FriendlyByteBuf buf = event.getBuffer(); BlockPos pos = buf.readBlockPos(); int before = buf.readVarInt(); + 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)); + } + } if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) { return; } @@ -2345,6 +2412,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; BlockPos blockPos = buf.readBlockPos(); int state = buf.readInt(); + // 移除不透明设置 + 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); + } + } boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; Object blockState = BlockStateUtils.idToBlockState(state); @@ -3249,7 +3323,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; } } @@ -3598,35 +3672,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); @@ -3664,11 +3744,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); @@ -3681,34 +3761,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; } @@ -3728,9 +3813,16 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (cancellable.isCancelled()) { return; } - + // 不处理调试棒 + if (itemInHand.vanillaId().equals(ItemKeys.DEBUG_STICK)) { + return; + } + // 已经有过交互了 + if (serverPlayer.lastSuccessfulInteractionTick() == serverPlayer.gameTicks()) { + return; + } // 必须从网络包层面处理,否则无法获取交互的具体实体 - 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()) { @@ -3750,9 +3842,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; } } @@ -3763,11 +3858,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); @@ -3805,7 +3900,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.handlers[MEntityTypes.BLOCK_DISPLAY$registryId] = simpleAddEntityHandler(BlockDisplayPacketHandler.INSTANCE); this.handlers[MEntityTypes.TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE); this.handlers[MEntityTypes.ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE); - this.handlers[MEntityTypes.ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + this.handlers[MEntityTypes.ITEM$registryId] = simpleAddEntityHandler(ItemPacketHandler.INSTANCE); this.handlers[MEntityTypes.ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); this.handlers[MEntityTypes.GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE); this.handlers[MEntityTypes.ENDERMAN$registryId] = simpleAddEntityHandler(EndermanPacketHandler.INSTANCE); @@ -3832,7 +3927,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.handlers[MEntityTypes.TNT$registryId] = simpleAddEntityHandler(PrimedTNTPacketHandler.INSTANCE); } if (VersionHelper.isOrAbove1_20_5()) { - this.handlers[MEntityTypes.OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + this.handlers[MEntityTypes.OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(ItemPacketHandler.INSTANCE); } this.handlers[MEntityTypes.FALLING_BLOCK$registryId] = (user, event) -> { FriendlyByteBuf buf = event.getBuffer(); @@ -3875,10 +3970,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); } @@ -3891,7 +3994,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); @@ -3902,7 +4005,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/CommonItemPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java index f9b57d6d3..4e264198e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java @@ -59,4 +59,4 @@ public class CommonItemPacketHandler implements EntityPacketHandler { FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); } } -} +} \ No newline at end of file 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/network/handler/ItemPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemPacketHandler.java new file mode 100644 index 000000000..78e62a2d1 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemPacketHandler.java @@ -0,0 +1,137 @@ +package net.momirealms.craftengine.bukkit.plugin.network.handler; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; +import net.momirealms.craftengine.bukkit.entity.data.ItemEntityData; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.bukkit.world.score.BukkitTeamManager; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemSettings; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.NetworkTextReplaceContext; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; +import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; +import net.momirealms.craftengine.core.plugin.text.minimessage.CustomTagResolver; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.ArrayUtils; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.LegacyChatFormatter; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; + +public class ItemPacketHandler implements EntityPacketHandler { + public static final ItemPacketHandler INSTANCE = new ItemPacketHandler(); + + @Override + public void handleSetEntityData(Player user, ByteBufPacketEvent event) { + if (Config.disableItemOperations()) return; + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + boolean changed = false; + List packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf); + Component nameToShow = null; + LegacyChatFormatter glowColor = null; + for (int i = 0; i < packedItems.size(); i++) { + Object packedItem = packedItems.get(i); + int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); + if (entityDataId == ItemEntityData.Item.id()) { + Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); + ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack); + + // 转换为客户端侧物品 + Optional optional = BukkitItemManager.instance().s2c(itemStack, user); + if (optional.isPresent()) { + changed = true; + itemStack = optional.get(); + Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); + packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack))); + } + + // 处理 drop-display 物品设置 + // 一定要处理经历过客户端侧组件修改的物品 + Item wrappedItem = BukkitItemManager.instance().wrap(itemStack); + Optional> optionalCustomItem = wrappedItem.getCustomItem(); + String showName = null; + if (optionalCustomItem.isPresent()) { + ItemSettings settings = optionalCustomItem.get().settings(); + showName = settings.dropDisplay(); + glowColor = settings.glowColor(); + } else if (Config.enableDefaultDropDisplay()) { + showName = Config.defaultDropDisplayFormat(); + } + + // 如果设定了自定义展示名 + if (showName != null) { + PlayerOptionalContext context = NetworkTextReplaceContext.of(user, ContextHolder.builder() + .withParameter(DirectContextParameters.COUNT, itemStack.getAmount())); + Optional optionalHoverComponent = wrappedItem.hoverNameComponent(); + Component hoverComponent; + if (optionalHoverComponent.isPresent()) { + hoverComponent = optionalHoverComponent.get(); + } else { + hoverComponent = Component.translatable(itemStack.translationKey()); + } + // 展示名称为空,则显示其hover name + if (showName.isEmpty()) { + nameToShow = hoverComponent; + } + // 显示自定义格式的名字 + else { + nameToShow = AdventureHelper.miniMessage().deserialize( + showName, + ArrayUtils.appendElementToArrayTail(context.tagResolvers(), new CustomTagResolver("name", hoverComponent)) + ); + } + } + + break; + } + } + if (glowColor != null) { + Object teamByColor = BukkitTeamManager.instance().getTeamByColor(glowColor); + if (teamByColor != null) { + changed = true; + outer: { + for (int i = 0; i < packedItems.size(); i++) { + Object packedItem = packedItems.get(i); + int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); + if (entityDataId == BaseEntityData.SharedFlags.id()) { + byte flags = (Byte) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); + flags |= (byte) 0x40; + packedItems.set(i, BaseEntityData.SharedFlags.createEntityData(flags)); + break outer; + } + } + packedItems.add(BaseEntityData.SharedFlags.createEntityData((byte) 0x40)); + } + Object entityLookup = FastNMS.INSTANCE.method$ServerLevel$getEntityLookup(user.clientSideWorld().serverWorld()); + Object entity = FastNMS.INSTANCE.method$EntityLookup$get(entityLookup, id); + if (entity != null) { + user.sendPacket(FastNMS.INSTANCE.method$ClientboundSetPlayerTeamPacket$createMultiplePlayerPacket(teamByColor, List.of(FastNMS.INSTANCE.method$Entity$getUUID(entity).toString()), true), false); + } + } + } + // 添加自定义显示名 + if (nameToShow != null) { + changed = true; + packedItems.add(ItemEntityData.CustomNameVisible.createEntityData(true)); + packedItems.add(ItemEntityData.CustomName.createEntityData(Optional.of(ComponentUtils.adventureToMinecraft(nameToShow)))); + } + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java index bed116f3e..19d48a0bf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java @@ -103,7 +103,7 @@ public record VisualBlockStatePacket(int[] data) implements ModPacket { for (int i = 0; i < serverSideBlockCount; i++) { ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount); if (state.isEmpty()) continue; - mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.vanillaBlockState().registryId(); + mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.visualBlockState().registryId(); } return new VisualBlockStatePacket(mappings); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/bukkit/CraftBukkitReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/bukkit/CraftBukkitReflections.java index 06546717e..e330f24ab 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/bukkit/CraftBukkitReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/bukkit/CraftBukkitReflections.java @@ -392,4 +392,12 @@ public final class CraftBukkitReflections { public static final Field field$MinecraftMerchant$title = requireNonNull( ReflectionUtils.getDeclaredField(clazz$CraftMerchantCustom$MinecraftMerchant, CoreReflections.clazz$Component, 0) ); + + public static final Class clazz$CraftTeam = requireNonNull( + ReflectionUtils.getClazz(BukkitReflectionUtils.assembleCBClass("scoreboard.CraftTeam")) + ); + + public static final Field field$CraftTeam$team = requireNonNull( + ReflectionUtils.getDeclaredField(clazz$CraftTeam, CoreReflections.clazz$PlayerTeam, 0) + ); } 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 4bef1277b..c1f11650e 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 @@ -24,7 +24,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; -import java.util.function.Consumer; import java.util.function.Function; import static java.util.Objects.requireNonNull; @@ -2863,12 +2862,12 @@ public final class CoreReflections { ); // 1.20.5+ - public static final Constructor constructor$AttributeInstance = - ReflectionUtils.getConstructor(clazz$AttributeInstance, clazz$Holder, Consumer.class); +// public static final Constructor constructor$AttributeInstance = +// ReflectionUtils.getConstructor(clazz$AttributeInstance, clazz$Holder, Consumer.class); - public static final Method method$AttributeInstance$setBaseValue = requireNonNull( - ReflectionUtils.getMethod(clazz$AttributeInstance, void.class, double.class) - ); +// public static final Method method$AttributeInstance$setBaseValue = requireNonNull( +// ReflectionUtils.getMethod(clazz$AttributeInstance, void.class, double.class) +// ); public static final Class clazz$Rotation = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( @@ -3958,6 +3957,27 @@ public final class CoreReflections { } } + // 1.20.2+ + public static final Method method$ServerConfigurationPacketListenerImpl$startNextTask = Optional.ofNullable(clazz$ServerConfigurationPacketListenerImpl) + .map(it -> ReflectionUtils.getDeclaredMethod(it, void.class, VersionHelper.isOrAbove1_20_5() ? new String[]{"startNextTask", "o"} : new String[]{"startNextTask", "p"})) + .orElse(null); + + public static final MethodHandle methodHandle$ServerConfigurationPacketListenerImpl$startNextTask; + + static { + try { + if (VersionHelper.isOrAbove1_20_2()) { + methodHandle$ServerConfigurationPacketListenerImpl$startNextTask = + ReflectionUtils.unreflectMethod(method$ServerConfigurationPacketListenerImpl$startNextTask) + .asType(MethodType.methodType(void.class, Object.class)); + } else { + methodHandle$ServerConfigurationPacketListenerImpl$startNextTask = null; + } + } catch (IllegalAccessException e) { + throw new ReflectionInitException("Failed to initialize reflection", e); + } + } + public static final Class clazz$JoinWorldTask = MiscUtils.requireNonNullIf( ReflectionUtils.getClazz( BukkitReflectionUtils.assembleMCClass("server.network.config.JoinWorldTask") @@ -4572,4 +4592,56 @@ public final class CoreReflections { "world.level.levelgen.feature.Feature" ) ); + + public static final Class clazz$EmptyBlockGetter = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.level.BlockAccessAir", + "world.level.EmptyBlockGetter" + ) + ); + + public static final Method method$EmptyBlockGetter$values = requireNonNull( + ReflectionUtils.getStaticMethod(clazz$EmptyBlockGetter, clazz$EmptyBlockGetter.arrayType()) + ); + + public static final Object instance$EmptyBlockGetter$INSTANCE; + + static { + try { + Object[] values = (Object[]) method$EmptyBlockGetter$values.invoke(null); + instance$EmptyBlockGetter$INSTANCE = values[0]; + } catch (ReflectiveOperationException e) { + 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) + ); + + public static final Class clazz$PlayerTeam = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.scores.ScoreboardTeam", + "world.scores.PlayerTeam" + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java index a8f62e3ac..9b1e00511 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/NetworkReflections.java @@ -1087,20 +1087,31 @@ public final class NetworkReflections { ReflectionUtils.getDeclaredConstructor(clazz$ClientboundMoveEntityPacket$Pos, int.class, short.class, short.class, short.class, boolean.class) ); - // 1.20.2+ - public static final Class clazz$ServerboundClientInformationPacket = - ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundClientInformationPacket")); + // 1.20.1 + public static final Class clazz$ServerboundClientInformationPacket0 = + BukkitReflectionUtils.findReobfOrMojmapClass( + "network.protocol.game.PacketPlayInSettings", + "network.protocol.game.ServerboundClientInformationPacket" + ); + + // 1.20.1 + public static final Field field$ServerboundClientInformationPacket$language = MiscUtils.requireNonNullIf(Optional.ofNullable(clazz$ServerboundClientInformationPacket0) + .map(it -> ReflectionUtils.getDeclaredField(it, String.class, 0)) + .orElse(null), !VersionHelper.isOrAbove1_20_2()); // 1.20.2+ - public static final Constructor constructor$ServerboundClientInformationPacket = Optional.ofNullable(clazz$ServerboundClientInformationPacket) - .map(it -> ReflectionUtils.getConstructor(it, 1)) - .orElse(null); + public static final Class clazz$ServerboundClientInformationPacket1 = MiscUtils.requireNonNullIf( + ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundClientInformationPacket")), + VersionHelper.isOrAbove1_20_2() + ); // 1.20.2+ - public static final Field field$ServerboundClientInformationPacket$information = Optional.ofNullable(clazz$ServerboundClientInformationPacket) - .map(it -> ReflectionUtils.getDeclaredField(it, 0)) - .orElse(null); - + public static final Field field$ServerboundClientInformationPacket$information = MiscUtils.requireNonNullIf( + Optional.ofNullable(clazz$ServerboundClientInformationPacket1) + .map(it -> ReflectionUtils.getDeclaredField(it, CoreReflections.clazz$ClientInformation, 0)) + .orElse(null), + VersionHelper.isOrAbove1_20_2() + ); public static final Class clazz$ClientboundSetTitleTextPacket = requireNonNull( ReflectionUtils.getClazz( 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 af6789fab..d9ebbbe69 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; @@ -23,7 +25,11 @@ import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.block.BlockStateWrapper; 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; @@ -31,17 +37,18 @@ 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; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; +import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; -import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.IntIdentityList; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.*; import org.bukkit.attribute.Attribute; @@ -50,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; @@ -67,9 +75,14 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; public class BukkitServerPlayer extends Player { public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale"); + public static final Key ENTITY_CULLING_DISTANCE_SCALE = Key.of("craftengine:entity_culling_distance_scale"); + public static final Key DISPLAY_ENTITY_VIEW_DISTANCE_SCALE = Key.of("craftengine:display_entity_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 @@ -87,8 +100,7 @@ public class BukkitServerPlayer extends Player { private Reference playerRef; private Reference serverPlayerRef; // client side dimension info - private int sectionCount; - private Key clientSideDimension; + private World clientSideWorld; // check main hand/offhand interaction private int lastSuccessfulInteraction; // to prevent duplicated events @@ -114,8 +126,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 @@ -126,18 +136,38 @@ public class BukkitServerPlayer extends Player { // cooldown data private CooldownData cooldownData; // tracked chunks - private ConcurrentLong2ReferenceChainedHashTable trackedChunks; + private ConcurrentLong2ReferenceChainedHashTable trackedChunks; // entity view private Map entityTypeView; - // selected client locale + // 通过指令或api设定的语言 @Nullable private Locale selectedLocale; + // 客户端选择的语言 + private Locale clientLocale; // 存储客户端在发送停止破坏包前正在破坏的最后一个方块 private BlockPos lastStopMiningPos; // 修复连续挖掘的标志位 private boolean isHackedBreak; // 上一次停止挖掘包发出的时间 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; + // 控制展示实体可见距离 + private double displayEntityViewDistance; public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -151,6 +181,7 @@ public class BukkitServerPlayer extends Player { } } } + this.culling = new EntityCulling(this); } public void setPlayer(org.bukkit.entity.Player player) { @@ -162,6 +193,11 @@ 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_DISTANCE_SCALE), PersistentDataType.DOUBLE); + this.displayEntityViewDistance = Optional.ofNullable(player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(DISPLAY_ENTITY_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE)).orElse(1d); + 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); @@ -477,21 +513,13 @@ public class BukkitServerPlayer extends Player { } @Override - public int clientSideSectionCount() { - return sectionCount; - } - - public void setClientSideSectionCount(int sectionCount) { - this.sectionCount = sectionCount; + public World clientSideWorld() { + return this.clientSideWorld; } @Override - public Key clientSideDimension() { - return clientSideDimension; - } - - public void setClientSideDimension(Key clientSideDimension) { - this.clientSideDimension = clientSideDimension; + public void setClientSideWorld(World world) { + this.clientSideWorld = world; } public void setConnectionState(ConnectionState connectionState) { @@ -510,10 +538,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) { @@ -525,12 +554,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.prepareBoundingBox(furniture.position(), aabbs::add, true); + } + 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) { @@ -553,16 +627,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)) { + + 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.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); + } + } + } + + private void cullEntity(boolean useRayTracing, VirtualCullableObject cullableObject) { + CullingData cullingData = cullableObject.cullable.cullingData(); + if (cullingData != null) { + boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing); + // 之前可见 + if (cullableObject.isShown) { + boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing); + if (!firstPersonVisible && !thirdPersonVisible) { + cullableObject.setShown(this, false); + } + } + // 之前不可见 + else { + // 但是第一人称可见了 + if (firstPersonVisible) { + // 下次再说 + if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) { + return; + } + cullableObject.setShown(this, true); return; } - this.previousEyeLocation = eyeLocation; - this.predictNextBlockToMine(); + if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) { + // 下次再说 + if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) { + return; + } + cullableObject.setShown(this, true); + } + // 仍然不可见 } + } else { + cullableObject.setShown(this, true); } } @@ -582,17 +719,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 @@ -612,7 +744,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); @@ -646,7 +778,7 @@ public class BukkitServerPlayer extends Player { // instant break boolean custom = immutableBlockState != null; if (custom && getDestroyProgress(state, pos) >= 1f) { - BlockStateWrapper vanillaBlockState = immutableBlockState.vanillaBlockState(); + BlockStateWrapper vanillaBlockState = immutableBlockState.visualBlockState(); // if it's not an instant break on client side, we should resend level event if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) { Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( @@ -679,8 +811,7 @@ public class BukkitServerPlayer extends Player { if (VersionHelper.isOrAbove1_20_5()) { Object serverPlayer = serverPlayer(); Object attributeInstance = CoreReflections.methodHandle$ServerPlayer$getAttributeMethod.invokeExact(serverPlayer, MAttributeHolders.BLOCK_BREAK_SPEED); - Object newPacket = NetworkReflections.methodHandle$ClientboundUpdateAttributesPacket0Constructor.invokeExact(entityID(), (List) Lists.newArrayList(attributeInstance)); - sendPacket(newPacket, true); + sendPacket(FastNMS.INSTANCE.constructor$ClientboundUpdateAttributesPacket(entityId(), Lists.newArrayList(attributeInstance)), true); } else { resetEffect(MMobEffects.MINING_FATIGUE); resetEffect(MMobEffects.HASTE); @@ -691,11 +822,11 @@ public class BukkitServerPlayer extends Player { CoreReflections.constructor$AttributeModifier.newInstance(KeyUtils.toResourceLocation(Key.DEFAULT_NAMESPACE, "custom_hardness"), -9999d, CoreReflections.instance$AttributeModifier$Operation$ADD_VALUE) : CoreReflections.constructor$AttributeModifier.newInstance(UUID.randomUUID(), Key.DEFAULT_NAMESPACE + ":custom_hardness", -9999d, CoreReflections.instance$AttributeModifier$Operation$ADD_VALUE); Object attributeSnapshot = NetworkReflections.constructor$ClientboundUpdateAttributesPacket$AttributeSnapshot.newInstance(MAttributeHolders.BLOCK_BREAK_SPEED, 1d, Lists.newArrayList(attributeModifier)); - Object newPacket = NetworkReflections.constructor$ClientboundUpdateAttributesPacket1.newInstance(entityID(), Lists.newArrayList(attributeSnapshot)); + Object newPacket = NetworkReflections.constructor$ClientboundUpdateAttributesPacket1.newInstance(entityId(), Lists.newArrayList(attributeSnapshot)); sendPacket(newPacket, true); } else { - Object fatiguePacket = MobEffectUtils.createPacket(MMobEffects.MINING_FATIGUE, entityID(), (byte) 9, -1, false, false, false); - Object hastePacket = MobEffectUtils.createPacket(MMobEffects.HASTE, entityID(), (byte) 0, -1, false, false, false); + Object fatiguePacket = MobEffectUtils.createPacket(MMobEffects.MINING_FATIGUE, entityId(), (byte) 9, -1, false, false, false); + Object hastePacket = MobEffectUtils.createPacket(MMobEffects.HASTE, entityId(), (byte) 0, -1, false, false, false); sendPackets(List.of(fatiguePacket, hastePacket), true); } } @@ -735,9 +866,9 @@ public class BukkitServerPlayer extends Player { Object effectInstance = CoreReflections.method$ServerPlayer$getEffect.invoke(serverPlayer(), mobEffect); Object packet; if (effectInstance != null) { - packet = NetworkReflections.constructor$ClientboundUpdateMobEffectPacket.newInstance(entityID(), effectInstance); + packet = NetworkReflections.constructor$ClientboundUpdateMobEffectPacket.newInstance(entityId(), effectInstance); } else { - packet = NetworkReflections.constructor$ClientboundRemoveMobEffectPacket.newInstance(entityID(), mobEffect); + packet = NetworkReflections.constructor$ClientboundRemoveMobEffectPacket.newInstance(entityId(), mobEffect); } sendPacket(packet, true); } @@ -752,7 +883,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; @@ -811,7 +942,7 @@ public class BukkitServerPlayer extends Player { // for simplified adventure break, switch mayBuild temporarily if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) { // check the appearance state - if (canBreak(hitPos, customState.vanillaBlockState().literalObject())) { + if (canBreak(hitPos, customState.visualBlockState().literalObject())) { // Error might occur so we use try here try { FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true); @@ -847,7 +978,7 @@ public class BukkitServerPlayer extends Player { } private void broadcastDestroyProgress(org.bukkit.entity.Player player, BlockPos hitPos, Object blockPos, int stage) { - Object packet = FastNMS.INSTANCE.constructor$ClientboundBlockDestructionPacket(Integer.MAX_VALUE - entityID(), blockPos, stage); + Object packet = FastNMS.INSTANCE.constructor$ClientboundBlockDestructionPacket(Integer.MAX_VALUE - entityId(), blockPos, stage); for (org.bukkit.entity.Player other : player.getWorld().getPlayers()) { Location otherLocation = other.getLocation(); double d0 = (double) hitPos.x() - otherLocation.getX(); @@ -908,7 +1039,7 @@ public class BukkitServerPlayer extends Player { } @Override - public int entityID() { + public int entityId() { return platformPlayer().getEntityId(); } @@ -1180,18 +1311,21 @@ public class BukkitServerPlayer extends Player { } @Override - public ChunkStatus getTrackedChunk(long chunkPos) { + public ClientChunk getTrackedChunk(long chunkPos) { return this.trackedChunks.get(chunkPos); } @Override - public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) { + public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) { this.trackedChunks.put(chunkPos, chunkStatus); } @Override public void removeTrackedChunk(long chunkPos) { this.trackedChunks.remove(chunkPos); + if (Config.entityCullingRayTracing()) { + this.culling.removeLastVisitChunkIfMatches((int) chunkPos, (int) (chunkPos >> 32)); + } } @Override @@ -1235,7 +1369,21 @@ public class BukkitServerPlayer extends Player { @Override public Locale locale() { - return this.platformPlayer().locale(); + if (this.clientLocale != null) { + return this.clientLocale; + } else { + org.bukkit.entity.Player player = this.platformPlayer(); + if (player != null) { + return player.locale(); + } else { + return Locale.US; + } + } + } + + @Override + public void setClientLocale(Locale clientLocale) { + this.clientLocale = clientLocale; } @Override @@ -1253,4 +1401,177 @@ public class BukkitServerPlayer extends Player { platformPlayer().getPersistentDataContainer().remove(KeyUtils.toNamespacedKey(SELECTED_LOCALE_KEY)); } } + + @Override + public void setEntityCullingDistanceScale(double value) { + value = Math.min(Math.max(0.125, value), 8); + this.culling.setDistanceScale(value); + platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_DISTANCE_SCALE), PersistentDataType.DOUBLE, value); + } + + @Override + public void setDisplayEntityViewDistanceScale(double value) { + value = Math.min(Math.max(0.125, value), 8); + this.displayEntityViewDistance = value; + platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(DISPLAY_ENTITY_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value); + } + + @Override + public double displayEntityViewDistance() { + return this.displayEntityViewDistance; + } + + @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); + } + + @Override + public void giveExperienceLevels(int levels) { + platformPlayer().giveExpLevels(levels); + } + + @Override + public int getXpNeededForNextLevel() { + return platformPlayer().getExperiencePointsNeededForNextLevel(); + } + + @Override + public void setExperiencePoints(int experiencePoints) { + float xpNeededForNextLevel = this.getXpNeededForNextLevel(); + float maxProgressThreshold = (xpNeededForNextLevel - 1.0F) / xpNeededForNextLevel; + float experienceProgress = MiscUtils.clamp(experiencePoints / xpNeededForNextLevel, 0.0F, maxProgressThreshold); + platformPlayer().setExp(experienceProgress); + } + + @Override + public void setExperienceLevels(int level) { + platformPlayer().setLevel(level); + } + + @Override + public void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent) { + PlayerUtils.sendTotemAnimation(this, totem, sound, silent); + } + + @Override + public void addTrackedBlockEntities(Map renders) { + for (Map.Entry entry : renders.entrySet()) { + this.trackedBlockEntityRenderers.put(entry.getKey(), new VirtualCullableObject(entry.getValue())); + } + } + + @Override + public void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { + this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); + } + + @Override + public VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos) { + return this.trackedBlockEntityRenderers.get(blockPos); + } + + @Override + public void removeTrackedBlockEntities(Collection renders) { + for (BlockPos render : renders) { + VirtualCullableObject remove = this.trackedBlockEntityRenderers.remove(render); + if (remove != null && remove.isShown()) { + remove.cullable().hide(this); + } + } + } + + @Override + public void clearTrackedBlockEntities() { + this.trackedBlockEntityRenderers.clear(); + } + + @Override + public int clearOrCountMatchingInventoryItems(Key itemId, int count) { + Predicate predicate = nmsStack -> this.plugin.itemManager().wrap(ItemStackUtils.asCraftMirror(nmsStack)).id().equals(itemId); + Object inventory = FastNMS.INSTANCE.method$Player$getInventory(serverPlayer()); + Object inventoryMenu = FastNMS.INSTANCE.field$Player$inventoryMenu(serverPlayer()); + Object craftSlots = FastNMS.INSTANCE.method$InventoryMenu$getCraftSlots(inventoryMenu); + return FastNMS.INSTANCE.method$Inventory$clearOrCountMatchingItems(inventory, predicate, count, craftSlots); + } + + @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 05a9600e1..8ff14ab33 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) { @@ -60,7 +56,7 @@ public final class EntityUtils { Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld(); Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); for (Object pose : List.of(CoreReflections.instance$Pose$STANDING, CoreReflections.instance$Pose$CROUCHING, CoreReflections.instance$Pose$SWIMMING)) { - BlockPos pos = new BlockPos(MiscUtils.fastFloor(x), MiscUtils.fastFloor(y), MiscUtils.fastFloor(z)); + BlockPos pos = new BlockPos(MiscUtils.floor(x), MiscUtils.floor(y), MiscUtils.floor(z)); try { double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos)); if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) { @@ -77,7 +73,7 @@ public final class EntityUtils { if (!canDismount) { continue; } - if (!FastNMS.INSTANCE.checkEntityCollision(serverLevel, List.of(newAABB))) { + if (!FastNMS.INSTANCE.checkEntityCollision(serverLevel, List.of(newAABB), o -> true)) { continue; } if (VersionHelper.isFolia()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java index dc44ac1a9..ca2d08ad8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java @@ -38,15 +38,13 @@ import org.bukkit.block.data.Directional; import org.bukkit.block.data.Levelled; import org.bukkit.block.data.Lightable; import org.bukkit.block.data.type.*; +import org.bukkit.block.data.type.Observer; import org.bukkit.entity.*; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; public final class InteractUtils { private static final Map, BlockData, BlockHitResult, Boolean>> INTERACTIONS = new HashMap<>(); @@ -806,7 +804,7 @@ public final class InteractUtils { if (entity instanceof Sheep sheep && sheep.readyToBeSheared() && ArrayUtils.contains(ItemKeys.DYES, item)) { DyeColor sheepColor = sheep.getColor(); if (sheepColor != null) { - String color = sheepColor.name().toLowerCase(); + String color = sheepColor.name().toLowerCase(Locale.ROOT); return !Key.of(color + "_dye").equals(id); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java index 82b9b8113..96de3b3c8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java @@ -57,7 +57,7 @@ public final class PlayerUtils { } } - public static void sendTotemAnimation(Player player, Item totem, @Nullable SoundData sound, boolean removeSound) { + public static void sendTotemAnimation(Player player, Item totem, @Nullable SoundData sound, boolean silent) { List packets = new ArrayList<>(); try { Object totemItem = totem.getLiteralObject(); @@ -71,22 +71,22 @@ public final class PlayerUtils { Object previousOffHandItem = player.getItemInHand(InteractionHand.OFF_HAND).getLiteralObject(); if (isMainHandTotem) { packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( - player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, BukkitItemManager.instance().uniqueEmptyItem().item().getLiteralObject())) + player.entityId(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, BukkitItemManager.instance().uniqueEmptyItem().item().getLiteralObject())) )); } packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( - player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, totemItem)) + player.entityId(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, totemItem)) )); packets.add(NetworkReflections.constructor$ClientboundEntityEventPacket.newInstance(player.serverPlayer(), (byte) 35)); if (isMainHandTotem) { packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( - player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, previousMainHandItem.getLiteralObject())) + player.entityId(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, previousMainHandItem.getLiteralObject())) )); } packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( - player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, previousOffHandItem)) + player.entityId(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, previousOffHandItem)) )); - if (sound != null || removeSound) { + if (sound != null || silent) { packets.add(NetworkReflections.constructor$ClientboundStopSoundPacket.newInstance( FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item.totem.use"), CoreReflections.instance$SoundSource$PLAYERS 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 082ae0d6c..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 @@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -100,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) { @@ -153,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 { @@ -296,9 +305,12 @@ public class BukkitWorldManager implements WorldManager, Listener { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { ImmutableBlockState customState = ceSection.getBlockState(x, y, z); - if (!customState.isEmpty() && customState.vanillaBlockState() != null) { - FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().literalObject(), false); - unsaved = true; + if (!customState.isEmpty()) { + BlockStateWrapper wrapper = customState.restoreBlockState(); + if (wrapper != null) { + FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, wrapper.literalObject(), false); + unsaved = true; + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/score/BukkitTeamManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/score/BukkitTeamManager.java new file mode 100644 index 000000000..49fb993ad --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/score/BukkitTeamManager.java @@ -0,0 +1,76 @@ +package net.momirealms.craftengine.bukkit.world.score; + +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; +import net.momirealms.craftengine.core.util.LegacyChatFormatter; +import net.momirealms.craftengine.core.world.score.TeamManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.Team; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class BukkitTeamManager implements TeamManager { + private static BukkitTeamManager instance; + private final BukkitCraftEngine plugin; + protected Set colorsInUse = new HashSet<>(); + private final Map teamByColor = new EnumMap<>(LegacyChatFormatter.class); + private boolean changed = false; + + public BukkitTeamManager(BukkitCraftEngine plugin) { + this.plugin = plugin; + instance = this; + } + + public static BukkitTeamManager instance() { + return instance; + } + + @Override + public void setColorInUse(LegacyChatFormatter color) { + this.colorsInUse.add(color); + this.changed = true; + } + + @Override + public void unload() { + this.changed = !this.colorsInUse.isEmpty(); + this.colorsInUse.clear(); + this.teamByColor.clear(); + } + + @Nullable + public Object getTeamByColor(LegacyChatFormatter color) { + return this.teamByColor.get(color); + } + + @SuppressWarnings("deprecation") + @Override + public void runDelayedSyncTasks() { + if (!this.changed) { + return; + } + Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard(); + for (LegacyChatFormatter color : LegacyChatFormatter.values()) { + Team team = scoreboard.getTeam(TEAM_PREFIX + color.name().toLowerCase(Locale.ROOT)); + if (this.colorsInUse.contains(color)) { + if (team == null) { + team = scoreboard.registerNewTeam(TEAM_PREFIX + color.name().toLowerCase(Locale.ROOT)); + team.setColor(ChatColor.valueOf(color.name())); + } + try { + Object nmsTeam = CraftBukkitReflections.field$CraftTeam$team.get(team); + this.teamByColor.put(color, nmsTeam); + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Could not get nms team", e); + } + } else { + if (team != null) { + team.unregister(); + } + } + } + } +} diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index a2bfdbeef..e9929cd06 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -129,6 +129,24 @@ unset_locale: usage: - /ce feature locale unset +set_display_entity_view_distance_scale: + enable: true + permission: ce.command.admin.set_display_entity_view_distance_scale + usage: + - /ce feature display-entity-view-distance-scale set + +set_entity_culling_distance_scale: + enable: true + permission: ce.command.admin.set_entity_culling_distance_scale + usage: + - /ce feature entity-culling-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 +276,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 c5c00829e..26716aa71 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 @@ -248,6 +250,10 @@ item: default: 10000 overrides: paper: 20000 + # Toggle whether to display the names of dropped items by default and configure the display format. This setting can be overridden for individual items. + default-drop-display: + enable: false + format: "x " equipment: # The sacrificed-vanilla-armor argument determines which vanilla armor gets completely removed (loses all its trims) @@ -552,11 +558,23 @@ chunk-system: remove: [] convert: {} -#client-optimization: -# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. -# entity-culling: -# enable: false -# whitelist-entities: [] +# [Premium Exclusive] +client-optimization: + # 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: @@ -564,4 +582,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/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 7fdbbe403..b10df0501 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -509,11 +509,10 @@ items: textures: texture: minecraft:block/custom/palm_planks -items#pfence: +items#fence: default:palm_fence: material: nether_brick - data: - item-name: + data::item-name: model: type: minecraft:model path: minecraft:item/custom/palm_fence_inventory @@ -526,6 +525,7 @@ items#pfence: block: default:palm_fence default:palm_fence_post: material: nether_brick + data::item-name: model: type: minecraft:model path: minecraft:block/custom/palm_fence_post @@ -535,6 +535,7 @@ items#pfence: texture: minecraft:block/custom/palm_planks default:palm_fence_side: material: nether_brick + data::item-name: model: type: minecraft:model path: minecraft:block/custom/palm_fence_side @@ -593,6 +594,7 @@ items#button: block: default:palm_button default:palm_button_pressed: material: nether_brick + data::item-name: model: type: minecraft:model path: minecraft:block/custom/palm_button_pressed @@ -602,6 +604,7 @@ items#button: texture: minecraft:block/custom/palm_planks default:palm_button_not_pressed: material: nether_brick + data::item-name: model: type: minecraft:model path: minecraft:block/custom/palm_button_not_pressed 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..bdd8f8131 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 @@ -53,6 +53,8 @@ items: behavior: type: block_item block: + events: + - template: default:rotatable_block loot: template: default:loot_table/self settings: @@ -93,7 +95,7 @@ items: state: barrier entity-renderer: item: default:sofa - yaw: 90 + rotation: 90 facing=north,shape=straight: state: barrier entity-renderer: @@ -102,12 +104,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 +118,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 +142,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..595ed0f67 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,25 @@ items: model: minecraft:item/custom/bench behavior: type: furniture_item + rules: + ground: + rotation: four + alignment: center furniture: + events: + - template: default:rotatable_furniture_4 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..010629735 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,17 +7,35 @@ 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 + data::item-name: default:flower_basket_wall: material: nether_brick model: minecraft:item/custom/flower_basket_wall + data::item-name: default:flower_basket_ceiling: material: nether_brick model: minecraft:item/custom/flower_basket_ceiling + data::item-name: furniture: default:flower_basket: + events: + - template: default:rotatable_furniture_8 + arguments: + blacklist_variants: + - wall settings: item: default:flower_basket sounds: @@ -27,15 +45,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 +64,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 +81,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 +90,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 +106,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..8fce322a6 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,21 @@ items: model: minecraft:item/custom/wooden_chair behavior: type: furniture_item + rules: + ground: + rotation: any + alignment: any furniture: + events: + - template: default:rotatable_furniture_8 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 @@ -29,6 +32,7 @@ items: - position: 0,0,0 type: interaction blocks-building: true + invisible: true width: 0.7 height: 1.2 interactive: true 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 ed493907e..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 @@ -1,5 +1,5 @@ templates: - # pillar/log blocks + # axis based blocks default:block_state/pillar: properties: axis: @@ -85,7 +85,11 @@ templates: template: default:block_state/__leaves__ arguments: auto_state: leaves - leaves_base_model: leaves + leaves_base_model: + type: condition + condition: ${tintable:-false} + on-true: leaves + on-false: cube_all # tintable leaves block default:block_state/tintable_leaves: template: default:block_state/__leaves__ @@ -1830,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/events.yml b/common-files/src/main/resources/resources/default/configuration/templates/events.yml new file mode 100644 index 000000000..cf28a87f6 --- /dev/null +++ b/common-files/src/main/resources/resources/default/configuration/templates/events.yml @@ -0,0 +1,76 @@ +templates: + default:rotatable_block: + on: right_click + conditions: + - type: expression + expression: + functions: + - type: update_interaction_tick + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + - type: swing_hand + - type: cycle_block_property + property: facing + rules: + north: east + east: south + south: west + west: north + default:rotatable_furniture_4: + on: right_click + conditions: + - type: expression + expression: + - type: "!has_item" + functions: + - type: update_interaction_tick + - type: rotate_furniture + degree: 90 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + on-failure: + - type: rotate_furniture + degree: 180 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + on-failure: + - type: rotate_furniture + degree: 270 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + default:rotatable_furniture_8: + on: right_click + conditions: + - type: expression + expression: + - type: "!has_item" + - type: "!match_furniture_variant" + variants: ${blacklist_variants:-null} + functions: + - type: update_interaction_tick + - type: rotate_furniture + degree: 45 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + on-failure: + - type: rotate_furniture + degree: 90 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} + on-failure: + - type: rotate_furniture + degree: 135 + on-success: + - type: swing_hand + - type: play_sound + sound: ${rotate_sound:-'minecraft:block.bamboo.place'} \ No newline at end of file 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 99a366d26..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 @@ -178,9 +178,8 @@ templates: # template: default:loot_table/ore # arguments: # ore_block: the ore block - # ore_drop: the drops of the ore + # 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/resources/internal/configuration/gui.yml b/common-files/src/main/resources/resources/internal/configuration/gui.yml index 87efc97c5..7835a1919 100644 --- a/common-files/src/main/resources/resources/internal/configuration/gui.yml +++ b/common-files/src/main/resources/resources/internal/configuration/gui.yml @@ -1,3 +1,12 @@ +categories: + craftengine:all: + priority: -1000 + name: + icon: minecraft:command_block + conditions: + - type: permission + permission: craftengine.admin + all-items: true images: internal:item_browser: height: 140 diff --git a/common-files/src/main/resources/resources/internal/configuration/mappings.yml b/common-files/src/main/resources/resources/internal/configuration/mappings.yml index 3ed3c4522..09d445d22 100644 --- a/common-files/src/main/resources/resources/internal/configuration/mappings.yml +++ b/common-files/src/main/resources/resources/internal/configuration/mappings.yml @@ -4324,6 +4324,282 @@ block-state-mappings: heavy_weighted_pressure_plate[power=14]: heavy_weighted_pressure_plate[power=1] heavy_weighted_pressure_plate[power=15]: heavy_weighted_pressure_plate[power=1] + #### Lightning Rod #### + $$>=1.21.9#lightning_rod: + exposed_lightning_rod[facing=down,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=down,powered=false,waterlogged=false] + exposed_lightning_rod[facing=down,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=down,powered=false,waterlogged=true] + exposed_lightning_rod[facing=down,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=down,powered=true,waterlogged=false] + exposed_lightning_rod[facing=down,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=down,powered=true,waterlogged=true] + exposed_lightning_rod[facing=east,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=east,powered=false,waterlogged=false] + exposed_lightning_rod[facing=east,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=east,powered=false,waterlogged=true] + exposed_lightning_rod[facing=east,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=east,powered=true,waterlogged=false] + exposed_lightning_rod[facing=east,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=east,powered=true,waterlogged=true] + exposed_lightning_rod[facing=north,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=north,powered=false,waterlogged=false] + exposed_lightning_rod[facing=north,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=north,powered=false,waterlogged=true] + exposed_lightning_rod[facing=north,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=north,powered=true,waterlogged=false] + exposed_lightning_rod[facing=north,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=north,powered=true,waterlogged=true] + exposed_lightning_rod[facing=south,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=south,powered=false,waterlogged=false] + exposed_lightning_rod[facing=south,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=south,powered=false,waterlogged=true] + exposed_lightning_rod[facing=south,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=south,powered=true,waterlogged=false] + exposed_lightning_rod[facing=south,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=south,powered=true,waterlogged=true] + exposed_lightning_rod[facing=up,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=up,powered=false,waterlogged=false] + exposed_lightning_rod[facing=up,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=up,powered=false,waterlogged=true] + exposed_lightning_rod[facing=up,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=up,powered=true,waterlogged=false] + exposed_lightning_rod[facing=up,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=up,powered=true,waterlogged=true] + exposed_lightning_rod[facing=west,powered=false,waterlogged=false]: waxed_exposed_lightning_rod[facing=west,powered=false,waterlogged=false] + exposed_lightning_rod[facing=west,powered=false,waterlogged=true]: waxed_exposed_lightning_rod[facing=west,powered=false,waterlogged=true] + exposed_lightning_rod[facing=west,powered=true,waterlogged=false]: waxed_exposed_lightning_rod[facing=west,powered=true,waterlogged=false] + exposed_lightning_rod[facing=west,powered=true,waterlogged=true]: waxed_exposed_lightning_rod[facing=west,powered=true,waterlogged=true] + lightning_rod[facing=down,powered=false,waterlogged=false]: waxed_lightning_rod[facing=down,powered=false,waterlogged=false] + lightning_rod[facing=down,powered=false,waterlogged=true]: waxed_lightning_rod[facing=down,powered=false,waterlogged=true] + lightning_rod[facing=down,powered=true,waterlogged=false]: waxed_lightning_rod[facing=down,powered=true,waterlogged=false] + lightning_rod[facing=down,powered=true,waterlogged=true]: waxed_lightning_rod[facing=down,powered=true,waterlogged=true] + lightning_rod[facing=east,powered=false,waterlogged=false]: waxed_lightning_rod[facing=east,powered=false,waterlogged=false] + lightning_rod[facing=east,powered=false,waterlogged=true]: waxed_lightning_rod[facing=east,powered=false,waterlogged=true] + lightning_rod[facing=east,powered=true,waterlogged=false]: waxed_lightning_rod[facing=east,powered=true,waterlogged=false] + lightning_rod[facing=east,powered=true,waterlogged=true]: waxed_lightning_rod[facing=east,powered=true,waterlogged=true] + lightning_rod[facing=north,powered=false,waterlogged=false]: waxed_lightning_rod[facing=north,powered=false,waterlogged=false] + lightning_rod[facing=north,powered=false,waterlogged=true]: waxed_lightning_rod[facing=north,powered=false,waterlogged=true] + lightning_rod[facing=north,powered=true,waterlogged=false]: waxed_lightning_rod[facing=north,powered=true,waterlogged=false] + lightning_rod[facing=north,powered=true,waterlogged=true]: waxed_lightning_rod[facing=north,powered=true,waterlogged=true] + lightning_rod[facing=south,powered=false,waterlogged=false]: waxed_lightning_rod[facing=south,powered=false,waterlogged=false] + lightning_rod[facing=south,powered=false,waterlogged=true]: waxed_lightning_rod[facing=south,powered=false,waterlogged=true] + lightning_rod[facing=south,powered=true,waterlogged=false]: waxed_lightning_rod[facing=south,powered=true,waterlogged=false] + lightning_rod[facing=south,powered=true,waterlogged=true]: waxed_lightning_rod[facing=south,powered=true,waterlogged=true] + lightning_rod[facing=up,powered=false,waterlogged=false]: waxed_lightning_rod[facing=up,powered=false,waterlogged=false] + lightning_rod[facing=up,powered=false,waterlogged=true]: waxed_lightning_rod[facing=up,powered=false,waterlogged=true] + lightning_rod[facing=up,powered=true,waterlogged=false]: waxed_lightning_rod[facing=up,powered=true,waterlogged=false] + lightning_rod[facing=up,powered=true,waterlogged=true]: waxed_lightning_rod[facing=up,powered=true,waterlogged=true] + lightning_rod[facing=west,powered=false,waterlogged=false]: waxed_lightning_rod[facing=west,powered=false,waterlogged=false] + lightning_rod[facing=west,powered=false,waterlogged=true]: waxed_lightning_rod[facing=west,powered=false,waterlogged=true] + lightning_rod[facing=west,powered=true,waterlogged=false]: waxed_lightning_rod[facing=west,powered=true,waterlogged=false] + lightning_rod[facing=west,powered=true,waterlogged=true]: waxed_lightning_rod[facing=west,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=down,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=down,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=down,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=down,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=down,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=down,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=down,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=down,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=east,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=east,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=east,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=east,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=east,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=east,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=east,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=east,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=north,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=north,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=north,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=north,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=north,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=north,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=north,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=north,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=south,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=south,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=south,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=south,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=south,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=south,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=south,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=south,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=up,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=up,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=up,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=up,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=up,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=up,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=up,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=up,powered=true,waterlogged=true] + oxidized_lightning_rod[facing=west,powered=false,waterlogged=false]: waxed_oxidized_lightning_rod[facing=west,powered=false,waterlogged=false] + oxidized_lightning_rod[facing=west,powered=false,waterlogged=true]: waxed_oxidized_lightning_rod[facing=west,powered=false,waterlogged=true] + oxidized_lightning_rod[facing=west,powered=true,waterlogged=false]: waxed_oxidized_lightning_rod[facing=west,powered=true,waterlogged=false] + oxidized_lightning_rod[facing=west,powered=true,waterlogged=true]: waxed_oxidized_lightning_rod[facing=west,powered=true,waterlogged=true] + weathered_lightning_rod[facing=down,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=down,powered=false,waterlogged=false] + weathered_lightning_rod[facing=down,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=down,powered=false,waterlogged=true] + weathered_lightning_rod[facing=down,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=down,powered=true,waterlogged=false] + weathered_lightning_rod[facing=down,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=down,powered=true,waterlogged=true] + weathered_lightning_rod[facing=east,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=east,powered=false,waterlogged=false] + weathered_lightning_rod[facing=east,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=east,powered=false,waterlogged=true] + weathered_lightning_rod[facing=east,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=east,powered=true,waterlogged=false] + weathered_lightning_rod[facing=east,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=east,powered=true,waterlogged=true] + weathered_lightning_rod[facing=north,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=north,powered=false,waterlogged=false] + weathered_lightning_rod[facing=north,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=north,powered=false,waterlogged=true] + weathered_lightning_rod[facing=north,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=north,powered=true,waterlogged=false] + weathered_lightning_rod[facing=north,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=north,powered=true,waterlogged=true] + weathered_lightning_rod[facing=south,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=south,powered=false,waterlogged=false] + weathered_lightning_rod[facing=south,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=south,powered=false,waterlogged=true] + weathered_lightning_rod[facing=south,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=south,powered=true,waterlogged=false] + weathered_lightning_rod[facing=south,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=south,powered=true,waterlogged=true] + weathered_lightning_rod[facing=up,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=up,powered=false,waterlogged=false] + weathered_lightning_rod[facing=up,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=up,powered=false,waterlogged=true] + weathered_lightning_rod[facing=up,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=up,powered=true,waterlogged=false] + weathered_lightning_rod[facing=up,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=up,powered=true,waterlogged=true] + weathered_lightning_rod[facing=west,powered=false,waterlogged=false]: waxed_weathered_lightning_rod[facing=west,powered=false,waterlogged=false] + weathered_lightning_rod[facing=west,powered=false,waterlogged=true]: waxed_weathered_lightning_rod[facing=west,powered=false,waterlogged=true] + weathered_lightning_rod[facing=west,powered=true,waterlogged=false]: waxed_weathered_lightning_rod[facing=west,powered=true,waterlogged=false] + weathered_lightning_rod[facing=west,powered=true,waterlogged=true]: waxed_weathered_lightning_rod[facing=west,powered=true,waterlogged=true] + + #### Copper Bars #### + $$>=1.21.9#copper_bars: + copper_bars[east=false,north=false,south=false,west=false,waterlogged=false]: waxed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false] + copper_bars[east=false,north=false,south=false,west=false,waterlogged=true]: waxed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true] + copper_bars[east=false,north=false,south=false,west=true,waterlogged=false]: waxed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false] + copper_bars[east=false,north=false,south=false,west=true,waterlogged=true]: waxed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true] + copper_bars[east=false,north=false,south=true,west=false,waterlogged=false]: waxed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false] + copper_bars[east=false,north=false,south=true,west=false,waterlogged=true]: waxed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true] + copper_bars[east=false,north=false,south=true,west=true,waterlogged=false]: waxed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false] + copper_bars[east=false,north=false,south=true,west=true,waterlogged=true]: waxed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true] + copper_bars[east=false,north=true,south=false,west=false,waterlogged=false]: waxed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false] + copper_bars[east=false,north=true,south=false,west=false,waterlogged=true]: waxed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true] + copper_bars[east=false,north=true,south=false,west=true,waterlogged=false]: waxed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false] + copper_bars[east=false,north=true,south=false,west=true,waterlogged=true]: waxed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true] + copper_bars[east=false,north=true,south=true,west=false,waterlogged=false]: waxed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false] + copper_bars[east=false,north=true,south=true,west=false,waterlogged=true]: waxed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true] + copper_bars[east=false,north=true,south=true,west=true,waterlogged=false]: waxed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false] + copper_bars[east=false,north=true,south=true,west=true,waterlogged=true]: waxed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true] + copper_bars[east=true,north=false,south=false,west=false,waterlogged=false]: waxed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false] + copper_bars[east=true,north=false,south=false,west=false,waterlogged=true]: waxed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true] + copper_bars[east=true,north=false,south=false,west=true,waterlogged=false]: waxed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false] + copper_bars[east=true,north=false,south=false,west=true,waterlogged=true]: waxed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true] + copper_bars[east=true,north=false,south=true,west=false,waterlogged=false]: waxed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false] + copper_bars[east=true,north=false,south=true,west=false,waterlogged=true]: waxed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true] + copper_bars[east=true,north=false,south=true,west=true,waterlogged=false]: waxed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false] + copper_bars[east=true,north=false,south=true,west=true,waterlogged=true]: waxed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true] + copper_bars[east=true,north=true,south=false,west=false,waterlogged=false]: waxed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false] + copper_bars[east=true,north=true,south=false,west=false,waterlogged=true]: waxed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true] + copper_bars[east=true,north=true,south=false,west=true,waterlogged=false]: waxed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false] + copper_bars[east=true,north=true,south=false,west=true,waterlogged=true]: waxed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true] + copper_bars[east=true,north=true,south=true,west=false,waterlogged=false]: waxed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false] + copper_bars[east=true,north=true,south=true,west=false,waterlogged=true]: waxed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true] + copper_bars[east=true,north=true,south=true,west=true,waterlogged=false]: waxed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false] + copper_bars[east=true,north=true,south=true,west=true,waterlogged=true]: waxed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true] + exposed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false] + exposed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true] + exposed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false] + exposed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true] + exposed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false] + exposed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true] + exposed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false] + exposed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true] + exposed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false] + exposed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true] + exposed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false] + exposed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true] + exposed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false] + exposed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true] + exposed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false] + exposed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true] + exposed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false] + exposed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true] + exposed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false] + exposed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true] + exposed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false] + exposed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true] + exposed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false] + exposed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true] + exposed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false] + exposed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true] + exposed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false] + exposed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true] + exposed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false] + exposed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true] + exposed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false]: waxed_exposed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false] + exposed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true]: waxed_exposed_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true] + oxidized_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false] + oxidized_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true] + oxidized_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false] + oxidized_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true] + oxidized_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false] + oxidized_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true] + oxidized_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false] + oxidized_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true] + oxidized_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false] + oxidized_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true] + oxidized_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false] + oxidized_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true] + oxidized_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false] + oxidized_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true] + oxidized_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false] + oxidized_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true] + oxidized_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false] + oxidized_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true] + oxidized_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false] + oxidized_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true] + oxidized_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false] + oxidized_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true] + oxidized_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false] + oxidized_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true] + oxidized_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false] + oxidized_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true] + oxidized_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false] + oxidized_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true] + oxidized_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false] + oxidized_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true] + oxidized_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false]: waxed_oxidized_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false] + oxidized_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true]: waxed_oxidized_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true] + weathered_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=false,south=false,west=false,waterlogged=false] + weathered_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=false,south=false,west=false,waterlogged=true] + weathered_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=false,south=false,west=true,waterlogged=false] + weathered_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=false,south=false,west=true,waterlogged=true] + weathered_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=false,south=true,west=false,waterlogged=false] + weathered_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=false,south=true,west=false,waterlogged=true] + weathered_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=false,south=true,west=true,waterlogged=false] + weathered_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=false,south=true,west=true,waterlogged=true] + weathered_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=true,south=false,west=false,waterlogged=false] + weathered_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=true,south=false,west=false,waterlogged=true] + weathered_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=true,south=false,west=true,waterlogged=false] + weathered_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=true,south=false,west=true,waterlogged=true] + weathered_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=true,south=true,west=false,waterlogged=false] + weathered_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=true,south=true,west=false,waterlogged=true] + weathered_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=false,north=true,south=true,west=true,waterlogged=false] + weathered_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=false,north=true,south=true,west=true,waterlogged=true] + weathered_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=false,south=false,west=false,waterlogged=false] + weathered_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=false,south=false,west=false,waterlogged=true] + weathered_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=false,south=false,west=true,waterlogged=false] + weathered_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=false,south=false,west=true,waterlogged=true] + weathered_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=false,south=true,west=false,waterlogged=false] + weathered_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=false,south=true,west=false,waterlogged=true] + weathered_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=false,south=true,west=true,waterlogged=false] + weathered_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=false,south=true,west=true,waterlogged=true] + weathered_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=true,south=false,west=false,waterlogged=false] + weathered_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=true,south=false,west=false,waterlogged=true] + weathered_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=true,south=false,west=true,waterlogged=false] + weathered_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=true,south=false,west=true,waterlogged=true] + weathered_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=true,south=true,west=false,waterlogged=false] + weathered_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=true,south=true,west=false,waterlogged=true] + weathered_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false]: waxed_weathered_copper_bars[east=true,north=true,south=true,west=true,waterlogged=false] + weathered_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true]: waxed_weathered_copper_bars[east=true,north=true,south=true,west=true,waterlogged=true] + + #### Copper Chain #### + $$>=1.21.9#copper_chain: + copper_chain[axis=x,waterlogged=false]: waxed_copper_chain[axis=x,waterlogged=false] + copper_chain[axis=x,waterlogged=true]: waxed_copper_chain[axis=x,waterlogged=true] + copper_chain[axis=y,waterlogged=false]: waxed_copper_chain[axis=y,waterlogged=false] + copper_chain[axis=y,waterlogged=true]: waxed_copper_chain[axis=y,waterlogged=true] + copper_chain[axis=z,waterlogged=false]: waxed_copper_chain[axis=z,waterlogged=false] + copper_chain[axis=z,waterlogged=true]: waxed_copper_chain[axis=z,waterlogged=true] + exposed_copper_chain[axis=x,waterlogged=false]: waxed_exposed_copper_chain[axis=x,waterlogged=false] + exposed_copper_chain[axis=x,waterlogged=true]: waxed_exposed_copper_chain[axis=x,waterlogged=true] + exposed_copper_chain[axis=y,waterlogged=false]: waxed_exposed_copper_chain[axis=y,waterlogged=false] + exposed_copper_chain[axis=y,waterlogged=true]: waxed_exposed_copper_chain[axis=y,waterlogged=true] + exposed_copper_chain[axis=z,waterlogged=false]: waxed_exposed_copper_chain[axis=z,waterlogged=false] + exposed_copper_chain[axis=z,waterlogged=true]: waxed_exposed_copper_chain[axis=z,waterlogged=true] + oxidized_copper_chain[axis=x,waterlogged=false]: waxed_oxidized_copper_chain[axis=x,waterlogged=false] + oxidized_copper_chain[axis=x,waterlogged=true]: waxed_oxidized_copper_chain[axis=x,waterlogged=true] + oxidized_copper_chain[axis=y,waterlogged=false]: waxed_oxidized_copper_chain[axis=y,waterlogged=false] + oxidized_copper_chain[axis=y,waterlogged=true]: waxed_oxidized_copper_chain[axis=y,waterlogged=true] + oxidized_copper_chain[axis=z,waterlogged=false]: waxed_oxidized_copper_chain[axis=z,waterlogged=false] + oxidized_copper_chain[axis=z,waterlogged=true]: waxed_oxidized_copper_chain[axis=z,waterlogged=true] + weathered_copper_chain[axis=x,waterlogged=false]: waxed_weathered_copper_chain[axis=x,waterlogged=false] + weathered_copper_chain[axis=x,waterlogged=true]: waxed_weathered_copper_chain[axis=x,waterlogged=true] + weathered_copper_chain[axis=y,waterlogged=false]: waxed_weathered_copper_chain[axis=y,waterlogged=false] + weathered_copper_chain[axis=y,waterlogged=true]: waxed_weathered_copper_chain[axis=y,waterlogged=true] + weathered_copper_chain[axis=z,waterlogged=false]: waxed_weathered_copper_chain[axis=z,waterlogged=false] + weathered_copper_chain[axis=z,waterlogged=true]: waxed_weathered_copper_chain[axis=z,waterlogged=true] + + #### Copper Lantern #### + $$>=1.21.9#copper_lantern: + copper_lantern[hanging=false,waterlogged=false]: waxed_copper_lantern[hanging=false,waterlogged=false] + copper_lantern[hanging=false,waterlogged=true]: waxed_copper_lantern[hanging=false,waterlogged=true] + copper_lantern[hanging=true,waterlogged=false]: waxed_copper_lantern[hanging=true,waterlogged=false] + copper_lantern[hanging=true,waterlogged=true]: waxed_copper_lantern[hanging=true,waterlogged=true] + exposed_copper_lantern[hanging=false,waterlogged=false]: waxed_exposed_copper_lantern[hanging=false,waterlogged=false] + exposed_copper_lantern[hanging=false,waterlogged=true]: waxed_exposed_copper_lantern[hanging=false,waterlogged=true] + exposed_copper_lantern[hanging=true,waterlogged=false]: waxed_exposed_copper_lantern[hanging=true,waterlogged=false] + exposed_copper_lantern[hanging=true,waterlogged=true]: waxed_exposed_copper_lantern[hanging=true,waterlogged=true] + oxidized_copper_lantern[hanging=false,waterlogged=false]: waxed_oxidized_copper_lantern[hanging=false,waterlogged=false] + oxidized_copper_lantern[hanging=false,waterlogged=true]: waxed_oxidized_copper_lantern[hanging=false,waterlogged=true] + oxidized_copper_lantern[hanging=true,waterlogged=false]: waxed_oxidized_copper_lantern[hanging=true,waterlogged=false] + oxidized_copper_lantern[hanging=true,waterlogged=true]: waxed_oxidized_copper_lantern[hanging=true,waterlogged=true] + weathered_copper_lantern[hanging=false,waterlogged=false]: waxed_weathered_copper_lantern[hanging=false,waterlogged=false] + weathered_copper_lantern[hanging=false,waterlogged=true]: waxed_weathered_copper_lantern[hanging=false,waterlogged=true] + weathered_copper_lantern[hanging=true,waterlogged=false]: waxed_weathered_copper_lantern[hanging=true,waterlogged=false] + weathered_copper_lantern[hanging=true,waterlogged=true]: waxed_weathered_copper_lantern[hanging=true,waterlogged=true] + #### Corals #### # Coral blocks are ideal for creating water blocks or wall-mounted blocks. But you have to sacrifice its dry appearance. # dead_brain_coral[waterlogged=false]: brain_coral[waterlogged=false] diff --git a/common-files/src/main/resources/resources/internal/configuration/translations.yml b/common-files/src/main/resources/resources/internal/configuration/translations.yml index af4c34ee5..6c1c57b0d 100644 --- a/common-files/src/main/resources/resources/internal/configuration/translations.yml +++ b/common-files/src/main/resources/resources/internal/configuration/translations.yml @@ -12,6 +12,7 @@ translations: internal.cooking_info: Recipe Information internal.cooking_info.0: 'Time: ticks' internal.cooking_info.1: 'Experience: ' + category.all.name: 'All Items' zh_cn: internal.next_page: 下一页 internal.previous_page: 上一页 @@ -25,6 +26,7 @@ translations: internal.cooking_info: 配方信息 internal.cooking_info.0: '时间: 刻' internal.cooking_info.1: '经验: ' + category.all.name: '全部物品' de: internal.next_page: Nächste Seite internal.previous_page: Vorherige Seite 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 761e46f38..edac2828b 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,11 @@ 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.display_entity_view_distance_scale.set.success: "Display entity view distance scale updated to for " +command.entity_culling_distance_scale.set.success: "Entity culling 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." @@ -87,7 +104,34 @@ warning.config.type.quaternionf: "Issue found in file - Failed t warning.config.type.vector3f: "Issue found in file - Failed to load '': Cannot cast '' to Vector3f type for option ''." warning.config.type.vec3d: "Issue found in file - Failed to load '': Cannot cast '' to Vec3d type for option ''." 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'." @@ -127,6 +171,7 @@ warning.config.condition.is_null.missing_argument: "Issue found in file warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" warning.config.condition.on_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'on_cooldown' condition." +warning.config.condition.inventory_has_item.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'inventory_has_item' condition." warning.config.structure.not_section: "Issue found in file - The config '' is expected to be a config section while it's actually a(n) ''." warning.config.image.duplicate: "Issue found in file - Duplicated image ''. Please check if there is the same configuration in other files." warning.config.image.missing_height: "Issue found in file - The image '' is missing the required 'height' argument." @@ -159,6 +204,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." @@ -172,6 +218,10 @@ warning.config.translation.unknown_locale: "Issue found in file warning.config.template.duplicate: "Issue found in file - Duplicated template ''. Please check if there is the same configuration in other files." warning.config.template.invalid: "Issue found in file - The config '' is using an invalid template ''." warning.config.template.argument.self_increase_int.invalid_range: "Issue found in file - The template '' is using a 'from' '' larger than 'to' '' in 'self_increase_int' argument." +warning.config.template.argument.to_upper_case.invalid_locale: "Issue found in file - The template '' is using an invalid locale '' in 'to_upper_case' argument." +warning.config.template.argument.to_upper_case.missing_value: "Issue found in file - The template '' is missing the required 'value' argument for 'to_upper_case' argument." +warning.config.template.argument.to_lower_case.invalid_locale: "Issue found in file - The template '' is using an invalid locale '' in 'to_lower_case' argument." +warning.config.template.argument.to_lower_case.missing_value: "Issue found in file - The template '' is missing the required 'value' argument for 'to_lower_case' argument." warning.config.template.argument.list.invalid_type: "Issue found in file - The template '' is using a 'list' argument which expects a 'List' as argument while the input argument is a(n) ''." warning.config.template.argument.missing_value: "Issue found in file - The config '' is missing the template argument for ''. Please use the arguments option to configure or set a default value for this parameter." warning.config.vanilla_loot.missing_type: "Issue found in file - The vanilla loot '' is missing the required 'type' argument." @@ -183,11 +233,17 @@ 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.element.text_display.missing_text: "Issue found in file - The furniture '' is missing the required 'text' argument for 'text_display' element." +warning.config.furniture.element.item.missing_item: "Issue found in file - The furniture '' is missing the required 'item' argument for 'item' element." +warning.config.furniture.element.armor_stand.missing_item: "Issue found in file - The furniture '' is missing the required 'item' argument for 'armor_stand' 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 ''." +warning.config.furniture.behavior.missing_type: "Issue found in file - The furniture '' is missing the required 'type' argument for behavior." +warning.config.furniture.behavior.invalid_type: "Issue found in file - The furniture '' is using an invalid behavior type ''." warning.config.item.duplicate: "Issue found in file - Duplicated item ''. Please check if there is the same configuration in other files." warning.config.item.settings.unknown: "Issue found in file - The item '' is using an unknown setting type ''." warning.config.item.settings.invulnerable.invalid_damage_source: "Issue found in file - The item '' is using an unknown damage source ''. Allowed sources: []." @@ -297,6 +353,8 @@ warning.config.block.state.entity_renderer.item_display.missing_item: "I warning.config.block.state.entity_renderer.text_display.missing_text: "Issue found in file - The block '' is missing the required 'text' argument for 'text_display' entity renderer." warning.config.block.state.entity_renderer.better_model.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'better_model' entity renderer." warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." +warning.config.block.state.entity_renderer.armor_stand.missing_item: "Issue found in file - The block '' is missing the required 'item' argument for 'armor_stand' entity renderer." +warning.config.block.state.entity_renderer.item.missing_item: "Issue found in file - The block '' is missing the required 'item' argument for 'item' entity renderer." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." warning.config.block.state.invalid_auto_state: "Issue found in file - The block '' is using an invalid auto-state ''. Allowed values: []." @@ -492,6 +550,10 @@ warning.config.function.if_else.missing_rules: "Issue found in file Issue found in file - The config '' is missing the required 'properties' argument for 'update_block_property' function." warning.config.function.transform_block.missing_block: "Issue found in file - The config '' is missing the required 'block' argument for 'transform_block' function." warning.config.function.cycle_block_property.missing_property: "Issue found in file - The config '' is missing the required 'property' argument for 'cycle_block_property' function." +warning.config.function.set_exp.missing_count: "Issue found in file - The config '' is missing the required 'count' argument for 'set_exp' function." +warning.config.function.set_level.missing_count: "Issue found in file - The config '' is missing the required 'count' argument for 'set_level' function." +warning.config.function.play_totem_animation.missing_item: "Issue found in file - The config '' is missing the required 'item' argument for 'play_totem_animation' function." +warning.config.function.clear_item.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'clear_item' function." warning.config.selector.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for selector." warning.config.selector.invalid_type: "Issue found in file - The config '' is using an invalid selector type ''." warning.config.selector.invalid_target: "Issue found in file - The config '' is using an invalid selector target ''." 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 3de669d54..5ea020fd7 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: "资源包重新加载完成. 耗时 毫秒" @@ -74,6 +88,9 @@ command.send_resource_pack.success.multiple: "发送资源包给 command.locale.set.failure: "区域设置格式无效: " command.locale.set.success: "已为 更新选定区域设置为 " command.locale.unset.success: "已清除 的选定区域设置" +command.display_entity_view_distance_scale.set.success: "已为 的展示实体可见距离百分比设置为 " +command.entity_culling_distance_scale.set.success: "已为 的实体剔除距离百分比设置为 " +command.entity_culling.toggle.success: "已为 的实体剔除状态设置为 " warning.network.resource_pack.unverified_uuid: "玩家 使用未经服务器验证的 UUID () 尝试请求获取资源包" warning.config.pack.duplicated_files: "发现重复文件 请通过 config.yml 的 'resource-pack.duplicated-files-handler' 部分解决" warning.config.yaml.duplicated_key: "在文件 发现问题 - 在第行发现重复的键 '', 这可能会导致一些意料之外的问题" @@ -87,7 +104,34 @@ warning.config.type.quaternionf: "在文件 发现问题 - 无 warning.config.type.vector3f: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为三维向量类型 (选项 '')" warning.config.type.vec3d: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度浮点数三维向量类型 (选项 '')" warning.config.type.map: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为映射类型 (选项 '')" +warning.config.type.aabb: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为轴对齐包围盒类型 (选项 '')" 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: "在文件 发现问题 - 配置项 '' 缺少数字参数" @@ -127,6 +171,7 @@ warning.config.condition.is_null.missing_argument: "在文件 warning.config.condition.hand.missing_hand: "在文件 发现问题 - 配置项 '' 缺少 'hand' 条件必需的 'hand' 参数" warning.config.condition.hand.invalid_hand: "在文件 发现问题 - 配置项 '' 使用了无效的 'hand' 参数 '' ('hand' 条件). 允许的手部类型: []" warning.config.condition.on_cooldown.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'on_cooldown' 条件必需的 'id' 参数" +warning.config.condition.inventory_has_item.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'inventory_has_item' 条件必需的 'id' 参数" warning.config.structure.not_section: "在文件 发现问题 - 配置项 '' 应为配置段落 但实际类型为 ''" warning.config.image.duplicate: "在文件 发现问题 - 重复的图片配置 '' 请检查其他文件中是否存在相同配置" warning.config.image.missing_height: "在文件 发现问题 - 图片 '' 缺少必需的 'height' 参数" @@ -159,6 +204,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' 参数" @@ -172,6 +218,10 @@ warning.config.translation.unknown_locale: "在文件 发现问 warning.config.template.duplicate: "在文件 发现问题 - 重复的模板 '' 请检查其他文件中是否存在相同配置" warning.config.template.invalid: "在文件 发现问题 - 配置项 '' 使用了无效的模板 ''" warning.config.template.argument.self_increase_int.invalid_range: "在文件 发现问题 - 模板 '' 在 'self_increase_int' 参数中使用了一个起始值 '' 大于终止值 ''" +warning.config.template.argument.to_upper_case.invalid_locale: "在文件 发现问题 - 模板 '' 在 'to_upper_case' 参数中使用了无效的区域设置 ''" +warning.config.template.argument.to_upper_case.missing_value: "在文件 发现问题 - 模板 '' 缺少 'to_upper_case' 参数所需的 'value' 参数" +warning.config.template.argument.to_lower_case.invalid_locale: "在文件 发现问题 - 模板 '' 在 'to_lower_case' 参数中使用了无效的区域设置 ''" +warning.config.template.argument.to_lower_case.missing_value: "在文件 发现问题 - 模板 '' 缺少 'to_lower_case' 参数所需的 'value' 参数" warning.config.template.argument.list.invalid_type: "在文件 发现问题 - 模板 '' 的 'list' 参数需要列表类型 但输入参数类型为 ''" warning.config.template.argument.missing_value: "在文件 发现问题 - 配置项 '' 缺少了 '' 必要的模板参数值. 请使用 arguments 选项进行配置或为此参数设定默认值" warning.config.vanilla_loot.missing_type: "在文件 发现问题 - 原版战利品 '' 缺少必需的 'type' 参数" @@ -183,11 +233,17 @@ 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.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的元素类型 ''" +warning.config.furniture.element.item_display.missing_item: "在文件 发现问题 - 家具 '' 的 'item_display' 元素缺少必需的 'item' 参数" +warning.config.furniture.element.text_display.missing_text: "在文件 发现问题 - 家具 '' 的 'text_display' 元素缺少必需的 'text' 参数" +warning.config.furniture.element.item.missing_item: "在文件 发现问题 - 家具 '' 的 'item' 元素缺少必需的 'item' 参数" +warning.config.furniture.element.armor_stand.missing_item: "在文件 发现问题 - 家具 '' 的 'armor_stand' 元素缺少必需的 'item' 参数" warning.config.furniture.settings.unknown: "在文件 发现问题 - 家具 '' 使用了未知的设置类型 ''" warning.config.furniture.hitbox.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的碰撞箱类型 ''" warning.config.furniture.hitbox.custom.invalid_entity: "在文件 发现问题 - 家具 '' 的自定义碰撞箱使用了无效的实体类型 ''" +warning.config.furniture.behavior.missing_type: "在文件 发现问题 - 家具 '' 的行为配置缺少必需的 'type' 参数" +warning.config.furniture.behavior.invalid_type: "在文件 发现问题 - 家具 '' 使用了无效的行为类型 ''" warning.config.item.duplicate: "在文件 发现问题 - 重复的物品 '' 请检查其他文件中是否存在相同配置" warning.config.item.settings.unknown: "在文件 发现问题 - 物品 '' 使用了未知的设置类型 ''" warning.config.item.settings.invulnerable.invalid_damage_source: "在文件 发现问题 - 物品 '' 物品使用了未知的伤害来源类型 '' 允许的来源: []" @@ -297,6 +353,8 @@ warning.config.block.state.entity_renderer.item_display.missing_item: " warning.config.block.state.entity_renderer.text_display.missing_text: "在文件 发现问题 - 方块 '' 缺少 'text_display' 实体渲染器所需的 'text' 参数" warning.config.block.state.entity_renderer.better_model.missing_model: "在文件 发现问题 - 方块 '' 缺少 'better_model' 实体渲染器所需的 'model' 参数" warning.config.block.state.entity_renderer.model_engine.missing_model: "在文件 发现问题 - 方块 '' 缺少 'model_engine' 实体渲染器所需的 'model' 参数" +warning.config.block.state.entity_renderer.armor_stand.missing_item: "在文件 发现问题 - 方块 '' 缺少 'armor_stand' 实体渲染器所需的 'item' 参数" +warning.config.block.state.entity_renderer.item.missing_item: "在文件 发现问题 - 方块 '' 缺少 'item' 实体渲染器所需的 'item' 参数" warning.config.block.state.variant.invalid_appearance: "在文件 发现问题 - 方块 '' 的变体 '' 使用了不存在的 appearance ''" warning.config.block.state.invalid_vanilla: "在文件 发现问题 - 方块 '' 使用了无效的原版方块状态 ''" warning.config.block.state.invalid_auto_state: "在文件 发现问题 - 方块 '' 使用了无效的自动状态 ''. 允许的值: []" @@ -388,6 +446,7 @@ warning.config.loot_table.function.apply_bonus.missing_enchantment: "在 warning.config.loot_table.function.apply_bonus.missing_formula: "在文件 发现问题 - '' 的战利品表配置错误 'apply_bonus' 函数缺少必需的 'formula' 参数" warning.config.loot_table.function.drop_exp.missing_count: "在文件 发现问题 - '' 的战利品表配置错误 'drop_exp' 函数缺少必需的 'count' 参数" warning.config.loot_table.function.set_count.missing_count: "在文件 发现问题 - '' 的战利品表配置错误 'set_count' 函数缺少必需的 'count' 参数" +warning.config.loot_table.function.apply_data.missing_data: "在文件 发现问题 - '' 的战利品表配置错误 'apply_data' 函数缺少必需的 'data' 参数" warning.config.loot_table.entry.missing_type: "在文件 发现问题 - '' 的战利品表配置错误 某个条目缺少必需的 'type' 参数" warning.config.loot_table.entry.invalid_type: "在文件 发现问题 - '' 的战利品表配置错误 某个条目使用了无效的条目类型 ''" warning.config.loot_table.entry.exp.missing_count: "在文件 发现问题 - '' 的战利品表配置错误 'exp' 条目缺少必需的 'count' 参数" @@ -491,6 +550,10 @@ warning.config.function.if_else.missing_rules: "在文件 发现 warning.config.function.update_block_property.missing_properties: "在文件 发现问题 - 配置项 '' 缺少 'update_block_property' 函数所需的 'properties' 参数" warning.config.function.transform_block.missing_block: "在文件 发现问题 - 配置项 '' 缺少 'transform_block' 函数所需的 'block' 参数" warning.config.function.cycle_block_property.missing_property: "在文件 发现问题 - 配置项 '' 缺少 'cycle_block_property' 函数所需的 'property' 参数" +warning.config.function.set_exp.missing_count: "在文件 发现问题 - 配置项 '' 缺少 'set_exp' 函数所需的 'count' 参数" +warning.config.function.set_level.missing_count: "在文件 发现问题 - 配置项 '' 缺少 'set_level' 函数所需的 'count' 参数" +warning.config.function.play_totem_animation.missing_item: "在文件 发现问题 - 配置项 '' 缺少 'play_totem_animation' 函数所需的 'item' 参数" +warning.config.function.clear_item.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'clear_item' 函数所需的 'id' 参数" warning.config.selector.missing_type: "在文件 发现问题 - 配置项 '' 缺少选择器必需的 'type' 参数" warning.config.selector.invalid_type: "在文件 发现问题 - 配置项 '' 使用了无效的选择器类型 ''" warning.config.selector.invalid_target: "在文件 发现问题 - 配置项 '' 使用了无效的选择器目标 ''" @@ -509,4 +572,4 @@ warning.config.resource_pack.invalid_overlay_format: "在 config.yml 的 warning.config.equipment.duplicate: "在文件 发现问题 - 重复的装备配置 ''. 请检查其他文件中是否存在相同配置" warning.config.equipment.missing_type: "在文件 发现问题 - 装备 '' 缺少必需的 'type' 参数" warning.config.equipment.invalid_type: "在文件 发现问题 - 装备 '' 使用了无效的 'type' 参数" -warning.config.equipment.invalid_sacrificed_armor: "在 config.yml 的 'equipment.sacrificed-vanilla-armor' 处发现问题 - 无效的原版盔甲类型 ''" +warning.config.equipment.invalid_sacrificed_armor: "在 config.yml 的 'equipment.sacrificed-vanilla-armor' 处发现问题 - 无效的原版盔甲类型 ''" \ No newline at end of file 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 66abb09a2..cc1b2fff5 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,8 @@ 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.Glowing; +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 +55,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获取自定义方块 @@ -85,6 +89,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 临时存储哪些视觉方块被使用了 protected final Set tempVisualBlockStatesInUse = new HashSet<>(); protected final Set tempVisualBlocksInUse = new HashSet<>(); + // 能遮挡视线的方块 + protected final boolean[] viewBlockingBlocks; // 声音映射表,和使用了哪些视觉方块有关 protected Map soundReplacements = Map.of(); // 是否使用了透明方块模型 @@ -102,6 +108,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount]; this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates); this.blockStateMappingParser = new BlockStateMappingParser(); + this.viewBlockingBlocks = new boolean[vanillaBlockStateCount + customBlockCount]; Arrays.fill(this.blockStateMappings, -1); } @@ -232,6 +239,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List> behaviorConfig); + public boolean isViewBlockingBlock(int stateId) { + return this.viewBlockingBlocks[stateId]; + } + protected abstract void updateTags(); protected abstract boolean isVanillaBlock(Key id); @@ -251,6 +262,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() { @@ -262,6 +274,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<>(); @@ -292,6 +314,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(); } @@ -321,6 +344,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); } @@ -593,7 +621,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.arrangeModelForStateAndVerify(visualBlockState, parseBlockModel(modelConfig)); } } - BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))); + BlockStateAppearance blockStateAppearance = new BlockStateAppearance( + visualBlockState, + parseBlockEntityRender(appearanceSection.get("entity-renderer")), + parseCullingData(appearanceSection.get("entity-culling")) + ); appearances.put(appearanceName, blockStateAppearance); if (anyAppearance == null) { anyAppearance = blockStateAppearance; @@ -631,7 +663,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem continue; } for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setVanillaBlockState(appearance.blockState()); + possibleState.setVisualBlockState(appearance.blockState()); + possibleState.setCullingData(appearance.cullingData()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -650,11 +683,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } state.setBehavior(blockBehavior); int internalId = state.customBlockState().registryId(); - BlockStateWrapper visualState = state.vanillaBlockState(); + BlockStateWrapper visualState = state.visualBlockState(); // 校验,为未绑定外观的强行添加外观 if (visualState == null) { visualState = anyAppearance.blockState(); - state.setVanillaBlockState(visualState); + state.setVisualBlockState(visualState); + state.setCullingData(anyAppearance.cullingData()); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); @@ -694,11 +728,28 @@ 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(); List> blockEntityElementConfigs = ResourceConfigUtils.parseConfigAsList(arguments, BlockEntityElementConfigs::fromMap); if (blockEntityElementConfigs.isEmpty()) return Optional.empty(); + for (BlockEntityElementConfig blockEntityElementConfig : blockEntityElementConfigs) { + if (blockEntityElementConfig instanceof Glowing glowing && glowing.glowColor() != null) { + AbstractBlockManager.this.plugin.teamManager().setColorInUse(glowing.glowColor()); + } + } return Optional.of(blockEntityElementConfigs.toArray(new BlockEntityElementConfig[0])); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java index 22bcf2b81..48edcfb58 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java @@ -10,21 +10,21 @@ import java.util.function.Predicate; public enum AutoStateGroup { NON_TINTABLE_LEAVES(List.of("no_tint_leaves", "leaves_no_tint", "non_tintable_leaves"), - Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES), + Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES), (w) -> !(boolean) w.getProperty("waterlogged") ), WATERLOGGED_NON_TINTABLE_LEAVES( List.of("waterlogged_no_tint_leaves", "waterlogged_leaves_no_tint", "waterlogged_non_tintable_leaves"), - Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES), + Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES), (w) -> w.getProperty("waterlogged") ), TINTABLE_LEAVES("tintable_leaves", - Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES), + Set.of(BlockKeys.OAK_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES), (w) -> !(boolean) w.getProperty("waterlogged") ), WATERLOGGED_TINTABLE_LEAVES( "waterlogged_tintable_leaves", - Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES), + Set.of(BlockKeys.OAK_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES), (w) -> w.getProperty("waterlogged") ), LEAVES("leaves", @@ -51,6 +51,7 @@ public enum AutoStateGroup { WEEPING_VINES(List.of("weeping_vines", "weeping_vine"), Set.of(BlockKeys.WEEPING_VINES), (w) -> true), TWISTING_VINES(List.of("twisting_vines", "twisting_vine"), Set.of(BlockKeys.TWISTING_VINES), (w) -> true), KELP("kelp", Set.of(BlockKeys.KELP), (w) -> true), + CHORUS("chorus", Set.of(BlockKeys.CHORUS_PLANT), (w) -> true), PRESSURE_PLATE("pressure_plate", Set.of(BlockKeys.LIGHT_WEIGHTED_PRESSURE_PLATE, BlockKeys.HEAVY_WEIGHTED_PRESSURE_PLATE), (w) -> true), SAPLING("sapling", Set.of(BlockKeys.OAK_SAPLING, BlockKeys.SPRUCE_SAPLING, BlockKeys.BIRCH_SAPLING, BlockKeys.JUNGLE_SAPLING, BlockKeys.ACACIA_SAPLING, BlockKeys.DARK_OAK_SAPLING, BlockKeys.CHERRY_SAPLING, BlockKeys.PALE_OAK_SAPLING), (w) -> true), MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true), diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index 6679672ae..59ea7c2a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -14,7 +14,6 @@ public final class BlockKeys { public static final Key TRIPWIRE = Key.of("minecraft:tripwire"); public static final Key CACTUS = Key.of("minecraft:cactus"); public static final Key POWDER_SNOW = Key.of("minecraft:powder_snow"); - // 功能方块 public static final Key CRAFTING_TABLE = Key.of("minecraft:crafting_table"); public static final Key STONECUTTER = Key.of("minecraft:stonecutter"); public static final Key CARTOGRAPHY_TABLE = Key.of("minecraft:cartography_table"); @@ -49,6 +48,8 @@ public final class BlockKeys { public static final Key WEEPING_VINES = Key.of("minecraft:weeping_vines"); public static final Key TWISTING_VINES = Key.of("minecraft:twisting_vines"); public static final Key KELP = Key.of("minecraft:kelp"); + public static final Key CHORUS_PLANT = Key.of("minecraft:chorus_plant"); + public static final Key BARRIER = Key.of("minecraft:barrier"); public static final Key CHEST = Key.of("minecraft:chest"); public static final Key BARREL = Key.of("minecraft:barrel"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java index ccc19d42e..0011324f1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java @@ -22,4 +22,8 @@ public final class BlockRegistryMirror { public static BlockStateWrapper stoneState() { return stoneState; } + + public static BlockStateWrapper[] blockStates() { + return blockStates; + } } 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 75e39df37..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,8 +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.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; import java.util.Optional; -public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer) { +public record BlockStateAppearance(BlockStateWrapper blockState, + Optional[]> blockEntityRenderer, + @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 2c837d796..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,6 +13,7 @@ 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; @@ -20,6 +21,7 @@ import net.momirealms.craftengine.core.world.World; 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.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,12 +35,16 @@ public final class ImmutableBlockState { private CompoundTag tag; private BlockStateWrapper customBlockState; - private BlockStateWrapper vanillaBlockState; + private BlockStateWrapper visualBlockState; + // 安全的,在卸载ce后还原的方块 + private BlockStateWrapper restoreBlockState; private BlockBehavior behavior; private BlockSettings settings; private BlockEntityType blockEntityType; @Nullable private BlockEntityElementConfig[] renderers; + @Nullable + private CullingData cullingData; ImmutableBlockState( Holder.Reference owner, @@ -84,6 +90,15 @@ public final class ImmutableBlockState { this.renderers = renderers; } + @Nullable + public CullingData cullingData() { + return cullingData; + } + + public void setCullingData(@Nullable CullingData cullingData) { + this.cullingData = cullingData; + } + public boolean hasBlockEntity() { return this.blockEntityType != null; } @@ -96,16 +111,30 @@ public final class ImmutableBlockState { return this.customBlockState; } + @Deprecated public BlockStateWrapper vanillaBlockState() { - return this.vanillaBlockState; + return this.visualBlockState; + } + + public BlockStateWrapper visualBlockState() { + return this.visualBlockState; + } + + @ApiStatus.Internal + public BlockStateWrapper restoreBlockState() { + return this.restoreBlockState; } public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) { this.customBlockState = customBlockState; } - public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) { - this.vanillaBlockState = vanillaBlockState; + public void setVisualBlockState(@NotNull BlockStateWrapper visualBlockState) { + this.visualBlockState = visualBlockState; + } + + public void setRestoreBlockState(BlockStateWrapper restoreBlockState) { + this.restoreBlockState = restoreBlockState; } public CompoundTag getNbtToSave() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviorFactory.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviorFactory.java index 287d7ecd4..3e2b97bad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviorFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviorFactory.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import java.util.Map; +// todo refactor this on 1.0 public interface BlockBehaviorFactory { BlockBehavior create(CustomBlock block, Map arguments); 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 4b45900a0..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,16 +2,22 @@ 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 org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental -public class ConstantBlockEntityRenderer { +public class ConstantBlockEntityRenderer implements Cullable { private final BlockEntityElement[] elements; + public final CullingData cullingData; - public ConstantBlockEntityRenderer(BlockEntityElement[] elements) { + public ConstantBlockEntityRenderer(BlockEntityElement[] elements, @Nullable CullingData cullingData) { this.elements = elements; + this.cullingData = cullingData; } + @Override public void show(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { @@ -20,6 +26,7 @@ public class ConstantBlockEntityRenderer { } } + @Override public void hide(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { @@ -47,4 +54,13 @@ public class ConstantBlockEntityRenderer { public BlockEntityElement[] elements() { return this.elements; } + + @Override + 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..380ece537 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 @@ -14,15 +14,24 @@ public abstract class BlockEntityElementConfigs { 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 final Key ARMOR_STAND = Key.of("craftengine:armor_stand"); - 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); + Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(null); + if (type == null) { + if (arguments.containsKey("text")) { + type = TEXT_DISPLAY; + } else { + type = ITEM_DISPLAY; + } + } + @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/block/parser/BlockStateParser.java b/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockStateParser.java index fd6cdd6dd..bc544e428 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockStateParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/parser/BlockStateParser.java @@ -10,10 +10,7 @@ import net.momirealms.craftengine.core.util.StringReader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import java.util.*; public final class BlockStateParser { private static final char START = '['; @@ -33,7 +30,7 @@ public final class BlockStateParser { private Property property; public BlockStateParser(String data, int cursor) { - this.reader = StringReader.simple(data.toLowerCase()); + this.reader = StringReader.simple(data.toLowerCase(Locale.ROOT)); this.reader.setCursor(cursor); this.cursor = cursor; this.replaceCursor = cursor; @@ -116,7 +113,7 @@ public final class BlockStateParser { suggestPropertyName(); return; } - if (used.contains(property.name().toLowerCase())) return; + if (used.contains(property.name().toLowerCase(Locale.ROOT))) return; used.add(input); reader.skipWhitespace(); @@ -159,7 +156,7 @@ public final class BlockStateParser { if (!reader.getRemaining().isEmpty()) return; String front = readPrefix(); for (Property p : properties) { - if (!used.contains(p.name().toLowerCase()) && p.name().toLowerCase().startsWith(input)) { + if (!used.contains(p.name().toLowerCase(Locale.ROOT)) && p.name().toLowerCase(Locale.ROOT).startsWith(input)) { this.suggestions.add(front + p.name() + EQUAL); } } @@ -172,7 +169,7 @@ public final class BlockStateParser { private void suggestValue() { for (Object val : property.possibleValues()) { - this.suggestions.add(readPrefix() + val.toString().toLowerCase()); + this.suggestions.add(readPrefix() + val.toString().toLowerCase(Locale.ROOT)); } } 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..7acf7a186 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(); @@ -25,7 +27,7 @@ public interface Entity { float yRot(); - int entityID(); + int entityId(); World world(); 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/display/TextDisplayAlignment.java b/core/src/main/java/net/momirealms/craftengine/core/entity/display/TextDisplayAlignment.java new file mode 100644 index 000000000..17c79fb26 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/display/TextDisplayAlignment.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.entity.display; + +public enum TextDisplayAlignment { + CENTER, + LEFT, + RIGHT +} + 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..3bf2300a1 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,19 +1,25 @@ package net.momirealms.craftengine.core.entity.furniture; -import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehaviorTypes; +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.entity.furniture.tick.TickingFurniture; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; +import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.world.Glowing; import org.incendo.cloud.suggestion.Suggestion; import org.joml.Vector3f; @@ -27,6 +33,18 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { // Cached command suggestions private final List cachedSuggestions = new ArrayList<>(); + protected final Int2ObjectOpenHashMap syncTickers = new Int2ObjectOpenHashMap<>(256, 0.5f); + protected final Int2ObjectOpenHashMap asyncTickers = new Int2ObjectOpenHashMap<>(256, 0.5f); + protected final TickersList syncTickingFurniture = new TickersList<>(); + protected final List pendingSyncTickingFurniture = new ArrayList<>(); + protected final TickersList asyncTickingFurniture = new TickersList<>(); + protected final List pendingAsyncTickingFurniture = new ArrayList<>(); + private boolean isTickingSyncFurniture = false; + private boolean isTickingAsyncFurniture = false; + + protected SchedulerTask syncTickTask; + protected SchedulerTask asyncTickTask; + public AbstractFurnitureManager(CraftEngine plugin) { this.plugin = plugin; this.furnitureParser = new FurnitureParser(); @@ -65,16 +83,88 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { return Collections.unmodifiableMap(this.byId); } + private void syncTick() { + this.isTickingSyncFurniture = true; + if (!this.pendingSyncTickingFurniture.isEmpty()) { + this.syncTickingFurniture.addAll(this.pendingSyncTickingFurniture); + this.pendingSyncTickingFurniture.clear(); + } + if (!this.syncTickingFurniture.isEmpty()) { + Object[] entities = this.syncTickingFurniture.elements(); + for (int i = 0, size = this.syncTickingFurniture.size(); i < size; i++) { + TickingFurniture entity = (TickingFurniture) entities[i]; + if (entity.isValid()) { + entity.tick(); + } else { + this.syncTickingFurniture.markAsRemoved(i); + this.syncTickers.remove(entity.entityId()); + } + } + this.syncTickingFurniture.removeMarkedEntries(); + } + this.isTickingSyncFurniture = false; + } + + private void asyncTick() { + this.isTickingAsyncFurniture = true; + if (!this.pendingAsyncTickingFurniture.isEmpty()) { + this.asyncTickingFurniture.addAll(this.pendingAsyncTickingFurniture); + this.pendingAsyncTickingFurniture.clear(); + } + if (!this.asyncTickingFurniture.isEmpty()) { + Object[] entities = this.asyncTickingFurniture.elements(); + for (int i = 0, size = this.asyncTickingFurniture.size(); i < size; i++) { + TickingFurniture entity = (TickingFurniture) entities[i]; + if (entity.isValid()) { + entity.tick(); + } else { + this.asyncTickingFurniture.markAsRemoved(i); + this.asyncTickers.remove(entity.entityId()); + } + } + this.asyncTickingFurniture.removeMarkedEntries(); + } + this.isTickingAsyncFurniture = false; + } + + public synchronized void addSyncFurnitureTicker(TickingFurniture ticker) { + if (this.isTickingSyncFurniture) { + this.pendingSyncTickingFurniture.add(ticker); + } else { + this.syncTickingFurniture.add(ticker); + } + } + + public synchronized void addAsyncFurnitureTicker(TickingFurniture ticker) { + if (this.isTickingAsyncFurniture) { + this.pendingAsyncTickingFurniture.add(ticker); + } else { + this.asyncTickingFurniture.add(ticker); + } + } + + @Override + public void delayedInit() { + if (this.syncTickTask == null || this.syncTickTask.cancelled()) + this.syncTickTask = CraftEngine.instance().scheduler().sync().runRepeating(this::syncTick, 1, 1); + if (this.asyncTickTask == null || this.asyncTickTask.cancelled()) + this.asyncTickTask = CraftEngine.instance().scheduler().sync().runAsyncRepeating(this::asyncTick, 1, 1); + } + + @Override + public void disable() { + if (this.syncTickTask != null && !this.syncTickTask.cancelled()) + this.syncTickTask.cancel(); + if (this.asyncTickTask != null && !this.asyncTickTask.cancelled()) + this.asyncTickTask.cancel(); + } + @Override public void unload() { 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 +197,85 @@ 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"); + + 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"); } - 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); + + Map variants = new LinkedHashMap<>(); + 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); + + // 收集颜色 + for (FurnitureElementConfig element : elements) { + if (element instanceof Glowing glowing && glowing.glowColor() != null) { + AbstractFurnitureManager.this.plugin.teamManager().setColorInUse(glowing.glowColor()); + } } - // external model providers + // 外部模型 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() + CustomFurniture furniture = CustomFurniture.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))) + .behavior(FurnitureBehaviorTypes.fromMap(ResourceConfigUtils.getAsMapOrNull(ResourceConfigUtils.get(section, "behaviors", "behavior"), "behavior"))) .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 index 19afdf32f..abfd362bf 100644 --- 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 @@ -1,44 +1,80 @@ 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 org.joml.Vector3f; import java.util.List; +import java.util.Locale; 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(); + Map variants(); - boolean isAllowedPlacement(AnchorType anchorType); + default FurnitureVariant anyVariant() { + return variants().values().iterator().next(); + } - Placement getPlacement(AnchorType anchorType); + default String anyVariantName() { + return variants().keySet().iterator().next(); + } - Placement getValidPlacement(AnchorType anchorType); + @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 { + 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 CustomFurnitureImpl.BuilderImpl(); + } interface Builder { Builder id(Key id); - Builder placement(Map placements); + Builder variants(Map variants); Builder settings(FurnitureSettings settings); @@ -46,15 +82,8 @@ public interface CustomFurniture { Builder events(Map>> events); + Builder behavior(FurnitureBehavior behavior); + 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/CustomFurnitureImpl.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurnitureImpl.java new file mode 100644 index 000000000..ca01c0538 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurnitureImpl.java @@ -0,0 +1,129 @@ +package net.momirealms.craftengine.core.entity.furniture; + +import com.google.common.collect.ImmutableSortedMap; +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 CustomFurnitureImpl implements CustomFurniture { + 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 CustomFurnitureImpl(@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 = ImmutableSortedMap.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 CustomFurniture build() { + return new CustomFurnitureImpl(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/Furniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java index e5a2bcd59..18e3d467c 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,297 @@ 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; +import java.util.concurrent.CompletableFuture; -public interface Furniture { - void initializeColliders(); +public abstract class Furniture implements Cullable { + public final CustomFurniture 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, CustomFurniture config) { + this.config = config; + this.dataAccessor = data; + this.metaDataEntity = metaDataEntity; + this.setVariantInternal(config.getVariant(data)); + } - void destroyColliders(); + public Entity metaDataEntity() { + return this.metaDataEntity; + } - void destroySeats(); + public FurnitureVariant getCurrentVariant() { + return this.currentVariant; + } - UUID uuid(); + public abstract boolean setVariant(String variantName); - int baseEntityId(); + public abstract CompletableFuture moveTo(WorldPosition position); + + protected abstract void refresh(); + + protected void clearColliders() { + if (this.colliders != null) { + for (Collider collider : this.colliders) { + collider.destroy(); + } + } + } + + protected void setVariantInternal(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; + } + } + + 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.prepareBoundingBox(position, aabbs::add, true); + } + 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) { + if (element != null) { + element.show(player); + } + } + for (FurnitureHitBox hitbox : this.hitboxes) { + if (hitbox != null) { + hitbox.show(player); + } + } + } - void setExtraData(FurnitureExtraData extraData); + @Override + public void hide(Player player) { + for (FurnitureElement element : this.elements) { + if (element != null) { + element.hide(player); + } + } + for (FurnitureHitBox hitbox : this.hitboxes) { + if (hitbox != null) { + 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 CustomFurniture 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/FurnitureDataAccessor.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureDataAccessor.java new file mode 100644 index 000000000..84357733d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureDataAccessor.java @@ -0,0 +1,138 @@ +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); + } + + @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(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..e36080e4c 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; @@ -17,30 +16,26 @@ public interface FurnitureManager extends Manageable { Key FURNITURE_EXTRA_DATA_KEY = Key.of("craftengine:furniture_extra_data"); Key FURNITURE_COLLISION = Key.of("craftengine:collision"); - String FURNITURE_ADMIN_NODE = "craftengine.furniture.admin"; - ConfigParser parser(); void initSuggestions(); Collection cachedSuggestions(); - Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound); + Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureDataAccessor extraData, boolean playSound); Optional furnitureById(Key id); 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/FurnitureSounds.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSounds.java index 4434eaed4..55584e83f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSounds.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSounds.java @@ -7,36 +7,29 @@ import java.util.Map; public class FurnitureSounds { public static final SoundData EMPTY_SOUND = new SoundData(Key.of("minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1); - public static final FurnitureSounds EMPTY = new FurnitureSounds(EMPTY_SOUND, EMPTY_SOUND, EMPTY_SOUND); + public static final FurnitureSounds EMPTY = new FurnitureSounds(EMPTY_SOUND, EMPTY_SOUND); private final SoundData breakSound; private final SoundData placeSound; - private final SoundData rotateSound; - public FurnitureSounds(SoundData breakSound, SoundData placeSound, SoundData rotateSound) { + public FurnitureSounds(SoundData breakSound, SoundData placeSound) { this.breakSound = breakSound; this.placeSound = placeSound; - this.rotateSound = rotateSound; } public static FurnitureSounds fromMap(Map map) { if (map == null) return EMPTY; return new FurnitureSounds( SoundData.create(map.getOrDefault("break", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), - SoundData.create(map.getOrDefault("place", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8), - SoundData.create(map.getOrDefault("rotate", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8) + SoundData.create(map.getOrDefault("place", "minecraft:intentionally_empty"), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_0_8) ); } public SoundData breakSound() { - return breakSound; + return this.breakSound; } public SoundData placeSound() { - return placeSound; - } - - public SoundData rotateSound() { - return rotateSound; + return this.placeSound; } } 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..c646e93b4 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/EmptyFurnitureBehavior.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.entity.furniture.behavior; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +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..aa76567e5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehavior.java @@ -0,0 +1,17 @@ +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; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +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/behavior/FurnitureBehaviorFactory.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorFactory.java new file mode 100644 index 000000000..1585fc9a9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorFactory.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.core.entity.furniture.behavior; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.Map; + +@ApiStatus.Experimental +public interface FurnitureBehaviorFactory { + + T create(Map properties); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorType.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorType.java new file mode 100644 index 000000000..0bf1ebc1d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorType.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.entity.furniture.behavior; + +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public record FurnitureBehaviorType(Key id, FurnitureBehaviorFactory factory) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorTypes.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorTypes.java new file mode 100644 index 000000000..9647f33fb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/behavior/FurnitureBehaviorTypes.java @@ -0,0 +1,35 @@ +package net.momirealms.craftengine.core.entity.furniture.behavior; + +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.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.ResourceKey; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +@ApiStatus.Experimental +public class FurnitureBehaviorTypes { + + public static FurnitureBehavior fromMap(@Nullable Map map) { + if (map == null || map.isEmpty()) return EmptyFurnitureBehavior.INSTANCE; + String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.furniture.behavior.missing_type"); + Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); + FurnitureBehaviorType furnitureBehaviorType = BuiltInRegistries.FURNITURE_BEHAVIOR_TYPE.getValue(key); + if (furnitureBehaviorType == null) { + throw new LocalizedResourceConfigException("warning.config.furniture.behavior.invalid_type", type); + } + return furnitureBehaviorType.factory().create(map); + } + + public static FurnitureBehaviorType register(Key id, FurnitureBehaviorFactory factory) { + FurnitureBehaviorType type = new FurnitureBehaviorType<>(id, factory); + ((WritableRegistry>) BuiltInRegistries.FURNITURE_BEHAVIOR_TYPE) + .register(ResourceKey.create(Registries.BLOCK_BEHAVIOR_FACTORY.location(), id), type); + return type; + } +} 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..10a7b4d09 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/element/FurnitureElementConfigs.java @@ -0,0 +1,40 @@ +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 final Key ARMOR_STAND = Key.of("craftengine:armor_stand"); + + 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(null); + if (type == null) { + if (arguments.containsKey("text")) { + type = TEXT_DISPLAY; + } else { + type = 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..319a0f6a1 --- /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 prepareBoundingBox(WorldPosition targetPos, Consumer aabbConsumer, boolean ignoreBlocksBuilding); + +} 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..8fe21e145 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/FurnitureTicker.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.entity.furniture.tick; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface FurnitureTicker { + + void tick(T furniture); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurniture.java new file mode 100644 index 000000000..b085e360d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurniture.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.entity.furniture.tick; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface TickingFurniture { + + void tick(); + + boolean isValid(); + + int entityId(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurnitureImpl.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurnitureImpl.java new file mode 100644 index 000000000..1388666e3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/tick/TickingFurnitureImpl.java @@ -0,0 +1,30 @@ +package net.momirealms.craftengine.core.entity.furniture.tick; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class TickingFurnitureImpl implements TickingFurniture { + private final T furniture; + private final FurnitureTicker ticker; + + public TickingFurnitureImpl(T furniture, FurnitureTicker ticker) { + this.furniture = furniture; + this.ticker = ticker; + } + + @Override + public void tick() { + this.ticker.tick(this.furniture); + } + + @Override + public boolean isValid() { + return this.furniture.isValid(); + } + + @Override + public int entityId() { + return this.furniture.entityId(); + } +} 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 c8088ef4f..f7f85bdf5 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 @@ -2,21 +2,23 @@ package net.momirealms.craftengine.core.entity.player; 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; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Position; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Locale; +import java.util.Map; public abstract class Player extends AbstractEntity implements NetWorkUser { private static final Key TYPE = Key.of("minecraft:player"); @@ -35,6 +37,10 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @Override public abstract Object serverPlayer(); + public abstract void setClientSideWorld(World world); + + public abstract void entityCullingTick(); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); @@ -180,11 +186,66 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract Locale locale(); + public abstract void setClientLocale(Locale clientLocale); + public abstract Locale selectedLocale(); public abstract void setSelectedLocale(@Nullable Locale locale); + public abstract void setEntityCullingDistanceScale(double value); + + public abstract void setDisplayEntityViewDistanceScale(double value); + + public abstract double displayEntityViewDistance(); + + 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); + + public abstract int getXpNeededForNextLevel(); + + public abstract void setExperiencePoints(int experiencePoints); + + public abstract void setExperienceLevels(int level); + + public abstract void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent); + + public abstract void addTrackedBlockEntities(Map renders); + + public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); + + public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos); + + public abstract void removeTrackedBlockEntities(Collection renders); + + public abstract void addTrackedFurniture(int entityId, Furniture furniture); + + public abstract void clearTrackedBlockEntities(); + + public abstract int clearOrCountMatchingInventoryItems(Key itemId, int count); + @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..6eacd6503 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 @@ -51,7 +51,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl private final ItemParser itemParser; private final EquipmentParser equipmentParser; protected final Map> externalItemSources = new HashMap<>(); - protected final Map> customItemsById = new HashMap<>(); + protected final Map> customItemsById = new LinkedHashMap<>(); protected final Map> customItemsByPath = new HashMap<>(); protected final Map> customItemTags = new HashMap<>(); protected final Map modernItemModels1_21_4 = new HashMap<>(); @@ -67,6 +67,9 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // 替代配方材料 protected final Map> ingredientSubstitutes = new HashMap<>(); + protected boolean featureFlag$keepOnDeathChance = false; + protected boolean featureFlag$destroyOnDeathChance = false; + protected AbstractItemManager(CraftEngine plugin) { super(plugin); this.itemParser = new ItemParser(); @@ -133,6 +136,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl @Override public void unload() { super.clearModelsToGenerate(); + this.clearFeatureFlags(); this.customItemsById.clear(); this.customItemsByPath.clear(); this.cachedCustomItemSuggestions.clear(); @@ -147,6 +151,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl this.ingredientSubstitutes.clear(); } + private void clearFeatureFlags() { + this.featureFlag$keepOnDeathChance = false; + this.featureFlag$destroyOnDeathChance = false; + } + @Override public Map equipments() { return Collections.unmodifiableMap(this.equipments); @@ -205,12 +214,13 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl this.cachedTotemSuggestions.add(Suggestion.suggestion(id.toString())); } // tags - Set tags = customItem.settings().tags(); + ItemSettings settings = customItem.settings(); + Set tags = settings.tags(); for (Key tag : tags) { this.customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(customItem.uniqueId()); } // ingredient substitutes - List substitutes = customItem.settings().ingredientSubstitutes(); + List substitutes = settings.ingredientSubstitutes(); if (!substitutes.isEmpty()) { for (Key key : substitutes) { if (VANILLA_ITEMS.contains(key)) { @@ -218,6 +228,15 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } } } + if (settings.keepOnDeathChance != 0) { + this.featureFlag$keepOnDeathChance = true; + } + if (settings.destroyOnDeathChance != 0) { + this.featureFlag$destroyOnDeathChance = true; + } + if (settings.glowColor != null) { + this.plugin.teamManager().setColorInUse(settings.glowColor); + } } return true; } @@ -317,6 +336,14 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return VANILLA_ITEMS.contains(item); } + public boolean featureFlag$keepOnDeathChance() { + return featureFlag$keepOnDeathChance; + } + + public boolean featureFlag$destroyOnDeathChance() { + return featureFlag$destroyOnDeathChance; + } + protected abstract CustomItem.Builder createPlatformItemBuilder(UniqueKey id, Key material, Key clientBoundMaterial); protected abstract void registerArmorTrimPattern(Collection equipments); @@ -351,6 +378,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 +400,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/ItemKeys.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java index 4242b8d6a..161ea63a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java @@ -65,6 +65,7 @@ public final class ItemKeys { public static final Key PURPLE_DYE = Key.of("minecraft:purple_dye"); public static final Key MAGENTA_DYE = Key.of("minecraft:magenta_dye"); public static final Key PINK_DYE = Key.of("minecraft:pink_dye"); + public static final Key DEBUG_STICK = Key.of("minecraft:debug_stick"); public static final Key CARROT = Key.of("minecraft:carrot"); public static final Key POTATO = Key.of("minecraft:potato"); 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 08e4310c5..ed04a975e 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; @@ -47,6 +47,12 @@ public class ItemSettings { Color dyeColor; @Nullable Color fireworkColor; + float keepOnDeathChance = 0f; + float destroyOnDeathChance = 0f; + @Nullable + String dropDisplay = Config.defaultDropDisplayFormat(); + @Nullable + LegacyChatFormatter glowColor = null; Map, Object> customData = new IdentityHashMap<>(4); private ItemSettings() {} @@ -109,7 +115,11 @@ public class ItemSettings { newSettings.dyeColor = settings.dyeColor; newSettings.fireworkColor = settings.fireworkColor; newSettings.ingredientSubstitutes = settings.ingredientSubstitutes; - newSettings.customData = settings.customData; + newSettings.keepOnDeathChance = settings.keepOnDeathChance; + newSettings.destroyOnDeathChance = settings.destroyOnDeathChance; + newSettings.glowColor = settings.glowColor; + newSettings.dropDisplay = settings.dropDisplay; + newSettings.customData = new IdentityHashMap<>(settings.customData); return newSettings; } @@ -134,6 +144,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); } @@ -225,6 +241,24 @@ public class ItemSettings { return this.compostProbability; } + public float keepOnDeathChance() { + return this.keepOnDeathChance; + } + + public float destroyOnDeathChance() { + return this.destroyOnDeathChance; + } + + @Nullable + public LegacyChatFormatter glowColor() { + return this.glowColor; + } + + @Nullable + public String dropDisplay() { + return this.dropDisplay; + } + public ItemSettings fireworkColor(Color color) { this.fireworkColor = color; return this; @@ -275,6 +309,11 @@ public class ItemSettings { return this; } + public ItemSettings dropDisplay(String showName) { + this.dropDisplay = showName; + return this; + } + public ItemSettings projectileMeta(ProjectileMeta projectileMeta) { this.projectileMeta = projectileMeta; return this; @@ -325,6 +364,21 @@ public class ItemSettings { return this; } + public ItemSettings keepOnDeathChance(float keepChance) { + this.keepOnDeathChance = keepChance; + return this; + } + + public ItemSettings destroyOnDeathChance(float destroyChance) { + this.destroyOnDeathChance = destroyChance; + return this; + } + + public ItemSettings glowColor(LegacyChatFormatter chatFormatter) { + this.glowColor = chatFormatter; + return this; + } + @FunctionalInterface public interface Modifier { @@ -355,19 +409,36 @@ public class ItemSettings { boolean bool = ResourceConfigUtils.getAsBoolean(value, "enchantable"); return settings -> settings.canEnchant(bool); })); + registerFactory("keep-on-death-chance", (value -> { + float chance = ResourceConfigUtils.getAsFloat(value, "keep-on-death-chance"); + return settings -> settings.keepOnDeathChance(MiscUtils.clamp(chance, 0, 1)); + })); + registerFactory("destroy-on-death-chance", (value -> { + float chance = ResourceConfigUtils.getAsFloat(value, "destroy-on-death-chance"); + return settings -> settings.destroyOnDeathChance(MiscUtils.clamp(chance, 0, 1)); + })); registerFactory("renameable", (value -> { boolean bool = ResourceConfigUtils.getAsBoolean(value, "renameable"); return settings -> settings.renameable(bool); })); + registerFactory("drop-display", (value -> { + if (value instanceof String name) { + return settings -> settings.dropDisplay(name); + } else { + boolean bool = ResourceConfigUtils.getAsBoolean(value, "drop-display"); + return settings -> settings.dropDisplay(bool ? "" : null); + } + })); + registerFactory("glow-color", (value -> { + LegacyChatFormatter chatFormatter = ResourceConfigUtils.getAsEnum(value, LegacyChatFormatter.class, LegacyChatFormatter.WHITE); + return settings -> settings.glowColor(chatFormatter); + })); registerFactory("anvil-repair-item", (value -> { - @SuppressWarnings("unchecked") - List> materials = (List>) value; - List anvilRepairItemList = new ArrayList<>(); - for (Map material : materials) { + List anvilRepairItemList = ResourceConfigUtils.parseConfigAsList(value, material -> { int amount = ResourceConfigUtils.getAsInt(material.getOrDefault("amount", 0), "amount"); double percent = ResourceConfigUtils.getAsDouble(material.getOrDefault("percent", 0), "percent"); - anvilRepairItemList.add(new AnvilRepairItem(MiscUtils.getAsStringList(material.get("target")), amount, percent)); - } + return new AnvilRepairItem(MiscUtils.getAsStringList(material.get("target")), amount, percent); + }); return settings -> settings.repairItems(anvilRepairItemList); })); registerFactory("fuel-time", (value -> { @@ -443,8 +514,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..57381eed6 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 @@ -3,10 +3,8 @@ package net.momirealms.craftengine.core.item.modifier; import com.google.gson.JsonElement; import net.momirealms.craftengine.core.item.*; import net.momirealms.craftengine.core.plugin.CraftEngine; -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.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.*; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; @@ -33,7 +31,7 @@ public class ComponentsModifier implements ItemDataModifier { } public List> components() { - return arguments; + return this.arguments; } private Tag parseValue(Object value) { @@ -41,7 +39,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 (Exception 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/AbstractRecipeSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java index b98b86b27..2e81fb837 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/AbstractRecipeSerializer.java @@ -133,12 +133,12 @@ public abstract class AbstractRecipeSerializer> implement return new CustomRecipeResult<>(CloneableConstantItem.of(result), recipeResult.count(), null); } - @Nullable + @NotNull protected Ingredient toIngredient(String item) { return toIngredient(List.of(item)); } - @Nullable + @NotNull protected Ingredient toIngredient(List items) { Set itemIds = new HashSet<>(); Set minecraftItemIds = new HashSet<>(); @@ -149,6 +149,9 @@ public abstract class AbstractRecipeSerializer> implement Key tag = Key.of(item.substring(1)); elements.add(new IngredientElement.Tag(tag)); List uniqueKeys = itemManager.itemIdsByTag(tag); + if (uniqueKeys.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.recipe.invalid_ingredient", item); + } itemIds.addAll(uniqueKeys); for (UniqueKey uniqueKey : uniqueKeys) { List ingredientSubstitutes = itemManager.getIngredientSubstitutes(uniqueKey.key()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java index 860d66187..f2f2a4340 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomShapelessRecipe.java @@ -37,12 +37,12 @@ public class CustomShapelessRecipe extends CustomCraftingTableRecipe { } public PlacementInfo placementInfo() { - return placementInfo; + return this.placementInfo; } @Override public List> ingredientsInUse() { - return ingredients; + return this.ingredients; } @SuppressWarnings("unchecked") 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/item/recipe/PlacementInfo.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/PlacementInfo.java index 69b8f6b43..df5c07a93 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/PlacementInfo.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/PlacementInfo.java @@ -19,7 +19,7 @@ public class PlacementInfo { IntList intList = new IntArrayList(i); for (int j = 0; j < i; j++) { Ingredient ingredient = ingredients.get(j); - if (ingredient.isEmpty()) { + if (ingredient == null || ingredient.isEmpty()) { return new PlacementInfo<>(List.of(), IntList.of()); } intList.add(j); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainderFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainderFactory.java index a52960288..e01ecae98 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainderFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainderFactory.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.item.recipe.remainder; import java.util.Map; -public interface CraftRemainderFactory { +public interface CraftRemainderFactory { - CraftRemainder create(Map args); + T create(Map args); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainders.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainders.java index ed1551cac..8bb4a868f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainders.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/CraftRemainders.java @@ -23,15 +23,15 @@ public class CraftRemainders { register(HURT_AND_BREAK, HurtAndBreakRemainder.FACTORY); } - public static void register(Key key, CraftRemainderFactory factory) { - ((WritableRegistry) BuiltInRegistries.CRAFT_REMAINDER_FACTORY) + public static void register(Key key, CraftRemainderFactory factory) { + ((WritableRegistry>) BuiltInRegistries.CRAFT_REMAINDER_FACTORY) .register(ResourceKey.create(Registries.CRAFT_REMAINDER_FACTORY.location(), key), factory); } public static CraftRemainder fromMap(Map map) { String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.settings.craft_remainder.missing_type"); Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); - CraftRemainderFactory factory = BuiltInRegistries.CRAFT_REMAINDER_FACTORY.getValue(key); + CraftRemainderFactory factory = BuiltInRegistries.CRAFT_REMAINDER_FACTORY.getValue(key); if (factory == null) { throw new LocalizedResourceConfigException("warning.config.item.settings.craft_remainder.invalid_type", type); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/FixedCraftRemainder.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/FixedCraftRemainder.java index 61302aa50..1275e7339 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/FixedCraftRemainder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/FixedCraftRemainder.java @@ -21,10 +21,10 @@ public class FixedCraftRemainder implements CraftRemainder { return (Item) CraftEngine.instance().itemManager().createWrappedItem(this.item, null); } - public static class Factory implements CraftRemainderFactory { + public static class Factory implements CraftRemainderFactory { @Override - public CraftRemainder create(Map args) { + public FixedCraftRemainder create(Map args) { Key item = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("item"), "warning.config.item.settings.craft_remainder.fixed.missing_item")); return new FixedCraftRemainder(item); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/HurtAndBreakRemainder.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/HurtAndBreakRemainder.java index 8d304a4f4..8ca7bc1b4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/HurtAndBreakRemainder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/HurtAndBreakRemainder.java @@ -28,10 +28,10 @@ public class HurtAndBreakRemainder implements CraftRemainder { } } - public static class Factory implements CraftRemainderFactory { + public static class Factory implements CraftRemainderFactory { @Override - public CraftRemainder create(Map args) { + public HurtAndBreakRemainder create(Map args) { int damage = ResourceConfigUtils.getAsInt(args.getOrDefault("damage", 1), "damage"); return new HurtAndBreakRemainder(damage); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/RecipeBasedCraftRemainder.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/RecipeBasedCraftRemainder.java index afe0b8e17..5cf66566e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/RecipeBasedCraftRemainder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/remainder/RecipeBasedCraftRemainder.java @@ -30,10 +30,10 @@ public class RecipeBasedCraftRemainder implements CraftRemainder { return this.fallback != null ? this.fallback.remainder(recipeId, item) : null; } - public static class Factory implements CraftRemainderFactory { + public static class Factory implements CraftRemainderFactory { @Override - public CraftRemainder create(Map args) { + public RecipeBasedCraftRemainder create(Map args) { Map remainders = new HashMap<>(); List remainderList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.requireNonNullOrThrow(args.get("terms"), "warning.config.item.settings.craft_remainder.recipe_based.missing_terms"), map -> { List recipes = MiscUtils.getAsStringList(map.get("recipes")).stream().map(Key::of).toList(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java index eea0f0f4b..e7badf2ff 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java @@ -39,6 +39,9 @@ public class LootConditions { register(CommonConditions.EXPRESSION, new ExpressionCondition.FactoryImpl<>()); register(CommonConditions.IS_NULL, new IsNullCondition.FactoryImpl<>()); register(CommonConditions.HAND, new HandCondition.FactoryImpl<>()); + register(CommonConditions.ON_COOLDOWN, new OnCooldownCondition.FactoryImpl<>()); + register(CommonConditions.INVENTORY_HAS_ITEM, new InventoryHasItemCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_FURNITURE_VARIANT, new MatchFurnitureVariantCondition.FactoryImpl<>()); } public static void register(Key key, ConditionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java index f07bd5d3c..f82d2a7e9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java @@ -44,7 +44,7 @@ public class LootPool { } if (this.compositeCondition.test(context)) { Consumer> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context); - int i = this.rolls.getInt(context) + MiscUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck()); + int i = this.rolls.getInt(context) + MiscUtils.floor(this.bonusRolls.getFloat(context) * context.luck()); for (int j = 0; j < i; ++j) { this.addRandomItem(createFunctionApplier(consumer, context), context); } 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..fc211d329 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 @@ -167,7 +167,7 @@ public abstract class AbstractPackManager implements PackManager { } this.initInternalData(); try (InputStream inputStream = plugin.resourceStream("internal/atlases/blocks.json")) { - this.vanillaAtlas = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject(); + this.vanillaAtlas = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject(); } catch (IOException e) { throw new RuntimeException("Failed to read internal/atlases/blocks.json", e); } @@ -221,7 +221,7 @@ public abstract class AbstractPackManager implements PackManager { private void loadModernItemModel(String path, BiConsumer callback) { try (InputStream inputStream = this.plugin.resourceStream(path)) { if (inputStream != null) { - JsonObject allModelsItems = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject(); + JsonObject allModelsItems = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject(); for (Map.Entry entry : allModelsItems.entrySet()) { if (entry.getValue() instanceof JsonObject modelJson) { callback.accept(Key.of(entry.getKey()), ModernItemModel.fromJson(modelJson)); @@ -236,7 +236,7 @@ public abstract class AbstractPackManager implements PackManager { private void loadInternalData(String path, BiConsumer callback) { try (InputStream inputStream = this.plugin.resourceStream(path)) { if (inputStream != null) { - JsonObject allModelsItems = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject(); + JsonObject allModelsItems = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject(); for (Map.Entry entry : allModelsItems.entrySet()) { if (entry.getValue() instanceof JsonObject modelJson) { callback.accept(Key.of(entry.getKey()), modelJson); @@ -251,7 +251,7 @@ public abstract class AbstractPackManager implements PackManager { private void loadInternalList(String path, Consumer callback) { try (InputStream inputStream = this.plugin.resourceStream(path)) { if (inputStream != null) { - JsonArray listJson = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonArray(); + JsonArray listJson = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonArray(); for (JsonElement element : listJson) { if (element instanceof JsonPrimitive primitiveJson) { callback.accept(Key.of("minecraft", primitiveJson.getAsString())); @@ -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) { @@ -471,6 +471,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/templates/loot_tables.yml"); plugin.saveResource("resources/default/configuration/templates/recipes.yml"); plugin.saveResource("resources/default/configuration/templates/tool_levels.yml"); + plugin.saveResource("resources/default/configuration/templates/events.yml"); plugin.saveResource("resources/default/configuration/categories.yml"); plugin.saveResource("resources/default/configuration/emoji.yml"); plugin.saveResource("resources/default/configuration/translations.yml"); @@ -695,7 +696,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 +726,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 +774,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 +793,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 +1048,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 +1115,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 +1161,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 +1176,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, @@ -1413,6 +1419,10 @@ public abstract class AbstractPackManager implements PackManager { oggToSoundEvents.put(Key.of(primitive.getAsString()), soundKey); } } else if (sound instanceof JsonObject soundObj && soundObj.has("name")) { + if (soundObj.has("type")) { + String type = soundObj.get("type").getAsString(); + if (!type.equals("file")) continue; + } String name = soundObj.get("name").getAsString(); oggToSoundEvents.put(Key.of(name), soundKey); } @@ -1767,7 +1777,7 @@ public abstract class AbstractPackManager implements PackManager { if (Files.exists(atlasPath) && Files.isRegularFile(atlasPath)) { try { previousAtlasSources = GsonHelper.readJsonFile(atlasPath).getAsJsonObject().getAsJsonArray("sources"); - } catch (Exception ignored) { + } catch (ClassCastException | IllegalStateException | IOException | JsonParseException ignored) { } } @@ -2231,7 +2241,7 @@ public abstract class AbstractPackManager implements PackManager { plugin.logger().warn("Failed to load internal/sounds.json"); return; } - soundTemplate = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject(); + soundTemplate = JsonParser.parseReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).getAsJsonObject(); } catch (IOException e) { plugin.logger().warn("Failed to load internal/sounds.json", e); return; @@ -2854,6 +2864,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/CachedConfigSection.java b/core/src/main/java/net/momirealms/craftengine/core/pack/CachedConfigSection.java index e8016ce2a..66165e422 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/CachedConfigSection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/CachedConfigSection.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.pack; import java.nio.file.Path; import java.util.Map; +import java.util.Objects; public class CachedConfigSection { private final Pack pack; @@ -10,10 +11,10 @@ public class CachedConfigSection { private final Map config; public CachedConfigSection(String prefix, Map config, Path filePath, Pack pack) { - this.config = config; - this.filePath = filePath; - this.pack = pack; - this.prefix = prefix; + this.config = Objects.requireNonNull(config); + this.filePath = Objects.requireNonNull(filePath); + this.pack = Objects.requireNonNull(pack); + this.prefix = Objects.requireNonNull(prefix); } public Map config() { 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..ecd57bc95 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; @@ -81,11 +82,11 @@ public class AlistHost implements ResourcePackHost { if (!Files.exists(cachePath) || !Files.isRegularFile(cachePath)) return; try (InputStream is = Files.newInputStream(cachePath)) { Map cache = GsonHelper.get().fromJson( - new InputStreamReader(is), + new InputStreamReader(is, StandardCharsets.UTF_8), 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..ccafa796a 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); } @@ -218,7 +219,7 @@ public class DropboxHost implements ResourcePackHost { } String credentials = this.appKey + ":" + this.appSecret; - String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()); + String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java index dec6095cd..9d59c88ac 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java @@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.util.Key; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -52,7 +53,7 @@ public class ExternalHost implements ResourcePackHost { } String uuid = Optional.ofNullable(arguments.get("uuid")).map(String::valueOf).orElse(null); if (uuid == null || uuid.isEmpty()) { - uuid = UUID.nameUUIDFromBytes(url.getBytes()).toString(); + uuid = UUID.nameUUIDFromBytes(url.getBytes(StandardCharsets.UTF_8)).toString(); } UUID hostUUID = UUID.fromString(uuid); String sha1 = arguments.getOrDefault("sha1", "").toString(); 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..4f8211779 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; @@ -49,7 +50,7 @@ public class GitLabHost implements ResourcePackHost { if (!Files.exists(cachePath) || !Files.isRegularFile(cachePath)) return; try (InputStream is = Files.newInputStream(cachePath)) { Map cache = GsonHelper.get().fromJson( - new InputStreamReader(is), + new InputStreamReader(is, StandardCharsets.UTF_8), new TypeToken>(){}.getType() ); this.url = cache.get("url"); @@ -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); @@ -154,13 +155,13 @@ public class GitLabHost implements ResourcePackHost { String filePartHeader = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath.getFileName() + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n"; - parts.add(filePartHeader.getBytes()); + parts.add(filePartHeader.getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(filePath)); - parts.add("\r\n".getBytes()); + parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); String endBoundary = "--" + boundary + "--\r\n"; - parts.add(endBoundary.getBytes()); + parts.add(endBoundary.getBytes(StandardCharsets.UTF_8)); return HttpRequest.BodyPublishers.ofByteArrays(parts); } 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..88d3d6c36 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; @@ -21,6 +22,7 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -61,7 +63,7 @@ public class LobFileHost implements ResourcePackHost { if (!Files.exists(cachePath) || !Files.isRegularFile(cachePath)) return; try (InputStream is = Files.newInputStream(cachePath)) { Map cache = GsonHelper.get().fromJson( - new InputStreamReader(is), + new InputStreamReader(is, StandardCharsets.UTF_8), new TypeToken>(){}.getType() ); this.url = cache.get("url"); @@ -70,7 +72,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()); @@ -190,8 +192,8 @@ public class LobFileHost implements ResourcePackHost { MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); try (InputStream is = Files.newInputStream(path); - DigestInputStream dis = new DigestInputStream(is, sha1Digest)) { - DigestInputStream dis2 = new DigestInputStream(dis, sha256Digest); + DigestInputStream dis = new DigestInputStream(is, sha1Digest); + DigestInputStream dis2 = new DigestInputStream(dis, sha256Digest)) { while (dis2.read() != -1) ; @@ -206,18 +208,18 @@ public class LobFileHost implements ResourcePackHost { String filePartHeader = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath.getFileName() + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n"; - parts.add(filePartHeader.getBytes()); + parts.add(filePartHeader.getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(filePath)); - parts.add("\r\n".getBytes()); + parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); String sha256Part = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"sha_256\"\r\n\r\n" + sha256Hash + "\r\n"; - parts.add(sha256Part.getBytes()); + parts.add(sha256Part.getBytes(StandardCharsets.UTF_8)); String endBoundary = "--" + boundary + "--\r\n"; - parts.add(endBoundary.getBytes()); + parts.add(endBoundary.getBytes(StandardCharsets.UTF_8)); return HttpRequest.BodyPublishers.ofByteArrays(parts); } 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..2665818ac 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,7 +8,9 @@ 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.NotNull; import java.io.FileNotFoundException; import java.io.IOException; @@ -33,7 +35,7 @@ public class OneDriveHost implements ResourcePackHost { private final String clientSecret; private final ProxySelector proxy; private final String uploadPath; - private Tuple refreshToken; + private Tuple<@NotNull String, @NotNull String, @NotNull Date> refreshToken; private String sha1; private String fileId; @@ -65,18 +67,18 @@ public class OneDriveHost implements ResourcePackHost { if (!Files.exists(cachePath) || !Files.isRegularFile(cachePath)) return; try (InputStream is = Files.newInputStream(cachePath)) { Map cache = GsonHelper.get().fromJson( - new InputStreamReader(is), + new InputStreamReader(is, StandardCharsets.UTF_8), new TypeToken>(){}.getType() ); this.refreshToken = Tuple.of( - cache.get("refresh-token"), - cache.get("access-token"), + Objects.requireNonNull(cache.get("refresh-token")), + Objects.requireNonNull(cache.get("access-token")), new Date(Long.parseLong(cache.get("refresh-token-expires-in")))); 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); @@ -187,7 +189,7 @@ public class OneDriveHost implements ResourcePackHost { if (this.refreshToken == null || this.refreshToken.mid().isEmpty() || this.refreshToken.right().before(new Date())) { try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { String formData = "client_id=" + URLEncoder.encode(this.clientId, StandardCharsets.UTF_8) + - "&client_secret=" + URLEncoder.encode(this.clientSecret, StandardCharsets.UTF_8) + + "&client_secret=" + URLEncoder.encode(Objects.requireNonNull(this.clientSecret), StandardCharsets.UTF_8) + "&refresh_token=" + URLEncoder.encode(this.refreshToken.left(), StandardCharsets.UTF_8) + "&grant_type=refresh_token" + "&scope=Files.ReadWrite.All+offline_access"; 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..74aa8fc9b 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,15 +34,17 @@ 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; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -public class SelfHostHttpServer { +public final 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; @@ -82,6 +86,12 @@ public class SelfHostHttpServer { private EventLoopGroup workerGroup; private Channel serverChannel; + private SelfHostHttpServer() { + if (instance != null) { + throw new IllegalStateException("SelfHostHttpServer is already initialized."); + } + } + public static SelfHostHttpServer instance() { if (instance == null) { instance = new SelfHostHttpServer(); @@ -97,13 +107,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 +173,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 +226,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 +238,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 +318,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 +367,7 @@ public class SelfHostHttpServer { } @Nullable - public ResourcePackDownloadData generateOneTimeUrl() { + public ResourcePackDownloadData generateOneTimeUrl(UUID user) { if (this.resourcePackBytes == null) return null; if (!this.useToken) { @@ -356,7 +375,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/pack/model/special/ShulkerBoxSpecialModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/special/ShulkerBoxSpecialModel.java index 7df3f9449..20e67ca70 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/special/ShulkerBoxSpecialModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/special/ShulkerBoxSpecialModel.java @@ -54,7 +54,7 @@ public class ShulkerBoxSpecialModel implements SpecialModel { public SpecialModel create(Map arguments) { float openness = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("openness", 0), "openness"); String texture = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("texture"), "warning.config.item.model.special.shulker_box.missing_texture"); - Direction orientation = Optional.ofNullable(arguments.get("orientation")).map(String::valueOf).map(String::toUpperCase).map(Direction::valueOf).orElse(null); + Direction orientation = Optional.ofNullable(arguments.get("orientation")).map(String::valueOf).map(s -> s.toUpperCase(Locale.ROOT)).map(Direction::valueOf).orElse(null); if (openness > 1 || openness < 0) { throw new LocalizedResourceConfigException("warning.config.item.model.special.shulker_box.invalid_openness", String.valueOf(openness)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfB.java b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfB.java index eb1fc463e..19e5ba0f3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfB.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; @@ -81,7 +82,7 @@ public final class ObfB { } private static String 雷雷宝宝打肚肚(String input) { - return input.replace('\\', '/').toLowerCase(); + return input.replace('\\', '/').toLowerCase(Locale.ROOT); } private static String[] 因为都叫你小学生(String path) { 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 8bbfb01c0..68fea801d 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 @@ -21,12 +21,13 @@ import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager import net.momirealms.craftengine.core.plugin.compatibility.PluginTaskRegistry; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; -import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; 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; @@ -40,6 +41,7 @@ import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.sound.SoundManager; import net.momirealms.craftengine.core.util.CompletableFutures; import net.momirealms.craftengine.core.world.WorldManager; +import net.momirealms.craftengine.core.world.score.TeamManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Logger; import org.jetbrains.annotations.ApiStatus; @@ -80,6 +82,8 @@ public abstract class CraftEngine implements Plugin { protected GlobalVariableManager globalVariableManager; protected ProjectileManager projectileManager; protected SeatManager seatManager; + protected EntityCullingManager entityCullingManager; + protected TeamManager teamManager; private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry(); private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry(); @@ -114,11 +118,13 @@ public abstract class CraftEngine implements Plugin { LegacyRecipeTypes.init(); // 初始化模板管理器 - this.templateManager = new TemplateManagerImpl(); + this.templateManager = TemplateManager.INSTANCE; // 初始化全局变量管理器 this.globalVariableManager = new GlobalVariableManager(); // 初始化物品浏览器 this.itemBrowserManager = new ItemBrowserManagerImpl(this); + // 初始化实体剔除器 + this.entityCullingManager = new EntityCullingManagerImpl(); } public void setUpConfigAndLocale() { @@ -159,6 +165,8 @@ public abstract class CraftEngine implements Plugin { this.advancementManager.reload(); this.projectileManager.reload(); this.seatManager.reload(); + this.entityCullingManager.reload(); + this.teamManager.reload(); } private void runDelayTasks(boolean reloadRecipe) { @@ -231,6 +239,8 @@ public abstract class CraftEngine implements Plugin { if (reloadRecipe) { this.recipeManager.runDelayedSyncTasks(); } + // 同步修改队伍 + this.teamManager.runDelayedSyncTasks(); long time4 = System.currentTimeMillis(); long syncTime = time4 - time3; this.reloadEventDispatcher.accept(this); @@ -350,6 +360,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(); @@ -563,6 +574,21 @@ public abstract class CraftEngine implements Plugin { return projectileManager; } + @Override + public EntityCullingManager entityCullingManager() { + return entityCullingManager; + } + + @Override + public TeamManager teamManager() { + return teamManager; + } + + @Override + public SeatManager seatManager() { + return seatManager; + } + @Override public Platform platform() { return platform; 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/Plugin.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java index 1813c2bd8..d4aae96e9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementManager; import net.momirealms.craftengine.core.block.BlockManager; import net.momirealms.craftengine.core.entity.furniture.FurnitureManager; import net.momirealms.craftengine.core.entity.projectile.ProjectileManager; +import net.momirealms.craftengine.core.entity.seat.SeatManager; import net.momirealms.craftengine.core.font.FontManager; import net.momirealms.craftengine.core.item.ItemManager; import net.momirealms.craftengine.core.item.recipe.RecipeManager; @@ -16,6 +17,7 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; import net.momirealms.craftengine.core.plugin.dependency.DependencyManager; +import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManager; import net.momirealms.craftengine.core.plugin.gui.GuiManager; import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManager; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; @@ -24,6 +26,7 @@ import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.sound.SoundManager; import net.momirealms.craftengine.core.world.WorldManager; +import net.momirealms.craftengine.core.world.score.TeamManager; import java.io.File; import java.io.InputStream; @@ -97,5 +100,11 @@ public interface Plugin { ProjectileManager projectileManager(); + EntityCullingManager entityCullingManager(); + + TeamManager teamManager(); + + SeatManager seatManager(); + Platform platform(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java index 0c0c82726..1a89da040 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java @@ -42,4 +42,6 @@ public interface CompatibilityManager { void executeMMSkill(String skill, float power, Player player); TagResolver[] createExternalTagResolvers(Context context); + + boolean isBedrockPlayer(Player player); } 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 c75eaa370..f343ce3f3 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; @@ -191,6 +193,8 @@ public class Config { protected Map item$custom_model_data_starting_value$overrides; protected boolean item$always_use_item_model; protected String item$default_material = ""; + protected boolean item$default_drop_display$enable = false; + protected String item$default_drop_display$format = null; protected String equipment$sacrificed_vanilla_armor$type; protected Key equipment$sacrificed_vanilla_armor$asset_id; @@ -203,6 +207,17 @@ public class Config { protected boolean emoji$contexts$sign; 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; + + protected boolean bedrock_edition_support$enable; + protected String bedrock_edition_support$player_prefix; + public Config(CraftEngine plugin) { this.plugin = plugin; this.configVersion = PluginProperties.getValue("config"); @@ -305,6 +320,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")); @@ -459,6 +476,8 @@ public class Config { item$custom_model_data_starting_value$default = config.getInt("item.custom-model-data-starting-value.default", 10000); item$always_use_item_model = config.getBoolean("item.always-use-item-model", true) && VersionHelper.isOrAbove1_21_2(); item$default_material = config.getString("item.default-material", ""); + item$default_drop_display$enable = config.getBoolean("item.default-drop-display.enable", false); + item$default_drop_display$format = item$default_drop_display$enable ? config.getString("item.default-drop-display.format", "x "): null; Section customModelDataOverridesSection = config.getSection("item.custom-model-data-starting-value.overrides"); if (customModelDataOverridesSection != null) { @@ -562,6 +581,21 @@ public class Config { emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); + // client optimization + 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); + + // bedrock support + bedrock_edition_support$enable = config.getBoolean("bedrock-edition-support.enable", true); + bedrock_edition_support$player_prefix = config.getString("bedrock-edition-support.player-prefix", "!"); + firstTime = false; } @@ -599,12 +633,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() { @@ -1152,6 +1186,50 @@ public class Config { return instance.resource_pack$optimization$texture$zopfli_iterations; } + public static boolean enableDefaultDropDisplay() { + return instance.item$default_drop_display$enable; + } + + public static String defaultDropDisplayFormat() { + return instance.item$default_drop_display$format; + } + + public static boolean enableEntityCulling() { + 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 static boolean enableBedrockEditionSupport() { + return instance.bedrock_edition_support$enable; + } + + public static String bedrockEditionPlayerPrefix() { + return instance.bedrock_edition_support$player_prefix; + } + 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/IdSectionConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java index a15b91c71..1acba1e29 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/IdSectionConfigParser.java @@ -29,13 +29,21 @@ public abstract class IdSectionConfigParser extends AbstractConfigParser { continue; } Map config = castToMap(section, false); + String node = cached.prefix() + "." + key; if ((boolean) config.getOrDefault("debug", false)) { - CraftEngine.instance().logger().info(GsonHelper.get().toJson(CraftEngine.instance().templateManager().applyTemplates(id, config))); + if (!ResourceConfigUtils.runCatching( + cached.filePath(), + node, + () -> CraftEngine.instance().logger().info(GsonHelper.get().toJson(CraftEngine.instance().templateManager().applyTemplates(id, config))), + () -> GsonHelper.get().toJson(section) + )) { + // 发生异常 + continue; + } } if (!(boolean) config.getOrDefault("enable", true)) { continue; } - String node = cached.prefix() + "." + key; ResourceConfigUtils.runCatching( cached.filePath(), node, 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 new file mode 100644 index 000000000..71336065d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ArgumentString.java @@ -0,0 +1,309 @@ +package net.momirealms.craftengine.core.plugin.config.template; + +import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.TagParser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public interface ArgumentString { + String rawValue(); + + Object get(Map arguments); + + final class Literal implements ArgumentString { + private final String value; + + public Literal(String value) { + this.value = value; + } + + public static Literal literal(String value) { + return new Literal(value); + } + + @Override + public String rawValue() { + return this.value; + } + + @Override + public Object get(Map arguments) { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Literal literal)) return false; + return this.value.equals(literal.value); + } + + @Override + public int hashCode() { + return this.value.hashCode(); + } + + @Override + public String toString() { + return "Literal(" + this.value + ")"; + } + } + + final class Placeholder implements ArgumentString { + private final String placeholder; + private final String rawText; + private final Object defaultValue; + private final boolean hasDefaultValue; + + public Placeholder(String placeholderContent) { + this.rawText = "${" + placeholderContent + "}"; + int separatorIndex = placeholderContent.indexOf(":-"); + if (separatorIndex == -1) { + this.placeholder = placeholderContent; + this.defaultValue = null; + this.hasDefaultValue = false; + } else { + this.placeholder = placeholderContent.substring(0, separatorIndex); + String defaultValueString = placeholderContent.substring(separatorIndex + 2); + Object parsed; + try { + parsed = TagParser.parseObjectFully(defaultValueString); + } catch (Exception 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; + } + this.hasDefaultValue = true; + } + } + + public static Placeholder placeholder(String placeholder) { + return new Placeholder(placeholder); + } + + @Override + public Object get(Map arguments) { + TemplateArgument replacement = arguments.get(this.placeholder); + if (replacement != null) { + return replacement.get(arguments); + } + if (this.hasDefaultValue) { + if (this.defaultValue == null) { + return null; + } + return ((TemplateManagerImpl) TemplateManager.INSTANCE).processUnknownValue(this.defaultValue, arguments); + } + throw new LocalizedResourceConfigException("warning.config.template.argument.missing_value", this.rawText); + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Placeholder that)) return false; + return this.placeholder.equals(that.placeholder); + } + + @Override + public int hashCode() { + return this.placeholder.hashCode(); + } + + @Override + public String toString() { + return "Placeholder(" + this.placeholder + ")"; + } + } + + final class Complex2 implements ArgumentString { + private final String rawText; + private final ArgumentString arg1; + private final ArgumentString arg2; + + public Complex2(String rawText, ArgumentString arg1, ArgumentString arg2) { + this.arg1 = arg1; + this.arg2 = arg2; + this.rawText = rawText; + } + + @Override + public Object get(Map arguments) { + Object arg1 = this.arg1.get(arguments); + Object arg2 = this.arg2.get(arguments); + if (arg1 == null && arg2 == null) return null; + if (arg1 == null) return String.valueOf(arg2); + if (arg2 == null) return String.valueOf(arg1); + return String.valueOf(arg1) + arg2; + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Complex2 that)) return false; + return this.rawText.equals(that.rawText); + } + + @Override + public int hashCode() { + return this.rawText.hashCode(); + } + + @Override + public String toString() { + return "Complex2(" + this.rawText + ")"; + } + } + + final class Complex implements ArgumentString { + private final List parts; + private final String rawText; + + public Complex(String rawText, List parts) { + this.parts = parts; + this.rawText = rawText; + } + + @Override + public Object get(Map arguments) { + StringBuilder result = new StringBuilder(); + boolean hasValue = false; + for (ArgumentString part : this.parts) { + Object arg = part.get(arguments); + if (arg != null) { + result.append(arg); + hasValue = true; + } + } + if (!hasValue) return null; + return result.toString(); + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Complex that)) return false; + return this.rawText.equals(that.rawText); + } + + @Override + public int hashCode() { + return this.rawText.hashCode(); + } + + @Override + public String toString() { + return "Complex(" + this.rawText + ")"; + } + } + + static ArgumentString preParse(String input) { + if (input == null || input.isEmpty()) { + return Literal.literal(""); + } + + List arguments = new ArrayList<>(); + StringBuilder currentLiteral = new StringBuilder(); + final int n = input.length(); + int i = 0; + + while (i < n) { + char c = input.charAt(i); + + // --- 1. 优先检测占位符触发器 --- + if (c == '$' && i + 1 < n && input.charAt(i + 1) == '{') { + + // a. 提交之前的普通文本 + if (!currentLiteral.isEmpty()) { + arguments.add(Literal.literal(currentLiteral.toString())); + currentLiteral.setLength(0); + } + + // b. 解析占位符内部,此处的逻辑拥有自己的转义规则 + int contentStartIndex = i + 2; + StringBuilder keyBuilder = new StringBuilder(); + int depth = 1; + int j = contentStartIndex; + boolean foundMatch = false; + + while (j < n) { + char innerChar = input.charAt(j); + + // --- 占位符内部的转义逻辑 --- + if (innerChar == '\\') { + if (j + 1 < n && (input.charAt(j + 1) == '{' || input.charAt(j + 1) == '}')) { + keyBuilder.append(input.charAt(j + 1)); + j += 2; + } else { + // 在占位符内部,一个无法识别的转义\依旧被当作普通\处理 + keyBuilder.append(innerChar); + j++; + } + } else if (innerChar == '{') { + depth++; + keyBuilder.append(innerChar); + j++; + } else if (innerChar == '}') { + depth--; + if (depth == 0) { // 找到匹配的闭合括号 + arguments.add(Placeholder.placeholder(keyBuilder.toString())); + i = j + 1; + foundMatch = true; + break; + } + keyBuilder.append(innerChar); + j++; + } else { + keyBuilder.append(innerChar); + j++; + } + } + + if (foundMatch) { + continue; + } else { + // 未找到闭合括号,将 '$' 视为普通字符 + currentLiteral.append(c); + i++; + } + } + // --- 2. 其次,只处理对触发器'$'的转义 --- + else if (c == '\\' && i + 1 < n && input.charAt(i + 1) == '$') { + currentLiteral.append('$'); // 直接添加 '$' + i += 2; // 跳过 '\' 和 '$' + } + // --- 3. 处理所有其他字符(包括独立的'\'和'{')为普通文本 --- + else { + currentLiteral.append(c); + i++; + } + } + + if (!currentLiteral.isEmpty()) { + arguments.add(Literal.literal(currentLiteral.toString())); + } + + return switch (arguments.size()) { + case 0 -> Literal.literal(""); + case 1 -> arguments.getFirst(); + case 2 -> new Complex2(input, arguments.get(0), arguments.get(1)); + default -> new Complex(input, arguments); + }; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java index 86840c238..bed0110ef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java @@ -2,309 +2,13 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.SNBTReader; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public interface TemplateManager extends Manageable { + TemplateManager INSTANCE = new TemplateManagerImpl(); + ConfigParser parser(); Object applyTemplates(Key id, Object input); - - interface ArgumentString { - - String rawValue(); - - Object get(Map arguments); - } - - final class Literal implements ArgumentString { - private final String value; - - public Literal(String value) { - this.value = value; - } - - public static Literal literal(String value) { - return new Literal(value); - } - - @Override - public String rawValue() { - return this.value; - } - - @Override - public Object get(Map arguments) { - return this.value; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Literal literal)) return false; - return this.value.equals(literal.value); - } - - @Override - public int hashCode() { - return this.value.hashCode(); - } - - @Override - public String toString() { - return "Literal(" + this.value + ")"; - } - } - - final class Placeholder implements ArgumentString { - private final String placeholder; - private final String rawText; - private final Object defaultValue; - private final boolean hasDefaultValue; - - public Placeholder(String placeholderContent) { - this.rawText = "${" + placeholderContent + "}"; - int separatorIndex = placeholderContent.indexOf(":-"); - if (separatorIndex == -1) { - this.placeholder = placeholderContent; - this.defaultValue = null; - this.hasDefaultValue = false; - } else { - this.placeholder = placeholderContent.substring(0, separatorIndex); - String defaultValueString = placeholderContent.substring(separatorIndex + 2); - try { - this.defaultValue = new SNBTReader(defaultValueString).deserializeAsJava(); - } catch (LocalizedResourceConfigException e) { - e.appendTailArgument(this.placeholder); - throw e; - } - this.hasDefaultValue = true; - } - } - - public static Placeholder placeholder(String placeholder) { - return new Placeholder(placeholder); - } - - @Override - public Object get(Map arguments) { - TemplateArgument replacement = arguments.get(this.placeholder); - if (replacement != null) { - return replacement.get(arguments); - } - if (this.hasDefaultValue) { - return this.defaultValue; - } - throw new LocalizedResourceConfigException("warning.config.template.argument.missing_value", this.rawText); - } - - @Override - public String rawValue() { - return this.rawText; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Placeholder that)) return false; - return this.placeholder.equals(that.placeholder); - } - - @Override - public int hashCode() { - return this.placeholder.hashCode(); - } - - @Override - public String toString() { - return "Placeholder(" + this.placeholder + ")"; - } - } - - final class Complex2 implements ArgumentString { - private final String rawText; - private final ArgumentString arg1; - private final ArgumentString arg2; - - public Complex2(String rawText, ArgumentString arg1, ArgumentString arg2) { - this.arg1 = arg1; - this.arg2 = arg2; - this.rawText = rawText; - } - - @Override - public Object get(Map arguments) { - Object arg1 = this.arg1.get(arguments); - Object arg2 = this.arg2.get(arguments); - if (arg1 == null && arg2 == null) return null; - if (arg1 == null) return String.valueOf(arg2); - if (arg2 == null) return String.valueOf(arg1); - return String.valueOf(arg1) + arg2; - } - - @Override - public String rawValue() { - return this.rawText; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Complex that)) return false; - return this.rawText.equals(that.rawText); - } - - @Override - public int hashCode() { - return this.rawText.hashCode(); - } - - @Override - public String toString() { - return "Complex2(" + this.rawText + ")"; - } - } - - final class Complex implements ArgumentString { - private final List parts; - private final String rawText; - - public Complex(String rawText, List parts) { - this.parts = parts; - this.rawText = rawText; - } - - @Override - public Object get(Map arguments) { - StringBuilder result = new StringBuilder(); - boolean hasValue = false; - for (ArgumentString part : this.parts) { - Object arg = part.get(arguments); - if (arg != null) { - result.append(arg); - hasValue = true; - } - } - if (!hasValue) return null; - return result.toString(); - } - - @Override - public String rawValue() { - return this.rawText; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Complex that)) return false; - return this.rawText.equals(that.rawText); - } - - @Override - public int hashCode() { - return this.rawText.hashCode(); - } - - @Override - public String toString() { - return "Complex(" + this.rawText + ")"; - } - } - - static ArgumentString preParse(String input) { - if (input == null || input.isEmpty()) { - return Literal.literal(""); - } - - List arguments = new ArrayList<>(); - StringBuilder currentLiteral = new StringBuilder(); - final int n = input.length(); - int i = 0; - - while (i < n) { - char c = input.charAt(i); - - // --- 1. 优先检测占位符触发器 --- - if (c == '$' && i + 1 < n && input.charAt(i + 1) == '{') { - - // a. 提交之前的普通文本 - if (!currentLiteral.isEmpty()) { - arguments.add(Literal.literal(currentLiteral.toString())); - currentLiteral.setLength(0); - } - - // b. 解析占位符内部,此处的逻辑拥有自己的转义规则 - int contentStartIndex = i + 2; - StringBuilder keyBuilder = new StringBuilder(); - int depth = 1; - int j = contentStartIndex; - boolean foundMatch = false; - - while (j < n) { - char innerChar = input.charAt(j); - - // --- 占位符内部的转义逻辑 --- - if (innerChar == '\\') { - if (j + 1 < n && (input.charAt(j + 1) == '{' || input.charAt(j + 1) == '}')) { - keyBuilder.append(input.charAt(j + 1)); - j += 2; - } else { - // 在占位符内部,一个无法识别的转义\依旧被当作普通\处理 - keyBuilder.append(innerChar); - j++; - } - } else if (innerChar == '{') { - depth++; - keyBuilder.append(innerChar); - j++; - } else if (innerChar == '}') { - depth--; - if (depth == 0) { // 找到匹配的闭合括号 - arguments.add(Placeholder.placeholder(keyBuilder.toString())); - i = j + 1; - foundMatch = true; - break; - } - keyBuilder.append(innerChar); - j++; - } else { - keyBuilder.append(innerChar); - j++; - } - } - - if (foundMatch) { - continue; - } else { - // 未找到闭合括号,将 '$' 视为普通字符 - currentLiteral.append(c); - i++; - } - } - // --- 2. 其次,只处理对触发器'$'的转义 --- - else if (c == '\\' && i + 1 < n && input.charAt(i + 1) == '$') { - currentLiteral.append('$'); // 直接添加 '$' - i += 2; // 跳过 '\' 和 '$' - } - // --- 3. 处理所有其他字符(包括独立的'\'和'{')为普通文本 --- - else { - currentLiteral.append(c); - i++; - } - } - - if (!currentLiteral.isEmpty()) { - arguments.add(Literal.literal(currentLiteral.toString())); - } - - return switch (arguments.size()) { - case 0 -> Literal.literal(""); - case 1 -> arguments.getFirst(); - case 2 -> new Complex2(input, arguments.get(0), arguments.get(1)); - default -> new Complex(input, arguments); - }; - } } 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 0d411d956..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 @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdObjectConfigParser; +import net.momirealms.craftengine.core.plugin.config.template.argument.*; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -14,16 +15,16 @@ import java.util.*; @SuppressWarnings("DuplicatedCode") public class TemplateManagerImpl implements TemplateManager { - private static final ArgumentString TEMPLATE = Literal.literal("template"); - private static final ArgumentString OVERRIDES = Literal.literal("overrides"); - private static final ArgumentString ARGUMENTS = Literal.literal("arguments"); - private static final ArgumentString MERGES = Literal.literal("merges"); + private static final ArgumentString TEMPLATE = ArgumentString.Literal.literal("template"); + private static final ArgumentString OVERRIDES = ArgumentString.Literal.literal("overrides"); + private static final ArgumentString ARGUMENTS = ArgumentString.Literal.literal("arguments"); + private static final ArgumentString MERGES = ArgumentString.Literal.literal("merges"); private final static Set NON_TEMPLATE_ARGUMENTS = new HashSet<>(Set.of(TEMPLATE, ARGUMENTS, OVERRIDES, MERGES)); private final Map templates = new HashMap<>(); private final TemplateParser templateParser; - public TemplateManagerImpl() { + protected TemplateManagerImpl() { this.templateParser = new TemplateParser(); } @@ -50,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)) { @@ -69,13 +75,13 @@ public class TemplateManagerImpl implements TemplateManager { )); } - private Object preprocessUnknownValue(Object value) { + public Object preprocessUnknownValue(Object value) { switch (value) { case Map map -> { Map in = MiscUtils.castToMap(map, false); Map out = new LinkedHashMap<>(map.size()); for (Map.Entry entry : in.entrySet()) { - out.put(TemplateManager.preParse(entry.getKey()), preprocessUnknownValue(entry.getValue())); + out.put(ArgumentString.preParse(entry.getKey()), preprocessUnknownValue(entry.getValue())); } return out; } @@ -87,7 +93,7 @@ public class TemplateManagerImpl implements TemplateManager { return objList; } case String string -> { - return TemplateManager.preParse(string); + return ArgumentString.preParse(string); } case null, default -> { return value; @@ -188,8 +194,8 @@ public class TemplateManagerImpl implements TemplateManager { // 处理一个类型未知的值,本方法只管将member处理好后,传递回调用者a @SuppressWarnings("unchecked") - private Object processUnknownValue(Object value, - Map arguments) { + public Object processUnknownValue(Object value, + Map arguments) { switch (value) { case Map innerMap -> // map下面还是个map吗?这并不一定 @@ -237,7 +243,7 @@ public class TemplateManagerImpl implements TemplateManager { // 如果模板id被用了参数,则应先应用参数后再查询模板 Object actualTemplate = templateId.get(parentArguments); if (actualTemplate == null) continue; // 忽略被null掉的模板 - Object template = Optional.ofNullable(this.templates.get(Key.of(actualTemplate.toString()))) + Object template = Optional.ofNullable(((TemplateManagerImpl) INSTANCE).templates.get(Key.of(actualTemplate.toString()))) .orElseThrow(() -> new LocalizedResourceConfigException("warning.config.template.invalid", actualTemplate.toString())); Object processedTemplate = processUnknownValue(template, arguments); if (processedTemplate != null) templateList.add(processedTemplate); @@ -294,7 +300,7 @@ public class TemplateManagerImpl implements TemplateManager { // 合并参数 @SuppressWarnings("unchecked") private Map mergeArguments(@NotNull Map childArguments, - @NotNull Map parentArguments) { + @NotNull Map parentArguments) { Map result = new LinkedHashMap<>(parentArguments); for (Map.Entry argumentEntry : childArguments.entrySet()) { Object placeholderObj = argumentEntry.getKey().get(result); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ConditionTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ConditionTemplateArgument.java new file mode 100644 index 000000000..f8f3c72be --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ConditionTemplateArgument.java @@ -0,0 +1,35 @@ +package net.momirealms.craftengine.core.plugin.config.template.argument; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public class ConditionTemplateArgument implements TemplateArgument { + public static final Factory FACTORY = new Factory(); + private final TemplateArgument result; + + private ConditionTemplateArgument(TemplateArgument result) { + this.result = result; + } + + @Override + public Key type() { + return TemplateArguments.CONDITION; + } + + @Override + public Object get(Map arguments) { + return this.result.get(arguments); + } + + public static class Factory implements TemplateArgumentFactory { + + @Override + public TemplateArgument create(Map arguments) { + TemplateArgument onTrue = TemplateArguments.fromObject(ResourceConfigUtils.get(arguments, "on-true", "on_true")); + TemplateArgument onFalse = TemplateArguments.fromObject(ResourceConfigUtils.get(arguments, "on-false", "on_false")); + return new ConditionTemplateArgument(ResourceConfigUtils.getAsBoolean(arguments.get("condition"), "condition") ? onTrue : onFalse); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java similarity index 83% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java index a92d6fdf2..cf02eeb46 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java @@ -1,7 +1,8 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import com.ezylang.evalex.Expression; import com.ezylang.evalex.data.EvaluationValue; +import net.momirealms.craftengine.core.plugin.config.template.ArgumentString; import net.momirealms.craftengine.core.util.Key; import java.util.Locale; @@ -11,11 +12,11 @@ import java.util.function.Function; public class ExpressionTemplateArgument implements TemplateArgument { public static final Factory FACTORY = new Factory(); - private final TemplateManager.ArgumentString expression; + private final ArgumentString expression; private final ValueType valueType; protected ExpressionTemplateArgument(String expression, ValueType valueType) { - this.expression = TemplateManager.preParse(expression); + this.expression = ArgumentString.preParse(expression); this.valueType = valueType; } @@ -38,9 +39,10 @@ public class ExpressionTemplateArgument implements TemplateArgument { protected enum ValueType { INT(e -> e.getNumberValue().intValue()), LONG(e -> e.getNumberValue().longValue()), - SHORT(e -> e.getNumberValue().shortValueExact()), + SHORT(e -> e.getNumberValue().shortValue()), DOUBLE(e -> e.getNumberValue().doubleValue()), FLOAT(e -> e.getNumberValue().floatValue()), + BYTE(e -> e.getNumberValue().byteValue()), BOOLEAN(EvaluationValue::getBooleanValue),; private final Function formatter; @@ -59,7 +61,7 @@ public class ExpressionTemplateArgument implements TemplateArgument { public TemplateArgument create(Map arguments) { return new ExpressionTemplateArgument( arguments.getOrDefault("expression", "").toString(), - ValueType.valueOf(arguments.getOrDefault("value-type", "double").toString().toUpperCase(Locale.ENGLISH)) + ValueType.valueOf(arguments.getOrDefault("value-type", "double").toString().toUpperCase(Locale.ROOT)) ); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ListTemplateArgument.java similarity index 95% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ListTemplateArgument.java index eae376ac0..3863b7841 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ListTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/MapTemplateArgument.java similarity index 92% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/MapTemplateArgument.java index 9df939320..b77c47bb0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/MapTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/NullTemplateArgument.java similarity index 91% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/NullTemplateArgument.java index 8a568d2af..51a0af576 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/NullTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ObjectTemplateArgument.java similarity index 88% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ObjectTemplateArgument.java index e78af6b66..980eadef6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ObjectTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/PlainStringTemplateArgument.java similarity index 92% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/PlainStringTemplateArgument.java index a9ef8d6ae..3305ac5c5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/PlainStringTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/SelfIncreaseIntTemplateArgument.java similarity index 56% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/SelfIncreaseIntTemplateArgument.java index d01b87124..b117622f4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/SelfIncreaseIntTemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; @@ -11,17 +11,30 @@ public class SelfIncreaseIntTemplateArgument implements TemplateArgument { private final int min; private final int max; private int current; + private final int step; + private final int stepInterval; + private int callCount; - public SelfIncreaseIntTemplateArgument(int min, int max) { + public SelfIncreaseIntTemplateArgument(int min, int max, int step, int stepInterval) { this.min = min; this.max = max; this.current = min; + this.step = step; + this.stepInterval = stepInterval; + this.callCount = 0; } @Override public String get(Map arguments) { String value = String.valueOf(this.current); - if (this.current < this.max) this.current += 1; + this.callCount++; + if (this.stepInterval <= 0 || this.callCount % this.stepInterval == 0) { + if (this.current + this.step <= this.max) { + this.current += this.step; + } else { + this.current = this.max; + } + } return value; } @@ -42,13 +55,27 @@ public class SelfIncreaseIntTemplateArgument implements TemplateArgument { return this.max; } + public int step() { + return this.step; + } + + public int stepInterval() { + return this.stepInterval; + } + + public int callCount() { + return this.callCount; + } + public static class Factory implements TemplateArgumentFactory { @Override public TemplateArgument create(Map arguments) { int from = ResourceConfigUtils.getAsInt(arguments.get("from"), "from"); int to = ResourceConfigUtils.getAsInt(arguments.get("to"), "to"); + int step = ResourceConfigUtils.getAsInt(arguments.getOrDefault("step", 1), "step"); + int stepInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("step-interval", 1), "step-interval"); if (from > to) throw new LocalizedResourceConfigException("warning.config.template.argument.self_increase_int.invalid_range", String.valueOf(from), String.valueOf(to)); - return new SelfIncreaseIntTemplateArgument(from, to); + return new SelfIncreaseIntTemplateArgument(from, to, step, stepInterval); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgument.java similarity index 71% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgument.java index 9816cfebc..6e029e88c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgument.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgumentFactory.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgumentFactory.java similarity index 64% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgumentFactory.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgumentFactory.java index 63812d722..a5976017a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgumentFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArgumentFactory.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import java.util.Map; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArguments.java similarity index 60% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArguments.java index 82fd38d45..9b1a8ef91 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/TemplateArguments.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.config.template; +package net.momirealms.craftengine.core.plugin.config.template.argument; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Registries; @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; +import java.util.List; import java.util.Map; public class TemplateArguments { @@ -14,8 +15,12 @@ public class TemplateArguments { public static final Key MAP = Key.of("craftengine:map"); public static final Key LIST = Key.of("craftengine:list"); public static final Key NULL = Key.of("craftengine:null"); + public static final Key CONDITION = Key.of("craftengine:condition"); public static final Key EXPRESSION = Key.of("craftengine:expression"); public static final Key OBJECT = Key.of("craftengine:object"); // No Factory, internal use + public static final Key TO_UPPER_CASE = Key.of("craftengine:to_upper_case"); + public static final Key TO_LOWER_CASE = Key.of("craftengine:to_lower_case"); + public static final Key WHEN = Key.of("craftengine:when"); public static void register(Key key, TemplateArgumentFactory factory) { ((WritableRegistry) BuiltInRegistries.TEMPLATE_ARGUMENT_FACTORY) @@ -29,14 +34,28 @@ public class TemplateArguments { register(LIST, ListTemplateArgument.FACTORY); register(NULL, NullTemplateArgument.FACTORY); register(EXPRESSION, ExpressionTemplateArgument.FACTORY); + register(CONDITION, ConditionTemplateArgument.FACTORY); + register(TO_UPPER_CASE, ToUpperCaseTemplateArgument.FACTORY); + register(TO_LOWER_CASE, ToLowerCaseTemplateArgument.FACTORY); + register(WHEN, WhenTemplateArgument.FACTORY); + } + + @SuppressWarnings("unchecked") + public static TemplateArgument fromObject(Object object) { + return switch (object) { + case null -> NullTemplateArgument.INSTANCE; + case List list -> new ListTemplateArgument((List) list); + case Map map -> fromMap((Map) map); + default -> new ObjectTemplateArgument(object); + }; } public static TemplateArgument fromMap(Map map) { - String type = (String) map.get("type"); - if (type == null) { + Object type = map.get("type"); + if (!(type instanceof String type0) || map.containsKey("__skip_template_argument__")) { return new MapTemplateArgument(map); } else { - Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); + Key key = Key.withDefaultNamespace(type0, Key.DEFAULT_NAMESPACE); TemplateArgumentFactory factory = BuiltInRegistries.TEMPLATE_ARGUMENT_FACTORY.getValue(key); if (factory == null) { throw new IllegalArgumentException("Unknown argument type: " + type); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToLowerCaseTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToLowerCaseTemplateArgument.java new file mode 100644 index 000000000..83e2c8dae --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToLowerCaseTemplateArgument.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.core.plugin.config.template.argument; + +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Locale; +import java.util.Map; + +public class ToLowerCaseTemplateArgument implements TemplateArgument { + public static final Factory FACTORY = new Factory(); + private final String result; + + private ToLowerCaseTemplateArgument(String result) { + this.result = result; + } + + @Override + public Key type() { + return TemplateArguments.TO_LOWER_CASE; + } + + @Override + public Object get(Map arguments) { + return this.result; + } + + public static class Factory implements TemplateArgumentFactory { + + @Override + public TemplateArgument create(Map arguments) { + String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value"), "warning.config.template.argument.to_lower_case.missing_value"); + String localeName = arguments.containsKey("locale") ? arguments.get("locale").toString() : null; + Locale locale = localeName != null ? TranslationManager.parseLocale(localeName) : Locale.ROOT; + if (locale == null) { + throw new LocalizedResourceConfigException("warning.config.template.argument.to_lower_case.invalid_locale", localeName); + } + return new ToLowerCaseTemplateArgument(text.toLowerCase(locale)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToUpperCaseTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToUpperCaseTemplateArgument.java new file mode 100644 index 000000000..d3600a5eb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ToUpperCaseTemplateArgument.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.core.plugin.config.template.argument; + +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Locale; +import java.util.Map; + +public class ToUpperCaseTemplateArgument implements TemplateArgument { + public static final Factory FACTORY = new Factory(); + private final String result; + + private ToUpperCaseTemplateArgument(String result) { + this.result = result; + } + + @Override + public Key type() { + return TemplateArguments.TO_UPPER_CASE; + } + + @Override + public Object get(Map arguments) { + return this.result; + } + + public static class Factory implements TemplateArgumentFactory { + + @Override + public TemplateArgument create(Map arguments) { + String text = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value"), "warning.config.template.argument.to_upper_case.missing_value"); + String localeName = arguments.containsKey("locale") ? arguments.get("locale").toString() : null; + Locale locale = localeName != null ? TranslationManager.parseLocale(localeName) : Locale.ROOT; + if (locale == null) { + throw new LocalizedResourceConfigException("warning.config.template.argument.to_upper_case.invalid_locale", localeName); + } + return new ToUpperCaseTemplateArgument(text.toUpperCase(locale)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/WhenTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/WhenTemplateArgument.java new file mode 100644 index 000000000..98dfcb7a0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/WhenTemplateArgument.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.core.plugin.config.template.argument; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public class WhenTemplateArgument implements TemplateArgument { + public static final Factory FACTORY = new Factory(); + private final TemplateArgument result; + + private WhenTemplateArgument(TemplateArgument result) { + this.result = result; + } + + @Override + public Key type() { + return TemplateArguments.WHEN; + } + + @Override + public Object get(Map arguments) { + return this.result.get(arguments); + } + + public static class Factory implements TemplateArgumentFactory { + + @Override + public TemplateArgument create(Map arguments) { + String source = ResourceConfigUtils.getAsStringOrNull(arguments.get("source")); + TemplateArgument fallback = TemplateArguments.fromObject(arguments.get("fallback")); + if (source == null) { + return new WhenTemplateArgument(fallback); + } + Map whenMap = ResourceConfigUtils.getAsMap(arguments.get("when"), "when"); + TemplateArgument value = whenMap.containsKey(source) ? TemplateArguments.fromObject(whenMap.get(source)) : fallback; + return new WhenTemplateArgument(value); + } + } +} 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/condition/CommonConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java index 991ea9e0a..256ff4e3b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java @@ -15,6 +15,7 @@ public final class CommonConditions { public static final Key MATCH_ENTITY = Key.of("craftengine:match_entity"); public static final Key MATCH_BLOCK = Key.of("craftengine:match_block"); public static final Key MATCH_BLOCK_PROPERTY = Key.from("craftengine:match_block_property"); + public static final Key MATCH_FURNITURE_VARIANT = Key.from("craftengine:match_furniture_variant"); public static final Key TABLE_BONUS = Key.from("craftengine:table_bonus"); public static final Key SURVIVES_EXPLOSION = Key.from("craftengine:survives_explosion"); public static final Key RANDOM = Key.from("craftengine:random"); @@ -31,4 +32,5 @@ public final class CommonConditions { public static final Key IS_NULL = Key.from("craftengine:is_null"); public static final Key HAND = Key.from("craftengine:hand"); public static final Key HAS_PLAYER = Key.from("craftengine:has_player"); + public static final Key INVENTORY_HAS_ITEM = Key.from("craftengine:inventory_has_item"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/InventoryHasItemCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/InventoryHasItemCondition.java new file mode 100644 index 000000000..432074e5d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/InventoryHasItemCondition.java @@ -0,0 +1,48 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +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.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; + +public class InventoryHasItemCondition implements Condition { + private final Key itemId; + private final NumberProvider count; + + public InventoryHasItemCondition(Key itemId, NumberProvider count) { + this.itemId = itemId; + this.count = count; + } + + @Override + public Key type() { + return CommonConditions.INVENTORY_HAS_ITEM; + } + + @Override + public boolean test(CTX ctx) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + if (optionalPlayer.isEmpty()) { + return false; + } + Player player = optionalPlayer.get(); + return player.clearOrCountMatchingInventoryItems(this.itemId, 0) >= this.count.getInt(ctx); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "id", "item"), "warning.config.condition.inventory_has_item.missing_id")); + NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); + return new InventoryHasItemCondition<>(itemId, count); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java index 9eee31c0c..7f5e977c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java @@ -40,7 +40,7 @@ public class MatchBlockCondition implements Condition Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - ExistingBlock blockAt = world.getBlock(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx))); + ExistingBlock blockAt = world.getBlock(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx))); return MiscUtils.matchRegex(blockAt.id().asString(), this.ids, this.regexMatch); } return false; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchFurnitureVariantCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchFurnitureVariantCondition.java new file mode 100644 index 000000000..31ded2c60 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchFurnitureVariantCondition.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.*; + +public class MatchFurnitureVariantCondition implements Condition { + private final Set variants; + + public MatchFurnitureVariantCondition(Collection variants) { + this.variants = new HashSet<>(variants); + } + + @Override + public Key type() { + return CommonConditions.MATCH_FURNITURE_VARIANT; + } + + @Override + public boolean test(CTX ctx) { + Optional furniture = ctx.getOptionalParameter(DirectContextParameters.FURNITURE); + return furniture.filter(value -> this.variants.contains(value.getCurrentVariant().name())).isPresent(); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + List variants = MiscUtils.getAsStringList(ResourceConfigUtils.get(arguments, "variant", "variants")); + return new MatchFurnitureVariantCondition<>(variants); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java index b17921ed4..6c9466d0a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java @@ -40,6 +40,8 @@ public class EventConditions { register(CommonConditions.IS_NULL, new IsNullCondition.FactoryImpl<>()); register(CommonConditions.HAND, new HandCondition.FactoryImpl<>()); register(CommonConditions.ON_COOLDOWN, new OnCooldownCondition.FactoryImpl<>()); + register(CommonConditions.INVENTORY_HAS_ITEM, new InventoryHasItemCondition.FactoryImpl<>()); + register(CommonConditions.MATCH_FURNITURE_VARIANT, new MatchFurnitureVariantCondition.FactoryImpl<>()); } public static void register(Key key, ConditionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index 447ce05f0..5cbe2e8db 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -43,6 +43,7 @@ public class EventFunctions { register(CommonFunctions.SPAWN_FURNITURE, new SpawnFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.REMOVE_FURNITURE, new RemoveFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.REPLACE_FURNITURE, new ReplaceFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.ROTATE_FURNITURE, new RotateFurnitureFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.MYTHIC_MOBS_SKILL, new MythicMobsSkillFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.TELEPORT, new TeleportFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.SET_VARIABLE, new SetVariableFunction.FactoryImpl<>(EventConditions::fromMap)); @@ -55,6 +56,11 @@ public class EventFunctions { register(CommonFunctions.WHEN, new WhenFunction.FactoryImpl<>(EventConditions::fromMap, EventFunctions::fromMap)); register(CommonFunctions.DAMAGE_ITEM, new DamageItemFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.CYCLE_BLOCK_PROPERTY, new CycleBlockPropertyFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_EXP, new SetExpFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_LEVEL, new SetLevelFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.PLAY_TOTEM_ANIMATION, new PlayTotemAnimationFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.CLOSE_INVENTORY, new CloseInventoryFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.CLEAR_ITEM, new ClearItemFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { 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/BreakBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java index 268f23984..ad9543d18 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java @@ -18,7 +18,7 @@ public class BreakBlockFunction extends AbstractConditional private final NumberProvider y; private final NumberProvider z; - public BreakBlockFunction(NumberProvider x, NumberProvider y, NumberProvider z, List> predicates) { + public BreakBlockFunction(List> predicates, NumberProvider y, NumberProvider z, NumberProvider x) { super(predicates); this.x = x; this.y = y; @@ -28,7 +28,7 @@ public class BreakBlockFunction extends AbstractConditional @Override public void runInternal(CTX ctx) { Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); - optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.fastFloor(x.getDouble(ctx)), MiscUtils.fastFloor(y.getDouble(ctx)), MiscUtils.fastFloor(z.getDouble(ctx)))); + optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.floor(x.getDouble(ctx)), MiscUtils.floor(y.getDouble(ctx)), MiscUtils.floor(z.getDouble(ctx)))); } @Override @@ -47,7 +47,7 @@ public class BreakBlockFunction extends AbstractConditional NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); - return new BreakBlockFunction<>(x, y, z, getPredicates(arguments)); + return new BreakBlockFunction<>(getPredicates(arguments), y, z, x); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ClearItemFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ClearItemFunction.java new file mode 100644 index 000000000..578266f53 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ClearItemFunction.java @@ -0,0 +1,54 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +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.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ClearItemFunction extends AbstractConditionalFunction { + private final Key itemId; + private final NumberProvider count; + + public ClearItemFunction(List> predicates, Key itemId, NumberProvider count) { + super(predicates); + this.itemId = itemId; + this.count = count; + } + + @Override + protected void runInternal(CTX ctx) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + if (optionalPlayer.isEmpty()) { + return; + } + Player player = optionalPlayer.get(); + player.clearOrCountMatchingInventoryItems(itemId, count.getInt(ctx)); + } + + @Override + public Key type() { + return CommonFunctions.CLEAR_ITEM; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "id", "item"), "warning.config.function.clear_item.missing_id")); + NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); + return new ClearItemFunction<>(getPredicates(arguments), itemId, count); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CloseInventoryFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CloseInventoryFunction.java new file mode 100644 index 000000000..4b99be7dd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CloseInventoryFunction.java @@ -0,0 +1,50 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +public class CloseInventoryFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + + public CloseInventoryFunction(List> predicates, @Nullable PlayerSelector selector) { + super(predicates); + this.selector = selector; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(Player::closeInventory); + } else { + for (Player viewer : this.selector.get(ctx)) { + viewer.closeInventory(); + } + } + } + + @Override + public Key type() { + return CommonFunctions.CLOSE_INVENTORY; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + return new CloseInventoryFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory())); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index bc638a23d..3083f3cf6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -32,6 +32,7 @@ public final class CommonFunctions { public static final Key SPAWN_FURNITURE = Key.of("craftengine:spawn_furniture"); public static final Key REMOVE_FURNITURE = Key.of("craftengine:remove_furniture"); public static final Key REPLACE_FURNITURE = Key.of("craftengine:replace_furniture"); + public static final Key ROTATE_FURNITURE = Key.of("craftengine:rotate_furniture"); public static final Key MYTHIC_MOBS_SKILL = Key.of("craftengine:mythic_mobs_skill"); public static final Key TELEPORT = Key.of("craftengine:teleport"); public static final Key TOAST = Key.of("craftengine:toast"); @@ -46,4 +47,9 @@ public final class CommonFunctions { public static final Key DUMMY = Key.of("craftengine:dummy"); public static final Key DAMAGE_ITEM = Key.of("craftengine:damage_item"); public static final Key CYCLE_BLOCK_PROPERTY = Key.of("craftengine:cycle_block_property"); + public static final Key SET_EXP = Key.of("craftengine:set_exp"); + public static final Key SET_LEVEL = Key.of("craftengine:set_level"); + public static final Key PLAY_TOTEM_ANIMATION = Key.of("craftengine:play_totem_animation"); + public static final Key CLOSE_INVENTORY = Key.of("craftengine:close_inventory"); + public static final Key CLEAR_ITEM = Key.of("craftengine:clear_item"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java index 6e1e58bf4..393468694 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -55,9 +56,9 @@ public class CycleBlockPropertyFunction extends AbstractCon Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isEmpty()) return; World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); } @@ -71,7 +72,7 @@ public class CycleBlockPropertyFunction extends AbstractCon if (value == null) { return wrapper.cycleProperty(this.property, inverse); } - String mapValue = this.rules.get(value.toString()); + String mapValue = this.rules.get(value.toString().toLowerCase(Locale.ROOT)); if (mapValue == null) { return wrapper.cycleProperty(this.property, inverse); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java index 97e197b55..6fd52692b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageFunction.java @@ -17,7 +17,7 @@ public class DamageFunction extends AbstractConditionalFunc private final Key damageType; private final NumberProvider amount; - public DamageFunction(PlayerSelector selector, Key damageType, NumberProvider amount, List> predicates) { + public DamageFunction(List> predicates, Key damageType, NumberProvider amount, PlayerSelector selector) { super(predicates); this.selector = selector; this.damageType = damageType; @@ -45,7 +45,7 @@ public class DamageFunction extends AbstractConditionalFunc PlayerSelector selector = PlayerSelectors.fromObject(arguments.getOrDefault("target", "self"), conditionFactory()); Key damageType = Key.of(ResourceConfigUtils.getAsStringOrNull(arguments.getOrDefault("damage-type", "generic"))); NumberProvider amount = NumberProviders.fromObject(arguments.getOrDefault("amount", 1f)); - return new DamageFunction<>(selector, damageType, amount, getPredicates(arguments)); + return new DamageFunction<>(getPredicates(arguments), damageType, amount, selector); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageItemFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageItemFunction.java index e5cbedec6..d1580f33b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageItemFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DamageItemFunction.java @@ -17,7 +17,7 @@ import java.util.Map; public class DamageItemFunction extends AbstractConditionalFunction { private final NumberProvider amount; - public DamageItemFunction(NumberProvider amount, List> predicates) { + public DamageItemFunction(List> predicates, NumberProvider amount) { super(predicates); this.amount = amount; } @@ -51,7 +51,7 @@ public class DamageItemFunction extends AbstractConditional @Override public Function create(Map arguments) { NumberProvider amount = NumberProviders.fromObject(arguments.getOrDefault("amount", 1)); - return new DamageItemFunction<>(amount, getPredicates(arguments)); + return new DamageItemFunction<>(getPredicates(arguments), amount); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java index 51316f239..a6ddff9b4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java @@ -20,7 +20,7 @@ public class LevelerExpFunction extends AbstractConditional private final String leveler; private final String plugin; - public LevelerExpFunction(NumberProvider count, String leveler, String plugin, PlayerSelector selector, List> predicates) { + public LevelerExpFunction(List> predicates, String leveler, String plugin, PlayerSelector selector, NumberProvider count) { super(predicates); this.count = count; this.leveler = leveler; @@ -58,7 +58,7 @@ public class LevelerExpFunction extends AbstractConditional Object count = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.leveler_exp.missing_count"); String leveler = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("leveler"), "warning.config.function.leveler_exp.missing_leveler"); String plugin = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("plugin"), "warning.config.function.leveler_exp.missing_plugin"); - return new LevelerExpFunction<>(NumberProviders.fromObject(count), leveler, plugin, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new LevelerExpFunction<>(getPredicates(arguments), leveler, plugin, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), NumberProviders.fromObject(count)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MythicMobsSkillFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MythicMobsSkillFunction.java index 031ed08da..9d97eb14c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MythicMobsSkillFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MythicMobsSkillFunction.java @@ -14,7 +14,7 @@ public class MythicMobsSkillFunction extends AbstractCondit private final String skill; private final float power; - public MythicMobsSkillFunction(String skill, float power, List> predicates) { + public MythicMobsSkillFunction(List> predicates, float power, String skill) { super(predicates); this.skill = skill; this.power = power; @@ -42,7 +42,7 @@ public class MythicMobsSkillFunction extends AbstractCondit public Function create(Map args) { String skill = ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("skill"), "warning.config.function.mythic_mobs_skill.missing_skill"); float power = ResourceConfigUtils.getAsFloat(args.getOrDefault("power", 1.0), "power"); - return new MythicMobsSkillFunction<>(skill, power, getPredicates(args)); + return new MythicMobsSkillFunction<>(getPredicates(args), power, skill); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java index 4893d19f8..dd4f29458 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java @@ -17,7 +17,7 @@ import java.util.Optional; public class ParticleFunction extends AbstractConditionalFunction { private final ParticleConfig config; - public ParticleFunction(ParticleConfig config, List> predicates) { + public ParticleFunction(List> predicates, ParticleConfig config) { super(predicates); this.config = config; } @@ -45,7 +45,7 @@ public class ParticleFunction extends AbstractConditionalFu @Override public Function create(Map arguments) { - return new ParticleFunction<>(ParticleConfig.fromMap$function(arguments), getPredicates(arguments)); + return new ParticleFunction<>(getPredicates(arguments), ParticleConfig.fromMap$function(arguments)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java index 1b6e8bf72..f71b2ae08 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java @@ -26,7 +26,7 @@ public class PlaceBlockFunction extends AbstractConditional private final NumberProvider z; private final NumberProvider updateFlags; - public PlaceBlockFunction(LazyReference lazyBlockState, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, List> predicates) { + public PlaceBlockFunction(List> predicates, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, LazyReference lazyBlockState) { super(predicates); this.lazyBlockState = lazyBlockState; this.x = x; @@ -40,7 +40,7 @@ public class PlaceBlockFunction extends AbstractConditional Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - world.setBlockState(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); + world.setBlockState(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); } } @@ -58,12 +58,7 @@ public class PlaceBlockFunction extends AbstractConditional @Override public Function create(Map arguments) { String state = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("block-state"), "warning.config.function.place_block.missing_block_state"); - return new PlaceBlockFunction<>(LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(state)), - NumberProviders.fromObject(arguments.getOrDefault("x", "")), - NumberProviders.fromObject(arguments.getOrDefault("y", "")), - NumberProviders.fromObject(arguments.getOrDefault("z", "")), - Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), - getPredicates(arguments)); + return new PlaceBlockFunction<>(getPredicates(arguments), NumberProviders.fromObject(arguments.getOrDefault("x", "")), NumberProviders.fromObject(arguments.getOrDefault("y", "")), NumberProviders.fromObject(arguments.getOrDefault("z", "")), Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(state))); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java index 00004bbb8..cc71ff1ba 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java @@ -31,15 +31,7 @@ public class PlaySoundFunction extends AbstractConditionalF private final PlayerSelector selector; public PlaySoundFunction( - Key soundEvent, - NumberProvider x, - NumberProvider y, - NumberProvider z, - NumberProvider volume, - NumberProvider pitch, - SoundSource source, - PlayerSelector selector, - List> predicates + List> predicates, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider volume, NumberProvider pitch, SoundSource source, PlayerSelector selector, Key soundEvent ) { super(predicates); this.soundEvent = soundEvent; @@ -89,7 +81,7 @@ public class PlaySoundFunction extends AbstractConditionalF NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", 1)); SoundSource source = Optional.ofNullable(arguments.get("source")).map(String::valueOf).map(it -> SoundSource.valueOf(it.toUpperCase(Locale.ENGLISH))).orElse(SoundSource.MASTER); PlayerSelector selector = PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()); - return new PlaySoundFunction<>(soundEvent, x, y, z, volume, pitch, source, selector, getPredicates(arguments)); + return new PlaySoundFunction<>(getPredicates(arguments), x, y, z, volume, pitch, source, selector, soundEvent); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlayTotemAnimationFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlayTotemAnimationFunction.java new file mode 100644 index 000000000..0b1eb11b7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlayTotemAnimationFunction.java @@ -0,0 +1,97 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.DataComponentKeys; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +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.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class PlayTotemAnimationFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final Key item; + @Nullable + private final Key sound; + private final NumberProvider volume; + private final NumberProvider pitch; + private final boolean silent; + + public PlayTotemAnimationFunction( + List> predicates, + PlayerSelector selector, + Key item, + @Nullable Key sound, + NumberProvider volume, + NumberProvider pitch, + boolean silent + ) { + super(predicates); + this.selector = selector; + this.item = item; + this.sound = sound; + this.volume = volume; + this.pitch = pitch; + this.silent = silent; + } + + @Override + protected void runInternal(CTX ctx) { + CustomItem customItem = CraftEngine.instance().itemManager().getCustomItem(this.item).orElse(null); + if (customItem == null) { + return; + } + SoundData soundData = null; + if (this.sound != null) { + soundData = SoundData.of( + this.sound, + SoundData.SoundValue.fixed(Math.max(this.volume.getFloat(ctx), 0f)), + SoundData.SoundValue.fixed(MiscUtils.clamp(this.pitch.getFloat(ctx), 0f, 2f)) + ); + } + for (Player player : this.selector.get(ctx)) { + Item buildItem = customItem.buildItem(player); + if (VersionHelper.isOrAbove1_21_2()) { + buildItem.setJavaComponent(DataComponentKeys.DEATH_PROTECTION, Map.of()); + } + player.sendTotemAnimation(buildItem, soundData, this.silent); + } + } + + @Override + public Key type() { + return CommonFunctions.PLAY_TOTEM_ANIMATION; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + PlayerSelector selector = PlayerSelectors.fromObject(arguments.getOrDefault("target", "self"), conditionFactory()); + Key item = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("item"), "warning.config.function.play_totem_animation.missing_item")); + @Nullable Key sound = Optional.ofNullable(arguments.get("sound")).map(String::valueOf).map(Key::of).orElse(null); + NumberProvider volume = NumberProviders.fromObject(arguments.getOrDefault("volume", 1f)); + NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", 1f)); + boolean silent = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("silent", false), "silent"); + return new PlayTotemAnimationFunction<>(getPredicates(arguments), selector, item, sound, volume, pitch, silent); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java index fa25af75f..cf87e324c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java @@ -21,7 +21,7 @@ public class PotionEffectFunction extends AbstractCondition private final boolean ambient; private final boolean particles; - public PotionEffectFunction(Key potionEffectType, NumberProvider duration, NumberProvider amplifier, boolean ambient, boolean particles, PlayerSelector selector, List> predicates) { + public PotionEffectFunction(List> predicates, NumberProvider duration, NumberProvider amplifier, boolean ambient, boolean particles, PlayerSelector selector, Key potionEffectType) { super(predicates); this.potionEffectType = potionEffectType; this.duration = duration; @@ -63,7 +63,7 @@ public class PotionEffectFunction extends AbstractCondition NumberProvider amplifier = NumberProviders.fromObject(arguments.getOrDefault("amplifier", 0)); boolean ambient = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("ambient", false), "ambient"); boolean particles = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("particles", true), "particles"); - return new PotionEffectFunction<>(effectType, duration, amplifier, ambient, particles, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new PotionEffectFunction<>(getPredicates(arguments), duration, amplifier, ambient, particles, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), effectType); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java index 84982ec78..5beae2853 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java @@ -19,7 +19,7 @@ public class RemoveCooldownFunction extends AbstractConditi private final String id; private final boolean all; - public RemoveCooldownFunction(String id, boolean all, PlayerSelector selector, List> predicates) { + public RemoveCooldownFunction(List> predicates, boolean all, PlayerSelector selector, String id) { super(predicates); this.selector = selector; this.id = id; @@ -59,10 +59,10 @@ public class RemoveCooldownFunction extends AbstractConditi public Function create(Map arguments) { boolean all = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("all", false), "all"); if (all) { - return new RemoveCooldownFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new RemoveCooldownFunction<>(getPredicates(arguments), true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), null); } else { String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.remove_cooldown.missing_id"); - return new RemoveCooldownFunction<>(id, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new RemoveCooldownFunction<>(getPredicates(arguments), false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), id); } } } 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..3fb802c80 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 @@ -24,7 +24,7 @@ public class RemoveFurnitureFunction extends AbstractCondit private final boolean dropLoot; private final boolean playSound; - public RemoveFurnitureFunction(boolean dropLoot, boolean playSound, List> predicates) { + public RemoveFurnitureFunction(List> predicates, boolean playSound, boolean dropLoot) { super(predicates); this.dropLoot = dropLoot; this.playSound = playSound; @@ -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) { @@ -80,7 +80,7 @@ public class RemoveFurnitureFunction extends AbstractCondit public Function create(Map arguments) { boolean dropLoot = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("drop-loot", true), "drop-loot"); boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound"); - return new RemoveFurnitureFunction<>(dropLoot, playSound, getPredicates(arguments)); + return new RemoveFurnitureFunction<>(getPredicates(arguments), playSound, dropLoot); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java index 55322a946..70af392c1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java @@ -17,7 +17,7 @@ public class RemovePotionEffectFunction extends AbstractCon private final Key potionEffectType; private final boolean all; - public RemovePotionEffectFunction(Key potionEffectType, boolean all, PlayerSelector selector, List> predicates) { + public RemovePotionEffectFunction(List> predicates, boolean all, PlayerSelector selector, Key potionEffectType) { super(predicates); this.potionEffectType = potionEffectType; this.selector = selector; @@ -54,10 +54,10 @@ public class RemovePotionEffectFunction extends AbstractCon public Function create(Map arguments) { boolean all = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("all", false), "all"); if (all) { - return new RemovePotionEffectFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new RemovePotionEffectFunction<>(getPredicates(arguments), true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), null); } else { Key effectType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("potion-effect"), "warning.config.function.remove_potion_effect.missing_potion_effect")); - return new RemovePotionEffectFunction<>(effectType, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new RemovePotionEffectFunction<>(getPredicates(arguments), false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), effectType); } } } 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..c2bfed095 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,21 +21,12 @@ 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; public ReplaceFurnitureFunction( - Key newFurnitureId, - NumberProvider x, - NumberProvider y, - NumberProvider z, - NumberProvider pitch, - NumberProvider yaw, - AnchorType anchorType, - boolean dropLoot, - boolean playSound, - List> predicates + List> predicates, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider pitch, NumberProvider yaw, String variant, boolean dropLoot, boolean playSound, Key newFurnitureId ) { super(predicates); this.newFurnitureId = newFurnitureId; @@ -45,7 +35,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 +61,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 +84,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<>(getPredicates(arguments), x, y, z, pitch, yaw, variant, dropLoot, playSound, furnitureId); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RotateFurnitureFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RotateFurnitureFunction.java new file mode 100644 index 000000000..eb16cedd0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RotateFurnitureFunction.java @@ -0,0 +1,74 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.furniture.Furniture; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +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.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.List; +import java.util.Map; + +public class RotateFurnitureFunction extends AbstractConditionalFunction { + private final NumberProvider degree; + private final List> successFunctions; + private final List> failureFunctions; + + public RotateFurnitureFunction(List> predicates, NumberProvider degree, List> successFunctions, List> failureFunctions) { + super(predicates); + this.degree = degree; + this.successFunctions = successFunctions; + this.failureFunctions = failureFunctions; + } + + @Override + public void runInternal(CTX ctx) { + ctx.getOptionalParameter(DirectContextParameters.FURNITURE).ifPresent(furniture -> rotateFurniture(ctx, furniture)); + } + + public void rotateFurniture(CTX ctx, Furniture furniture) { + if (!furniture.isValid()) return; + WorldPosition position = furniture.position(); + WorldPosition newPos = new WorldPosition(position.world, position.x, position.y, position.z, position.xRot, position.yRot + this.degree.getFloat(ctx)); + furniture.moveTo(newPos).thenAccept(success -> { + if (success) { + for (Function successFunction : this.successFunctions) { + successFunction.run(ctx); + } + } else { + for (Function failureFunction : this.failureFunctions) { + failureFunction.run(ctx); + } + } + }); + } + + @Override + public Key type() { + return CommonFunctions.ROTATE_FURNITURE; + } + + public NumberProvider degree() { + return this.degree; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + NumberProvider degree = NumberProviders.fromObject(arguments.getOrDefault("degree", 90)); + List> onSuccess = ResourceConfigUtils.parseConfigAsList(arguments.get("on-success"), EventFunctions::fromMap); + List> onFailure = ResourceConfigUtils.parseConfigAsList(arguments.get("on-failure"), EventFunctions::fromMap); + return new RotateFurnitureFunction<>(getPredicates(arguments), degree, onSuccess, onFailure); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java index 020c44fe7..154bbc919 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -21,7 +21,7 @@ public class RunFunction extends AbstractConditionalFunctio private final List> functions; private final NumberProvider delay; - public RunFunction(List> functions, NumberProvider delay, List> predicates) { + public RunFunction(List> predicates, NumberProvider delay, List> functions) { super(predicates); this.functions = functions; this.delay = delay; @@ -48,7 +48,7 @@ public class RunFunction extends AbstractConditionalFunctio for (Function function : functions) { function.run(ctx); } - }, delay, pos.world().platformWorld(), MiscUtils.fastFloor(pos.x()) >> 4, MiscUtils.fastFloor(pos.z()) >> 4); + }, delay, pos.world().platformWorld(), MiscUtils.floor(pos.x()) >> 4, MiscUtils.floor(pos.z()) >> 4); } } } @@ -75,7 +75,7 @@ public class RunFunction extends AbstractConditionalFunctio for (Map function : functions) { fun.add(this.functionFactory.apply(function)); } - return new RunFunction<>(fun, delay, getPredicates(arguments)); + return new RunFunction<>(getPredicates(arguments), delay, fun); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java index 5634d5f19..a773bdcb1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java @@ -21,7 +21,7 @@ public class SetCooldownFunction extends AbstractConditiona private final String id; private final boolean add; - public SetCooldownFunction(TextProvider time, String id, boolean add, PlayerSelector selector, List> predicates) { + public SetCooldownFunction(List> predicates, String id, boolean add, PlayerSelector selector, TextProvider time) { super(predicates); this.time = time; this.add = add; @@ -66,7 +66,7 @@ public class SetCooldownFunction extends AbstractConditiona String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.set_cooldown.missing_id"); String time = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("time"), "warning.config.function.set_cooldown.missing_time"); boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); - return new SetCooldownFunction<>(TextProviders.fromString(time), id, add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new SetCooldownFunction<>(getPredicates(arguments), id, add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), TextProviders.fromString(time)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java index aa005230c..b6cd3f22d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java @@ -17,7 +17,7 @@ public class SetCountFunction extends AbstractConditionalFu private final NumberProvider count; private final boolean add; - public SetCountFunction(NumberProvider count, boolean add, List> predicates) { + public SetCountFunction(List> predicates, boolean add, NumberProvider count) { super(predicates); this.count = count; this.add = add; @@ -51,7 +51,7 @@ public class SetCountFunction extends AbstractConditionalFu public Function create(Map arguments) { Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.set_count.missing_count"); boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); - return new SetCountFunction<>(NumberProviders.fromObject(value), add, getPredicates(arguments)); + return new SetCountFunction<>(getPredicates(arguments), add, NumberProviders.fromObject(value)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetExpFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetExpFunction.java new file mode 100644 index 000000000..5e07b336a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetExpFunction.java @@ -0,0 +1,61 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +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.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +public class SetExpFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final NumberProvider count; + private final BiConsumer operation; + + public SetExpFunction(List> predicates, PlayerSelector selector, NumberProvider count, BiConsumer operation) { + super(predicates); + this.selector = selector; + this.count = count; + this.operation = operation; + } + + @Override + protected void runInternal(CTX ctx) { + for (Player player : this.selector.get(ctx)) { + this.operation.accept(player, this.count.getInt(ctx)); + } + } + + @Override + public Key type() { + return CommonFunctions.SET_EXP; + } + + public static class FactoryImpl extends AbstractFactory { + private static final BiConsumer ADD_POINTS = Player::giveExperiencePoints; + private static final BiConsumer SET_POINTS = (player, experience) -> { + if (experience < player.getXpNeededForNextLevel()) { + player.setExperiencePoints(experience); + } + }; + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + PlayerSelector selector = PlayerSelectors.fromObject(arguments.getOrDefault("target", "self"), conditionFactory()); + Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.set_exp.missing_count"); + boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); + return new SetExpFunction<>(getPredicates(arguments), selector, NumberProviders.fromObject(value), add ? ADD_POINTS : SET_POINTS); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java index 544a2a147..1c77bab4e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java @@ -19,7 +19,7 @@ public class SetFoodFunction extends AbstractConditionalFun private final NumberProvider count; private final boolean add; - public SetFoodFunction(NumberProvider count, boolean add, PlayerSelector selector, List> predicates) { + public SetFoodFunction(List> predicates, boolean add, PlayerSelector selector, NumberProvider count) { super(predicates); this.count = count; this.add = add; @@ -54,7 +54,7 @@ public class SetFoodFunction extends AbstractConditionalFun public Function create(Map arguments) { Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("food"), "warning.config.function.set_food.missing_food"); boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); - return new SetFoodFunction<>(NumberProviders.fromObject(value), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new SetFoodFunction<>(getPredicates(arguments), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), NumberProviders.fromObject(value)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetLevelFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetLevelFunction.java new file mode 100644 index 000000000..e95194529 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetLevelFunction.java @@ -0,0 +1,57 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +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.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +public class SetLevelFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final NumberProvider count; + private final BiConsumer operation; + + public SetLevelFunction(List> predicates, PlayerSelector selector, NumberProvider count, BiConsumer operation) { + super(predicates); + this.selector = selector; + this.count = count; + this.operation = operation; + } + + @Override + protected void runInternal(CTX ctx) { + for (Player player : this.selector.get(ctx)) { + this.operation.accept(player, this.count.getInt(ctx)); + } + } + + @Override + public Key type() { + return CommonFunctions.SET_LEVEL; + } + + public static class FactoryImpl extends AbstractFactory { + private static final BiConsumer ADD_LEVELS = Player::giveExperienceLevels; + private static final BiConsumer SET_LEVELS = Player::setExperienceLevels; + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + PlayerSelector selector = PlayerSelectors.fromObject(arguments.getOrDefault("target", "self"), conditionFactory()); + Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.set_level.missing_count"); + boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); + return new SetLevelFunction<>(getPredicates(arguments), selector, NumberProviders.fromObject(value), add ? ADD_LEVELS : SET_LEVELS); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java index 9c6d24e98..56fbef9d2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java @@ -19,7 +19,7 @@ public class SetSaturationFunction extends AbstractConditio private final NumberProvider count; private final boolean add; - public SetSaturationFunction(NumberProvider count, boolean add, PlayerSelector selector, List> predicates) { + public SetSaturationFunction(List> predicates, boolean add, PlayerSelector selector, NumberProvider count) { super(predicates); this.count = count; this.add = add; @@ -54,7 +54,7 @@ public class SetSaturationFunction extends AbstractConditio public Function create(Map arguments) { Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("saturation"), "warning.config.function.set_saturation.missing_saturation"); boolean add = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("add", false), "add"); - return new SetSaturationFunction<>(NumberProviders.fromObject(value), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + return new SetSaturationFunction<>(getPredicates(arguments), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), NumberProviders.fromObject(value)); } } } 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..e7e91d5e2 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,19 +22,11 @@ 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( - Key furnitureId, - NumberProvider x, - NumberProvider y, - NumberProvider z, - NumberProvider pitch, - NumberProvider yaw, - AnchorType anchorType, - boolean playSound, - List> predicates + List> predicates, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider pitch, NumberProvider yaw, String variant, boolean playSound, Key furnitureId ) { super(predicates); this.furnitureId = furnitureId; @@ -45,7 +35,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 +49,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 +76,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<>(getPredicates(arguments), x, y, z, pitch, yaw, variant, playSound, furnitureId); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java index 935fa210e..3f79089c6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java @@ -15,7 +15,7 @@ import java.util.Optional; public class SwingHandFunction extends AbstractConditionalFunction { private final Optional hand; - public SwingHandFunction(Optional hand, List> predicates) { + public SwingHandFunction(List> predicates, Optional hand) { super(predicates); this.hand = hand; } @@ -46,7 +46,7 @@ public class SwingHandFunction extends AbstractConditionalF @Override public Function create(Map arguments) { Optional optionalHand = Optional.ofNullable(arguments.get("hand")).map(it -> InteractionHand.valueOf(it.toString().toUpperCase(Locale.ENGLISH))); - return new SwingHandFunction<>(optionalHand, getPredicates(arguments)); + return new SwingHandFunction<>(getPredicates(arguments), optionalHand); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java index 09da0dc26..2cd56d21e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java @@ -30,7 +30,7 @@ public class TransformBlockFunction extends AbstractConditi private final NumberProvider z; private final NumberProvider updateFlags; - public TransformBlockFunction(LazyReference lazyBlockState, CompoundTag properties, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, List> predicates) { + public TransformBlockFunction(List> predicates, CompoundTag properties, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, LazyReference lazyBlockState) { super(predicates); this.properties = properties; this.x = x; @@ -45,9 +45,9 @@ public class TransformBlockFunction extends AbstractConditi Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties); CompoundTag newProperties = new CompoundTag(); for (String propertyName : existingBlockState.getPropertyNames()) { @@ -84,13 +84,8 @@ public class TransformBlockFunction extends AbstractConditi } } return new TransformBlockFunction<>( - LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(block)), - properties, - NumberProviders.fromObject(arguments.getOrDefault("x", "")), - NumberProviders.fromObject(arguments.getOrDefault("y", "")), - NumberProviders.fromObject(arguments.getOrDefault("z", "")), - Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), - getPredicates(arguments)); + getPredicates(arguments), properties, NumberProviders.fromObject(arguments.getOrDefault("x", "")), NumberProviders.fromObject(arguments.getOrDefault("y", "")), NumberProviders.fromObject(arguments.getOrDefault("z", "")), Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), LazyReference.lazyReference(() -> CraftEngine.instance().blockManager().createBlockState(block)) + ); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java index 693647cf5..dd079a78c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java @@ -26,7 +26,7 @@ public class UpdateBlockPropertyFunction extends AbstractCo private final NumberProvider z; private final NumberProvider updateFlags; - public UpdateBlockPropertyFunction(CompoundTag properties, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, List> predicates) { + public UpdateBlockPropertyFunction(List> predicates, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, CompoundTag properties) { super(predicates); this.properties = properties; this.x = x; @@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction extends AbstractCo Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); ExistingBlock blockAt = world.getBlock(x, y, z); BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); @@ -67,12 +67,8 @@ public class UpdateBlockPropertyFunction extends AbstractCo for (Map.Entry entry : state.entrySet()) { properties.putString(entry.getKey(), String.valueOf(entry.getValue())); } - return new UpdateBlockPropertyFunction<>(properties, - NumberProviders.fromObject(arguments.getOrDefault("x", "")), - NumberProviders.fromObject(arguments.getOrDefault("y", "")), - NumberProviders.fromObject(arguments.getOrDefault("z", "")), - Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), - getPredicates(arguments)); + return new UpdateBlockPropertyFunction<>(getPredicates(arguments), NumberProviders.fromObject(arguments.getOrDefault("x", "")), NumberProviders.fromObject(arguments.getOrDefault("y", "")), NumberProviders.fromObject(arguments.getOrDefault("z", "")), Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())), properties + ); } } } 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 628b8d54c..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; @@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); 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/context/parameter/PlayerParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java index ee6033ccd..668a0c6d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java @@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java index c646658fb..3808e3f00 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java @@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); } @SuppressWarnings("unchecked") 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 72c6ecdc0..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,453 +1,408 @@ 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; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Arrays; -import java.util.BitSet; -public class EntityCulling { - - // 面掩码常量 - private static final int ON_MIN_X = 0x01; - private static final int ON_MAX_X = 0x02; - private static final int ON_MIN_Y = 0x04; - private static final int ON_MAX_Y = 0x08; - private static final int ON_MIN_Z = 0x10; - private static final int ON_MAX_Z = 0x20; - - private final int reach; - private final double aabbExpansion; - private final DataProvider provider; - private final OcclusionCache cache; - - // 重用数据结构,减少GC压力 - private final BitSet skipList = new BitSet(); - private final MutableVec3d[] targetPoints = new MutableVec3d[15]; - private final MutableVec3d targetPos = new MutableVec3d(0, 0, 0); - private final int[] cameraPos = new int[3]; - private final boolean[] dotselectors = new boolean[14]; +public final class EntityCulling { + public static final int MAX_SAMPLES = 14; + private final Player player; + private final boolean[] dotSelectors = new boolean[MAX_SAMPLES]; + private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; private final int[] lastHitBlock = new int[3]; - - // 状态标志 - private boolean allowRayChecks = false; - private boolean allowWallClipping = false; + 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(int maxDistance, DataProvider provider) { - this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5); - } - - public EntityCulling(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) { - this.reach = maxDistance; - this.provider = provider; - this.cache = cache; - this.aabbExpansion = aabbExpansion; - // 预先初始化点对象 - for(int i = 0; i < targetPoints.length; i++) { - targetPoints[i] = new MutableVec3d(0, 0, 0); + public EntityCulling(Player player) { + this.player = player; + for (int i = 0; i < MAX_SAMPLES; i++) { + this.targetPoints[i] = new MutableVec3d(0,0,0); } } - public boolean isAABBVisible(Vec3d aabbMin, MutableVec3d aabbMax, MutableVec3d viewerPosition) { - try { - // 计算包围盒范围 - int maxX = MiscUtils.fastFloor(aabbMax.x + aabbExpansion); - int maxY = MiscUtils.fastFloor(aabbMax.y + aabbExpansion); - int maxZ = MiscUtils.fastFloor(aabbMax.z + aabbExpansion); - int minX = MiscUtils.fastFloor(aabbMin.x - aabbExpansion); - int minY = MiscUtils.fastFloor(aabbMin.y - aabbExpansion); - int minZ = MiscUtils.fastFloor(aabbMin.z - aabbExpansion); - - cameraPos[0] = MiscUtils.fastFloor(viewerPosition.x); - cameraPos[1] = MiscUtils.fastFloor(viewerPosition.y); - cameraPos[2] = MiscUtils.fastFloor(viewerPosition.z); - - // 判断是否在包围盒内部 - Relative relX = Relative.from(minX, maxX, cameraPos[0]); - Relative relY = Relative.from(minY, maxY, cameraPos[1]); - Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]); - - if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { - return true; - } - - skipList.clear(); - - // 1. 快速检查缓存 - int id = 0; - for (int x = minX; x <= maxX; x++) { - for (int y = minY; y <= maxY; y++) { - for (int z = minZ; z <= maxZ; z++) { - int cachedValue = getCacheValue(x, y, z); - if (cachedValue == 1) return true; // 缓存显示可见 - if (cachedValue != 0) skipList.set(id); // 缓存显示不可见或遮挡 - id++; - } - } - } - - allowRayChecks = false; - id = 0; - - // 2. 遍历体素进行光线投射检查 - for (int x = minX; x <= maxX; x++) { - // 预计算X轴面的可见性和边缘数据 - byte visibleOnFaceX = 0; - byte faceEdgeDataX = 0; - if (x == minX) { faceEdgeDataX |= ON_MIN_X; if (relX == Relative.POSITIVE) visibleOnFaceX |= ON_MIN_X; } - if (x == maxX) { faceEdgeDataX |= ON_MAX_X; if (relX == Relative.NEGATIVE) visibleOnFaceX |= ON_MAX_X; } - - for (int y = minY; y <= maxY; y++) { - byte visibleOnFaceY = visibleOnFaceX; - byte faceEdgeDataY = faceEdgeDataX; - if (y == minY) { faceEdgeDataY |= ON_MIN_Y; if (relY == Relative.POSITIVE) visibleOnFaceY |= ON_MIN_Y; } - if (y == maxY) { faceEdgeDataY |= ON_MAX_Y; if (relY == Relative.NEGATIVE) visibleOnFaceY |= ON_MAX_Y; } - - for (int z = minZ; z <= maxZ; z++) { - // 如果缓存已标记为不可见,跳过 - if(skipList.get(id++)) continue; - - byte visibleOnFace = visibleOnFaceY; - byte faceEdgeData = faceEdgeDataY; - if (z == minZ) { faceEdgeData |= ON_MIN_Z; if (relZ == Relative.POSITIVE) visibleOnFace |= ON_MIN_Z; } - if (z == maxZ) { faceEdgeData |= ON_MAX_Z; if (relZ == Relative.NEGATIVE) visibleOnFace |= ON_MAX_Z; } - - if (visibleOnFace != 0) { - targetPos.set(x, y, z); - // 检查单个体素是否可见 - if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) { - return true; - } - } - } - } - } - return false; - } catch (Throwable t) { - t.printStackTrace(); - return true; // 发生异常默认可见,防止渲染错误 - } + public void setDistanceScale(double distanceScale) { + this.distanceScale = distanceScale; } - // 接口定义 - public interface DataProvider { - boolean prepareChunk(int chunkX, int chunkZ); - boolean isOpaqueFullCube(int x, int y, int z); - default void cleanup() {} - default void checkingPosition(MutableVec3d[] targetPoints, int size, MutableVec3d viewerPosition) {} + public double distanceScale() { + return distanceScale; } - /** - * 检查单个体素是否对观察者可见 - */ - private boolean isVoxelVisible(MutableVec3d viewerPosition, MutableVec3d position, byte faceData, byte visibleOnFace) { - int targetSize = 0; - Arrays.fill(dotselectors, false); - - // 根据相对位置选择需要检测的关键点(角点和面中心点) - if((visibleOnFace & ON_MIN_X) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_X) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[8] = true; - } - if((visibleOnFace & ON_MIN_Y) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Y) != 0) { dotselectors[3] = dotselectors[4] = dotselectors[7] = true; } - dotselectors[9] = true; - } - if((visibleOnFace & ON_MIN_Z) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Z) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[10] = true; - } - if((visibleOnFace & ON_MAX_X) != 0){ - dotselectors[4] = true; - if((faceData & ~ON_MAX_X) != 0) { dotselectors[5] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[11] = true; - } - if((visibleOnFace & ON_MAX_Y) != 0){ - dotselectors[1] = true; - if((faceData & ~ON_MAX_Y) != 0) { dotselectors[2] = dotselectors[5] = dotselectors[6] = true; } - dotselectors[12] = true; - } - if((visibleOnFace & ON_MAX_Z) != 0){ - dotselectors[2] = true; - if((faceData & ~ON_MAX_Z) != 0) { dotselectors[3] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[13] = true; - } - - // 填充目标点,使用偏移量防止Z-Fighting或精度问题 - if (dotselectors[0]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.05); - if (dotselectors[1]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.05); - if (dotselectors[2]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.95); - if (dotselectors[3]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.95); - if (dotselectors[4]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.05); - if (dotselectors[5]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.05); - if (dotselectors[6]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.95); - if (dotselectors[7]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.95); - // 面中心点 - if (dotselectors[8]) targetPoints[targetSize++].add(position, 0.05, 0.5, 0.5); - if (dotselectors[9]) targetPoints[targetSize++].add(position, 0.5, 0.05, 0.5); - if (dotselectors[10]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.05); - if (dotselectors[11]) targetPoints[targetSize++].add(position, 0.95, 0.5, 0.5); - if (dotselectors[12]) targetPoints[targetSize++].add(position, 0.5, 0.95, 0.5); - if (dotselectors[13]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.95); - - return isVisible(viewerPosition, targetPoints, targetSize); + public void restoreTokenOnTick() { + this.currentTokens = Math.min(Config.entityCullingRateLimitingBucketSize(), this.currentTokens + Config.entityCullingRateLimitingRestorePerTick()); } - // 优化:使用基本数据类型代替对象分配 - private boolean rayIntersection(int[] b, MutableVec3d rayOrigin, double dirX, double dirY, double dirZ) { - double invX = 1.0 / dirX; - double invY = 1.0 / dirY; - double invZ = 1.0 / dirZ; - - double t1 = (b[0] - rayOrigin.x) * invX; - double t2 = (b[0] + 1 - rayOrigin.x) * invX; - double t3 = (b[1] - rayOrigin.y) * invY; - double t4 = (b[1] + 1 - rayOrigin.y) * invY; - double t5 = (b[2] - rayOrigin.z) * invZ; - double t6 = (b[2] + 1 - rayOrigin.z) * invZ; - - double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); - double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); - - // tmax > 0: 射线与AABB相交,但AABB在身后 - // tmin > tmax: 射线不相交 - return tmax > 0 && tmin <= tmax; - } - - /** - * 基于网格的光线追踪 (DDA算法) - */ - private boolean isVisible(MutableVec3d start, MutableVec3d[] targets, int size) { - int startX = cameraPos[0]; - int startY = cameraPos[1]; - int startZ = cameraPos[2]; - - for (int v = 0; v < size; v++) { - MutableVec3d target = targets[v]; - - double relX = start.x - target.x; - double relY = start.y - target.y; - double relZ = start.z - target.z; - - // 优化:避免在此处创建新的Vec3d对象进行归一化 - if(allowRayChecks) { - double len = Math.sqrt(relX * relX + relY * relY + relZ * relZ); - // 传入归一化后的方向分量 - if (rayIntersection(lastHitBlock, start, relX / len, relY / len, relZ / len)) { - continue; - } - } - - double dimAbsX = Math.abs(relX); - double dimAbsY = Math.abs(relY); - double dimAbsZ = Math.abs(relZ); - - double dimFracX = 1f / dimAbsX; - double dimFracY = 1f / dimAbsY; - double dimFracZ = 1f / dimAbsZ; - - int intersectCount = 1; - int x_inc, y_inc, z_inc; - double t_next_y, t_next_x, t_next_z; - - // 初始化DDA步进参数 - if (dimAbsX == 0f) { - x_inc = 0; t_next_x = dimFracX; - } else if (target.x > start.x) { - x_inc = 1; - intersectCount += MiscUtils.fastFloor(target.x) - startX; - t_next_x = (startX + 1 - start.x) * dimFracX; - } else { - x_inc = -1; - intersectCount += startX - MiscUtils.fastFloor(target.x); - t_next_x = (start.x - startX) * dimFracX; - } - - if (dimAbsY == 0f) { - y_inc = 0; t_next_y = dimFracY; - } else if (target.y > start.y) { - y_inc = 1; - intersectCount += MiscUtils.fastFloor(target.y) - startY; - t_next_y = (startY + 1 - start.y) * dimFracY; - } else { - y_inc = -1; - intersectCount += startY - MiscUtils.fastFloor(target.y); - t_next_y = (start.y - startY) * dimFracY; - } - - if (dimAbsZ == 0f) { - z_inc = 0; t_next_z = dimFracZ; - } else if (target.z > start.z) { - z_inc = 1; - intersectCount += MiscUtils.fastFloor(target.z) - startZ; - t_next_z = (startZ + 1 - start.z) * dimFracZ; - } else { - z_inc = -1; - intersectCount += startZ - MiscUtils.fastFloor(target.z); - t_next_z = (start.z - startZ) * dimFracZ; - } - - boolean finished = stepRay(startX, startY, startZ, - dimFracX, dimFracY, dimFracZ, intersectCount, - x_inc, y_inc, z_inc, - t_next_y, t_next_x, t_next_z); - - provider.cleanup(); - if (finished) { - cacheResult(targets[0], true); - return true; - } else { - allowRayChecks = true; - } + public boolean takeToken() { + if (this.currentTokens > 0) { + this.currentTokens--; + return true; } - cacheResult(targets[0], false); return false; } - private boolean stepRay(int currentX, int currentY, int currentZ, - double distInX, double distInY, double distInZ, - int n, int x_inc, int y_inc, int z_inc, - double t_next_y, double t_next_x, double t_next_z) { - - allowWallClipping = true; // 初始允许穿墙直到移出起始方块 + public boolean isVisible(CullingData cullable, Vec3d cameraPos, boolean rayTracing) { + // 情空标志位 + this.canCheckLastHitBlock = false; + this.hitBlockCount = 0; + AABB aabb = cullable.aabb; + double aabbExpansion = cullable.aabbExpansion; - for (; n > 1; n--) { - // 检查缓存状态:2=遮挡 - int cVal = getCacheValue(currentX, currentY, currentZ); - if (cVal == 2 && !allowWallClipping) { - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; + 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; + double cameraZ = cameraPos.z; + + Relative relX = Relative.from(minX, maxX, cameraX); + Relative relY = Relative.from(minY, maxY, cameraY); + Relative relZ = Relative.from(minZ, maxZ, cameraZ); + + // 相机位于实体内部 + if (relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { + return true; + } + + // 如果设置了最大距离 + double maxDistance = cullable.maxDistance * this.distanceScale; + if (maxDistance > 0) { + // 计算AABB到相机的最小距离 + double distanceSq = 0.0; + // 计算XYZ轴方向的距离 + distanceSq += distanceSq(minX, maxX, cameraX, relX); + distanceSq += distanceSq(minY, maxY, cameraY, relY); + distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ); + // 检查距离是否超过最大值 + double maxDistanceSq = maxDistance * maxDistance; + // 超过最大距离,剔除 + if (distanceSq > maxDistanceSq) { return false; } + } - if (cVal == 0) { - // 未缓存,查询Provider - int chunkX = currentX >> 4; - int chunkZ = currentZ >> 4; - if (!provider.prepareChunk(chunkX, chunkZ)) return false; + if (!rayTracing || !cullable.rayTracing) { + return true; + } - if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) { - if (!allowWallClipping) { - cache.setLastHidden(); - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; - return false; - } - } else { - allowWallClipping = false; - cache.setLastVisible(); + // 清空之前的缓存 + Arrays.fill(this.dotSelectors, false); + if (relX == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[2] = this.dotSelectors[4] = this.dotSelectors[6] = this.dotSelectors[10] = true; + } else if (relX == Relative.NEGATIVE) { + this.dotSelectors[1] = this.dotSelectors[3] = this.dotSelectors[5] = this.dotSelectors[7] = this.dotSelectors[11] = true; + } + if (relY == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[12] = true; + } else if (relY == Relative.NEGATIVE) { + this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[13] = true; + } + if (relZ == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[8] = true; + } else if (relZ == Relative.NEGATIVE) { + this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[9] = true; + } + + int size = 0; + if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ); + if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ); + if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ); + if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ); + if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ); + if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ); + if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ); + if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ); + // 面中心点 + double averageX = (minX + maxX) / 2.0; + double averageY = (minY + maxY) / 2.0; + double averageZ = (minZ + maxZ) / 2.0; + if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ); + if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ); + 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 (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); + } + + /** + * 检测射线与轴对齐边界框(AABB)是否相交 + * 使用slab方法进行射线-AABB相交检测 + */ + private boolean rayIntersection(int x, int y, int z, Vec3d rayOrigin, MutableVec3d rayDirection) { + // 计算射线方向的倒数,避免除法运算 + // 这对于处理射线方向分量为0的情况很重要 + MutableVec3d inverseRayDirection = new MutableVec3d(1, 1, 1).divide(rayDirection); + + // 计算射线与边界框各对面(slab)的相交参数 + // 对于每个轴,计算射线进入和退出该轴对应两个平面的时间 + double tMinX = (x - rayOrigin.x) * inverseRayDirection.x; + double tMaxX = (x + 1 - rayOrigin.x) * inverseRayDirection.x; + double tMinY = (y - rayOrigin.y) * inverseRayDirection.y; + double tMaxY = (y + 1 - rayOrigin.y) * inverseRayDirection.y; + double tMinZ = (z - rayOrigin.z) * inverseRayDirection.z; + double tMaxZ = (z + 1 - rayOrigin.z) * inverseRayDirection.z; + + // 计算射线进入边界框的最大时间(最近进入点) + // 需要取各轴进入时间的最大值,因为射线必须进入所有轴的范围内 + double tEntry = Math.max(Math.max(Math.min(tMinX, tMaxX), Math.min(tMinY, tMaxY)), Math.min(tMinZ, tMaxZ)); + + // 计算射线退出边界框的最短时间(最早退出点) + // 需要取各轴退出时间的最小值,因为射线一旦退出任一轴的范围就离开了边界框 + double tExit = Math.min(Math.min(Math.max(tMinX, tMaxX), Math.max(tMinY, tMaxY)), Math.max(tMinZ, tMaxZ)); + + // 如果最早退出时间大于0,说明整个边界框在射线起点后面 + // 这种情况我们视为不相交,因为通常我们只关心射线前方的相交 + if (tExit > 0) { + return false; + } + + // 如果进入时间大于退出时间,说明没有有效的相交区间 + // 这发生在射线完全错过边界框的情况下 + // 满足以下条件说明射线与边界框相交: + // 1. 进入时间 <= 退出时间(存在有效相交区间) + // 2. 退出时间 <= 0(边界框至少有一部分在射线起点前方或包含起点) + return tEntry <= tExit; + } + + /** + * 使用3D DDA算法检测从起点到多个目标点的视线是否通畅 + * 算法基于数字微分分析,遍历射线路径上的所有方块 + */ + private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) { + + // 起点所在方块的整数坐标(世界坐标转换为方块坐标) + int startBlockX = MiscUtils.floor(start.x); + int startBlockY = MiscUtils.floor(start.y); + int startBlockZ = MiscUtils.floor(start.z); + + // 遍历所有目标点进行视线检测 + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + MutableVec3d currentTarget = targets[targetIndex]; + + // 计算起点到目标的相对向量(世界坐标差) + double deltaX = start.x - currentTarget.x; + double deltaY = start.y - currentTarget.y; + double deltaZ = start.z - currentTarget.z; + + // 检查之前命中的方块,大概率还是命中 + if (this.canCheckLastHitBlock) { + if (rayIntersection(this.lastHitBlock[0], this.lastHitBlock[1], this.lastHitBlock[2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { + continue; } - } else if(cVal == 1) { - allowWallClipping = false; } - // DDA算法选择下一个体素 - if (t_next_y < t_next_x && t_next_y < t_next_z) { - currentY += y_inc; - t_next_y += distInY; - } else if (t_next_x < t_next_y && t_next_x < t_next_z) { - currentX += x_inc; - t_next_x += distInX; + // 计算相对向量的绝对值,用于确定各方向上的距离 + double absDeltaX = Math.abs(deltaX); + double absDeltaY = Math.abs(deltaY); + double absDeltaZ = Math.abs(deltaZ); + + // 预计算每单位距离在各方块边界上的步进增量 + // 这些值表示射线穿过一个方块所需的时间分数 + double stepIncrementX = 1.0 / absDeltaX; + double stepIncrementY = 1.0 / absDeltaY; + double stepIncrementZ = 1.0 / absDeltaZ; + + // 射线将穿过的总方块数量(包括起点和终点) + int totalBlocksToCheck = 1; + + // 各方块坐标的步进方向(1: 正向, -1: 反向, 0: 静止) + int stepDirectionX, stepDirectionY, stepDirectionZ; + + // 到下一个方块边界的时间参数(射线参数化表示) + double nextStepTimeX, nextStepTimeY, nextStepTimeZ; + + // X方向步进参数计算 + if (absDeltaX == 0.0) { + // X方向无变化,射线平行于YZ平面 + stepDirectionX = 0; + nextStepTimeX = stepIncrementX; + } else if (currentTarget.x > start.x) { + // 目标在起点右侧,向右步进 + stepDirectionX = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.x) - startBlockX; + nextStepTimeX = (startBlockX + 1 - start.x) * stepIncrementX; } else { - currentZ += z_inc; - t_next_z += distInZ; + // 目标在起点左侧,向左步进 + stepDirectionX = -1; + totalBlocksToCheck += startBlockX - MiscUtils.floor(currentTarget.x); + nextStepTimeX = (start.x - startBlockX) * stepIncrementX; + } + + // Y方向步进参数计算 + if (absDeltaY == 0.0) { + // Y方向无变化,射线平行于XZ平面 + stepDirectionY = 0; + nextStepTimeY = stepIncrementY; + } else if (currentTarget.y > start.y) { + // 目标在起点上方,向上步进 + stepDirectionY = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.y) - startBlockY; + nextStepTimeY = (startBlockY + 1 - start.y) * stepIncrementY; + } else { + // 目标在起点下方,向下步进 + stepDirectionY = -1; + totalBlocksToCheck += startBlockY - MiscUtils.floor(currentTarget.y); + nextStepTimeY = (start.y - startBlockY) * stepIncrementY; + } + + // Z方向步进参数计算 + if (absDeltaZ == 0.0) { + // Z方向无变化,射线平行于XY平面 + stepDirectionZ = 0; + nextStepTimeZ = stepIncrementZ; + } else if (currentTarget.z > start.z) { + // 目标在起点前方,向前步进 + stepDirectionZ = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.z) - startBlockZ; + nextStepTimeZ = (startBlockZ + 1 - start.z) * stepIncrementZ; + } else { + // 目标在起点后方,向后步进 + stepDirectionZ = -1; + totalBlocksToCheck += startBlockZ - MiscUtils.floor(currentTarget.z); + nextStepTimeZ = (start.z - startBlockZ) * stepIncrementZ; + } + + // 执行DDA步进算法,遍历射线路径上的所有方块 + boolean isLineOfSightClear = stepRay( + startBlockX, startBlockY, startBlockZ, + stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck, + stepDirectionX, stepDirectionY, stepDirectionZ, + nextStepTimeY, nextStepTimeX, nextStepTimeZ); + + // 如果当前目标点可见立即返回 + if (isLineOfSightClear) { + return true; + } else { + this.canCheckLastHitBlock = true; } } + + return false; + } + + 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 currentBlockX = startingX; + int currentBlockY = startingY; + int currentBlockZ = startingZ; + + // 遍历射线路径上的所有方块 + for (; remainingSteps > 0; remainingSteps--) { + + // 检查当前方块是否遮挡视线 + if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { + this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; + this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; + this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; + return false; // 视线被遮挡,立即返回 + } + + // 基于时间参数选择下一个要遍历的方块方向 + // 选择距离最近的方块边界作为下一步 + if (nextStepTimeY < nextStepTimeX && nextStepTimeY < nextStepTimeZ) { + // Y方向边界最近,垂直移动 + currentBlockY += stepDirectionY; + nextStepTimeY += stepSizeY; + } else if (nextStepTimeX < nextStepTimeY && nextStepTimeX < nextStepTimeZ) { + // X方向边界最近,水平移动 + currentBlockX += stepDirectionX; + nextStepTimeX += stepSizeX; + } else { + // Z方向边界最近,深度移动 + currentBlockZ += stepDirectionZ; + nextStepTimeZ += stepSizeZ; + } + } + + // 成功遍历所有中间方块,视线通畅 return true; } - // 缓存状态:-1=无效, 0=未检查, 1=可见, 2=遮挡 - private int getCacheValue(int x, int y, int z) { - x -= cameraPos[0]; - y -= cameraPos[1]; - z -= cameraPos[2]; - if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2 || Math.abs(z) > reach - 2) { + 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; } - return cache.getState(x + reach, y + reach, z + reach); + 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 void cacheResult(MutableVec3d vector, boolean result) { - int cx = MiscUtils.fastFloor(vector.x) - cameraPos[0] + reach; - int cy = MiscUtils.fastFloor(vector.y) - cameraPos[1] + reach; - int cz = MiscUtils.fastFloor(vector.z) - cameraPos[2] + reach; - if (result) cache.setVisible(cx, cy, cz); - else cache.setHidden(cx, cy, cz); + private double distanceSq(double min, double max, double camera, Relative rel) { + if (rel == Relative.NEGATIVE) { + double dx = camera - max; + return dx * dx; + } else if (rel == Relative.POSITIVE) { + double dx = min - camera; + return dx * dx; + } + return 0d; } - public void resetCache() { - this.cache.resetCache(); + private boolean isOccluding(int x, int y, int z) { + int chunkX = x >> 4; + int chunkZ = z >> 4; + ClientChunk trackedChunk; + // 使用上次记录的值,比每次走hash都更快 + if (chunkX == this.lastVisitChunkX && chunkZ == this.lastVisitChunkZ) { + trackedChunk = this.lastVisitChunk; + } else { + trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(chunkX, chunkZ)); + this.lastVisitChunk = trackedChunk; + this.lastVisitChunkX = chunkX; + this.lastVisitChunkZ = chunkZ; + } + if (trackedChunk == null) { + return false; + } + return trackedChunk.isOccluding(x, y, z); + } + + public void removeLastVisitChunkIfMatches(int chunkX, int chunkZ) { + if (this.lastVisitChunk != null && this.lastVisitChunkX == chunkX && this.lastVisitChunkZ == chunkZ) { + this.lastVisitChunk = null; + } } private enum Relative { INSIDE, POSITIVE, NEGATIVE; - public static Relative from(int min, int max, int pos) { - if (max > pos && min > pos) return POSITIVE; - else if (min < pos && max < pos) return NEGATIVE; + public static Relative from(double min, double max, double pos) { + if (min > pos) return POSITIVE; + else if (max < pos) return NEGATIVE; return INSIDE; } } - - public interface OcclusionCache { - void resetCache(); - void setVisible(int x, int y, int z); - void setHidden(int x, int y, int z); - int getState(int x, int y, int z); - void setLastHidden(); - void setLastVisible(); - } - - // 使用位运算压缩存储状态的缓存实现 - public static class ArrayOcclusionCache implements OcclusionCache { - private final int reachX2; - private final byte[] cache; - private int entry, offset; - - public ArrayOcclusionCache(int reach) { - this.reachX2 = reach * 2; - // 每一个位置占2位 - this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4 + 1]; - } - - @Override - public void resetCache() { - Arrays.fill(cache, (byte) 0); - } - - private void calcIndex(int x, int y, int z) { - int positionKey = x + y * reachX2 + z * reachX2 * reachX2; - entry = positionKey / 4; - offset = (positionKey % 4) * 2; - } - - @Override - public void setVisible(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << offset; - } - - @Override - public void setHidden(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << (offset + 1); - } - - @Override - public int getState(int x, int y, int z) { - calcIndex(x, y, z); - return (cache[entry] >> offset) & 3; - } - - @Override - public void setLastVisible() { - cache[entry] |= 1 << offset; - } - - @Override - public void setLastHidden() { - cache[entry] |= 1 << (offset + 1); - } - } -} \ No newline at end of file +} 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..f0d52c08c --- /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 ((player.uuid().hashCode() & 0x7FFFFFFF) % 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); + } 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/gui/category/Category.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java index c4c18bb80..903e9264e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/Category.java @@ -1,10 +1,12 @@ package net.momirealms.craftengine.core.plugin.gui.category; +import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; public class Category implements Comparable { private final Key id; @@ -14,8 +16,9 @@ public class Category implements Comparable { private final List members; private final int priority; private final boolean hidden; + private final Predicate condition; - public Category(Key id, String displayName, List displayLore, Key icon, List members, int priority, boolean hidden) { + public Category(Key id, String displayName, List displayLore, Key icon, List members, int priority, boolean hidden, Predicate condition) { this.id = id; this.displayName = displayName; this.members = new ArrayList<>(members); @@ -23,6 +26,7 @@ public class Category implements Comparable { this.priority = priority; this.displayLore = new ArrayList<>(displayLore); this.hidden = hidden; + this.condition = condition; } public void addMember(String member) { @@ -45,6 +49,11 @@ public class Category implements Comparable { return hidden; } + @NotNull + public Predicate condition() { + return condition; + } + public List displayLore() { return displayLore; } 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..a8f463235 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 @@ -7,20 +7,25 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.ItemManager; import net.momirealms.craftengine.core.item.recipe.*; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; +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.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventConditions; import net.momirealms.craftengine.core.plugin.gui.*; import net.momirealms.craftengine.core.plugin.gui.Ingredient; import net.momirealms.craftengine.core.util.*; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; @SuppressWarnings("DuplicatedCode") public class ItemBrowserManagerImpl implements ItemBrowserManager { @@ -109,14 +114,27 @@ 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(); - List members = MiscUtils.getAsStringList(section.getOrDefault("list", List.of())); + List members; + if (ResourceConfigUtils.getAsBoolean(section.get("all-items"), "all-items")) { + ItemManager itemManager = ItemBrowserManagerImpl.this.plugin.itemManager(); + members = itemManager.loadedItems().keySet().stream().filter(it -> !itemManager.isVanillaItem(it)).map(Key::asString).collect(Collectors.toList()); + } else { + members = MiscUtils.getAsStringList(section.getOrDefault("list", List.of())); + } Key icon = Key.of(section.getOrDefault("icon", ItemKeys.STONE).toString()); int priority = ResourceConfigUtils.getAsInt(section.getOrDefault("priority", 0), "priority"); - Category category = new Category(id, name, MiscUtils.getAsStringList(section.getOrDefault("lore", List.of())), icon, new ArrayList<>(members), priority, - ResourceConfigUtils.getAsBoolean(section.getOrDefault("hidden", false), "hidden")); + List lore = MiscUtils.getAsStringList(section.getOrDefault("lore", List.of())); + boolean hidden = ResourceConfigUtils.getAsBoolean(section.getOrDefault("hidden", false), "hidden"); + List> conditionList = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(section, "conditions", "condition"), EventConditions::fromMap); + Category category = new Category(id, name, lore, icon, new ArrayList<>(members), priority, hidden, MiscUtils.allOf(conditionList)); if (ItemBrowserManagerImpl.this.byId.containsKey(id)) { ItemBrowserManagerImpl.this.byId.get(id).merge(category); } else { @@ -157,6 +175,9 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { ); List iconList = this.categoryOnMainPage.stream().map(it -> { + if (!it.condition().test(PlayerOptionalContext.of(player))) { + return null; + } Item item = this.plugin.itemManager().createWrappedItem(it.icon(), player); if (ItemUtils.isEmpty(item)) { this.plugin.logger().warn("Can't not find item " + it.icon() + " for category icon"); @@ -246,6 +267,9 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { item = Objects.requireNonNull(this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player)); item.customNameJson(AdventureHelper.componentToJson(Component.text(subCategoryId).color(NamedTextColor.RED).decoration(TextDecoration.ITALIC, false))); } else { + if (!subCategory.condition().test(PlayerOptionalContext.of(player))) { + return null; + } item = this.plugin.itemManager().createWrappedItem(subCategory.icon(), player); if (ItemUtils.isEmpty(item)) { if (!subCategory.icon().equals(ItemKeys.AIR)) { @@ -317,7 +341,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }); } - }).toList(); + }).filter(Objects::nonNull).toList(); PagedGui.builder() .addIngredients(itemList) 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..fafd81f68 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,7 @@ 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_DISPLAY_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS = Component.translatable().key("command.display_entity_view_distance_scale.set.success"); + TranslatableComponent.Builder COMMAND_ENTITY_CULLING_DISTANCE_SCALE_SET_SUCCESS = Component.translatable().key("command.entity_culling_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/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 501200a8a..ab1d0d520 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -6,7 +6,8 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -65,9 +66,7 @@ public interface NetWorkUser { @ApiStatus.Internal ConnectionState encoderState(); - int clientSideSectionCount(); - - Key clientSideDimension(); + World clientSideWorld(); Object serverPlayer(); @@ -89,9 +88,9 @@ public interface NetWorkUser { boolean isChunkTracked(long chunkPos); - ChunkStatus getTrackedChunk(long chunkPos); + ClientChunk getTrackedChunk(long chunkPos); - void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus); + void addTrackedChunk(long chunkPos, ClientChunk chunkStatus); void clearTrackedChunks(); 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/plugin/text/minimessage/CustomTagResolver.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/CustomTagResolver.java new file mode 100644 index 000000000..1d66bf4d7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/CustomTagResolver.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.plugin.text.minimessage; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CustomTagResolver implements TagResolver { + private final String name; + private final Component replacement; + + public CustomTagResolver(String name, Component replacement) { + this.name = name; + this.replacement = replacement; + } + + @Override + @Nullable + public Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + if (!has(name)) { + return null; + } + return Tag.selfClosingInserting(this.replacement); + } + + @Override + public boolean has(@NotNull String name) { + return this.name.equals(name); + } +} 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 312550c23..85a3de7bd 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,9 @@ 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.behavior.FurnitureBehaviorType; +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; @@ -37,7 +39,7 @@ import net.momirealms.craftengine.core.pack.model.special.SpecialModelFactory; import net.momirealms.craftengine.core.pack.model.special.SpecialModelReader; import net.momirealms.craftengine.core.pack.model.tint.TintFactory; import net.momirealms.craftengine.core.pack.model.tint.TintReader; -import net.momirealms.craftengine.core.plugin.config.template.TemplateArgumentFactory; +import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgumentFactory; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.context.function.FunctionFactory; @@ -76,7 +78,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 +89,12 @@ 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 CRAFT_REMAINDER_FACTORY = createConstantBoundRegistry(Registries.CRAFT_REMAINDER_FACTORY, 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); + public static final Registry> FURNITURE_BEHAVIOR_TYPE = createConstantBoundRegistry(Registries.FURNITURE_BEHAVIOR_TYPE, 32); 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 866abd070..102dec975 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,9 @@ 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.behavior.FurnitureBehaviorType; +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; @@ -37,7 +39,7 @@ import net.momirealms.craftengine.core.pack.model.special.SpecialModelFactory; import net.momirealms.craftengine.core.pack.model.special.SpecialModelReader; import net.momirealms.craftengine.core.pack.model.tint.TintFactory; import net.momirealms.craftengine.core.pack.model.tint.TintReader; -import net.momirealms.craftengine.core.plugin.config.template.TemplateArgumentFactory; +import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgumentFactory; import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.context.function.FunctionFactory; @@ -78,7 +80,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 +92,9 @@ 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> CRAFT_REMAINDER_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("craft_remainder_factory")); + 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")); + public static final ResourceKey>> FURNITURE_BEHAVIOR_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("furniture_behavior_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/Color.java b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java index 5f37d42b0..38299e43b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java @@ -38,7 +38,7 @@ public class Color { } public static Color fromVector3f(Vector3f vec) { - return new Color(0 << 24 /*不可省略*/ | MiscUtils.fastFloor(vec.x) << 16 | MiscUtils.fastFloor(vec.y) << 8 | MiscUtils.fastFloor(vec.z)); + return new Color(0 << 24 /*不可省略*/ | MiscUtils.floor(vec.x) << 16 | MiscUtils.floor(vec.y) << 8 | MiscUtils.floor(vec.z)); } public static int opaque(int color) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Key.java b/core/src/main/java/net/momirealms/craftengine/core/util/Key.java index a09969dd3..f580210ac 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Key.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Key.java @@ -39,9 +39,7 @@ public record Key(String namespace, String value) { @Override public int hashCode() { - int result = this.namespace.hashCode(); - result = 31 * result + this.value.hashCode(); - return result; + return 31 * this.namespace.hashCode() + this.value.hashCode(); } @Override @@ -54,7 +52,7 @@ public record Key(String namespace, String value) { } if (!(obj instanceof Key key)) return false; // 先比value命中率高 - return this.value.equals(key.value()) && this.namespace.equals(key.namespace()); + return this.value.equals(key.value) && this.namespace.equals(key.namespace); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/LegacyChatFormatter.java b/core/src/main/java/net/momirealms/craftengine/core/util/LegacyChatFormatter.java new file mode 100644 index 000000000..5f71615ca --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/LegacyChatFormatter.java @@ -0,0 +1,26 @@ +package net.momirealms.craftengine.core.util; + +public enum LegacyChatFormatter { + BLACK, + DARK_BLUE, + DARK_GREEN, + DARK_AQUA, + DARK_RED, + DARK_PURPLE, + GOLD, + GRAY, + DARK_GRAY, + BLUE, + GREEN, + AQUA, + RED, + LIGHT_PURPLE, + YELLOW, + WHITE, + OBFUSCATED, + BOLD, + STRIKETHROUGH, + UNDERLINE, + ITALIC, + RESET; +} 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 2e28001c2..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 @@ -21,19 +21,19 @@ public class MiscUtils { } }); - public static int fastFloor(double value) { + public static int floor(double value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } - public static int fastFloor(float value) { + public static int floor(float value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } public static int lerpDiscrete(float delta, int start, int end) { int i = end - start; - return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); + return start + floor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); } public static int murmurHash3Mixer(int value) { @@ -270,7 +270,7 @@ public class MiscUtils { } public static byte packDegrees(float degrees) { - return (byte) fastFloor(degrees * 256.0F / 360.0F); + return (byte) floor(degrees * 256.0F / 360.0F); } public static float unpackDegrees(byte degrees) { @@ -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 415893a3a..f445270dc 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 @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -35,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; } @@ -262,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); + } } } } @@ -315,17 +324,20 @@ public final class ResourceConfigUtils { } } - public static void runCatching(Path configPath, String node, Runnable runnable, Supplier config) { + public static boolean runCatching(Path configPath, String node, Runnable runnable, Supplier config) { try { runnable.run(); + return true; } catch (LocalizedException e) { printWarningRecursively(e, configPath, node); + return false; } catch (Exception e) { String message = "Unexpected error loading file " + configPath + " - '" + node + "'."; if (config != null) { message += " Configuration details: " + config.get(); } CraftEngine.instance().logger().warn(message, e); + return false; } } @@ -341,4 +353,56 @@ public final class ResourceConfigUtils { } TranslationManager.instance().log(e.node(), e.arguments()); } + + public static AABB getAsAABB(Object o, String option) { + 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(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); + } + } + } + } } 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/SkullUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/SkullUtils.java index a74d6273f..e1d753c86 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SkullUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SkullUtils.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.util; +import java.nio.charset.StandardCharsets; import java.util.Base64; public final class SkullUtils { @@ -8,7 +9,7 @@ public final class SkullUtils { public static String identifierFromBase64(String base64) { byte[] decodedBytes = Base64.getDecoder().decode(base64); - String decodedString = new String(decodedBytes); + String decodedString = new String(decodedBytes, StandardCharsets.UTF_8); int urlStartIndex = decodedString.indexOf("\"url\":\"") + 7; int urlEndIndex = decodedString.indexOf("\"", urlStartIndex); String textureUrl = decodedString.substring(urlStartIndex, urlEndIndex); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java b/core/src/main/java/net/momirealms/craftengine/core/util/TickersList.java similarity index 87% rename from core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java rename to core/src/main/java/net/momirealms/craftengine/core/util/TickersList.java index 4ff57411a..ed7ded3c9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/TickersList.java @@ -6,10 +6,8 @@ package net.momirealms.craftengine.core.util; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import java.util.Arrays; -import java.util.Collection; /** * A list for ServerLevel's blockEntityTickers @@ -20,26 +18,17 @@ import java.util.Collection; * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, * since we don't need to resize the array every single remove. */ -public final class BlockEntityTickersList extends ObjectArrayList { +public final class TickersList extends ObjectArrayList { private final IntOpenHashSet toRemove = new IntOpenHashSet(); private int startSearchFromIndex = -1; /** * Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. */ - public BlockEntityTickersList() { + public TickersList() { super(); } - /** - * Creates a new array list and fills it with a given collection. - * - * @param c a collection that will be used to fill the array list. - */ - public BlockEntityTickersList(final Collection c) { - super(c); - } - /** * Marks an entry as removed * diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 38a9acf20..71838e8c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -23,7 +23,7 @@ public class BlockPos extends Vec3i { } public static BlockPos fromVec3d(Vec3d vec) { - return new BlockPos(MiscUtils.fastFloor(vec.x), MiscUtils.fastFloor(vec.y), MiscUtils.fastFloor(vec.z)); + return new BlockPos(MiscUtils.floor(vec.x), MiscUtils.floor(vec.y), MiscUtils.floor(vec.z)); } public static BlockPos of(long packedPos) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 60098466b..df9472248 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; -import net.momirealms.craftengine.core.util.BlockEntityTickersList; +import net.momirealms.craftengine.core.util.TickersList; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; @@ -25,9 +25,9 @@ public abstract class CEWorld { protected final WorldHeight worldHeightAccessor; protected List pendingLightSections = new ArrayList<>(); protected final Set lightSections = ConcurrentHashMap.newKeySet(128); - protected final BlockEntityTickersList tickingSyncBlockEntities = new BlockEntityTickersList(); + protected final TickersList syncTickingBlockEntities = new TickersList<>(); protected final List pendingSyncTickingBlockEntities = new ArrayList<>(); - protected final BlockEntityTickersList tickingAsyncBlockEntities = new BlockEntityTickersList(); + protected final TickersList asyncTickingBlockEntities = new TickersList<>(); protected final List pendingAsyncTickingBlockEntities = new ArrayList<>(); protected volatile boolean isTickingSyncBlockEntities = false; protected volatile boolean isTickingAsyncBlockEntities = false; @@ -51,19 +51,15 @@ public abstract class CEWorld { public void setTicking(boolean ticking) { if (ticking) { - if (this.syncTickTask == null || this.syncTickTask.cancelled()) { + if (this.syncTickTask == null || this.syncTickTask.cancelled()) this.syncTickTask = CraftEngine.instance().scheduler().sync().runRepeating(this::syncTick, 1, 1); - } - if (this.asyncTickTask == null || this.asyncTickTask.cancelled()) { + if (this.asyncTickTask == null || this.asyncTickTask.cancelled()) this.asyncTickTask = CraftEngine.instance().scheduler().sync().runAsyncRepeating(this::asyncTick, 1, 1); - } } else { - if (this.syncTickTask != null && !this.syncTickTask.cancelled()) { + if (this.syncTickTask != null && !this.syncTickTask.cancelled()) this.syncTickTask.cancel(); - } - if (this.asyncTickTask != null && !this.asyncTickTask.cancelled()) { + if (this.asyncTickTask != null && !this.asyncTickTask.cancelled()) this.asyncTickTask.cancel(); - } } } @@ -194,39 +190,39 @@ public abstract class CEWorld { public abstract void updateLight(); - public void addSyncBlockEntityTicker(TickingBlockEntity ticker) { + public synchronized void addSyncBlockEntityTicker(TickingBlockEntity ticker) { if (this.isTickingSyncBlockEntities) { this.pendingSyncTickingBlockEntities.add(ticker); } else { - this.tickingSyncBlockEntities.add(ticker); + this.syncTickingBlockEntities.add(ticker); } } - public void addAsyncBlockEntityTicker(TickingBlockEntity ticker) { + public synchronized void addAsyncBlockEntityTicker(TickingBlockEntity ticker) { if (this.isTickingAsyncBlockEntities) { this.pendingAsyncTickingBlockEntities.add(ticker); } else { - this.tickingAsyncBlockEntities.add(ticker); + this.asyncTickingBlockEntities.add(ticker); } } protected void tickSyncBlockEntities() { this.isTickingSyncBlockEntities = true; if (!this.pendingSyncTickingBlockEntities.isEmpty()) { - this.tickingSyncBlockEntities.addAll(this.pendingSyncTickingBlockEntities); + this.syncTickingBlockEntities.addAll(this.pendingSyncTickingBlockEntities); this.pendingSyncTickingBlockEntities.clear(); } - if (!this.tickingSyncBlockEntities.isEmpty()) { - Object[] entities = this.tickingSyncBlockEntities.elements(); - for (int i = 0, size = this.tickingSyncBlockEntities.size(); i < size; i++) { + if (!this.syncTickingBlockEntities.isEmpty()) { + Object[] entities = this.syncTickingBlockEntities.elements(); + for (int i = 0, size = this.syncTickingBlockEntities.size(); i < size; i++) { TickingBlockEntity entity = (TickingBlockEntity) entities[i]; if (entity.isValid()) { entity.tick(); } else { - this.tickingSyncBlockEntities.markAsRemoved(i); + this.syncTickingBlockEntities.markAsRemoved(i); } } - this.tickingSyncBlockEntities.removeMarkedEntries(); + this.syncTickingBlockEntities.removeMarkedEntries(); } this.isTickingSyncBlockEntities = false; } @@ -234,20 +230,20 @@ public abstract class CEWorld { protected void tickAsyncBlockEntities() { this.isTickingAsyncBlockEntities = true; if (!this.pendingAsyncTickingBlockEntities.isEmpty()) { - this.tickingAsyncBlockEntities.addAll(this.pendingAsyncTickingBlockEntities); + this.asyncTickingBlockEntities.addAll(this.pendingAsyncTickingBlockEntities); this.pendingAsyncTickingBlockEntities.clear(); } - if (!this.tickingAsyncBlockEntities.isEmpty()) { - Object[] entities = this.tickingAsyncBlockEntities.elements(); - for (int i = 0, size = this.tickingAsyncBlockEntities.size(); i < size; i++) { + if (!this.asyncTickingBlockEntities.isEmpty()) { + Object[] entities = this.asyncTickingBlockEntities.elements(); + for (int i = 0, size = this.asyncTickingBlockEntities.size(); i < size; i++) { TickingBlockEntity entity = (TickingBlockEntity) entities[i]; if (entity.isValid()) { entity.tick(); } else { - this.tickingAsyncBlockEntities.markAsRemoved(i); + this.asyncTickingBlockEntities.markAsRemoved(i); } } - this.tickingAsyncBlockEntities.removeMarkedEntries(); + this.asyncTickingBlockEntities.removeMarkedEntries(); } this.isTickingAsyncBlockEntities = false; } 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 new file mode 100644 index 000000000..404594503 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.core.world; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.entityculling.CullingData; +import org.jetbrains.annotations.Nullable; + +public interface Cullable { + + void show(Player player); + + void hide(Player player); + + @Nullable + CullingData cullingData(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java index 6944337ed..fed33e417 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java @@ -23,9 +23,9 @@ public class EntityHitResult { } private BlockPos getBlockPos() { - int x = MiscUtils.fastFloor(this.position.x); - int y = MiscUtils.fastFloor(this.position.y); - int z = MiscUtils.fastFloor(this.position.z); + int x = MiscUtils.floor(this.position.x); + int y = MiscUtils.floor(this.position.y); + int z = MiscUtils.floor(this.position.z); if (this.direction == Direction.UP) { if (this.position.y % 1 == 0) { y--; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Glowing.java b/core/src/main/java/net/momirealms/craftengine/core/world/Glowing.java new file mode 100644 index 000000000..b237e34b5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Glowing.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.world; + +import net.momirealms.craftengine.core.util.LegacyChatFormatter; +import org.jetbrains.annotations.Nullable; + +public interface Glowing { + + @Nullable + LegacyChatFormatter glowColor(); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java index 1f4a989ac..dd469e7ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java @@ -14,9 +14,9 @@ public class MutableVec3d implements Position { } public MutableVec3d toCenter() { - this.x = MiscUtils.fastFloor(x) + 0.5; - this.y = MiscUtils.fastFloor(y) + 0.5; - this.z = MiscUtils.fastFloor(z) + 0.5; + this.x = MiscUtils.floor(x) + 0.5; + this.y = MiscUtils.floor(y) + 0.5; + this.z = MiscUtils.floor(z) + 0.5; return this; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java index fac06da92..48c234da8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java @@ -17,4 +17,39 @@ public class SectionPos extends Vec3i { public static int sectionRelative(int rel) { return rel & 15; } + + public static SectionPos of(long packed) { + return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); + } + + public long asLong() { + return ((long) this.x & 4194303L) << 42 | (long) this.y & 1048575L | ((long) this.z & 4194303L) << 20; + } + + public ChunkPos asChunkPos() { + return new ChunkPos(this.x, this.z); + } + + public static short packSectionRelativePos(BlockPos pos) { + return (short) ((pos.x & 15) << 8 | (pos.z & 15) << 4 | pos.y & 15); + } + + public static BlockPos unpackSectionRelativePos(short encoded) { + int x = (encoded >> 8) & 15; + int z = (encoded >> 4) & 15; + int y = encoded & 15; + return new BlockPos(x, y, z); + } + + public final int minBlockX() { + return this.x << 4; + } + + public final int minBlockY() { + return this.y << 4; + } + + public final int minBlockZ() { + return this.z << 4; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index 7165596a0..fe3cb6938 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -15,7 +15,7 @@ public class Vec3d implements Position { } public Vec3d toCenter() { - return new Vec3d(MiscUtils.fastFloor(x) + 0.5, MiscUtils.fastFloor(y) + 0.5, MiscUtils.fastFloor(z) + 0.5); + return new Vec3d(MiscUtils.floor(x) + 0.5, MiscUtils.floor(y) + 0.5, MiscUtils.floor(z) + 0.5); } public Vec3d add(Vec3d vec) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java index 82b2e4bbb..bd4a707e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java @@ -4,9 +4,9 @@ import net.momirealms.craftengine.core.util.Direction; public class Vec3i implements Comparable { public static final Vec3i ZERO = new Vec3i(0, 0, 0); - protected int x; - protected int y; - protected int z; + public final int x; + public final int y; + public final int z; public Vec3i(int x, int y, int z) { this.x = x; @@ -30,21 +30,6 @@ public class Vec3i implements Comparable { return x == 0 && y == 0 && z == 0 ? this : new Vec3i(this.x() + x, this.y() + y, this.z() + z); } - protected Vec3i setX(int x) { - this.x = x; - return this; - } - - protected Vec3i setY(int y) { - this.y = y; - return this; - } - - protected Vec3i setZ(int z) { - this.z = z; - return this; - } - @Override public boolean equals(Object object) { return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z; 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/ArrayPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java index 0ace21c98..f7926684a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java @@ -77,15 +77,24 @@ public class ArrayPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.size; ++i) { + for (int i = 0; i < this.size; ++i) { if (predicate.test(this.array[i])) { return true; } } - return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.size; ++i) { + if (!predicate.test(this.array[i])) { + return false; + } + } + return true; + } + @Override public T get(int id) { if (id >= 0 && id < this.size) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java index 02d0b190d..ae9be1758 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java @@ -67,7 +67,7 @@ public class BiMapPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.getSize(); ++i) { + for (int i = 0; i < this.getSize(); ++i) { if (predicate.test(this.map.get(i))) { return true; } @@ -75,6 +75,16 @@ public class BiMapPalette implements Palette { return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.getSize(); ++i) { + if (!predicate.test(this.map.get(i))) { + return false; + } + } + return true; + } + @Override public T get(int id) { T object = this.map.get(id); 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 a0babe284..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 @@ -11,8 +11,11 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; 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; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; import net.momirealms.sparrow.nbt.ListTag; @@ -23,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) { @@ -69,30 +74,34 @@ 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); } } public void spawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.show(player); + if (Config.enableEntityCulling()) { + player.addTrackedBlockEntities(this.constantBlockEntityRenderers); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.show(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.show(player); @@ -105,8 +114,12 @@ public class CEChunk { public void despawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.hide(player); + if (Config.enableEntityCulling()) { + player.removeTrackedBlockEntities(this.constantBlockEntityRenderers.keySet()); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.hide(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.hide(player); @@ -129,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); + 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(); @@ -137,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]; @@ -147,7 +171,20 @@ public class CEChunk { elements[0] = element; if (hasTrackedBy) { for (Player player : trackedBy) { - element.transform(player); + // 如果启用剔除,则暂时保留原先可见度,因为大概率可见度不发生变化 + if (Config.enableEntityCulling()) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown) { + element.transform(player); + } + if (trackedBlockEntity != null) { + trackedBlockEntity.setCullable(renderer); + } else { + player.addTrackedBlockEntity(pos, renderer); + } + } else { + element.transform(player); + } } } break outer; @@ -157,14 +194,66 @@ public class CEChunk { elements[0] = element; if (hasTrackedBy) { for (Player player : trackedBy) { - previousElement.hide(player); - element.show(player); + 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).createExact(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; + } + } + } + /* + * 可变换部分 + */ for (int j = 0; j < previousElements.length; j++) { BlockEntityElement previousElement = previousElements[j]; if (previousElement != null && config.elementClass().isInstance(previousElement)) { @@ -173,23 +262,37 @@ public class CEChunk { previousElements[j] = null; elements[i] = newElement; if (hasTrackedBy) { - for (Player player : trackedBy) { - newElement.transform(player); + 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) { - for (Player player : trackedBy) { - newElement.show(player); + 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) { @@ -198,15 +301,33 @@ 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) { for (Player player : trackedBy) { - renderer.show(player); + if (Config.enableEntityCulling()) { + player.addTrackedBlockEntity(pos, renderer); + } else { + renderer.show(player); + } } } } @@ -233,8 +354,14 @@ public class CEChunk { ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos); if (removed != null) { if (hide) { - for (Player player : getTrackedBy()) { - removed.hide(player); + if (Config.enableEntityCulling()) { + for (Player player : getTrackedBy()) { + player.removeTrackedBlockEntities(List.of(pos)); + } + } else { + for (Player player : getTrackedBy()) { + removed.hide(player); + } } } } @@ -395,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; } // 设置方块实体所在世界 @@ -439,7 +566,7 @@ public class CEChunk { return Collections.unmodifiableCollection(this.blockEntities.values()); } - public List constantBlockEntityRenderers() { + public List constantBlockEntityRendererPositions() { try { this.renderLock.readLock().lock(); return new ArrayList<>(this.constantBlockEntityRenderers.keySet()); @@ -539,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; } @@ -553,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/ChunkStatus.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java deleted file mode 100644 index 744068aa6..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.world.chunk; - -public class ChunkStatus { - - public ChunkStatus() { - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java index 51ee07710..1fd1f1155 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java @@ -29,6 +29,11 @@ public class IdListPalette implements Palette { return true; } + @Override + public boolean allMatch(Predicate predicate) { + return true; + } + @Override public T get(int id) { T object = this.idList.get(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java index f190afaee..73d6523ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java @@ -13,6 +13,8 @@ public interface Palette { boolean hasAny(Predicate predicate); + boolean allMatch(Predicate predicate); + T get(int id); int getSize(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java index afcfe09be..b52a6308c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java @@ -45,6 +45,15 @@ public class SingularPalette implements Palette { } } + @Override + public boolean allMatch(Predicate predicate) { + if (this.entry == null) { + throw new IllegalStateException("Use of an uninitialized palette"); + } else { + return predicate.test(this.entry); + } + } + @Override public T get(int id) { if (this.entry != null && id == 0) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java new file mode 100644 index 000000000..b99d3a574 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java @@ -0,0 +1,55 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.craftengine.core.world.WorldHeight; +import org.jetbrains.annotations.Nullable; + +public class ClientChunk { + @Nullable + public final ClientSection[] sections; + private final WorldHeight worldHeight; + + public ClientChunk(ClientSection[] sections, WorldHeight worldHeight) { + this.sections = sections; + this.worldHeight = worldHeight; + } + + @Nullable + public ClientSection[] sections() { + return sections; + } + + public boolean isOccluding(int x, int y, int z) { + if (this.sections == null) return false; + int index = sectionIndex(SectionPos.blockToSectionCoord(y)); + if (index < 0 || index >= this.sections.length) return false; + ClientSection section = this.sections[index]; + if (section == null) return false; + return section.isOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15); + } + + public void setOccluding(int x, int y, int z, boolean occluding) { + if (this.sections == null) return; + int index = sectionIndex(SectionPos.blockToSectionCoord(y)); + if (index < 0 || index >= this.sections.length) return; + ClientSection section = this.sections[index]; + if (section == null) return; + section.setOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15, occluding); + } + + public int sectionIndex(int sectionId) { + return sectionId - this.worldHeight.getMinSection(); + } + + @Nullable + public ClientSection sectionByIndex(int sectionIndex) { + if (this.sections == null) return null; + return this.sections[sectionIndex]; + } + + @Nullable + public ClientSection sectionById(int sectionId) { + if (this.sections == null) return null; + return this.sections[sectionIndex(sectionId)]; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java new file mode 100644 index 000000000..32e668d15 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class ClientSection { + private ClientSectionOcclusionStorage storage; + + public ClientSection(ClientSectionOcclusionStorage storage) { + this.storage = storage; + } + + public boolean isOccluding(int x, int y, int z) { + return isOccluding((y << 4 | z) << 4 | x); + } + + public boolean isOccluding(int index) { + return this.storage.isOccluding(index); + } + + public void setOccluding(int x, int y, int z, boolean value) { + this.setOccluding((y << 4 | z) << 4 | x, value); + } + + public void setOccluding(int index, boolean value) { + boolean wasOccluding = this.storage.isOccluding(index); + if (wasOccluding != value) { + if (this.storage instanceof PackedOcclusionStorage arrayStorage) { + arrayStorage.set(index, value); + } else { + PackedOcclusionStorage newStorage = new PackedOcclusionStorage(wasOccluding); + newStorage.set(index, value); + this.storage = newStorage; + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java new file mode 100644 index 000000000..0a53ede3d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public interface ClientSectionOcclusionStorage { + + boolean isOccluding(int index); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java new file mode 100644 index 000000000..10e3b08dd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import java.util.Arrays; + +public class PackedOcclusionStorage implements ClientSectionOcclusionStorage { + private static final int SIZE = 4096; + private static final int LONGS = SIZE / 64; + private final long[] data; + + public PackedOcclusionStorage() { + this.data = new long[LONGS]; + } + + public PackedOcclusionStorage(boolean defaultValue) { + this.data = new long[LONGS]; + if (defaultValue) { + Arrays.fill(this.data, -1L); // 所有位设为1 + } + } + + @Override + public boolean isOccluding(int index) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + return (this.data[arrayIndex] & (1L << bitIndex)) != 0; + } + + public void set(int index, boolean occlusion) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + if (occlusion) { + this.data[arrayIndex] |= (1L << bitIndex); + } else { + this.data[arrayIndex] &= ~(1L << bitIndex); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java new file mode 100644 index 000000000..7da13ba23 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class SingularOcclusionStorage implements ClientSectionOcclusionStorage { + private final boolean isOccluding; + + public SingularOcclusionStorage(boolean isOccluding) { + this.isOccluding = isOccluding; + } + + @Override + public boolean isOccluding(int index) { + return this.isOccluding; + } +} 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 new file mode 100644 index 000000000..fef5e547d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -0,0 +1,36 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.Cullable; + +public class VirtualCullableObject { + 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; + } + + public boolean isShown() { + return isShown; + } + + public void setShown(Player player, boolean shown) { + if (this.isShown == shown) return; + this.isShown = shown; + if (shown) { + this.cullable.show(player); + } else { + this.cullable.hide(player); + } + } +} 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 ef1357235..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 @@ -32,7 +32,7 @@ public final class DefaultChunkSerializer { if (!blockEntities.isEmpty()) { chunkNbt.put("block_entities", blockEntities); } - ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers()); + ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRendererPositions()); if (!blockEntityRenders.isEmpty()) { chunkNbt.put("block_entity_renderers", blockEntityRenders); } @@ -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 9a837a003..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 { @@ -35,6 +38,17 @@ public class AABB { this.maxZ = Math.max(pos1.z, pos2.z); } + public AABB move(BlockPos pos) { + return new AABB( + this.minX + pos.x + 0.5, + this.minY + pos.y + 0.5, + this.minZ + pos.z + 0.5, + this.maxX + pos.x + 0.5, + this.maxY + pos.y + 0.5, + this.maxZ + pos.z + 0.5 + ); + } + public AABB(BlockPos pos) { this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1); } @@ -46,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, @@ -57,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; @@ -136,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/core/src/main/java/net/momirealms/craftengine/core/world/score/TeamManager.java b/core/src/main/java/net/momirealms/craftengine/core/world/score/TeamManager.java new file mode 100644 index 000000000..af772e294 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/score/TeamManager.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.world.score; + +import net.momirealms.craftengine.core.plugin.Manageable; +import net.momirealms.craftengine.core.util.LegacyChatFormatter; + +public interface TeamManager extends Manageable { + String TEAM_PREFIX = "ce_"; + + void setColorInUse(LegacyChatFormatter color); +} diff --git a/gradle.properties b/gradle.properties index 5ad74eca6..1488dee0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings -project_version=0.0.65.12.2 -config_version=58 -lang_version=40 +project_version=0.0.66 +config_version=60 +lang_version=43 project_group=net.momirealms latest_supported_version=1.21.10 @@ -38,7 +38,7 @@ zstd_version=1.5.7-6 commons_io_version=2.21.0 commons_lang3_version=3.20.0 sparrow_nbt_version=0.10.6 -sparrow_util_version=0.65 +sparrow_util_version=0.68 fastutil_version=8.5.18 netty_version=4.1.128.Final joml_version=1.10.8 @@ -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.137 +nms_helper_version=1.0.148 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.38.7 @@ -59,9 +59,9 @@ concurrent_util_version=0.0.3 bucket4j_version=8.15.0 # Proxy settings -#systemProp.socks.proxyHost=127.0.0.1 -#systemProp.socks.proxyPort=7890 -#systemProp.http.proxyHost=127.0.0.1 -#systemProp.http.proxyPort=7890 -#systemProp.https.proxyHost=127.0.0.1 -#systemProp.https.proxyPort=7890 \ No newline at end of file +systemProp.socks.proxyHost=127.0.0.1 +systemProp.socks.proxyPort=7890 +systemProp.http.proxyHost=127.0.0.1 +systemProp.http.proxyPort=7890 +systemProp.https.proxyHost=127.0.0.1 +systemProp.https.proxyPort=7890 \ No newline at end of file diff --git a/wiki b/wiki index bec8bb7cf..eb2118f0d 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit bec8bb7cf8c4331dd54bdc4bb27edfa664805e66 +Subproject commit eb2118f0dbf8c6faab8f023434d6e3c5c861a59d