diff --git a/.gitignore b/.gitignore index b914f987b..60e1aa00d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ build/ !/libs/*.jar !/gradle/wrapper/*.jar .vscode/settings.json +/runPaper/ \ No newline at end of file diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index 0d7569af6..6dea04b48 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -111,12 +111,21 @@ tasks { publishing { repositories { maven { + name = "releases" url = uri("https://repo.momirealms.net/releases") credentials(PasswordCredentials::class) { username = System.getenv("REPO_USERNAME") password = System.getenv("REPO_PASSWORD") } } + maven { + name = "snapshot" + url = uri("https://repo.momirealms.net/snapshots") + credentials(PasswordCredentials::class) { + username = System.getenv("REPO_USERNAME") + password = System.getenv("REPO_PASSWORD") + } + } } publications { create("mavenJava") { @@ -137,5 +146,35 @@ publishing { } } } + create("mavenJavaSnapshot") { + groupId = "net.momirealms" + artifactId = "craft-engine-bukkit" + version = "${rootProject.properties["project_version"]}-SNAPSHOT" + artifact(tasks["sourcesJar"]) + from(components["shadow"]) + pom { + name = "CraftEngine API" + url = "https://github.com/Xiao-MoMi/craft-engine" + licenses { + license { + name = "GNU General Public License v3.0" + url = "https://www.gnu.org/licenses/gpl-3.0.html" + distribution = "repo" + } + } + } + } } } + +tasks.register("publishRelease") { + group = "publishing" + description = "Publishes to the release repository" + dependsOn("publishMavenJavaPublicationToReleaseRepository") +} + +tasks.register("publishSnapshot") { + group = "publishing" + description = "Publishes to the snapshot repository" + dependsOn("publishMavenJavaSnapshotPublicationToSnapshotRepository") +} \ No newline at end of file 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 new file mode 100644 index 000000000..a208e2901 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/CheckItemExpansion.java @@ -0,0 +1,118 @@ +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; +import net.momirealms.craftengine.core.util.Key; +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; + + public CheckItemExpansion(CraftEngine plugin) { + this.plugin = plugin; + } + + @Override + public @NotNull String getIdentifier() { + return "checkceitem"; + } + + @Override + public @NotNull String getAuthor() { + return "jhqwqmc"; + } + + @Override + public @NotNull String getVersion() { + return "1.0"; + } + + @Override + public boolean persist() { + return true; + } + + /** + * 用法:(小括号括起来的为必填,中括号括起来的为选填) + *
+ * %checkceitem_count_(namespace):(path)% + *
+ * %checkceitem_has_(namespace):(path):[amount]% + *
+ * %checkceitem_id_[main_hand/off_hand/slot]% + *
+ * %checkceitem_iscustom_[main_hand/off_hand/slot]% + */ + @Override + public @Nullable String onPlaceholderRequest(Player bukkitPlayer, @NotNull String params) { + if (bukkitPlayer == null) return null; + BukkitServerPlayer player = BukkitCraftEngine.instance().adapt(bukkitPlayer); + if (player == null) return null; + int index = params.indexOf('_'); + String action = index > 0 ? params.substring(0, index) : params; + String[] param = index > 0 ? params.substring(index + 1).split(":", 3) : new String[0]; + return switch (action) { + case "count" -> param.length < 2 ? null : String.valueOf(getItemCount(player, param)); + case "has" -> { + if (param.length < 2) yield null; + int requiredAmount; + try { + requiredAmount = param.length < 3 ? 1 : Integer.parseInt(param[2]); + } catch (NumberFormatException e) { + yield null; + } + if (requiredAmount < 1) yield "true"; + yield String.valueOf(getItemCount(player, param) >= requiredAmount); + } + case "id" -> { + Item item = getItem(player, param); + if (item == null) yield null; + yield item.id().asString(); + } + case "iscustom" -> { + Item item = getItem(player, param); + if (item == null) yield null; + yield String.valueOf(item.isCustomItem()); + } + default -> null; + }; + } + + @Nullable + private Item getItem(BukkitServerPlayer player, String[] param) { + if (param.length < 1 || param[0] == null || param[0].isEmpty()) { + return player.getItemInHand(InteractionHand.MAIN_HAND); + } + return switch (param[0]) { + case "main_hand" -> player.getItemInHand(InteractionHand.MAIN_HAND); + case "off_hand" -> player.getItemInHand(InteractionHand.OFF_HAND); + default -> { + try { + int slot = Integer.parseInt(param[0]); + yield player.getItemBySlot(Math.max(slot, 0)); + } catch (NumberFormatException e) { + yield null; + } + } + }; + } + + 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); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/PlaceholderAPIUtils.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/PlaceholderAPIUtils.java index e96cb46dc..9f4dce361 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/PlaceholderAPIUtils.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/papi/PlaceholderAPIUtils.java @@ -20,5 +20,6 @@ public class PlaceholderAPIUtils { public static void registerExpansions(CraftEngine plugin) { new ImageExpansion(plugin).register(); new ShiftExpansion(plugin).register(); + new CheckItemExpansion(plugin).register(); } } diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 8c6dea9f2..592494310 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -85,5 +85,6 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") } } diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index cd95284cf..0e0f45f54 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -1,8 +1,12 @@ import net.minecrell.pluginyml.paper.PaperPluginDescription +import xyz.jpenilla.runpaper.task.RunServer +import xyz.jpenilla.runtask.pluginsapi.DownloadPluginsSpec +import java.net.URI plugins { id("com.gradleup.shadow") version "9.2.2" id("de.eldoria.plugin-yml.paper") version "0.7.1" + id("xyz.jpenilla.run-paper") version "3.0.2" } repositories { @@ -168,5 +172,62 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") } } + +/** + * Register Run Dev Server Tasks + */ +listOf( + "1.21.10", + "1.21.8", + "1.21.5", + "1.21.4", + "1.21.2", + "1.21.1", + "1.20.6", + "1.20.4", + "1.20.2", + "1.20.1", +).forEach { + registerPaperTask(it) +} + +fun registerPaperTask( + version: String, + dirName: String = version, + javaVersion : Int = 21, + serverJar: File? = null, + downloadPlugins: Action? = null +) { + listOf(version, "${version}-with-viaversion").forEach { taskName -> + tasks.register(taskName, RunServer::class) { + group = "run dev server" + minecraftVersion(version) + serverJar?.let { serverJar(it) } + pluginJars.from(tasks.shadowJar.flatMap { it.archiveFile }) + runDirectory = rootProject.layout.projectDirectory.dir("runPaper/${dirName}") + javaLauncher = javaToolchains.launcherFor { + vendor = JvmVendorSpec.JETBRAINS + languageVersion = JavaLanguageVersion.of(javaVersion) + } + systemProperties["com.mojang.eula.agree"] = true + jvmArgs("-Ddisable.watchdog=true") + jvmArgs("-Xlog:redefine+class*=info") + jvmArgs("-XX:+AllowEnhancedClassRedefinition") + if (taskName.contains("viaversion")) { + downloadPlugins { + url("https://ci.viaversion.com/job/ViaVersion/lastBuild/artifact/build/libs/${getJenkinsArtifactFileName("https://ci.viaversion.com/job/ViaVersion/lastSuccessfulBuild/api/json?tree=artifacts[*]")}") + url("https://ci.viaversion.com/view/ViaBackwards/job/ViaBackwards/662/artifact/build/libs/${getJenkinsArtifactFileName("https://ci.viaversion.com/job/ViaBackwards/lastSuccessfulBuild/api/json?tree=artifacts[*]")}") + } + } + } + } +} + +fun getJenkinsArtifactFileName(url: String): String { + val response = URI.create(url).toURL().readText() + val regex = """"fileName":"([^"]+)"""".toRegex() + return regex.find(response)?.groupValues?.get(1) ?: throw Exception("fileName not found") +} \ No newline at end of file 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 0a6cf5176..8da86d668 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 @@ -97,7 +97,7 @@ public final class BlockEventListener implements Listener { } } - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + @EventHandler(priority = EventPriority.MONITOR) public void onPlayerBreak(BlockBreakEvent event) { org.bukkit.block.Block block = event.getBlock(); Object blockState = BlockStateUtils.getBlockState(block); @@ -109,7 +109,7 @@ public final class BlockEventListener implements Listener { WorldPosition position = new WorldPosition(world, location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5); Item itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); - if (!ItemUtils.isEmpty(itemInHand)) { + if (!event.isCancelled() && !ItemUtils.isEmpty(itemInHand)) { Optional> optionalCustomItem = itemInHand.getCustomItem(); if (optionalCustomItem.isPresent()) { Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); @@ -129,41 +129,49 @@ public final class BlockEventListener implements Listener { } if (!BlockStateUtils.isVanillaBlock(stateId)) { - ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId); + ImmutableBlockState state = this.manager.getImmutableBlockStateUnsafe(stateId); if (!state.isEmpty()) { - // double check adventure mode to prevent dupe - if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) { - return; - } + if (!event.isCancelled()) { + // double check adventure mode to prevent dupe + if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) { + return; + } - // trigger api event - CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state); - boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent); - if (isCancelled) { - event.setCancelled(true); - return; - } + // trigger api event + CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state); + boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent); + if (isCancelled) { + event.setCancelled(true); + return; + } - // execute functions - Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); - PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) - .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) - .withParameter(DirectContextParameters.EVENT, cancellable) - .withParameter(DirectContextParameters.POSITION, position) - .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand) - ); - state.owner().value().execute(context, EventTrigger.BREAK); - if (cancellable.isCancelled()) { - return; - } + // execute functions + Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); + PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() + .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) + .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) + .withParameter(DirectContextParameters.EVENT, cancellable) + .withParameter(DirectContextParameters.POSITION, position) + .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand) + ); + state.owner().value().execute(context, EventTrigger.BREAK); + if (cancellable.isCancelled()) { + return; + } - // play sound - serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK); + // play sound + serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK); + } + // Restore sounds in cancelled events + else { + if (Config.processCancelledBreak()) { + serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK); + } + } } } else { // override vanilla block loots - if (player.getGameMode() != GameMode.CREATIVE) { + if (!event.isCancelled() && player.getGameMode() != GameMode.CREATIVE) { this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> { if (!event.isDropItems()) { return; @@ -185,7 +193,7 @@ public final class BlockEventListener implements Listener { }); } // sound system - if (Config.enableSoundSystem()) { + if (Config.enableSoundSystem() && (!event.isCancelled() || Config.processCancelledBreak())) { Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); @@ -223,7 +231,7 @@ public final class BlockEventListener implements Listener { } } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + @EventHandler(priority = EventPriority.LOW) public void onStep(GenericGameEvent event) { if (event.getEvent() != GameEvent.STEP) return; Entity entity = event.getEntity(); @@ -242,11 +250,14 @@ public final class BlockEventListener implements Listener { .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) ), EventTrigger.STEP); - if (cancellable.isCancelled()) { + 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()); } 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$stepSound(soundType); Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java index 851371fa6..6e0bb2680 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java @@ -34,7 +34,7 @@ public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavio net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos))); world.playBlockSound(position, customState.settings().sounds().breakSound()); - FastNMS.INSTANCE.method$Level$destroyBlock(level, blockPos, true); + FastNMS.INSTANCE.method$LevelWriter$destroyBlock(level, blockPos, true); } }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java index f35d35159..a156a4f61 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AttachedStemBlockBehavior.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior; 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.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.util.HorizontalDirection; @@ -20,7 +21,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -public class AttachedStemBlockBehavior extends BukkitBlockBehavior { +public class AttachedStemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final Key fruit; 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 e819e7515..11133b67d 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 @@ -46,6 +46,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SEAT_BLOCK = Key.from("craftengine:seat_block"); 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 void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -90,5 +91,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SEAT_BLOCK, SeatBlockBehavior.FACTORY); register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY); register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY); + register(HANGABLE_BLOCK, HangableBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java index d932532cf..91e96fda6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.block.behavior; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -9,53 +8,36 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.core.block.*; +import net.momirealms.craftengine.core.block.BlockBehavior; +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.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.parser.BlockStateParser; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Direction; -import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.LazyReference; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.bukkit.block.BlockState; import org.bukkit.event.block.BlockFormEvent; +import org.jetbrains.annotations.Nullable; import java.util.Map; -import java.util.Optional; import java.util.concurrent.Callable; public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final Key targetBlock; // TODO 更宽泛的,使用state,似乎也不是很好的方案? - private Object defaultBlockState; - private ImmutableBlockState defaultImmutableBlockState; + private final LazyReference<@Nullable ImmutableBlockState> targetBlock; - public ConcretePowderBlockBehavior(CustomBlock block, Key targetBlock) { + public ConcretePowderBlockBehavior(CustomBlock block, String targetBlock) { super(block); - this.targetBlock = targetBlock; - } - - public ImmutableBlockState defaultImmutableBlockState() { - if (this.defaultImmutableBlockState == null) { - this.getDefaultBlockState(); - } - return this.defaultImmutableBlockState; + this.targetBlock = LazyReference.lazyReference(() -> BlockStateParser.deserialize(targetBlock)); } public Object getDefaultBlockState() { - if (this.defaultBlockState != null) { - return this.defaultBlockState; - } - Optional optionalCustomBlock = BukkitBlockManager.instance().blockById(this.targetBlock); - if (optionalCustomBlock.isPresent()) { - CustomBlock customBlock = optionalCustomBlock.get(); - this.defaultBlockState = customBlock.defaultState().customBlockState().literalObject(); - this.defaultImmutableBlockState = customBlock.defaultState(); - } else { - CraftEngine.instance().logger().warn("Failed to create solid block " + this.targetBlock + " in ConcretePowderBlockBehavior"); - this.defaultBlockState = MBlocks.STONE$defaultState; - this.defaultImmutableBlockState = EmptyBlock.STATE; - } - return this.defaultBlockState; + ImmutableBlockState state = this.targetBlock.get(); + return state != null ? state.customBlockState().literalObject() : MBlocks.STONE$defaultState; } @SuppressWarnings("UnstableApiUsage") @@ -72,7 +54,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { craftBlockState.setBlockData(BlockStateUtils.fromBlockData(getDefaultBlockState())); BlockFormEvent event = new BlockFormEvent(craftBlockState.getBlock(), craftBlockState); if (!EventUtils.fireAndCheckCancel(event)) { - return defaultImmutableBlockState(); + return this.targetBlock.get(); } else { return super.updateStateForPlacement(context, state); } @@ -148,7 +130,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { String solidBlock = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("solid-block"), "warning.config.block.behavior.concrete.missing_solid"); - return new ConcretePowderBlockBehavior(block, Key.of(solidBlock)); + return new ConcretePowderBlockBehavior(block, solidBlock); } } } 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 2e6793e35..7ae23af7f 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 @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.InteractionResult; +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.context.UseOnContext; @@ -121,14 +122,15 @@ public class CropBlockBehavior extends BukkitBlockBehavior { } @Override - public void performBoneMeal(Object thisBlock, Object[] args) throws Exception { + public void performBoneMeal(Object thisBlock, Object[] args) { this.performBoneMeal(args[0], args[2], args[3]); } @Override public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { Item item = context.getItem(); - if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + Player player = context.getPlayer(); + if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode()) return InteractionResult.PASS; if (isMaxAge(state)) return InteractionResult.PASS; @@ -144,7 +146,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { sendSwing = true; } if (sendSwing) { - context.getPlayer().swingHand(context.getHand()); + player.swingHand(context.getHand()); } return InteractionResult.SUCCESS; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java index 098092f9f..a93cdf0e3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DoorBlockBehavior.java @@ -15,6 +15,7 @@ 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.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.type.DoorHinge; import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf; @@ -46,7 +47,7 @@ import java.util.Optional; import java.util.concurrent.Callable; @SuppressWarnings("DuplicatedCode") -public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior { +public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property halfProperty; private final Property facingProperty; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java index 520c7b37d..bb84f9bd7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceBlockBehavior.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.BlockStateWrapper; 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.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.BooleanProperty; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; @@ -30,7 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -public class FenceBlockBehavior extends BukkitBlockBehavior { +public class FenceBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final BooleanProperty northProperty; private final BooleanProperty eastProperty; 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 03f0d1a11..27ffdea31 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 @@ -12,6 +12,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.Player; @@ -40,7 +41,7 @@ import java.util.Optional; import java.util.concurrent.Callable; @SuppressWarnings("DuplicatedCode") -public class FenceGateBlockBehavior extends BukkitBlockBehavior { +public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final Property inWallProperty; @@ -144,6 +145,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") private void playerToggle(UseOnContext context, ImmutableBlockState state) { 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())) { 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 d04773e38..a53af5459 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 @@ -14,6 +14,7 @@ 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.entity.player.InteractionResult; +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.context.UseOnContext; @@ -83,7 +84,8 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { @Override public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { Item item = context.getItem(); - if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + Player player = context.getPlayer(); + if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode()) return InteractionResult.PASS; BlockPos pos = context.getClickedPos(); BukkitExistingBlock upper = (BukkitExistingBlock) context.getLevel().getBlock(pos.x(), pos.y() + 1, pos.z()); @@ -102,7 +104,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { sendSwing = true; } if (sendSwing) { - context.getPlayer().swingHand(context.getHand()); + player.swingHand(context.getHand()); } return InteractionResult.SUCCESS; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangableBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangableBlockBehavior.java new file mode 100644 index 000000000..a02fdd2f9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangableBlockBehavior.java @@ -0,0 +1,93 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockBehavior; +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.block.behavior.IsPathFindableBlockBehavior; +import net.momirealms.craftengine.core.block.properties.BooleanProperty; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class HangableBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final BooleanProperty hanging; + + public HangableBlockBehavior(CustomBlock customBlock, BooleanProperty hanging) { + super(customBlock); + this.hanging = hanging; + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + BooleanProperty hanging = (BooleanProperty) state.owner().value().getProperty("hanging"); + if (hanging == null) return state; + @Nullable BooleanProperty waterlogged = (BooleanProperty) state.owner().value().getProperty("waterlogged"); + Object world = context.getLevel().serverWorld(); + Object blockPos = LocationUtils.toBlockPos(context.getClickedPos()); + Object fluidType = FastNMS.INSTANCE.method$FluidState$getType(FastNMS.INSTANCE.method$BlockGetter$getFluidState(world, blockPos)); + for (Direction direction : context.getNearestLookingDirections()) { + if (direction.axis() != Direction.Axis.Y) continue; + ImmutableBlockState blockState = state.with(hanging, direction == Direction.UP); + if (!FastNMS.INSTANCE.method$BlockStateBase$canSurvive(blockState.customBlockState().literalObject(), world, blockPos)) continue; + return waterlogged != null ? blockState.with(waterlogged, fluidType == MFluids.WATER) : blockState; + } + return state; + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object state = args[0]; + Object world = args[1]; + Object blockPos = args[2]; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return false; + BooleanProperty hangingProperty = (BooleanProperty) blockState.owner().value().getProperty("hanging"); + if (hangingProperty == null) return false; + Boolean hanging = blockState.get(hangingProperty); + Object relativePos = FastNMS.INSTANCE.method$BlockPos$relative(blockPos, hanging ? CoreReflections.instance$Direction$UP : CoreReflections.instance$Direction$DOWN); + return FastNMS.INSTANCE.method$Block$canSupportCenter(world, relativePos, hanging ? CoreReflections.instance$Direction$DOWN : CoreReflections.instance$Direction$UP); + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (state == null) return MBlocks.AIR$defaultState; + @Nullable BooleanProperty waterlogged = (BooleanProperty) state.owner().value().getProperty("waterlogged"); + if (waterlogged != null && state.get(waterlogged)) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleFluidTick(args[updateShape$level], args[updateShape$blockPos], MFluids.WATER, 5); + } + BooleanProperty hanging = (BooleanProperty) state.owner().value().getProperty("hanging"); + if (hanging == null) return MBlocks.AIR$defaultState; + if ((state.get(hanging) ? CoreReflections.instance$Direction$UP : CoreReflections.instance$Direction$DOWN) == args[updateShape$direction] + && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos])) { + return MBlocks.AIR$defaultState; + } + return superMethod.call(); + } + + @Override + public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) { + return false; + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + BooleanProperty hanging = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("hanging"), "warning.config.block.behavior.hangable.missing_hanging"); + return new HangableBlockBehavior(block, hanging); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java index 85924d053..3d921fb77 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java @@ -165,7 +165,7 @@ public class LeavesBlockBehavior extends BukkitBlockBehavior { public BlockBehavior create(CustomBlock block, Map arguments) { Property persistent = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("persistent"), "warning.config.block.behavior.leaves.missing_persistent"); Property distance = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("distance"), "warning.config.block.behavior.leaves.missing_distance"); - int actual = distance.possibleValues().get(distance.possibleValues().size() - 1); + int actual = distance.possibleValues().getLast(); return new LeavesBlockBehavior(block, actual, distance, persistent); } } 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 fa3535e88..9f13df44c 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 @@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.InteractionResult; +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.context.UseOnContext; @@ -148,7 +149,8 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { @Override public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { Item item = context.getItem(); - if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + Player player = context.getPlayer(); + 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(); @@ -162,7 +164,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { sendSwing = true; } if (sendSwing) { - context.getPlayer().swingHand(context.getHand()); + player.swingHand(context.getHand()); } return InteractionResult.SUCCESS; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java index cf3137dd0..732aeb27e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SlabBlockBehavior.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior; 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.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.type.SlabType; import net.momirealms.craftengine.core.item.CustomItem; @@ -24,7 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -public class SlabBlockBehavior extends BukkitBlockBehavior { +public class SlabBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property typeProperty; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java index 6556345dc..933e2b3de 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StackableBlockBehavior.java @@ -46,7 +46,7 @@ public class StackableBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { Player player = context.getPlayer(); - if (player.isSecondaryUseActive()) { + if (player == null || player.isSecondaryUseActive()) { return InteractionResult.PASS; } Item item = (Item) context.getItem(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java index 69755b7ac..452528c54 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StemBlockBehavior.java @@ -13,6 +13,7 @@ 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.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.util.*; @@ -21,7 +22,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -public class StemBlockBehavior extends BukkitBlockBehavior { +public class StemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final IntegerProperty ageProperty; private final Key fruit; 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 e945d546c..c297d3289 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 @@ -14,6 +14,7 @@ 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.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf; import net.momirealms.craftengine.core.entity.player.InteractionResult; @@ -40,7 +41,7 @@ import java.util.Optional; import java.util.concurrent.Callable; @SuppressWarnings("DuplicatedCode") -public class TrapDoorBlockBehavior extends BukkitBlockBehavior { +public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property halfProperty; private final Property facingProperty; @@ -117,6 +118,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") private void playerToggle(UseOnContext context, ImmutableBlockState state) { 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())) { player.swingHand(context.getHand()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java index c487b4ab1..3ddde1aa7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/UnsafeCompositeBlockBehavior.java @@ -2,10 +2,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.FallOnBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.PlaceLiquidBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.*; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.UseOnContext; @@ -18,7 +15,7 @@ import java.util.Optional; import java.util.concurrent.Callable; public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior - implements FallOnBlockBehavior, PlaceLiquidBlockBehavior { + implements FallOnBlockBehavior, PlaceLiquidBlockBehavior, IsPathFindableBlockBehavior { private final AbstractBlockBehavior[] behaviors; public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List behaviors) { @@ -237,12 +234,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior @Override public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + boolean processed = false; for (AbstractBlockBehavior behavior : this.behaviors) { - if (!behavior.isPathFindable(thisBlock, args, superMethod)) { - return false; + if (behavior instanceof IsPathFindableBlockBehavior pathFindableBlockBehavior) { + if (!pathFindableBlockBehavior.isPathFindable(thisBlock, args, superMethod)) { + return false; + } else { + processed = true; + } } } - return (boolean) superMethod.call(); + if (!processed) return (boolean) superMethod.call(); + return true; } @Override 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 d3a2d1dd8..a8842a81f 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 @@ -84,7 +84,15 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { return null; } - return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || !previous.config.position.equals(this.position)); + 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 || + !previous.config.position.equals(this.position) + ); } @Override 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 14f4a3210..43b6af680 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 @@ -72,7 +72,15 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { return null; } - return new TextDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || !previous.config.position.equals(this.position)); + 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 || + !previous.config.position.equals(this.position) + ); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 9fee10396..08f477aaa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -115,7 +115,7 @@ public class BukkitItemManager extends AbstractItemManager { } @Override - public Optional> s2c(Item item, Player player) { + public Optional> s2c(Item item, @Nullable Player player) { if (item.isEmpty()) return Optional.empty(); return this.networkItemHandler.s2c(item, player); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java index eb3c19461..264f0c49e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java @@ -22,6 +22,7 @@ import net.momirealms.sparrow.nbt.ListTag; import net.momirealms.sparrow.nbt.StringTag; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -119,7 +120,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler> s2c(Item wrapped, Player player) { + public Optional> s2c(Item wrapped, @Nullable Player player) { boolean forceReturn = false; // 处理收纳袋 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java index e92092a98..532a84788 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java @@ -19,6 +19,7 @@ import net.momirealms.sparrow.nbt.ListTag; import net.momirealms.sparrow.nbt.StringTag; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -110,7 +111,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler> s2c(Item wrapped, Player player) { + public Optional> s2c(Item wrapped, @Nullable Player player) { boolean forceReturn = false; // 处理收纳袋 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 271930a23..23fde9447 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 @@ -13,6 +13,8 @@ public class BukkitItemBehaviors extends ItemBehaviors { public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); public static final Key DOUBLE_HIGH_BLOCK_ITEM = Key.from("craftengine:double_high_block_item"); public static final Key WALL_BLOCK_ITEM = Key.from("craftengine:wall_block_item"); + public static final Key CEILING_BLOCK_ITEM = Key.from("craftengine:ceiling_block_item"); + public static final Key GROUND_BLOCK_ITEM = Key.from("craftengine:ground_block_item"); public static void init() { register(EMPTY, EmptyItemBehavior.FACTORY); @@ -24,5 +26,7 @@ public class BukkitItemBehaviors extends ItemBehaviors { register(AXE_ITEM, AxeItemBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY); register(WALL_BLOCK_ITEM, WallBlockItemBehavior.FACTORY); + register(CEILING_BLOCK_ITEM, CeilingBlockItemBehavior.FACTORY); + register(GROUND_BLOCK_ITEM, GroundBlockItemBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CeilingBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CeilingBlockItemBehavior.java new file mode 100644 index 000000000..2a38e53a6 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/CeilingBlockItemBehavior.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.bukkit.item.behavior; + +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.Key; + +import java.nio.file.Path; +import java.util.Map; + +public class CeilingBlockItemBehavior extends BlockItemBehavior { + public static final Factory FACTORY = new Factory(); + + public CeilingBlockItemBehavior(Key ceilingBlockId) { + super(ceilingBlockId); + } + + @Override + public InteractionResult useOnBlock(UseOnContext context) { + return this.place(new BlockPlaceContext(context)); + } + + @Override + public InteractionResult place(BlockPlaceContext context) { + if (context.getClickedFace() != Direction.DOWN) { + return InteractionResult.PASS; + } + return super.place(context); + } + + public static class Factory implements ItemBehaviorFactory { + @Override + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { + Object id = arguments.get("block"); + if (id == null) { + throw new LocalizedResourceConfigException("warning.config.item.behavior.ceiling_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for ceiling_block_item behavior")); + } + if (id instanceof Map map) { + addPendingSection(pack, path, node, key, map); + return new CeilingBlockItemBehavior(key); + } else { + return new CeilingBlockItemBehavior(Key.of(id.toString())); + } + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/GroundBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/GroundBlockItemBehavior.java new file mode 100644 index 000000000..05956b049 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/GroundBlockItemBehavior.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.bukkit.item.behavior; + +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Direction; +import net.momirealms.craftengine.core.util.Key; + +import java.nio.file.Path; +import java.util.Map; + +public class GroundBlockItemBehavior extends BlockItemBehavior { + public static final Factory FACTORY = new Factory(); + + public GroundBlockItemBehavior(Key ceilingBlockId) { + super(ceilingBlockId); + } + + @Override + public InteractionResult useOnBlock(UseOnContext context) { + return this.place(new BlockPlaceContext(context)); + } + + @Override + public InteractionResult place(BlockPlaceContext context) { + if (context.getClickedFace() != Direction.UP) { + return InteractionResult.PASS; + } + return super.place(context); + } + + public static class Factory implements ItemBehaviorFactory { + @Override + public ItemBehavior create(Pack pack, Path path, String node, Key key, Map arguments) { + Object id = arguments.get("block"); + if (id == null) { + throw new LocalizedResourceConfigException("warning.config.item.behavior.ground_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for ground_block_item behavior")); + } + if (id instanceof Map map) { + addPendingSection(pack, path, node, key, map); + return new GroundBlockItemBehavior(key); + } else { + return new GroundBlockItemBehavior(Key.of(id.toString())); + } + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java index 94b5254f5..58d597fa8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WallBlockItemBehavior.java @@ -24,6 +24,7 @@ public class WallBlockItemBehavior extends BlockItemBehavior { return this.place(new BlockPlaceContext(context)); } + @Override public InteractionResult place(BlockPlaceContext context) { if (context.getClickedFace().stepY() != 0) { return InteractionResult.PASS; 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 27dbb0857..6ce68c38d 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 @@ -33,6 +33,7 @@ public class BukkitCommandManager extends AbstractCommandManager new ReloadCommand(this, plugin), new GetItemCommand(this, plugin), new GiveItemCommand(this, plugin), + new ClearItemCommand(this, plugin), new ItemBrowserPlayerCommand(this, plugin), new ItemBrowserAdminCommand(this, plugin), new SearchRecipePlayerCommand(this, plugin), @@ -63,8 +64,9 @@ public class BukkitCommandManager extends AbstractCommandManager new SendResourcePackCommand(this, plugin), new DebugSaveDefaultResourcesCommand(this, plugin), new DebugCleanCacheCommand(this, plugin), - new DebugGenerateInternalAssetsCommand(this, plugin) -// new OverrideGiveCommand(this, plugin) + new DebugGenerateInternalAssetsCommand(this, plugin), + new DebugCustomModelDataCommand(this, plugin), + new DebugImageCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ClearItemCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ClearItemCommand.java new file mode 100644 index 000000000..c387f4adf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ClearItemCommand.java @@ -0,0 +1,98 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.util.ItemStackUtils; +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 net.momirealms.craftengine.core.util.Key; +import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.data.MultiplePlayerSelector; +import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; +import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class ClearItemCommand extends BukkitCommandFeature { + + public ClearItemCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(FlagKeys.SILENT_FLAG) + .required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true)) + .required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions()); + } + })) + .optional("amount", IntegerParser.integerParser(0)) + .handler(context -> { + MultiplePlayerSelector selector = context.get("player"); + int amount = context.getOrDefault("amount", -1); + NamespacedKey namespacedKey = context.get("id"); + Key itemId = Key.of(namespacedKey.namespace(), namespacedKey.value()); + Predicate predicate = nmsStack -> { + Optional id = BukkitItemManager.instance().wrap(ItemStackUtils.asCraftMirror(nmsStack)).customId(); + return id.isPresent() && id.get().equals(itemId); + }; + int totalCount = 0; + Collection players = selector.values(); + for (Player player : players) { + Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); + Object inventory = FastNMS.INSTANCE.method$Player$getInventory(serverPlayer); + Object inventoryMenu = FastNMS.INSTANCE.field$Player$inventoryMenu(serverPlayer); + totalCount += FastNMS.INSTANCE.method$Inventory$clearOrCountMatchingItems(inventory, predicate, amount, FastNMS.INSTANCE.method$InventoryMenu$getCraftSlots(inventoryMenu)); + FastNMS.INSTANCE.method$AbstractContainerMenu$broadcastChanges(FastNMS.INSTANCE.field$Player$containerMenu(serverPlayer)); + FastNMS.INSTANCE.method$InventoryMenu$slotsChanged(inventoryMenu, inventory); + } + if (totalCount == 0) { + if (players.size() == 1) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_FAILED_SINGLE, Component.text(players.iterator().next().getName())); + } else { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_FAILED_MULTIPLE, Component.text(players.size())); + } + } else { + if (amount == 0) { + if (players.size() == 1) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_TEST_SINGLE, Component.text(totalCount), Component.text(players.iterator().next().getName())); + } else { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_TEST_MULTIPLE, Component.text(totalCount), Component.text(players.size())); + } + } else { + if (players.size() == 1) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_SUCCESS_SINGLE, Component.text(totalCount), Component.text(players.iterator().next().getName())); + } else { + handleFeedback(context, MessageConstants.COMMAND_ITEM_CLEAR_SUCCESS_MULTIPLE, Component.text(totalCount), Component.text(players.size())); + } + } + } + }); + } + + @Override + public String getFeatureID() { + return "clear_item"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCustomModelDataCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCustomModelDataCommand.java new file mode 100644 index 000000000..21d08f6f6 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCustomModelDataCommand.java @@ -0,0 +1,88 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; +import net.momirealms.craftengine.bukkit.api.CraftEngineItems; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.Key; +import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class DebugCustomModelDataCommand extends BukkitCommandFeature { + + public DebugCustomModelDataCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .optional("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().itemManager().cachedCustomItemSuggestions()); + } + })) + .handler(this::handleCommand); + } + + @Override + public String getFeatureID() { + return "debug_custom_model_data"; + } + + private void handleCommand(CommandContext context) { + NamespacedKey namespacedKey = context.getOrDefault("id", null); + @Nullable BukkitServerPlayer player = context.sender() instanceof Player p ? BukkitAdaptors.adapt(p) : null; + + if (namespacedKey != null) { + Key itemId = KeyUtils.namespacedKey2Key(namespacedKey); + CustomItem customItem = CraftEngineItems.byId(itemId); + if (customItem == null) return; + Item item = customItem.buildItem(player); + sendMessage(context, getCustomModelData(item, player)); + return; + } + + if (player != null) { + Item item = player.getItemInHand(InteractionHand.MAIN_HAND).copyWithCount(1); + sendMessage(context, getCustomModelData(item, player)); + } + } + + private int getCustomModelData(Item itemStack, BukkitServerPlayer player) { + return plugin().itemManager().s2c(itemStack, player) + .map(Item::customModelData) + .orElse(itemStack.customModelData()) + .orElse(0); + } + + private void sendMessage(CommandContext context, int customModelData) { + Component message = Component.text(customModelData) + .hoverEvent(Component.text("Copy", NamedTextColor.YELLOW)) + .clickEvent(ClickEvent.suggestCommand(String.valueOf(customModelData))); + plugin().senderFactory().wrap(context.sender()).sendMessage(message); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugImageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugImageCommand.java new file mode 100644 index 000000000..9ccb4d1ea --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugImageCommand.java @@ -0,0 +1,93 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.core.font.BitmapImage; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.FormatUtils; +import net.momirealms.craftengine.core.util.Key; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class DebugImageCommand extends BukkitCommandFeature { + + public DebugImageCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().fontManager().cachedImagesSuggestions()); + } + })) + .optional("row", IntegerParser.integerParser(0)) + .optional("column", IntegerParser.integerParser(0)) + .handler(context -> { + Key imageId = KeyUtils.namespacedKey2Key(context.get("id")); + plugin().fontManager().bitmapImageByImageId(imageId).ifPresent(image -> { + int row = context.getOrDefault("row", 0); + int column = context.getOrDefault("column", 0); + String string = image.isValidCoordinate(row, column) + ? imageId.asString() + ((row != 0 || column != 0) ? ":" + row + ":" + column : "") // 自动最小化 + : imageId.asString() + ":" + (row = 0) + ":" + (column = 0); // 因为是无效的所以说要强调告诉获取的是00 + Component component = Component.empty().children( + List.of( + Component.text(string) + .hoverEvent(image.componentAt(row, column).color(NamedTextColor.WHITE)) + .clickEvent(ClickEvent.suggestCommand(string)), + getHelperInfo(image, row, column) + ) + ); + plugin().senderFactory().wrap(context.sender()).sendMessage(component); + }); + }); + } + + @Override + public String getFeatureID() { + return "debug_image"; + } + + private static TextComponent getHelperInfo(BitmapImage image, int row, int column) { + String raw = new String(Character.toChars(image.codepointAt(row, column))); + String font = image.font().toString(); + return Component.empty().children(List.of( + Component.text(" "), + Component.text("[MiniMessage]") + .color(TextColor.color(255,192,203)) + .hoverEvent(Component.text("Copy", NamedTextColor.YELLOW)) + .clickEvent(ClickEvent.suggestCommand(FormatUtils.miniMessageFont(raw, font))), + Component.text(" "), + Component.text("[MineDown]") + .color(TextColor.color(123,104,238)) + .hoverEvent(Component.text("Copy", NamedTextColor.YELLOW)) + .clickEvent(ClickEvent.suggestCommand(FormatUtils.mineDownFont(raw, font))), + Component.text(" "), + Component.text("[RAW]") + .color(TextColor.color(119,136,153)) + .hoverEvent(Component.text("Copy", NamedTextColor.YELLOW)) + .clickEvent(ClickEvent.suggestCommand("{\"text\":\"" + raw + "\",\"font\":\"" + font + "\"}")) + )); + } +} 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 99cf38b5f..4d7761189 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 @@ -3044,10 +3044,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes @Override public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { if (Config.disableItemOperations()) return; + BukkitServerPlayer player = (BukkitServerPlayer) user; + if (!player.isOnline()) return; MutableBoolean changed = new MutableBoolean(false); FriendlyByteBuf buf = event.getBuffer(); BukkitItemManager itemManager = BukkitItemManager.instance(); - BukkitServerPlayer player = (BukkitServerPlayer) user; Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf.source()); List> entries = buf.readCollection(ArrayList::new, byteBuf -> { RecipeBookEntry entry = RecipeBookEntry.read(byteBuf, __ -> itemManager.wrap(FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf))); 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 3487d233f..af6789fab 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 @@ -946,6 +946,13 @@ public class BukkitServerPlayer extends Player { return BukkitItemManager.instance().wrap(hand == InteractionHand.MAIN_HAND ? inventory.getItemInMainHand() : inventory.getItemInOffHand()); } + @NotNull + @Override + public Item getItemBySlot(int slot) { + PlayerInventory inventory = platformPlayer().getInventory(); + return BukkitItemManager.instance().wrap(inventory.getItem(slot)); + } + @Override public World world() { return new BukkitWorld(platformPlayer().getWorld()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java index a2981f6be..ad53f6d70 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.bukkit.sound; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; 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; @@ -9,13 +11,15 @@ import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.sound.AbstractSoundManager; import net.momirealms.craftengine.core.sound.JukeboxSong; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; public class BukkitSoundManager extends AbstractSoundManager { @@ -25,6 +29,65 @@ public class BukkitSoundManager extends AbstractSoundManager { Object resourceLocation = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); VANILLA_SOUND_EVENTS.add(KeyUtils.resourceLocationToKey(resourceLocation)); } + this.registerSongs(this.loadLastRegisteredSongs()); + } + + @Override + public void disable() { + this.saveLastRegisteredSongs(super.songs); + super.disable(); + } + + private void saveLastRegisteredSongs(Map songs) { + if (songs == null || songs.isEmpty()) return; + Path persistSongPath = this.plugin.dataFolderPath() + .resolve("cache") + .resolve("jukebox-songs.json"); + try { + Files.createDirectories(persistSongPath.getParent()); + JsonObject cache = new JsonObject(); + for (Map.Entry entry : songs.entrySet()) { + JsonObject songJson = new JsonObject(); + JukeboxSong song = entry.getValue(); + songJson.addProperty("sound_event", song.sound().asString()); + songJson.add("description", AdventureHelper.componentToJsonElement(song.description())); + songJson.addProperty("length_in_seconds", song.lengthInSeconds()); + songJson.addProperty("comparator_output", song.comparatorOutput()); + songJson.addProperty("range", song.range()); + cache.add(entry.getKey().asString(), songJson); + } + GsonHelper.writeJsonFile(cache, persistSongPath); + } catch (IOException e) { + this.plugin.logger().warn("Failed to save registered songs.", e); + } + } + + private Map loadLastRegisteredSongs() { + Path persistSongPath = this.plugin.dataFolderPath() + .resolve("cache") + .resolve("jukebox-songs.json"); + if (Files.exists(persistSongPath) && Files.isRegularFile(persistSongPath)) { + try { + Map songs = new HashMap<>(); + JsonObject cache = GsonHelper.readJsonFile(persistSongPath).getAsJsonObject(); + for (Map.Entry songEntry : cache.entrySet()) { + Key id = Key.of(songEntry.getKey()); + if (songEntry.getValue() instanceof JsonObject jo) { + songs.put(id, new JukeboxSong( + Key.of(jo.get("sound_event").getAsString()), + AdventureHelper.jsonElementToComponent(jo.get("description")), + jo.get("length_in_seconds").getAsFloat(), + jo.get("comparator_output").getAsInt(), + jo.get("range").getAsFloat() + )); + } + } + return songs; + } catch (IOException e) { + this.plugin.logger().warn("Failed to load registered songs.", e); + } + } + return Map.of(); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemStackUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemStackUtils.java index bbebf2a97..2e6425910 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemStackUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemStackUtils.java @@ -51,4 +51,8 @@ public final class ItemStackUtils { Item wrappedItem = BukkitItemManager.instance().wrap(itemStack); return UniqueIdItem.of(wrappedItem); } + + public static ItemStack asCraftMirror(Object itemStack) { + return FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack); + } } diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index f2ca1e16b..a2bfdbeef 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -43,6 +43,13 @@ give_item: - /craftengine item give - /ce item give +clear_item: + enable: true + permission: ce.command.admin.clear_item + usage: + - /craftengine item clear + - /ce item clear + item_browser_player: enable: true permission: ce.command.player.item_browser @@ -230,6 +237,20 @@ debug_clean_cache: - /craftengine debug clean-cache - /ce debug clean-cache +debug_custom_model_data: + enable: true + permission: ce.command.debug.custom_model_data + usage: + - /craftengine debug custom-model-data + - /ce debug custom-model-data + +debug_image: + enable: true + permission: ce.command.debug.image + usage: + - /craftengine debug image + - /ce debug image + debug_generate_internal_assets: enable: false permission: ce.command.debug.generate_internal_assets diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 139001e20..c5c00829e 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -193,11 +193,17 @@ resource-pack: ip: "localhost" port: 8163 protocol: "http" + # Blocks all requests from non-Minecraft clients. deny-non-minecraft-request: true + # Generates a single-use, time-limited download link for each player. one-time-token: true - rate-limit: - max-requests: 10 - reset-interval: 30 + 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 + # Minimum guaranteed download speed per player to ensure acceptable download performance during concurrent downloads + min-download-speed-per-player: 50_000 # 50KB/s + # Prevent a single IP from sending too many resource pack download requests in a short time period + qps-per-ip: 5/60 # 5 requests per 60 seconds item: # [Premium Exclusive] @@ -261,6 +267,10 @@ block: # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience. sound-system: enable: true + # Should we process events that were canceled by other plugins to restore sounds? + process-cancelled-events: + step: true + break: true # Adventure mode requires correct tools to break custom blocks. # Vanilla clients cannot recognize custom block IDs (e.g., craftengine:custom_100). # @@ -542,6 +552,12 @@ 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: [] + # Enables or disables debug mode debug: common: false diff --git a/common-files/src/main/resources/craft-engine.properties b/common-files/src/main/resources/craft-engine.properties index 23c13b14f..1b396c4fe 100644 --- a/common-files/src/main/resources/craft-engine.properties +++ b/common-files/src/main/resources/craft-engine.properties @@ -34,4 +34,5 @@ reactive-streams=${reactive_streams_version} amazon-sdk-s3=${amazon_awssdk_version} amazon-sdk-eventstream=${amazon_awssdk_eventstream_version} evalex=${evalex_version} -jimfs=${jimfs_version} \ No newline at end of file +jimfs=${jimfs_version} +bucket4j=${bucket4j_version} \ No newline at end of file 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 df459b4d2..ed493907e 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 @@ -61,7 +61,7 @@ templates: model: path: ${model_path} generation: - parent: minecraft:block/leaves + parent: minecraft:block/${leaves_base_model} textures: all: ${texture_path} waterlogged: @@ -83,15 +83,21 @@ templates: # any leaves block default:block_state/leaves: template: default:block_state/__leaves__ - arguments::auto_state: leaves + arguments: + auto_state: leaves + leaves_base_model: leaves # tintable leaves block default:block_state/tintable_leaves: template: default:block_state/__leaves__ - arguments::auto_state: tintable_leaves + arguments: + auto_state: tintable_leaves + leaves_base_model: leaves # non-tintable leaves block default:block_state/non_tintable_leaves: template: default:block_state/__leaves__ - arguments::auto_state: non_tintable_leaves + arguments: + auto_state: non_tintable_leaves + leaves_base_model: cube_all # trapdoor block default:block_state/trapdoor: properties: diff --git a/common-files/src/main/resources/resources/default/configuration/translations.yml b/common-files/src/main/resources/resources/default/configuration/translations.yml index 13f1a691f..60e1ff8df 100644 --- a/common-files/src/main/resources/resources/default/configuration/translations.yml +++ b/common-files/src/main/resources/resources/default/configuration/translations.yml @@ -127,6 +127,70 @@ translations: emoji.tip: 使用''来发送表情'' emoji.time: '当前时间: ' emoji.location: '当前坐标: ,,' + fr_fr: + item.chinese_lantern: Lanterne Chinoise + item.fairy_flower: Fleur Fée + item.reed: Roseau + item.flame_cane: Canne de Flamme + item.ender_pearl_flower_seeds: Graines de Fleur de Perle du Néant + item.bench: Banc + item.table_lamp: Lampe de Table + item.wooden_chair: Chaise en Bois + item.topaz_rod: Canne à Topaze + item.topaz_bow: Arc en Topaze + item.topaz_crossbow: Arbalète en Topaze + item.topaz_pickaxe: Pioche en Topaze + item.topaz_axe: Hache en Topaze + item.topaz_hoe: Houe en Topaze + item.topaz_shovel: Pelle en Topaze + item.topaz_sword: Épée en Topaze + item.topaz_helmet: Casque en Topaze + item.topaz_chestplate: Plastron en Topaze + item.topaz_leggings: Jambières en Topaze + item.topaz_boots: Bottes en Topaze + item.topaz_trident: Trident en Topaze + item.topaz_ore: Minerai de Topaze + item.deepslate_topaz_ore: Minerai de Topaze en Schiste Sombre + item.topaz: Topaze + item.palm_log: Bois de Palmier + item.stripped_palm_log: Bois de Palmier Écorcé + item.palm_wood: Bois de Palmier + item.stripped_palm_wood: Bois de Palmier Écorcé + item.palm_planks: Planches de Palmier + item.palm_sapling: Pousse de Palmier + item.palm_leaves: Feuilles de Palmier + item.palm_trapdoor: Trappe en Palmier + item.palm_door: Porte en Palmier + item.palm_fence_gate: Portail de Barrière en Palmier + item.palm_slab: Dalle en Palmier + item.palm_stairs: Escalier en Palmier + item.palm_pressure_plate: Plaque de Pression en Palmier + item.palm_button: Bouton en Palmier + item.palm_fence: Barrière en Palmier + item.netherite_anvil: Enclume en Netherite + item.gunpowder_block: Bloc de Poudre à Canon + item.solid_gunpowder_block: Bloc de Poudre à Canon Solide + item.copper_coil: Bobine de Cuivre + item.flame_elytra: Élytres de Flamme + item.pebble: Caillou + item.cap: Casquette + item.flower_basket: Panier de Fleurs + item.chessboard_block: Bloc Damier + item.safe_block: Coffre-Fort + item.sofa: Canapé + item.amethyst_torch: Torche en Améthyste + item.hami_melon_slice: Tranche de Melon de Hami + item.hami_melon: Melon de Hami + item.hami_melon_seeds: Graines de Melon de Hami + item.magma_fruit: Fruit de Magma + category.default.name: Ressources par Défaut + category.default.lore: Contient la configuration par défaut de CraftEngine + category.palm_tree: Palmier + category.topaz: Topaze + category.misc: Divers + emoji.tip: Utilisez '' pour envoyer l'emoji '' + emoji.time: 'Heure actuelle: ' + emoji.location: 'Coordonnées actuelles: ,,' # This section is for localizing internal block IDs (craftengine:xxx_xx). # Some other plugins support displaying block names using lang components. # This might be useful for the client-side, but it's not mandatory. @@ -203,3 +267,39 @@ lang: block_name:default:hami_melon_stem: 哈密瓜茎 block_name:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:magma_plant: 岩浆植物 + fr_fr: + block_name:default:chinese_lantern: Lanterne Chinoise + block_name:default:netherite_anvil: Enclume en Netherite + block_name:default:topaz_ore: Minerai de Topaze + block_name:default:deepslate_topaz_ore: Minerai de Topaze en Schiste Sombre + block_name:default:palm_log: Bois de Palmier + block_name:default:stripped_palm_log: Bois de Palmier Écorcé + block_name:default:palm_wood: Bois de Palmier + block_name:default:stripped_palm_wood: Bois de Palmier Écorcé + block_name:default:palm_planks: Planches de Palmier + block_name:default:palm_sapling: Pousse de Palmier + block_name:default:palm_leaves: Feuilles de Palmier + block_name:default:palm_trapdoor: Trappe en Palmier + block_name:default:palm_door: Porte en Palmier + block_name:default:palm_fence_gate: Portail de Barrière en Palmier + block_name:default:palm_slab: Dalle en Palmier + block_name:default:palm_stairs: Escalier en Palmier + block_name:default:palm_button: Bouton en Palmier + block_name:default:palm_fence: Barrière en Palmier + block_name:default:fairy_flower: Fleur Fée + block_name:default:reed: Roseau + block_name:default:flame_cane: Canne de Flamme + block_name:default:ender_pearl_flower: Fleur de Perle du Néant + block_name:default:gunpowder_block: Bloc de Poudre à Canon + block_name:default:solid_gunpowder_block: Bloc de Poudre à Canon Solide + block_name:default:copper_coil: Bobine de Cuivre + block_name:default:chessboard_block: Bloc Damier + block_name:default:safe_block: Coffre-Fort + block_name:default:sleeper_sofa: Canapé + block_name:default:sofa: Canapé + block_name:default:amethyst_torch: Torche en Améthyste + block_name:default:amethyst_wall_torch: Torche en Améthyste + block_name:default:hami_melon: Melon de Hami + block_name:default:hami_melon_stem: Tige de Melon de Hami + block_name:default:attached_hami_melon_stem: Tige de Melon de Hami + block_name:default:magma_plant: Plante de Magma 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 7b47d61f2..3ed3c4522 100644 --- a/common-files/src/main/resources/resources/internal/configuration/mappings.yml +++ b/common-files/src/main/resources/resources/internal/configuration/mappings.yml @@ -842,6 +842,7 @@ block-state-mappings: #### Leaves #### # The 'distance' and 'persistent' properties are used under the hood to optimize how leaves decay, but visually? They look exactly the same. # These are some of the few block types that actually support transparent textures. + # tintable leaves oak_leaves[distance=1,persistent=true,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true] oak_leaves[distance=1,persistent=true,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false] oak_leaves[distance=1,persistent=false,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true] @@ -868,58 +869,6 @@ block-state-mappings: oak_leaves[distance=6,persistent=false,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false] oak_leaves[distance=7,persistent=false,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true] oak_leaves[distance=7,persistent=false,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=1,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=1,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=1,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=1,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=2,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=2,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=2,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=2,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=3,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=3,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=3,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=3,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=4,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=4,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=4,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=4,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=5,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=5,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=5,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=5,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=6,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=6,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=6,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=6,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - spruce_leaves[distance=7,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] - spruce_leaves[distance=7,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=1,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=1,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=1,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=1,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=2,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=2,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=2,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=2,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=3,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=3,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=3,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=3,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=4,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=4,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=4,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=4,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=5,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=5,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=5,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=5,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=6,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=6,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=6,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=6,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] - birch_leaves[distance=7,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] - birch_leaves[distance=7,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] jungle_leaves[distance=1,persistent=true,waterlogged=true]: jungle_leaves[distance=7,persistent=true,waterlogged=true] jungle_leaves[distance=1,persistent=true,waterlogged=false]: jungle_leaves[distance=7,persistent=true,waterlogged=false] jungle_leaves[distance=1,persistent=false,waterlogged=true]: jungle_leaves[distance=7,persistent=true,waterlogged=true] @@ -972,32 +921,6 @@ block-state-mappings: acacia_leaves[distance=6,persistent=false,waterlogged=false]: acacia_leaves[distance=7,persistent=true,waterlogged=false] acacia_leaves[distance=7,persistent=false,waterlogged=true]: acacia_leaves[distance=7,persistent=true,waterlogged=true] acacia_leaves[distance=7,persistent=false,waterlogged=false]: acacia_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=1,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=1,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=1,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=1,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=2,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=2,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=2,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=2,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=3,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=3,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=3,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=3,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=4,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=4,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=4,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=4,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=5,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=5,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=5,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=5,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=6,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=6,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=6,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=6,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] - cherry_leaves[distance=7,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] - cherry_leaves[distance=7,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] dark_oak_leaves[distance=1,persistent=true,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true] dark_oak_leaves[distance=1,persistent=true,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false] dark_oak_leaves[distance=1,persistent=false,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true] @@ -1024,33 +947,6 @@ block-state-mappings: dark_oak_leaves[distance=6,persistent=false,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false] dark_oak_leaves[distance=7,persistent=false,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true] dark_oak_leaves[distance=7,persistent=false,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false] - $$>=1.21.4#leaves: - pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] - pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] - pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] mangrove_leaves[distance=1,persistent=true,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true] mangrove_leaves[distance=1,persistent=true,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false] mangrove_leaves[distance=1,persistent=false,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true] @@ -1077,6 +973,7 @@ block-state-mappings: mangrove_leaves[distance=6,persistent=false,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false] mangrove_leaves[distance=7,persistent=false,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true] mangrove_leaves[distance=7,persistent=false,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false] + # non tintable leaves azalea_leaves[distance=1,persistent=true,waterlogged=true]: azalea_leaves[distance=7,persistent=true,waterlogged=true] azalea_leaves[distance=1,persistent=true,waterlogged=false]: azalea_leaves[distance=7,persistent=true,waterlogged=false] azalea_leaves[distance=1,persistent=false,waterlogged=true]: azalea_leaves[distance=7,persistent=true,waterlogged=true] @@ -1129,6 +1026,113 @@ block-state-mappings: flowering_azalea_leaves[distance=6,persistent=false,waterlogged=false]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] flowering_azalea_leaves[distance=7,persistent=false,waterlogged=true]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true] flowering_azalea_leaves[distance=7,persistent=false,waterlogged=false]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=1,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=1,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=1,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=1,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=2,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=2,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=2,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=2,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=3,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=3,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=3,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=3,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=4,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=4,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=4,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=4,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=5,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=5,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=5,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=5,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=6,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=6,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=6,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=6,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + cherry_leaves[distance=7,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true] + cherry_leaves[distance=7,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false] + $$>=1.21.4#leaves: + pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true] + pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false] + ## Spruce leaves always use color #619961 + # spruce_leaves[distance=1,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=1,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=1,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=1,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=2,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=2,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=2,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=2,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=3,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=3,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=3,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=3,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=4,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=4,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=4,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=4,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=5,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=5,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=5,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=5,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=6,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=6,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=6,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=6,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + # spruce_leaves[distance=7,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true] + # spruce_leaves[distance=7,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false] + ## Birch leaves always use color #80a755 + # birch_leaves[distance=1,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=1,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=1,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=1,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=2,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=2,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=2,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=2,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=3,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=3,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=3,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=3,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=4,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=4,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=4,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=4,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=5,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=5,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=5,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=5,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=6,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=6,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=6,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=6,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] + # birch_leaves[distance=7,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true] + # birch_leaves[distance=7,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false] #### Tripwire #### # Tripwires actually have 128 different states, but we're keeping just two of them to match vanilla's visual styles. @@ -4447,4 +4451,4 @@ block-state-mappings: # chorus_plant[down=false,east=false,north=false,south=false,up=true,west=true]: chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] # chorus_plant[down=false,east=false,north=false,south=false,up=true,west=false]: chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] # chorus_plant[down=false,east=false,north=false,south=false,up=false,west=true]: chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] - # chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] \ No newline at end of file + # chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] 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 6912f3f26..af4c34ee5 100644 --- a/common-files/src/main/resources/resources/internal/configuration/translations.yml +++ b/common-files/src/main/resources/resources/internal/configuration/translations.yml @@ -37,4 +37,17 @@ translations: internal.get_item.1: Rechtsklick, um einen Stapel zu nehmen internal.cooking_info: Rezeptinformationen internal.cooking_info.0: 'Zeit: Ticks' - internal.cooking_info.1: 'Erfahrung: ' \ No newline at end of file + internal.cooking_info.1: 'Erfahrung: ' + fr_fr: + internal.next_page: Page Suivante + internal.previous_page: Page Précédente + internal.return: Retour à la Page Précédente + internal.exit: Quitter + internal.next_recipe: Recette Suivante + internal.previous_recipe: Recette Précédente + internal.get_item: Obtenir l'Objet + internal.get_item.0: Clic Gauche pour en prendre un + internal.get_item.1: Clic Droit pour prendre une pile + internal.cooking_info: Informations sur la Recette + internal.cooking_info.0: 'Temps: ticks' + internal.cooking_info.1: 'Expérience: ' diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 7fa0208a1..761e46f38 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -49,6 +49,12 @@ command.item.get.failure.not_exist: "':'':''>" command.item.give.success.multiple: "':'':''>" command.item.give.failure.not_exist: "'>" +command.item.clear.failed.single: "'>" +command.item.clear.failed.multiple: "'>" +command.item.clear.success.single: "':''>" +command.item.clear.success.multiple: "':''>" +command.item.clear.test.single: "':''>" +command.item.clear.test.multiple: "':''>" command.search_recipe.not_found: "No recipe found for this item" command.search_usage.not_found: "No usage found for this item" command.search_recipe.no_item: "Please hold an item before running this command" @@ -74,6 +80,7 @@ warning.config.yaml.duplicated_key: "Issue found in file - Found du warning.config.yaml.inconsistent_value_type: "Issue found in file - Found duplicated key '' at line with different value types, this might cause unexpected results." warning.config.type.int: "Issue found in file - Failed to load '': Cannot cast '' to integer type for option ''." warning.config.type.boolean: "Issue found in file - Failed to load '': Cannot cast '' to boolean type for option ''." +warning.config.type.long: "Issue found in file - Failed to load '': Cannot cast '' to long type for option ''." warning.config.type.float: "Issue found in file - Failed to load '': Cannot cast '' to float type for option ''." warning.config.type.double: "Issue found in file - Failed to load '': Cannot cast '' to double type for option ''." warning.config.type.quaternionf: "Issue found in file - Failed to load '': Cannot cast '' to Quaternionf type for option ''." @@ -213,6 +220,8 @@ warning.config.item.behavior.missing_type: "Issue found in file warning.config.item.behavior.invalid_type: "Issue found in file - The item '' is using an invalid item behavior type ''." warning.config.item.behavior.block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'block_item' behavior." warning.config.item.behavior.wall_block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'wall_block_item' behavior." +warning.config.item.behavior.ceiling_block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'ceiling_block_item' behavior." +warning.config.item.behavior.ground_block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'ground_block_item' behavior." warning.config.item.behavior.furniture.missing_furniture: "Issue found in file - The item '' is missing the required 'furniture' argument for 'furniture_item' behavior." warning.config.item.behavior.liquid_collision.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'liquid_collision_block_item' behavior." warning.config.item.behavior.double_high.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'double_high_block_item' behavior." @@ -357,6 +366,7 @@ warning.config.block.behavior.attached_stem.missing_stem: "Issue found i warning.config.block.behavior.chime.missing_sounds_projectile_hit: "Issue found in file - The block '' is missing the required 'sounds.projectile-hit' argument for 'chime_block' behavior." warning.config.block.behavior.surface_spreading.missing_base_block: "Issue found in file - The block '' is missing the required 'base-block' argument for 'surface_spreading_block' behavior." warning.config.block.behavior.snowy.missing_snowy: "Issue found in file - The block '' is missing the required 'snowy' property for 'snowy_block' behavior." +warning.config.block.behavior.hangable.missing_hanging: "Issue found in file - The block '' is missing the required 'hanging' property for 'hangable_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.conflict: "Issue found in file - Failed to generate model for '' as two or more configurations attempt to generate different json models with the same path: ''." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 52a01b05e..3de669d54 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -49,6 +49,12 @@ command.item.get.failure.not_exist: "':'':''>" command.item.give.success.multiple: "':'':''>" command.item.give.failure.not_exist: "'>" +command.item.clear.failed.single: "'>" +command.item.clear.failed.multiple: "'>" +command.item.clear.success.single: "':''>" +command.item.clear.success.multiple: "':''>" +command.item.clear.test.single: "':''>" +command.item.clear.test.multiple: "':''>" command.search_recipe.not_found: "找不到此物品的配方" command.search_usage.not_found: "找不到此物品的用途" command.search_recipe.no_item: "请手持物品后再执行此命令" @@ -74,6 +80,7 @@ warning.config.yaml.duplicated_key: "在文件 发现问题 - 在 warning.config.yaml.inconsistent_value_type: "在文件 发现问题 - 在第行发现重复且值类型不同的键 '', 这可能会导致一些意料之外的问题" warning.config.type.int: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为整数类型 (选项 '')" warning.config.type.boolean: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为布尔类型 (选项 '')" +warning.config.type.long: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为长整型类型 (选项 '')" warning.config.type.float: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为浮点数类型 (选项 '')" warning.config.type.double: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度类型 (选项 '')" warning.config.type.quaternionf: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为四元数类型 (选项 '')" @@ -213,6 +220,8 @@ warning.config.item.behavior.missing_type: "在文件 发现问 warning.config.item.behavior.invalid_type: "在文件 发现问题 - 物品 '' 使用了无效的行为类型 ''" warning.config.item.behavior.block.missing_block: "在文件 发现问题 - 物品 '' 的 'block_item' 行为缺少必需的 'block' 参数" warning.config.item.behavior.wall_block.missing_block: "在文件 发现问题 - 物品 '' 缺少 'wall_block_item' 行为所需的 'block' 参数" +warning.config.item.behavior.ceiling_block.missing_block: "在文件 发现问题 - 物品 '' 缺少 'ceiling_block_item' 行为所需的 'block' 参数" +warning.config.item.behavior.ground_block.missing_block: "在文件 发现问题 - 物品 '' 缺少 'ground_block_item' 行为所需的 'block' 参数" warning.config.item.behavior.furniture.missing_furniture: "在文件 发现问题 - 物品 '' 的 'furniture_item' 行为缺少必需的 'furniture' 参数" warning.config.item.behavior.liquid_collision.missing_block: "在文件 发现问题 - 物品 '' 的 'liquid_collision_block_item' 行为缺少必需的 'block' 参数" warning.config.item.behavior.double_high.missing_block: "在文件 发现问题 - 物品 '' 的 'double_high_block_item' 行为缺少必需的 'block' 参数" @@ -357,6 +366,7 @@ warning.config.block.behavior.attached_stem.missing_stem: "在文件 在文件 发现问题 - 方块 '' 的 'chime_block' 行为缺少必需的 'sounds.projectile-hit' 选项" warning.config.block.behavior.surface_spreading.missing_base_block: "在文件 发现问题 - 方块 '' 的 'surface_spreading_block' 行为缺少必需的 'base-block' 选项" warning.config.block.behavior.snowy.missing_snowy: "在文件 发现问题 - 方块 '' 的 'snowy_block' 行为缺少必需的 'snowy' 属性" +warning.config.block.behavior.hangable.missing_hanging: "在文件 发现问题 - 方块 '' 的 'hangable_block' 行为缺少必需的 'hanging' 属性" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 9ad35c52d..6c138b529 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") // S3 - implementation("net.momirealms:craft-engine-s3:0.8") + implementation("net.momirealms:craft-engine-s3:0.9") // Util compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") // Adventure @@ -69,6 +69,8 @@ dependencies { compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") // concurrentutil compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}") + // bucket4j + compileOnly("com.bucket4j:bucket4j_jdk17-core:${rootProject.properties["bucket4j_version"]}") } java { @@ -107,18 +109,28 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") // bucket4j } } publishing { repositories { maven { + name = "releases" url = uri("https://repo.momirealms.net/releases") credentials(PasswordCredentials::class) { username = System.getenv("REPO_USERNAME") password = System.getenv("REPO_PASSWORD") } } + maven { + name = "snapshot" + url = uri("https://repo.momirealms.net/snapshots") + credentials(PasswordCredentials::class) { + username = System.getenv("REPO_USERNAME") + password = System.getenv("REPO_PASSWORD") + } + } } publications { create("mavenJava") { @@ -139,5 +151,35 @@ publishing { } } } + create("mavenJavaSnapshot") { + groupId = "net.momirealms" + artifactId = "craft-engine-core" + version = "${rootProject.properties["project_version"]}-SNAPSHOT" + artifact(tasks["sourcesJar"]) + from(components["shadow"]) + pom { + name = "CraftEngine API" + url = "https://github.com/Xiao-MoMi/craft-engine" + licenses { + license { + name = "GNU General Public License v3.0" + url = "https://www.gnu.org/licenses/gpl-3.0.html" + distribution = "repo" + } + } + } + } } +} + +tasks.register("publishRelease") { + group = "publishing" + description = "Publishes to the release repository" + dependsOn("publishMavenJavaPublicationToReleaseRepository") +} + +tasks.register("publishSnapshot") { + group = "publishing" + description = "Publishes to the snapshot repository" + dependsOn("publishMavenJavaSnapshotPublicationToSnapshotRepository") } \ 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 e9ff489b8..66abb09a2 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 @@ -550,7 +550,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } AutoStateGroup group = AutoStateGroup.byId(autoStateType); if (group == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values())); + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateType, EnumUtils.toString(AutoStateGroup.values())); } futureVisualStates.put(appearanceName, this.visualBlockStateAllocator.requestAutoState(autoStateId, group)); } else { @@ -704,7 +704,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem @NotNull private Map> parseBlockProperties(Map propertiesSection) { - Map> properties = new HashMap<>(); + Map> properties = new LinkedHashMap<>(); for (Map.Entry entry : propertiesSection.entrySet()) { Property property = Properties.fromMap(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey())); properties.put(entry.getKey(), property); 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 569fc7d87..22bcf2b81 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.SPRUCE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), + Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_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.SPRUCE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), + Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES), (w) -> w.getProperty("waterlogged") ), TINTABLE_LEAVES("tintable_leaves", - Set.of(BlockKeys.OAK_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_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), (w) -> !(boolean) w.getProperty("waterlogged") ), WATERLOGGED_TINTABLE_LEAVES( "waterlogged_tintable_leaves", - Set.of(BlockKeys.OAK_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_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), (w) -> w.getProperty("waterlogged") ), LEAVES("leaves", diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index 310131a79..6d794219f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -73,7 +73,7 @@ public abstract class BlockBehavior { superMethod.call(); } - // 1.20+ BlockState state, LevelReader world, BlockPos pos + // BlockState state, LevelReader world, BlockPos pos public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { return (boolean) superMethod.call(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/IsPathFindableBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/IsPathFindableBlockBehavior.java new file mode 100644 index 000000000..7fe7d8e06 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/IsPathFindableBlockBehavior.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.block.behavior; + +import java.util.concurrent.Callable; + +public interface IsPathFindableBlockBehavior { + + // 1.20-1.20.4 BlockState state, BlockGetter world, BlockPos pos, PathComputationType type + // 1.20.5+ BlockState state, PathComputationType pathComputationType + boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception; +} 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 ab16f7a96..52ea8252d 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 @@ -132,7 +132,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { .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(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) + .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")) 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 82ad30715..c8088ef4f 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 @@ -26,6 +26,9 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @NotNull public abstract Item getItemInHand(InteractionHand hand); + @NotNull + public abstract Item getItemBySlot(int slot); + @Override public abstract Object platformPlayer(); 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 640e66d9a..df9e4fdff 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 @@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import org.ahocorasick.trie.Token; import org.ahocorasick.trie.Trie; +import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; import javax.imageio.ImageIO; @@ -52,6 +53,8 @@ public abstract class AbstractFontManager implements FontManager { protected Map emojiMapper; protected List emojiList; protected List allEmojiSuggestions; + // Cached command suggestions + protected final List cachedImagesSuggestions = new ArrayList<>(); public AbstractFontManager(CraftEngine plugin) { this.plugin = plugin; @@ -95,6 +98,7 @@ public abstract class AbstractFontManager implements FontManager { public void unload() { this.fonts.clear(); this.images.clear(); + this.cachedImagesSuggestions.clear(); this.illegalChars.clear(); this.emojis.clear(); this.networkTagTrie = null; @@ -415,6 +419,12 @@ public abstract class AbstractFontManager implements FontManager { return Optional.ofNullable(this.fonts.get(id)); } + + @Override + public Collection cachedImagesSuggestions() { + return Collections.unmodifiableCollection(this.cachedImagesSuggestions); + } + private Font getOrCreateFont(Key key) { return this.fonts.computeIfAbsent(key, Font::new); } @@ -712,6 +722,7 @@ public abstract class AbstractFontManager implements FontManager { } AbstractFontManager.this.images.put(id, bitmapImage); + AbstractFontManager.this.cachedImagesSuggestions.add(Suggestion.suggestion(id.asString())); }, () -> GsonHelper.get().toJson(section))); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java index 0c9e46730..1aad2075b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.sparrow.nbt.Tag; +import org.incendo.cloud.suggestion.Suggestion; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -80,6 +81,8 @@ public interface FontManager extends Manageable { Optional fontById(Key font); + Collection cachedImagesSuggestions(); + int codepointByImageId(Key imageId, int x, int y); default int codepointByImageId(Key imageId) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index 3b017a27a..ba87486ee 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -113,7 +113,7 @@ public interface ItemManager extends Manageable, ModelGenerator { Optional> c2s(Item item); - Optional> s2c(Item item, Player player); + Optional> s2c(Item item, @Nullable Player player); UniqueIdItem uniqueEmptyItem(); 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 96cead759..08e4310c5 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 @@ -47,6 +47,7 @@ public class ItemSettings { Color dyeColor; @Nullable Color fireworkColor; + Map, Object> customData = new IdentityHashMap<>(4); private ItemSettings() {} @@ -108,6 +109,7 @@ public class ItemSettings { newSettings.dyeColor = settings.dyeColor; newSettings.fireworkColor = settings.fireworkColor; newSettings.ingredientSubstitutes = settings.ingredientSubstitutes; + newSettings.customData = settings.customData; return newSettings; } @@ -123,73 +125,86 @@ public class ItemSettings { return settings; } + @SuppressWarnings("unchecked") + public T getCustomData(CustomDataType type) { + return (T) this.customData.get(type); + } + + public void clearCustomData() { + this.customData.clear(); + } + + public void addCustomData(CustomDataType key, T value) { + this.customData.put(key, value); + } + public ProjectileMeta projectileMeta() { - return projectileMeta; + return this.projectileMeta; } public boolean disableVanillaBehavior() { - return disableVanillaBehavior; + return this.disableVanillaBehavior; } public Repairable repairable() { - return repairable; + return this.repairable; } public int fuelTime() { - return fuelTime; + return this.fuelTime; } public boolean renameable() { - return renameable; + return this.renameable; } public Set tags() { - return tags; + return this.tags; } public Tristate dyeable() { - return dyeable; + return this.dyeable; } public boolean canEnchant() { - return canEnchant; + return this.canEnchant; } public List repairItems() { - return anvilRepairItems; + return this.anvilRepairItems; } public boolean respectRepairableComponent() { - return respectRepairableComponent; + return this.respectRepairableComponent; } public List ingredientSubstitutes() { - return ingredientSubstitutes; + return this.ingredientSubstitutes; } @Nullable public FoodData foodData() { - return foodData; + return this.foodData; } @Nullable public Key consumeReplacement() { - return consumeReplacement; + return this.consumeReplacement; } @Nullable public CraftRemainder craftRemainder() { - return craftRemainder; + return this.craftRemainder; } @Nullable public Helmet helmet() { - return helmet; + return this.helmet; } @Nullable public ItemEquipment equipment() { - return equipment; + return this.equipment; } @Nullable @@ -203,11 +218,11 @@ public class ItemSettings { } public List invulnerable() { - return invulnerable; + return this.invulnerable; } public float compostProbability() { - return compostProbability; + return this.compostProbability; } public ItemSettings fireworkColor(Color color) { @@ -384,6 +399,9 @@ public class ItemSettings { registerFactory("equippable", (value -> { Map args = MiscUtils.castToMap(value, false); EquipmentData data = EquipmentData.fromMap(args); + if (data.assetId() == null) { + throw new IllegalArgumentException("Please move 'equippable' option to 'data' section."); + } ComponentBasedEquipment componentBasedEquipment = ComponentBasedEquipment.FACTORY.create(data.assetId(), args); ((AbstractItemManager) CraftEngine.instance().itemManager()).addOrMergeEquipment(componentBasedEquipment); ItemEquipment itemEquipment = new ItemEquipment(Tristate.FALSE, data, componentBasedEquipment); @@ -482,7 +500,7 @@ public class ItemSettings { registerFactory("ingredient-substitute", (value -> settings -> settings.ingredientSubstitutes(MiscUtils.getAsStringList(value).stream().map(Key::of).toList()))); } - private static void registerFactory(String id, ItemSettings.Modifier.Factory factory) { + public static void registerFactory(String id, ItemSettings.Modifier.Factory factory) { FACTORIES.put(id, factory); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java index 529bd293e..1ee213ea7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java @@ -18,7 +18,7 @@ public interface NetworkItemHandler { String NETWORK_OPERATION = "type"; String NETWORK_VALUE = "value"; - Optional> s2c(Item itemStack, Player player); + Optional> s2c(Item itemStack, @Nullable Player player); Optional> c2s(Item itemStack); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/AbstractEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/AbstractEquipment.java index 19eba975e..7d0428166 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/AbstractEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/AbstractEquipment.java @@ -2,15 +2,17 @@ package net.momirealms.craftengine.core.item.equipment; import net.momirealms.craftengine.core.util.Key; +import java.util.Objects; + public abstract class AbstractEquipment implements Equipment { protected final Key assetId; protected AbstractEquipment(Key assetId) { - this.assetId = assetId; + this.assetId = Objects.requireNonNull(assetId, "asset-id cannot be null"); } @Override public Key assetId() { - return assetId; + return this.assetId; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java index f60d497de..079732a56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java @@ -143,5 +143,21 @@ public class ComponentBasedEquipment extends AbstractEquipment implements Suppli return dyeData; } } + + @Override + public @NotNull String toString() { + return "Layer{" + + "texture='" + texture + '\'' + + ", data=" + data + + ", usePlayerTexture=" + usePlayerTexture + + '}'; + } + } + + @Override + public String toString() { + return "ComponentBasedEquipment{" + + "layers=" + this.layers + + '}'; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/entry/EmptyLoopEntryContainer.java b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/EmptyLoopEntryContainer.java new file mode 100644 index 000000000..32d71af6c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/EmptyLoopEntryContainer.java @@ -0,0 +1,43 @@ +package net.momirealms.craftengine.core.loot.entry; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.loot.LootConditions; +import net.momirealms.craftengine.core.loot.LootContext; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +public class EmptyLoopEntryContainer extends AbstractSingleLootEntryContainer { + public static final Factory FACTORY = new Factory<>(); + + protected EmptyLoopEntryContainer(List> conditions, int weight, int quality) { + super(conditions, null, weight, quality); + } + + @Override + protected void createItem(Consumer> lootConsumer, LootContext context) {} + + @Override + public Key type() { + return LootEntryContainers.EMPTY; + } + + public static class Factory implements LootEntryContainerFactory { + @SuppressWarnings("unchecked") + @Override + public LootEntryContainer create(Map arguments) { + int weight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("weight", 1), "weight"); + int quality = ResourceConfigUtils.getAsInt(arguments.getOrDefault("quality", 0), "quality"); + List> conditions = Optional.ofNullable(arguments.get("conditions")) + .map(it -> LootConditions.fromMapList((List>) it)) + .orElse(Collections.emptyList()); + return new EmptyLoopEntryContainer<>(conditions, weight, quality); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/entry/LootEntryContainers.java b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/LootEntryContainers.java index 82c667a40..f28403072 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/entry/LootEntryContainers.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/entry/LootEntryContainers.java @@ -18,6 +18,7 @@ public class LootEntryContainers { public static final Key ITEM = Key.from("craftengine:item"); public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item"); public static final Key EXP = Key.from("craftengine:exp"); + public static final Key EMPTY = Key.from("craftengine:empty"); static { register(ALTERNATIVES, AlternativesLootEntryContainer.FACTORY); @@ -25,6 +26,7 @@ public class LootEntryContainers { register(ITEM, SingleItemLootEntryContainer.FACTORY); register(EXP, ExpLootEntryContainer.FACTORY); register(FURNITURE_ITEM, FurnitureItemLootEntryContainer.FACTORY); + register(EMPTY, EmptyLoopEntryContainer.FACTORY); } public static void register(Key key, LootEntryContainerFactory factory) { 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 1246d5bad..1aa740527 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 @@ -35,6 +35,7 @@ import net.momirealms.craftengine.core.plugin.locale.LangData; 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.plugin.logger.Debugger; import net.momirealms.craftengine.core.sound.AbstractSoundManager; import net.momirealms.craftengine.core.sound.SoundEvent; import net.momirealms.craftengine.core.util.*; @@ -1122,7 +1123,7 @@ public abstract class AbstractPackManager implements PackManager { futures.add(CompletableFuture.runAsync(() -> { try { byte[] previousImageBytes = Files.readAllBytes(imagePath); - byte[] optimized = optimizeImage(previousImageBytes); + byte[] optimized = optimizeImage(imagePath, previousImageBytes); previousBytes.addAndGet(previousImageBytes.length); if (optimized.length < previousImageBytes.length) { afterBytes.addAndGet(optimized.length); @@ -1190,9 +1191,13 @@ public abstract class AbstractPackManager implements PackManager { } } - private byte[] optimizeImage(byte[] previousImageBytes) throws IOException { + private byte[] optimizeImage(Path imagePath, byte[] previousImageBytes) throws IOException { try (ByteArrayInputStream is = new ByteArrayInputStream(previousImageBytes)) { BufferedImage src = ImageIO.read(is); + if (src == null) { + Debugger.RESOURCE_PACK.debug(() -> "Cannot read image " + imagePath.toString()); + return previousImageBytes; + } if (src.getType() == BufferedImage.TYPE_CUSTOM) { return previousImageBytes; } @@ -1539,7 +1544,14 @@ public abstract class AbstractPackManager implements PackManager { } } if (Config.fixTextureAtlas()) { - texturesToFix.add(key); + String imagePath = "assets/" + key.namespace() + "/textures/" + key.value() + ".png"; + for (Path rootPath : rootPaths) { + if (Files.exists(rootPath.resolve(imagePath))) { + texturesToFix.add(key); + continue label; + } + } + TranslationManager.instance().log("warning.config.resource_pack.generation.missing_model_texture", entry.getValue().stream().distinct().toList().toString(), imagePath); } else { TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString()); } @@ -1903,6 +1915,11 @@ public abstract class AbstractPackManager implements PackManager { private void processComponentBasedEquipment(ComponentBasedEquipment componentBasedEquipment, Path generatedPackPath) { Key assetId = componentBasedEquipment.assetId(); + if (assetId == null) { + this.plugin.logger().severe("Asset id is null for equipment " + componentBasedEquipment); + return; + } + if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4)) { Path equipmentPath = generatedPackPath .resolve("assets") 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 4f93508c7..7d8937de1 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 @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.pack.host.impl; +import io.github.bucket4j.Bandwidth; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; @@ -8,10 +9,10 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.nio.file.Path; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.UUID; @@ -76,14 +77,28 @@ 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"); - Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); - int maxRequests = 5; - int resetInterval = 20_000; - if (rateMap != null) { - maxRequests = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests"); - resetInterval = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval") * 1000; + + + Bandwidth limit = null; + Map rateLimitingSection = ResourceConfigUtils.getAsMapOrNull(arguments.get("rate-limiting"), "rate-limiting"); + long maxBandwidthUsage = 0L; + long minDownloadSpeed = 50_000L; + if (rateLimitingSection != null) { + if (rateLimitingSection.containsKey("qps-per-ip")) { + String qps = rateLimitingSection.get("qps-per-ip").toString(); + String[] split = qps.split("/", 2); + if (split.length == 1) split = new String[]{split[0], "1"}; + int maxRequests = ResourceConfigUtils.getAsInt(split[0], "qps-per-ip"); + int resetInterval = ResourceConfigUtils.getAsInt(split[1], "qps-per-ip"); + limit = Bandwidth.builder() + .capacity(maxRequests) + .refillGreedy(maxRequests, Duration.ofSeconds(resetInterval)) + .build(); + } + 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, maxRequests, resetInterval, oneTimeToken); + selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed); 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 c320f80df..9a88c719c 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 @@ -3,18 +3,27 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Scheduler; +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.*; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.*; +import io.netty.handler.stream.ChunkedStream; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler; 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 org.jetbrains.annotations.Nullable; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URLEncoder; @@ -23,28 +32,35 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.time.Duration; 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 { private static SelfHostHttpServer instance; private final Cache oneTimePackUrls = Caffeine.newBuilder() - .maximumSize(256) + .maximumSize(1024) .scheduler(Scheduler.systemScheduler()) .expireAfterWrite(1, TimeUnit.MINUTES) .build(); - private final Cache ipAccessCache = Caffeine.newBuilder() - .maximumSize(256) + private final Cache ipRateLimiters = Caffeine.newBuilder() + .maximumSize(1024) .scheduler(Scheduler.systemScheduler()) - .expireAfterWrite(10, TimeUnit.MINUTES) + .expireAfterAccess(5, TimeUnit.MINUTES) .build(); private final AtomicLong totalRequests = new AtomicLong(); private final AtomicLong blockedRequests = new AtomicLong(); - private int rateLimit = 1; - private long rateLimitInterval = 1000; + private Bandwidth limitPerIp = Bandwidth.builder() + .capacity(1) + .refillGreedy(1, Duration.ofSeconds(1)) + .initialTokens(1) + .build(); + private String ip = "localhost"; private int port = -1; private String protocol = "http"; @@ -52,6 +68,12 @@ public class SelfHostHttpServer { private boolean denyNonMinecraft = true; private boolean useToken; + private long globalUploadRateLimit = 0; + private long minDownloadSpeed = 50_000; + private GlobalChannelTrafficShapingHandler trafficShapingHandler; + private ScheduledExecutorService virtualTrafficExecutor; + private final ChannelGroup activeDownloadChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + private byte[] resourcePackBytes; private String packHash; private UUID packUUID; @@ -72,17 +94,25 @@ public class SelfHostHttpServer { String url, boolean denyNonMinecraft, String protocol, - int maxRequests, - int resetInterval, - boolean token) { + Bandwidth limitPerIp, + boolean token, + long globalUploadRateLimit, + long minDownloadSpeed) { this.ip = ip; this.url = url; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; - this.rateLimit = maxRequests; - this.rateLimitInterval = resetInterval; + this.limitPerIp = limitPerIp; this.useToken = token; - + if (this.globalUploadRateLimit != globalUploadRateLimit || this.minDownloadSpeed != minDownloadSpeed) { + this.globalUploadRateLimit = globalUploadRateLimit; + this.minDownloadSpeed = minDownloadSpeed; + if (this.trafficShapingHandler != null) { + long initSize = globalUploadRateLimit <= 0 ? 0 : Math.max(minDownloadSpeed, globalUploadRateLimit); + this.trafficShapingHandler.setWriteLimit(initSize); + this.trafficShapingHandler.setWriteChannelLimit(initSize); + } + } if (port <= 0 || port > 65535) { throw new IllegalArgumentException("Invalid port: " + port); } @@ -104,7 +134,17 @@ public class SelfHostHttpServer { private void initializeServer() { bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(); - + virtualTrafficExecutor = Executors.newScheduledThreadPool(1, Thread.ofVirtual().factory()); + long initSize = globalUploadRateLimit <= 0 ? 0 : Math.max(minDownloadSpeed, globalUploadRateLimit); + trafficShapingHandler = new GlobalChannelTrafficShapingHandler( + virtualTrafficExecutor, + initSize, + 0, // 全局读取不限 + initSize, // 默认单通道和总体一致 + 0, // 单通道读取不限 + 100, // checkInterval (ms) + 10_000 // maxTime (ms) + ); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) @@ -112,7 +152,9 @@ public class SelfHostHttpServer { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast("trafficShaping", trafficShapingHandler); pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(1048576)); pipeline.addLast(new RequestHandler()); } @@ -128,6 +170,17 @@ public class SelfHostHttpServer { @ChannelHandler.Sharable private class RequestHandler extends SimpleChannelInboundHandler { + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + // 有人走了,其他人的速度上限提高 + if (activeDownloadChannels.contains(ctx.channel())) { + activeDownloadChannels.remove(ctx.channel()); + rebalanceBandwidth(); + } + } + @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { totalRequests.incrementAndGet(); @@ -136,7 +189,7 @@ public class SelfHostHttpServer { String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress()) .getAddress().getHostAddress(); - if (checkRateLimit(clientIp)) { + if (!checkIpRateLimit(clientIp)) { sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded"); blockedRequests.incrementAndGet(); return; @@ -159,6 +212,7 @@ 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)) { @@ -168,6 +222,7 @@ public class SelfHostHttpServer { } } + // 不是Minecraft客户端 if (denyNonMinecraft) { String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT); if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { @@ -177,22 +232,47 @@ public class SelfHostHttpServer { } } + // 没有资源包 if (resourcePackBytes == null) { sendError(ctx, HttpResponseStatus.NOT_FOUND, "Resource pack missing"); blockedRequests.incrementAndGet(); return; } - FullHttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, - HttpResponseStatus.OK, - Unpooled.wrappedBuffer(resourcePackBytes) - ); - response.headers() - .set(HttpHeaderNames.CONTENT_TYPE, "application/zip") - .set(HttpHeaderNames.CONTENT_LENGTH, resourcePackBytes.length); + // 新人来了,所有人的速度上限降低 + if (!activeDownloadChannels.contains(ctx.channel())) { + activeDownloadChannels.add(ctx.channel()); + rebalanceBandwidth(); + } - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + // 告诉客户端资源包大小 + long fileLength = resourcePackBytes.length; + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + HttpUtil.setContentLength(response, fileLength); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/zip"); + boolean keepAlive = HttpUtil.isKeepAlive(request); + if (keepAlive) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + } + ctx.write(response); + + // 发送分段资源包 + ChunkedStream chunkedStream = new ChunkedStream(new ByteArrayInputStream(resourcePackBytes), 8192); + HttpChunkedInput httpChunkedInput = new HttpChunkedInput(chunkedStream); + ChannelFuture sendFileFuture = ctx.writeAndFlush(httpChunkedInput); + if (!keepAlive) { + sendFileFuture.addListener(ChannelFutureListener.CLOSE); + } + + // 监听下载完成(成功或失败),以便在下载结束后(如果不关闭连接)也能移除计数 + // 注意:如果是 Keep-Alive,连接不会断,但下载结束了。 + // 为了精确控制,可以在这里监听 operationComplete + sendFileFuture.addListener((ChannelFutureListener) future -> { + if (activeDownloadChannels.contains(ctx.channel())) { + activeDownloadChannels.remove(ctx.channel()); + rebalanceBandwidth(); + } + }); } private void handleMetrics(ChannelHandlerContext ctx) { @@ -213,23 +293,11 @@ public class SelfHostHttpServer { ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } - private boolean checkRateLimit(String clientIp) { - IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); - long now = System.currentTimeMillis(); - - if (record == null) { - record = new IpAccessRecord(now, 1); - ipAccessCache.put(clientIp, record); - return false; - } - - if (now - record.lastAccessTime > rateLimitInterval) { - record.lastAccessTime = now; - record.accessCount = 1; - return false; - } - - return ++record.accessCount > rateLimit; + private boolean checkIpRateLimit(String clientIp) { + if (limitPerIp == null) return true; + Bucket rateLimiter = ipRateLimiters.get(clientIp, k -> Bucket.builder().addLimit(limitPerIp).build()); + assert rateLimiter != null; + return rateLimiter.tryConsume(1); } private boolean validateToken(String token) { @@ -257,6 +325,28 @@ public class SelfHostHttpServer { } } + private synchronized void rebalanceBandwidth() { + if (globalUploadRateLimit == 0) { + trafficShapingHandler.setWriteChannelLimit(0); + return; + } + + int activeCount = activeDownloadChannels.size(); + if (activeCount == 0) { + trafficShapingHandler.setWriteChannelLimit(globalUploadRateLimit); + return; + } + + // 计算平均带宽:全局总量 / 当前人数 + long fairRate = globalUploadRateLimit / activeCount; + + // 确保不低于最小保障速率(可选,防止除法导致过小) + fairRate = Math.max(fairRate, this.minDownloadSpeed); + + // 更新 Handler 配置 + trafficShapingHandler.setWriteChannelLimit(fairRate); + } + @Nullable public ResourcePackDownloadData generateOneTimeUrl() { if (this.resourcePackBytes == null) return null; @@ -275,6 +365,17 @@ public class SelfHostHttpServer { } public void disable() { + // 释放流量整形资源 + if (trafficShapingHandler != null) { + trafficShapingHandler.release(); + trafficShapingHandler = null; + } + // 关闭专用线程池 + if (virtualTrafficExecutor != null) { + virtualTrafficExecutor.shutdown(); + virtualTrafficExecutor = null; + } + activeDownloadChannels.close(); if (serverChannel != null) { serverChannel.close().awaitUninterruptibly(); bossGroup.shutdownGracefully(); @@ -312,14 +413,4 @@ public class SelfHostHttpServer { CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e); } } - - private static class IpAccessRecord { - long lastAccessTime; - int accessCount; - - IpAccessRecord(long lastAccessTime, int accessCount) { - this.lastAccessTime = lastAccessTime; - this.accessCount = accessCount; - } - } } \ No newline at end of file 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 750f04f10..8bbfb01c0 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 @@ -267,8 +267,11 @@ public abstract class CraftEngine implements Plugin { this.vanillaLootManager.delayedInit(); // 注册脱离坐骑监听器 this.seatManager.delayedInit(); - // 注册世界加载相关监听器 - this.worldManager.delayedInit(); + + if (!Config.delayConfigurationLoad()) { + // 注册世界加载相关监听器 + this.worldManager.delayedInit(); + } // 延迟任务 this.beforeEnableTaskRegistry.executeTasks(); @@ -310,6 +313,7 @@ public abstract class CraftEngine implements Plugin { } else { try { this.reloadPlugin(Runnable::run, Runnable::run, true); + this.worldManager.delayedInit(); } catch (Exception e) { this.logger.severe("Failed to reload plugin on delayed enable stage", e); } @@ -415,7 +419,8 @@ public abstract class CraftEngine implements Plugin { Dependencies.LZ4, Dependencies.EVALEX, Dependencies.NETTY_HTTP, - Dependencies.JIMFS + Dependencies.JIMFS, + Dependencies.BUCKET_4_J ); } 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 76ff553a7..c75eaa370 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 @@ -138,6 +138,8 @@ public class Config { protected ColliderType furniture$collision_entity_type; protected boolean block$sound_system$enable; + protected boolean block$sound_system$process_cancelled_events$step; + protected boolean block$sound_system$process_cancelled_events$break; protected boolean block$simplify_adventure_break_check; protected boolean block$simplify_adventure_place_check; protected boolean block$predict_breaking; @@ -475,6 +477,8 @@ public class Config { // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); + block$sound_system$process_cancelled_events$step = config.getBoolean("block.sound-system.process-cancelled-events.step", true); + block$sound_system$process_cancelled_events$break = config.getBoolean("block.sound-system.process-cancelled-events.break", true); block$simplify_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", false); block$simplify_adventure_place_check = config.getBoolean("block.simplify-adventure-place-check", false); block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true); @@ -675,6 +679,14 @@ public class Config { return instance.block$sound_system$enable; } + public static boolean processCancelledStep() { + return instance.block$sound_system$process_cancelled_events$step; + } + + public static boolean processCancelledBreak() { + return instance.block$sound_system$process_cancelled_events$break; + } + public static boolean simplifyAdventureBreakCheck() { return instance.block$simplify_adventure_break_check; } 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 3f382abdc..0d411d956 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 @@ -52,11 +52,11 @@ public class TemplateManagerImpl implements TemplateManager { @Override public void parseObject(Pack pack, Path path, String node, Key id, Object obj) { - if (templates.containsKey(id)) { + if (TemplateManagerImpl.this.templates.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.template.duplicate"); } // 预处理会将 string类型的键或值解析为ArgumentString,以加速模板应用。所以处理后不可能存在String类型。 - templates.put(id, preprocessUnknownValue(obj)); + TemplateManagerImpl.this.templates.put(id, preprocessUnknownValue(obj)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java index 8cc171076..eea93f8c8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java @@ -5,6 +5,7 @@ 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.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.EnumUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -42,7 +43,7 @@ public class HandCondition implements Condition { try { return new HandCondition<>(InteractionHand.valueOf(hand.toUpperCase(Locale.ENGLISH))); } catch (IllegalArgumentException e) { - throw new LocalizedResourceConfigException("warning.config.condition.hand.invalid_hand", hand); + throw new LocalizedResourceConfigException("warning.config.condition.hand.invalid_hand", hand, EnumUtils.toString(InteractionHand.values())); } } } 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 358116670..6e1e58bf4 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 @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.plugin.context.function; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.plugin.context.Condition; @@ -10,9 +11,9 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar 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.ExistingBlock; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; @@ -20,15 +21,28 @@ import java.util.Optional; public class CycleBlockPropertyFunction extends AbstractConditionalFunction { private final String property; + @Nullable + private final Map rules; + @Nullable private final NumberProvider inverse; private final NumberProvider x; private final NumberProvider y; private final NumberProvider z; private final NumberProvider updateFlags; - public CycleBlockPropertyFunction(List> predicates, String property, NumberProvider inverse, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags) { + public CycleBlockPropertyFunction( + List> predicates, + String property, + @Nullable Map rules, + @Nullable NumberProvider inverse, + NumberProvider x, + NumberProvider y, + NumberProvider z, + NumberProvider updateFlags + ) { super(predicates); this.property = property; + this.rules = rules; this.inverse = inverse; this.x = x; this.y = y; @@ -44,11 +58,26 @@ public class CycleBlockPropertyFunction extends AbstractCon int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); - ExistingBlock blockAt = world.getBlock(x, y, z); - BlockStateWrapper wrapper = blockAt.blockState().cycleProperty(this.property, this.inverse.getInt(ctx) == 0); + BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); } + private BlockStateWrapper updateBlockState(BlockStateWrapper wrapper, CTX ctx) { + boolean inverse = this.inverse != null && this.inverse.getInt(ctx) == 0; + if (this.rules == null) { + return wrapper.cycleProperty(this.property, inverse); + } + Object value = wrapper.getProperty(this.property); + if (value == null) { + return wrapper.cycleProperty(this.property, inverse); + } + String mapValue = this.rules.get(value.toString()); + if (mapValue == null) { + return wrapper.cycleProperty(this.property, inverse); + } + return wrapper.withProperty(this.property, mapValue); + } + @Override public Key type() { return CommonFunctions.CYCLE_BLOCK_PROPERTY; @@ -62,8 +91,15 @@ public class CycleBlockPropertyFunction extends AbstractCon @Override public Function create(Map arguments) { + String property = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("property"), "warning.config.function.cycle_block_property.missing_property"); + Map rules; + if (arguments.containsKey("rules")) { + rules = new Object2ObjectOpenHashMap<>(); + MiscUtils.castToMap(arguments.get("rules"), false).forEach((k, v) -> rules.put(k, v.toString())); + } else rules = null; return new CycleBlockPropertyFunction<>(getPredicates(arguments), - ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("property"), "warning.config.function.cycle_block_property.missing_property"), + property, + rules, NumberProviders.fromObject(arguments.getOrDefault("inverse", "")), NumberProviders.fromObject(arguments.getOrDefault("x", "")), NumberProviders.fromObject(arguments.getOrDefault("y", "")), diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 0d44161bb..bd66b18cb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -372,6 +372,13 @@ public class Dependencies { List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) ); + public static final Dependency BUCKET_4_J = new Dependency( + "bucket4j", + "com{}bucket4j", + "bucket4j_jdk17-core", + List.of(Relocation.of("bucket4j", "io{}github{}bucket4j")) + ); + public static final Dependency NETTY_HTTP = new Dependency( "netty-codec-http", "io{}netty", 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 new file mode 100644 index 000000000..72c6ecdc0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -0,0 +1,453 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.world.MutableVec3d; +import net.momirealms.craftengine.core.world.Vec3d; + +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]; + private final int[] lastHitBlock = new int[3]; + + // 状态标志 + private boolean allowRayChecks = false; + private boolean allowWallClipping = false; + + 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 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 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) {} + } + + /** + * 检查单个体素是否对观察者可见 + */ + 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); + } + + // 优化:使用基本数据类型代替对象分配 + 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; + } + } + 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; // 初始允许穿墙直到移出起始方块 + + for (; n > 1; n--) { + // 检查缓存状态:2=遮挡 + int cVal = getCacheValue(currentX, currentY, currentZ); + if (cVal == 2 && !allowWallClipping) { + lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; + return false; + } + + if (cVal == 0) { + // 未缓存,查询Provider + int chunkX = currentX >> 4; + int chunkZ = currentZ >> 4; + if (!provider.prepareChunk(chunkX, chunkZ)) return false; + + 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(); + } + } 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; + } else { + currentZ += z_inc; + t_next_z += distInZ; + } + } + 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) { + return -1; + } + return cache.getState(x + reach, y + reach, z + reach); + } + + 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); + } + + public void resetCache() { + this.cache.resetCache(); + } + + 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; + 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/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 882d17c90..b488bde08 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 @@ -35,4 +35,10 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_LOCALE_SET_FAILURE = Component.translatable().key("command.locale.set.failure"); TranslatableComponent.Builder COMMAND_LOCALE_SET_SUCCESS = Component.translatable().key("command.locale.set.success"); TranslatableComponent.Builder COMMAND_LOCALE_UNSET_SUCCESS = Component.translatable().key("command.locale.unset.success"); + TranslatableComponent.Builder COMMAND_ITEM_CLEAR_SUCCESS_SINGLE = Component.translatable().key("command.item.clear.success.single"); + TranslatableComponent.Builder COMMAND_ITEM_CLEAR_SUCCESS_MULTIPLE = Component.translatable().key("command.item.clear.success.multiple"); + TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_SINGLE = Component.translatable().key("command.item.clear.failed.single"); + 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"); } 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 c66cc68c1..44cd01d8c 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 @@ -37,7 +37,7 @@ public class TranslationManagerImpl implements TranslationManager { private final Set installed = ConcurrentHashMap.newKeySet(); private final Path translationsDirectory; private final String langVersion; - private final String[] supportedLanguages; + private final Set supportedLanguages; private final Map translationFallback = new LinkedHashMap<>(); private Locale selectedLocale = DEFAULT_LOCALE; private MiniMessageTranslationRegistry registry; @@ -52,7 +52,7 @@ public class TranslationManagerImpl implements TranslationManager { this.plugin = plugin; this.translationsDirectory = this.plugin.dataFolderPath().resolve("translations"); this.langVersion = PluginProperties.getValue("lang-version"); - this.supportedLanguages = PluginProperties.getValue("supported-languages").split(","); + this.supportedLanguages = Arrays.stream(PluginProperties.getValue("supported-languages").split(",")).collect(Collectors.toSet()); this.langParser = new LangParser(); this.translationParser = new TranslationParser(); Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); @@ -201,7 +201,7 @@ public class TranslationManagerImpl implements TranslationManager { Map data = yaml.load(inputStream); if (data == null) return FileVisitResult.CONTINUE; String langVersion = data.getOrDefault("lang-version", ""); - if (!langVersion.equals(TranslationManagerImpl.this.langVersion)) { + if (!langVersion.equals(TranslationManagerImpl.this.langVersion) && TranslationManagerImpl.this.supportedLanguages.contains(localeName)) { data = updateLangFile(data, path); } cachedFile = new CachedTranslation(data, lastModifiedTime, size); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CustomDataType.java b/core/src/main/java/net/momirealms/craftengine/core/util/CustomDataType.java new file mode 100644 index 000000000..48145fdd8 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CustomDataType.java @@ -0,0 +1,4 @@ +package net.momirealms.craftengine.core.util; + +public class CustomDataType { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/LazyReference.java b/core/src/main/java/net/momirealms/craftengine/core/util/LazyReference.java index 1a1fcfe89..bba794fd8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/LazyReference.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/LazyReference.java @@ -6,6 +6,8 @@ public interface LazyReference { T get(); + boolean initialized(); + static LazyReference lazyReference(final Supplier supplier) { return new LazyReference<>() { private T value; @@ -17,6 +19,11 @@ public interface LazyReference { } return this.value; } + + @Override + public boolean initialized() { + return this.value != null; + } }; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/PngOptimizer.java b/core/src/main/java/net/momirealms/craftengine/core/util/PngOptimizer.java index 03e1a17b5..adebfaf37 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/PngOptimizer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/PngOptimizer.java @@ -131,7 +131,7 @@ public class PngOptimizer { private byte[] tryNormal(BufferedImage src, boolean hasAlpha, boolean isGrayscale) throws IOException { byte[] bytes = generatePngData(src, hasAlpha, isGrayscale); - int zopfli = Config.zopfliIterations(); + int zopfli = Config.optimizeTexture() ? Config.zopfliIterations() : 0; return zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes); } @@ -177,7 +177,7 @@ public class PngOptimizer { writeChunkPLTE(paletteOs, palette); } byte[] bytes = generatePaletteData(src, palette); - int zopfli = Config.zopfliIterations(); + int zopfli = Config.optimizeTexture() ? Config.zopfliIterations() : 0; paletteOs.write(zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes)); return Pair.of(palette, paletteOs.toByteArray()); } 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 3c21da4ce..415893a3a 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 @@ -134,7 +134,7 @@ public final class ResourceConfigUtils { } case String s -> { try { - return Integer.parseInt(s); + return Integer.parseInt(s.replace("_", "")); } catch (NumberFormatException e) { throw new LocalizedResourceConfigException("warning.config.type.int", e, s, option); } @@ -218,6 +218,30 @@ public final class ResourceConfigUtils { } } + public static long getAsLong(Object o, String option) { + switch (o) { + case null -> { + return 0; + } + case Long l -> { + return l; + } + case Number number -> { + return number.longValue(); + } + case String s -> { + try { + return Long.parseLong(s.replace("_", "")); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.long", e, s, option); + } + } + default -> { + throw new LocalizedResourceConfigException("warning.config.type.long", o.toString(), option); + } + } + } + @SuppressWarnings("unchecked") public static Map getAsMap(Object obj, String option) { if (obj instanceof Map map) { 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 new file mode 100644 index 000000000..1f4a989ac --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java @@ -0,0 +1,127 @@ +package net.momirealms.craftengine.core.world; + +import net.momirealms.craftengine.core.util.MiscUtils; + +public class MutableVec3d implements Position { + public double x; + public double y; + public double z; + + public MutableVec3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public MutableVec3d toCenter() { + this.x = MiscUtils.fastFloor(x) + 0.5; + this.y = MiscUtils.fastFloor(y) + 0.5; + this.z = MiscUtils.fastFloor(z) + 0.5; + return this; + } + + public MutableVec3d add(MutableVec3d vec) { + this.x += vec.x; + this.y += vec.y; + this.z += vec.z; + return this; + } + + public MutableVec3d add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + public MutableVec3d divide(MutableVec3d vec3d) { + this.x /= vec3d.x; + this.z /= vec3d.z; + this.y /= vec3d.y; + return this; + } + + public MutableVec3d normalize() { + double mag = Math.sqrt(x * x + y * y + z * z); + this.x /= mag; + this.y /= mag; + this.z /= mag; + return this; + } + + public static double distanceToSqr(MutableVec3d vec1, MutableVec3d vec2) { + double dx = vec2.x - vec1.x; + double dy = vec2.y - vec1.y; + double dz = vec2.z - vec1.z; + return dx * dx + dy * dy + dz * dz; + } + + public void set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void add(MutableVec3d vec3d, double x, double y, double z) { + this.x += (vec3d.x + x); + this.y += (vec3d.y + y); + this.z += (vec3d.z + z); + } + + public void add(Vec3d vec3d, double x, double y, double z) { + this.x += (vec3d.x + x); + this.y += (vec3d.y + y); + this.z += (vec3d.z + z); + } + + public void setX(double x) { + this.x = x; + } + + public void setY(double y) { + this.y = y; + } + + public void setZ(double z) { + this.z = z; + } + + @Override + public double x() { + return x; + } + + @Override + public double y() { + return y; + } + + @Override + public double z() { + return z; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MutableVec3d vec3d)) return false; + return this.x == vec3d.x && this.y == vec3d.y && this.z == vec3d.z; + } + + @Override + public int hashCode() { + int result = Double.hashCode(x); + result = 31 * result + Double.hashCode(y); + result = 31 * result + Double.hashCode(z); + return result; + } + + @Override + public String toString() { + return "Vec3d{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} 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 e24e99037..7165596a0 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 @@ -72,7 +72,7 @@ public class Vec3d implements Position { public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Vec3d vec3d)) return false; - return Double.compare(x, vec3d.x) == 0 && Double.compare(y, vec3d.y) == 0 && Double.compare(z, vec3d.z) == 0; + return this.x == vec3d.x && this.y == vec3d.y && this.z == vec3d.z; } @Override diff --git a/gradle.properties b/gradle.properties index ff17174fc..5ad74eca6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings -project_version=0.0.65.8 -config_version=55 -lang_version=38 +project_version=0.0.65.12.2 +config_version=58 +lang_version=40 project_group=net.momirealms latest_supported_version=1.21.10 @@ -28,34 +28,35 @@ cloud_paper_version=2.0.0-beta.13 cloud_minecraft_extras_version=2.0.0-beta.13 boosted_yaml_version=1.3.7 bstats_version=3.1.0 -caffeine_version=3.2.2 -placeholder_api_version=2.11.6 +caffeine_version=3.2.3 +placeholder_api_version=2.11.7 vault_version=1.7 guava_version=33.5.0-jre lz4_version=1.8.0 geantyref_version=1.3.16 -zstd_version=1.5.7-4 -commons_io_version=2.20.0 -commons_lang3_version=3.19.0 +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.60 +sparrow_util_version=0.65 fastutil_version=8.5.18 -netty_version=4.1.127.Final +netty_version=4.1.128.Final joml_version=1.10.8 datafixerupper_version=8.0.16 mojang_brigadier_version=1.0.18 -byte_buddy_version=1.17.8 +byte_buddy_version=1.18.1 ahocorasick_version=0.6.3 snake_yaml_version=2.5 -anti_grief_version=1.0.4 -nms_helper_version=1.0.134 +anti_grief_version=1.0.5 +nms_helper_version=1.0.137 evalex_version=3.5.0 reactive_streams_version=1.0.4 -amazon_awssdk_version=2.34.5 +amazon_awssdk_version=2.38.7 amazon_awssdk_eventstream_version=1.0.1 jimfs_version=1.3.1 authlib_version=7.0.60 concurrent_util_version=0.0.3 +bucket4j_version=8.15.0 # Proxy settings #systemProp.socks.proxyHost=127.0.0.1