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

Merge pull request #478 from Xiao-MoMi/dev

同步dev分支
This commit is contained in:
XiaoMoMi
2025-11-24 01:22:13 +08:00
committed by GitHub
85 changed files with 2287 additions and 342 deletions

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ build/
!/libs/*.jar !/libs/*.jar
!/gradle/wrapper/*.jar !/gradle/wrapper/*.jar
.vscode/settings.json .vscode/settings.json
/runPaper/

View File

@@ -111,12 +111,21 @@ tasks {
publishing { publishing {
repositories { repositories {
maven { maven {
name = "releases"
url = uri("https://repo.momirealms.net/releases") url = uri("https://repo.momirealms.net/releases")
credentials(PasswordCredentials::class) { credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME") username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD") 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 { publications {
create<MavenPublication>("mavenJava") { create<MavenPublication>("mavenJava") {
@@ -137,5 +146,35 @@ publishing {
} }
} }
} }
create<MavenPublication>("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")
}

View File

@@ -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;
}
/**
* 用法:(小括号括起来的为必填,中括号括起来的为选填)
* </br>
* %checkceitem_count_(namespace):(path)%
* </br>
* %checkceitem_has_(namespace):(path):[amount]%
* </br>
* %checkceitem_id_[main_hand/off_hand/slot]%
* </br>
* %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<Object> 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);
}
}

View File

@@ -20,5 +20,6 @@ public class PlaceholderAPIUtils {
public static void registerExpansions(CraftEngine plugin) { public static void registerExpansions(CraftEngine plugin) {
new ImageExpansion(plugin).register(); new ImageExpansion(plugin).register();
new ShiftExpansion(plugin).register(); new ShiftExpansion(plugin).register();
new CheckItemExpansion(plugin).register();
} }
} }

View File

@@ -85,5 +85,6 @@ tasks {
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") 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.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.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j")
} }
} }

View File

@@ -1,8 +1,12 @@
import net.minecrell.pluginyml.paper.PaperPluginDescription import net.minecrell.pluginyml.paper.PaperPluginDescription
import xyz.jpenilla.runpaper.task.RunServer
import xyz.jpenilla.runtask.pluginsapi.DownloadPluginsSpec
import java.net.URI
plugins { plugins {
id("com.gradleup.shadow") version "9.2.2" id("com.gradleup.shadow") version "9.2.2"
id("de.eldoria.plugin-yml.paper") version "0.7.1" id("de.eldoria.plugin-yml.paper") version "0.7.1"
id("xyz.jpenilla.run-paper") version "3.0.2"
} }
repositories { 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.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.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.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<DownloadPluginsSpec>? = 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")
}

View File

@@ -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) { public void onPlayerBreak(BlockBreakEvent event) {
org.bukkit.block.Block block = event.getBlock(); org.bukkit.block.Block block = event.getBlock();
Object blockState = BlockStateUtils.getBlockState(block); 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); WorldPosition position = new WorldPosition(world, location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5);
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); Item<ItemStack> itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND);
if (!ItemUtils.isEmpty(itemInHand)) { if (!event.isCancelled() && !ItemUtils.isEmpty(itemInHand)) {
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem(); Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isPresent()) { if (optionalCustomItem.isPresent()) {
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
@@ -129,41 +129,49 @@ public final class BlockEventListener implements Listener {
} }
if (!BlockStateUtils.isVanillaBlock(stateId)) { if (!BlockStateUtils.isVanillaBlock(stateId)) {
ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId); ImmutableBlockState state = this.manager.getImmutableBlockStateUnsafe(stateId);
if (!state.isEmpty()) { if (!state.isEmpty()) {
// double check adventure mode to prevent dupe if (!event.isCancelled()) {
if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) { // double check adventure mode to prevent dupe
return; if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) {
} return;
}
// trigger api event // trigger api event
CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state); CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state);
boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent); boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent);
if (isCancelled) { if (isCancelled) {
event.setCancelled(true); event.setCancelled(true);
return; return;
} }
// execute functions // execute functions
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block))
.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state)
.withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.EVENT, cancellable)
.withParameter(DirectContextParameters.POSITION, position) .withParameter(DirectContextParameters.POSITION, position)
.withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand)
); );
state.owner().value().execute(context, EventTrigger.BREAK); state.owner().value().execute(context, EventTrigger.BREAK);
if (cancellable.isCancelled()) { if (cancellable.isCancelled()) {
return; return;
} }
// play sound // play sound
serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK); 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 { } else {
// override vanilla block loots // override vanilla block loots
if (player.getGameMode() != GameMode.CREATIVE) { if (!event.isCancelled() && player.getGameMode() != GameMode.CREATIVE) {
this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> { this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> {
if (!event.isDropItems()) { if (!event.isDropItems()) {
return; return;
@@ -185,7 +193,7 @@ public final class BlockEventListener implements Listener {
}); });
} }
// sound system // sound system
if (Config.enableSoundSystem()) { if (Config.enableSoundSystem() && (!event.isCancelled() || Config.processCancelledBreak())) {
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType); Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); 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) { public void onStep(GenericGameEvent event) {
if (event.getEvent() != GameEvent.STEP) return; if (event.getEvent() != GameEvent.STEP) return;
Entity entity = event.getEntity(); Entity entity = event.getEntity();
@@ -242,11 +250,14 @@ public final class BlockEventListener implements Listener {
.withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block)) .withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block))
.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state)
), EventTrigger.STEP); ), EventTrigger.STEP);
if (cancellable.isCancelled()) { if (cancellable.isCancelled() && !Config.processCancelledStep()) {
return; return;
} }
player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get()); 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()) { } else if (Config.enableSoundSystem()) {
if (event.isCancelled() && !Config.processCancelledStep()) {
return;
}
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState); Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$stepSound(soundType); Object soundEvent = FastNMS.INSTANCE.field$SoundType$stepSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);

View File

@@ -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)); 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))); WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos)));
world.playBlockSound(position, customState.settings().sounds().breakSound()); world.playBlockSound(position, customState.settings().sounds().breakSound());
FastNMS.INSTANCE.method$Level$destroyBlock(level, blockPos, true); FastNMS.INSTANCE.method$LevelWriter$destroyBlock(level, blockPos, true);
} }
}); });
} }

View File

@@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.HorizontalDirection; import net.momirealms.craftengine.core.util.HorizontalDirection;
@@ -20,7 +21,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class AttachedStemBlockBehavior extends BukkitBlockBehavior { public class AttachedStemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty; private final Property<HorizontalDirection> facingProperty;
private final Key fruit; private final Key fruit;

View File

@@ -46,6 +46,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key SEAT_BLOCK = Key.from("craftengine:seat_block"); 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 SURFACE_SPREADING_BLOCK = Key.from("craftengine:surface_spreading_block");
public static final Key SNOWY_BLOCK = Key.from("craftengine:snowy_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() { public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -90,5 +91,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SEAT_BLOCK, SeatBlockBehavior.FACTORY); register(SEAT_BLOCK, SeatBlockBehavior.FACTORY);
register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY); register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY);
register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY); register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY);
register(HANGABLE_BLOCK, HangableBlockBehavior.FACTORY);
} }
} }

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.block.behavior; 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.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; 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.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils; 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.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.parser.BlockStateParser;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Direction; 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 net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.event.block.BlockFormEvent; import org.bukkit.event.block.BlockFormEvent;
import org.jetbrains.annotations.Nullable;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { public class ConcretePowderBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Key targetBlock; // TODO 更宽泛的使用state似乎也不是很好的方案 private final LazyReference<@Nullable ImmutableBlockState> targetBlock;
private Object defaultBlockState;
private ImmutableBlockState defaultImmutableBlockState;
public ConcretePowderBlockBehavior(CustomBlock block, Key targetBlock) { public ConcretePowderBlockBehavior(CustomBlock block, String targetBlock) {
super(block); super(block);
this.targetBlock = targetBlock; this.targetBlock = LazyReference.lazyReference(() -> BlockStateParser.deserialize(targetBlock));
}
public ImmutableBlockState defaultImmutableBlockState() {
if (this.defaultImmutableBlockState == null) {
this.getDefaultBlockState();
}
return this.defaultImmutableBlockState;
} }
public Object getDefaultBlockState() { public Object getDefaultBlockState() {
if (this.defaultBlockState != null) { ImmutableBlockState state = this.targetBlock.get();
return this.defaultBlockState; return state != null ? state.customBlockState().literalObject() : MBlocks.STONE$defaultState;
}
Optional<CustomBlock> 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;
} }
@SuppressWarnings("UnstableApiUsage") @SuppressWarnings("UnstableApiUsage")
@@ -72,7 +54,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior {
craftBlockState.setBlockData(BlockStateUtils.fromBlockData(getDefaultBlockState())); craftBlockState.setBlockData(BlockStateUtils.fromBlockData(getDefaultBlockState()));
BlockFormEvent event = new BlockFormEvent(craftBlockState.getBlock(), craftBlockState); BlockFormEvent event = new BlockFormEvent(craftBlockState.getBlock(), craftBlockState);
if (!EventUtils.fireAndCheckCancel(event)) { if (!EventUtils.fireAndCheckCancel(event)) {
return defaultImmutableBlockState(); return this.targetBlock.get();
} else { } else {
return super.updateStateForPlacement(context, state); return super.updateStateForPlacement(context, state);
} }
@@ -148,7 +130,7 @@ public class ConcretePowderBlockBehavior extends BukkitBlockBehavior {
@Override @Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) { public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
String solidBlock = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("solid-block"), "warning.config.block.behavior.concrete.missing_solid"); 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);
} }
} }
} }

View File

@@ -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.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult; 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.Item;
import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -121,14 +122,15 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
} }
@Override @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]); this.performBoneMeal(args[0], args[2], args[3]);
} }
@Override @Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem(); 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; return InteractionResult.PASS;
if (isMaxAge(state)) if (isMaxAge(state))
return InteractionResult.PASS; return InteractionResult.PASS;
@@ -144,7 +146,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
sendSwing = true; sendSwing = true;
} }
if (sendSwing) { if (sendSwing) {
context.getPlayer().swingHand(context.getHand()); player.swingHand(context.getHand());
} }
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }

View File

@@ -15,6 +15,7 @@ import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.Property;
import net.momirealms.craftengine.core.block.properties.type.DoorHinge; import net.momirealms.craftengine.core.block.properties.type.DoorHinge;
import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf; import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf;
@@ -46,7 +47,7 @@ import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior { public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Property<DoubleBlockHalf> halfProperty; private final Property<DoubleBlockHalf> halfProperty;
private final Property<HorizontalDirection> facingProperty; private final Property<HorizontalDirection> facingProperty;

View File

@@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.block.properties.BooleanProperty;
import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -30,7 +31,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class FenceBlockBehavior extends BukkitBlockBehavior { public class FenceBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final BooleanProperty northProperty; private final BooleanProperty northProperty;
private final BooleanProperty eastProperty; private final BooleanProperty eastProperty;

View File

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.*; import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.entity.player.Player;
@@ -40,7 +41,7 @@ import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
public class FenceGateBlockBehavior extends BukkitBlockBehavior { public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty; private final Property<HorizontalDirection> facingProperty;
private final Property<Boolean> inWallProperty; private final Property<Boolean> inWallProperty;
@@ -144,6 +145,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void playerToggle(UseOnContext context, ImmutableBlockState state) { private void playerToggle(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer(); Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player); 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<ItemStack>) context.getItem())) { if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand()); player.swingHand(context.getHand());

View File

@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.entity.player.InteractionResult; 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.Item;
import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -83,7 +84,8 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
@Override @Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem(); 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; return InteractionResult.PASS;
BlockPos pos = context.getClickedPos(); BlockPos pos = context.getClickedPos();
BukkitExistingBlock upper = (BukkitExistingBlock) context.getLevel().getBlock(pos.x(), pos.y() + 1, pos.z()); BukkitExistingBlock upper = (BukkitExistingBlock) context.getLevel().getBlock(pos.x(), pos.y() + 1, pos.z());
@@ -102,7 +104,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
sendSwing = true; sendSwing = true;
} }
if (sendSwing) { if (sendSwing) {
context.getPlayer().swingHand(context.getHand()); player.swingHand(context.getHand());
} }
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }

View File

@@ -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<Object> 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<Object> 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<Object> superMethod) {
return false;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
BooleanProperty hanging = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("hanging"), "warning.config.block.behavior.hangable.missing_hanging");
return new HangableBlockBehavior(block, hanging);
}
}
}

View File

@@ -165,7 +165,7 @@ public class LeavesBlockBehavior extends BukkitBlockBehavior {
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) { public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<Boolean> persistent = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("persistent"), "warning.config.block.behavior.leaves.missing_persistent"); Property<Boolean> persistent = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("persistent"), "warning.config.block.behavior.leaves.missing_persistent");
Property<Integer> distance = (Property<Integer>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("distance"), "warning.config.block.behavior.leaves.missing_distance"); Property<Integer> distance = (Property<Integer>) 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); return new LeavesBlockBehavior(block, actual, distance, persistent);
} }
} }

View File

@@ -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.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult; 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.Item;
import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -148,7 +149,8 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
@Override @Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem(); 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; return InteractionResult.PASS;
boolean sendSwing = false; boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject(); Object visualState = state.vanillaBlockState().literalObject();
@@ -162,7 +164,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
sendSwing = true; sendSwing = true;
} }
if (sendSwing) { if (sendSwing) {
context.getPlayer().swingHand(context.getHand()); player.swingHand(context.getHand());
} }
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }

View File

@@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.Property;
import net.momirealms.craftengine.core.block.properties.type.SlabType; import net.momirealms.craftengine.core.block.properties.type.SlabType;
import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.CustomItem;
@@ -24,7 +25,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class SlabBlockBehavior extends BukkitBlockBehavior { public class SlabBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Property<SlabType> typeProperty; private final Property<SlabType> typeProperty;

View File

@@ -46,7 +46,7 @@ public class StackableBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer(); Player player = context.getPlayer();
if (player.isSecondaryUseActive()) { if (player == null || player.isSecondaryUseActive()) {
return InteractionResult.PASS; return InteractionResult.PASS;
} }
Item<ItemStack> item = (Item<ItemStack>) context.getItem(); Item<ItemStack> item = (Item<ItemStack>) context.getItem();

View File

@@ -13,6 +13,7 @@ import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
@@ -21,7 +22,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class StemBlockBehavior extends BukkitBlockBehavior { public class StemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final IntegerProperty ageProperty; private final IntegerProperty ageProperty;
private final Key fruit; private final Key fruit;

View File

@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; 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.Property;
import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf; import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf;
import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -40,7 +41,7 @@ import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
public class TrapDoorBlockBehavior extends BukkitBlockBehavior { public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory(); public static final Factory FACTORY = new Factory();
private final Property<SingleBlockHalf> halfProperty; private final Property<SingleBlockHalf> halfProperty;
private final Property<HorizontalDirection> facingProperty; private final Property<HorizontalDirection> facingProperty;
@@ -117,6 +118,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void playerToggle(UseOnContext context, ImmutableBlockState state) { private void playerToggle(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer(); Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player); 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<ItemStack>) context.getItem())) { if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand()); player.swingHand(context.getHand());

View File

@@ -2,10 +2,7 @@ package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.block.behavior.*;
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.entity.player.InteractionResult; import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -18,7 +15,7 @@ import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior { implements FallOnBlockBehavior, PlaceLiquidBlockBehavior, IsPathFindableBlockBehavior {
private final AbstractBlockBehavior[] behaviors; private final AbstractBlockBehavior[] behaviors;
public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List<AbstractBlockBehavior> behaviors) { public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List<AbstractBlockBehavior> behaviors) {
@@ -237,12 +234,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
@Override @Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception { public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
boolean processed = false;
for (AbstractBlockBehavior behavior : this.behaviors) { for (AbstractBlockBehavior behavior : this.behaviors) {
if (!behavior.isPathFindable(thisBlock, args, superMethod)) { if (behavior instanceof IsPathFindableBlockBehavior pathFindableBlockBehavior) {
return false; if (!pathFindableBlockBehavior.isPathFindable(thisBlock, args, superMethod)) {
return false;
} else {
processed = true;
}
} }
} }
return (boolean) superMethod.call(); if (!processed) return (boolean) superMethod.call();
return true;
} }
@Override @Override

View File

@@ -84,7 +84,15 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null; 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 @Override

View File

@@ -72,7 +72,15 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) { if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null; 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 @Override

View File

@@ -115,7 +115,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
} }
@Override @Override
public Optional<Item<ItemStack>> s2c(Item<ItemStack> item, Player player) { public Optional<Item<ItemStack>> s2c(Item<ItemStack> item, @Nullable Player player) {
if (item.isEmpty()) return Optional.empty(); if (item.isEmpty()) return Optional.empty();
return this.networkItemHandler.s2c(item, player); return this.networkItemHandler.s2c(item, player);
} }

View File

@@ -22,6 +22,7 @@ import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.StringTag; import net.momirealms.sparrow.nbt.StringTag;
import net.momirealms.sparrow.nbt.Tag; import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -119,7 +120,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler<ItemSt
} }
@Override @Override
public Optional<Item<ItemStack>> s2c(Item<ItemStack> wrapped, Player player) { public Optional<Item<ItemStack>> s2c(Item<ItemStack> wrapped, @Nullable Player player) {
boolean forceReturn = false; boolean forceReturn = false;
// 处理收纳袋 // 处理收纳袋

View File

@@ -19,6 +19,7 @@ import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.StringTag; import net.momirealms.sparrow.nbt.StringTag;
import net.momirealms.sparrow.nbt.Tag; import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -110,7 +111,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler<ItemSt
} }
@Override @Override
public Optional<Item<ItemStack>> s2c(Item<ItemStack> wrapped, Player player) { public Optional<Item<ItemStack>> s2c(Item<ItemStack> wrapped, @Nullable Player player) {
boolean forceReturn = false; boolean forceReturn = false;
// 处理收纳袋 // 处理收纳袋

View File

@@ -13,6 +13,8 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); 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 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 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() { public static void init() {
register(EMPTY, EmptyItemBehavior.FACTORY); register(EMPTY, EmptyItemBehavior.FACTORY);
@@ -24,5 +26,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
register(AXE_ITEM, AxeItemBehavior.FACTORY); register(AXE_ITEM, AxeItemBehavior.FACTORY);
register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY); register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY);
register(WALL_BLOCK_ITEM, WallBlockItemBehavior.FACTORY); register(WALL_BLOCK_ITEM, WallBlockItemBehavior.FACTORY);
register(CEILING_BLOCK_ITEM, CeilingBlockItemBehavior.FACTORY);
register(GROUND_BLOCK_ITEM, GroundBlockItemBehavior.FACTORY);
} }
} }

View File

@@ -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<String, Object> 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()));
}
}
}
}

View File

@@ -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<String, Object> 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()));
}
}
}
}

View File

@@ -24,6 +24,7 @@ public class WallBlockItemBehavior extends BlockItemBehavior {
return this.place(new BlockPlaceContext(context)); return this.place(new BlockPlaceContext(context));
} }
@Override
public InteractionResult place(BlockPlaceContext context) { public InteractionResult place(BlockPlaceContext context) {
if (context.getClickedFace().stepY() != 0) { if (context.getClickedFace().stepY() != 0) {
return InteractionResult.PASS; return InteractionResult.PASS;

View File

@@ -33,6 +33,7 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new ReloadCommand(this, plugin), new ReloadCommand(this, plugin),
new GetItemCommand(this, plugin), new GetItemCommand(this, plugin),
new GiveItemCommand(this, plugin), new GiveItemCommand(this, plugin),
new ClearItemCommand(this, plugin),
new ItemBrowserPlayerCommand(this, plugin), new ItemBrowserPlayerCommand(this, plugin),
new ItemBrowserAdminCommand(this, plugin), new ItemBrowserAdminCommand(this, plugin),
new SearchRecipePlayerCommand(this, plugin), new SearchRecipePlayerCommand(this, plugin),
@@ -63,8 +64,9 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new SendResourcePackCommand(this, plugin), new SendResourcePackCommand(this, plugin),
new DebugSaveDefaultResourcesCommand(this, plugin), new DebugSaveDefaultResourcesCommand(this, plugin),
new DebugCleanCacheCommand(this, plugin), new DebugCleanCacheCommand(this, plugin),
new DebugGenerateInternalAssetsCommand(this, plugin) new DebugGenerateInternalAssetsCommand(this, plugin),
// new OverrideGiveCommand(this, plugin) new DebugCustomModelDataCommand(this, plugin),
new DebugImageCommand(this, plugin)
)); ));
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager(); final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -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<CommandSender> {
public ClearItemCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true))
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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<Object> predicate = nmsStack -> {
Optional<Key> id = BukkitItemManager.instance().wrap(ItemStackUtils.asCraftMirror(nmsStack)).customId();
return id.isPresent() && id.get().equals(itemId);
};
int totalCount = 0;
Collection<Player> 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";
}
}

View File

@@ -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<CommandSender> {
public DebugCustomModelDataCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.optional("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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<CommandSender> 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<ItemStack> customItem = CraftEngineItems.byId(itemId);
if (customItem == null) return;
Item<ItemStack> item = customItem.buildItem(player);
sendMessage(context, getCustomModelData(item, player));
return;
}
if (player != null) {
Item<ItemStack> item = player.getItemInHand(InteractionHand.MAIN_HAND).copyWithCount(1);
sendMessage(context, getCustomModelData(item, player));
}
}
private int getCustomModelData(Item<ItemStack> itemStack, BukkitServerPlayer player) {
return plugin().itemManager().s2c(itemStack, player)
.map(Item::customModelData)
.orElse(itemStack.customModelData())
.orElse(0);
}
private void sendMessage(CommandContext<CommandSender> 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);
}
}

View File

@@ -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<CommandSender> {
public DebugImageCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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 + "\"}"))
));
}
}

View File

@@ -3044,10 +3044,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
@Override @Override
public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) {
if (Config.disableItemOperations()) return; if (Config.disableItemOperations()) return;
BukkitServerPlayer player = (BukkitServerPlayer) user;
if (!player.isOnline()) return;
MutableBoolean changed = new MutableBoolean(false); MutableBoolean changed = new MutableBoolean(false);
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
BukkitItemManager itemManager = BukkitItemManager.instance(); BukkitItemManager itemManager = BukkitItemManager.instance();
BukkitServerPlayer player = (BukkitServerPlayer) user;
Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf.source()); Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf.source());
List<RecipeBookEntry<ItemStack>> entries = buf.readCollection(ArrayList::new, byteBuf -> { List<RecipeBookEntry<ItemStack>> entries = buf.readCollection(ArrayList::new, byteBuf -> {
RecipeBookEntry<ItemStack> entry = RecipeBookEntry.read(byteBuf, __ -> itemManager.wrap(FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf))); RecipeBookEntry<ItemStack> entry = RecipeBookEntry.read(byteBuf, __ -> itemManager.wrap(FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf)));

View File

@@ -946,6 +946,13 @@ public class BukkitServerPlayer extends Player {
return BukkitItemManager.instance().wrap(hand == InteractionHand.MAIN_HAND ? inventory.getItemInMainHand() : inventory.getItemInOffHand()); return BukkitItemManager.instance().wrap(hand == InteractionHand.MAIN_HAND ? inventory.getItemInMainHand() : inventory.getItemInOffHand());
} }
@NotNull
@Override
public Item<ItemStack> getItemBySlot(int slot) {
PlayerInventory inventory = platformPlayer().getInventory();
return BukkitItemManager.instance().wrap(inventory.getItem(slot));
}
@Override @Override
public World world() { public World world() {
return new BukkitWorld(platformPlayer().getWorld()); return new BukkitWorld(platformPlayer().getWorld());

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.bukkit.sound; 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.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; 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.plugin.CraftEngine;
import net.momirealms.craftengine.core.sound.AbstractSoundManager; import net.momirealms.craftengine.core.sound.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.JukeboxSong; 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.Key;
import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Collection; import java.io.IOException;
import java.util.Map; import java.nio.file.Files;
import java.util.Optional; import java.nio.file.Path;
import java.util.Set; import java.util.*;
public class BukkitSoundManager extends AbstractSoundManager { public class BukkitSoundManager extends AbstractSoundManager {
@@ -25,6 +29,65 @@ public class BukkitSoundManager extends AbstractSoundManager {
Object resourceLocation = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); Object resourceLocation = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
VANILLA_SOUND_EVENTS.add(KeyUtils.resourceLocationToKey(resourceLocation)); 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<Key, JukeboxSong> 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<Key, JukeboxSong> 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<Key, JukeboxSong> loadLastRegisteredSongs() {
Path persistSongPath = this.plugin.dataFolderPath()
.resolve("cache")
.resolve("jukebox-songs.json");
if (Files.exists(persistSongPath) && Files.isRegularFile(persistSongPath)) {
try {
Map<Key, JukeboxSong> songs = new HashMap<>();
JsonObject cache = GsonHelper.readJsonFile(persistSongPath).getAsJsonObject();
for (Map.Entry<String, JsonElement> 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 @Override

View File

@@ -51,4 +51,8 @@ public final class ItemStackUtils {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack); Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
return UniqueIdItem.of(wrappedItem); return UniqueIdItem.of(wrappedItem);
} }
public static ItemStack asCraftMirror(Object itemStack) {
return FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack);
}
} }

View File

@@ -43,6 +43,13 @@ give_item:
- /craftengine item give - /craftengine item give
- /ce 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: item_browser_player:
enable: true enable: true
permission: ce.command.player.item_browser permission: ce.command.player.item_browser
@@ -230,6 +237,20 @@ debug_clean_cache:
- /craftengine debug clean-cache - /craftengine debug clean-cache
- /ce 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: debug_generate_internal_assets:
enable: false enable: false
permission: ce.command.debug.generate_internal_assets permission: ce.command.debug.generate_internal_assets

View File

@@ -193,11 +193,17 @@ resource-pack:
ip: "localhost" ip: "localhost"
port: 8163 port: 8163
protocol: "http" protocol: "http"
# Blocks all requests from non-Minecraft clients.
deny-non-minecraft-request: true deny-non-minecraft-request: true
# Generates a single-use, time-limited download link for each player.
one-time-token: true one-time-token: true
rate-limit: rate-limiting:
max-requests: 10 # Maximum bandwidth per second to prevent server instability for other players during resource pack downloads
reset-interval: 30 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: item:
# [Premium Exclusive] # [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. # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience.
sound-system: sound-system:
enable: true 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. # Adventure mode requires correct tools to break custom blocks.
# Vanilla clients cannot recognize custom block IDs (e.g., craftengine:custom_100). # Vanilla clients cannot recognize custom block IDs (e.g., craftengine:custom_100).
# #
@@ -542,6 +552,12 @@ chunk-system:
remove: [] remove: []
convert: {} 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 # Enables or disables debug mode
debug: debug:
common: false common: false

View File

@@ -34,4 +34,5 @@ reactive-streams=${reactive_streams_version}
amazon-sdk-s3=${amazon_awssdk_version} amazon-sdk-s3=${amazon_awssdk_version}
amazon-sdk-eventstream=${amazon_awssdk_eventstream_version} amazon-sdk-eventstream=${amazon_awssdk_eventstream_version}
evalex=${evalex_version} evalex=${evalex_version}
jimfs=${jimfs_version} jimfs=${jimfs_version}
bucket4j=${bucket4j_version}

View File

@@ -61,7 +61,7 @@ templates:
model: model:
path: ${model_path} path: ${model_path}
generation: generation:
parent: minecraft:block/leaves parent: minecraft:block/${leaves_base_model}
textures: textures:
all: ${texture_path} all: ${texture_path}
waterlogged: waterlogged:
@@ -83,15 +83,21 @@ templates:
# any leaves block # any leaves block
default:block_state/leaves: default:block_state/leaves:
template: default:block_state/__leaves__ template: default:block_state/__leaves__
arguments::auto_state: leaves arguments:
auto_state: leaves
leaves_base_model: leaves
# tintable leaves block # tintable leaves block
default:block_state/tintable_leaves: default:block_state/tintable_leaves:
template: default:block_state/__leaves__ template: default:block_state/__leaves__
arguments::auto_state: tintable_leaves arguments:
auto_state: tintable_leaves
leaves_base_model: leaves
# non-tintable leaves block # non-tintable leaves block
default:block_state/non_tintable_leaves: default:block_state/non_tintable_leaves:
template: default:block_state/__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 # trapdoor block
default:block_state/trapdoor: default:block_state/trapdoor:
properties: properties:

View File

@@ -127,6 +127,70 @@ translations:
emoji.tip: 使用<yellow>'<arg:keyword>'</yellow>来发送表情'<arg:emoji>' emoji.tip: 使用<yellow>'<arg:keyword>'</yellow>来发送表情'<arg:emoji>'
emoji.time: '<bold>当前时间: <papi:player_world_time_12></bold>' emoji.time: '<bold>当前时间: <papi:player_world_time_12></bold>'
emoji.location: '<bold>当前坐标: <papi:player_x>,<papi:player_y>,<papi:player_z></bold>' emoji.location: '<bold>当前坐标: <papi:player_x>,<papi:player_y>,<papi:player_z></bold>'
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 <yellow>'<arg:keyword>'</yellow> pour envoyer l'emoji '<arg:emoji>'
emoji.time: '<bold>Heure actuelle: <papi:player_world_time_12></bold>'
emoji.location: '<bold>Coordonnées actuelles: <papi:player_x>,<papi:player_y>,<papi:player_z></bold>'
# This section is for localizing internal block IDs (craftengine:xxx_xx). # This section is for localizing internal block IDs (craftengine:xxx_xx).
# Some other plugins support displaying block names using lang components. # Some other plugins support displaying block names using lang components.
# This might be useful for the client-side, but it's not mandatory. # 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:hami_melon_stem: 哈密瓜茎
block_name:default:attached_hami_melon_stem: 哈密瓜茎 block_name:default:attached_hami_melon_stem: 哈密瓜茎
block_name:default:magma_plant: 岩浆植物 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

View File

@@ -842,6 +842,7 @@ block-state-mappings:
#### Leaves #### #### Leaves ####
# The 'distance' and 'persistent' properties are used under the hood to optimize how leaves decay, but visually? They look exactly the same. # 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. # 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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=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=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] 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 #### #### Tripwire ####
# Tripwires actually have 128 different states, but we're keeping just two of them to match vanilla's visual styles. # 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=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=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=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] # 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]

View File

@@ -37,4 +37,17 @@ translations:
internal.get_item.1: Rechtsklick, um einen Stapel zu nehmen internal.get_item.1: Rechtsklick, um einen Stapel zu nehmen
internal.cooking_info: Rezeptinformationen internal.cooking_info: Rezeptinformationen
internal.cooking_info.0: 'Zeit: <arg:cooking_time> Ticks' internal.cooking_info.0: 'Zeit: <arg:cooking_time> Ticks'
internal.cooking_info.1: 'Erfahrung: <arg:cooking_experience>' internal.cooking_info.1: 'Erfahrung: <arg:cooking_experience>'
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: <arg:cooking_time>ticks'
internal.cooking_info.1: 'Expérience: <arg:cooking_experience>'

View File

@@ -49,6 +49,12 @@ command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0
command.item.give.success.single: "<lang:commands.give.success.single:'<arg:0>':'<arg:1>':'<arg:2>'>" command.item.give.success.single: "<lang:commands.give.success.single:'<arg:0>':'<arg:1>':'<arg:2>'>"
command.item.give.success.multiple: "<lang:commands.give.success.multiple:'<arg:0>':'<arg:1>':'<arg:2>'>" command.item.give.success.multiple: "<lang:commands.give.success.multiple:'<arg:0>':'<arg:1>':'<arg:2>'>"
command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>" command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
command.item.clear.failed.single: "<red><lang:clear.failed.single:'<arg:0>'></red>"
command.item.clear.failed.multiple: "<red><lang:clear.failed.multiple:'<arg:0>'></red>"
command.item.clear.success.single: "<lang:commands.clear.success.single:'<arg:0>':'<arg:1>'>"
command.item.clear.success.multiple: "<lang:commands.clear.success.multiple:'<arg:0>':'<arg:1>'>"
command.item.clear.test.single: "<lang:commands.clear.test.single:'<arg:0>':'<arg:1>'>"
command.item.clear.test.multiple: "<lang:commands.clear.test.multiple:'<arg:0>':'<arg:1>'>"
command.search_recipe.not_found: "<red>No recipe found for this item</red>" command.search_recipe.not_found: "<red>No recipe found for this item</red>"
command.search_usage.not_found: "<red>No usage found for this item</red>" command.search_usage.not_found: "<red>No usage found for this item</red>"
command.search_recipe.no_item: "<red>Please hold an item before running this command</red>" command.search_recipe.no_item: "<red>Please hold an item before running this command</red>"
@@ -74,6 +80,7 @@ warning.config.yaml.duplicated_key: "<red>Issue found in file <arg:0> - Found du
warning.config.yaml.inconsistent_value_type: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2> with different value types, this might cause unexpected results.</red>" warning.config.yaml.inconsistent_value_type: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2> with different value types, this might cause unexpected results.</red>"
warning.config.type.int: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to integer type for option '<arg:3>'.</yellow>" warning.config.type.int: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to integer type for option '<arg:3>'.</yellow>"
warning.config.type.boolean: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to boolean type for option '<arg:3>'.</yellow>" warning.config.type.boolean: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to boolean type for option '<arg:3>'.</yellow>"
warning.config.type.long: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to long type for option '<arg:3>'.</yellow>"
warning.config.type.float: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to float type for option '<arg:3>'.</yellow>" warning.config.type.float: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to float type for option '<arg:3>'.</yellow>"
warning.config.type.double: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to double type for option '<arg:3>'.</yellow>" warning.config.type.double: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to double type for option '<arg:3>'.</yellow>"
warning.config.type.quaternionf: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Quaternionf type for option '<arg:3>'.</yellow>" warning.config.type.quaternionf: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Quaternionf type for option '<arg:3>'.</yellow>"
@@ -213,6 +220,8 @@ warning.config.item.behavior.missing_type: "<yellow>Issue found in file <arg:0>
warning.config.item.behavior.invalid_type: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is using an invalid item behavior type '<arg:2>'.</yellow>" warning.config.item.behavior.invalid_type: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is using an invalid item behavior type '<arg:2>'.</yellow>"
warning.config.item.behavior.block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'block_item' behavior.</yellow>" warning.config.item.behavior.block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'block_item' behavior.</yellow>"
warning.config.item.behavior.wall_block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'wall_block_item' behavior.</yellow>" warning.config.item.behavior.wall_block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'wall_block_item' behavior.</yellow>"
warning.config.item.behavior.ceiling_block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'ceiling_block_item' behavior.</yellow>"
warning.config.item.behavior.ground_block.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'ground_block_item' behavior.</yellow>"
warning.config.item.behavior.furniture.missing_furniture: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'furniture' argument for 'furniture_item' behavior.</yellow>" warning.config.item.behavior.furniture.missing_furniture: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'furniture' argument for 'furniture_item' behavior.</yellow>"
warning.config.item.behavior.liquid_collision.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'liquid_collision_block_item' behavior.</yellow>" warning.config.item.behavior.liquid_collision.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'liquid_collision_block_item' behavior.</yellow>"
warning.config.item.behavior.double_high.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'double_high_block_item' behavior.</yellow>" warning.config.item.behavior.double_high.missing_block: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'block' argument for 'double_high_block_item' behavior.</yellow>"
@@ -357,6 +366,7 @@ warning.config.block.behavior.attached_stem.missing_stem: "<yellow>Issue found i
warning.config.block.behavior.chime.missing_sounds_projectile_hit: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'sounds.projectile-hit' argument for 'chime_block' behavior.</yellow>" warning.config.block.behavior.chime.missing_sounds_projectile_hit: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'sounds.projectile-hit' argument for 'chime_block' behavior.</yellow>"
warning.config.block.behavior.surface_spreading.missing_base_block: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'base-block' argument for 'surface_spreading_block' behavior.</yellow>" warning.config.block.behavior.surface_spreading.missing_base_block: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'base-block' argument for 'surface_spreading_block' behavior.</yellow>"
warning.config.block.behavior.snowy.missing_snowy: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'snowy' property for 'snowy_block' behavior.</yellow>" warning.config.block.behavior.snowy.missing_snowy: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'snowy' property for 'snowy_block' behavior.</yellow>"
warning.config.block.behavior.hangable.missing_hanging: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'hanging' property for 'hangable_block' behavior.</yellow>"
warning.config.model.generation.missing_parent: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'parent' argument in 'generation' section.</yellow>" warning.config.model.generation.missing_parent: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'parent' argument in 'generation' section.</yellow>"
warning.config.model.generation.conflict: "<yellow>Issue found in file <arg:0> - Failed to generate model for '<arg:1>' as two or more configurations attempt to generate different json models with the same path: '<arg:2>'.</yellow>" warning.config.model.generation.conflict: "<yellow>Issue found in file <arg:0> - Failed to generate model for '<arg:1>' as two or more configurations attempt to generate different json models with the same path: '<arg:2>'.</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid display position '<arg:2>' in 'generation.display' section. Allowed display positions: [<arg:3>]</yellow>" warning.config.model.generation.invalid_display_position: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid display position '<arg:2>' in 'generation.display' section. Allowed display positions: [<arg:3>]</yellow>"

View File

@@ -49,6 +49,12 @@ command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0
command.item.give.success.single: "<lang:commands.give.success.single:'<arg:0>':'<arg:1>':'<arg:2>'>" command.item.give.success.single: "<lang:commands.give.success.single:'<arg:0>':'<arg:1>':'<arg:2>'>"
command.item.give.success.multiple: "<lang:commands.give.success.multiple:'<arg:0>':'<arg:1>':'<arg:2>'>" command.item.give.success.multiple: "<lang:commands.give.success.multiple:'<arg:0>':'<arg:1>':'<arg:2>'>"
command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>" command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
command.item.clear.failed.single: "<red><lang:clear.failed.single:'<arg:0>'></red>"
command.item.clear.failed.multiple: "<red><lang:clear.failed.multiple:'<arg:0>'></red>"
command.item.clear.success.single: "<lang:commands.clear.success.single:'<arg:0>':'<arg:1>'>"
command.item.clear.success.multiple: "<lang:commands.clear.success.multiple:'<arg:0>':'<arg:1>'>"
command.item.clear.test.single: "<lang:commands.clear.test.single:'<arg:0>':'<arg:1>'>"
command.item.clear.test.multiple: "<lang:commands.clear.test.multiple:'<arg:0>':'<arg:1>'>"
command.search_recipe.not_found: "<red>找不到此物品的配方</red>" command.search_recipe.not_found: "<red>找不到此物品的配方</red>"
command.search_usage.not_found: "<red>找不到此物品的用途</red>" command.search_usage.not_found: "<red>找不到此物品的用途</red>"
command.search_recipe.no_item: "<red>请手持物品后再执行此命令</red>" command.search_recipe.no_item: "<red>请手持物品后再执行此命令</red>"
@@ -74,6 +80,7 @@ warning.config.yaml.duplicated_key: "<red>在文件 <arg:0> 发现问题 - 在
warning.config.yaml.inconsistent_value_type: "<red>在文件 <arg:0> 发现问题 - 在第<arg:2>行发现重复且值类型不同的键 '<arg:1>', 这可能会导致一些意料之外的问题</red>" warning.config.yaml.inconsistent_value_type: "<red>在文件 <arg:0> 发现问题 - 在第<arg:2>行发现重复且值类型不同的键 '<arg:1>', 这可能会导致一些意料之外的问题</red>"
warning.config.type.int: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为整数类型 (选项 '<arg:3>')</yellow>" warning.config.type.int: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为整数类型 (选项 '<arg:3>')</yellow>"
warning.config.type.boolean: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为布尔类型 (选项 '<arg:3>')</yellow>" warning.config.type.boolean: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为布尔类型 (选项 '<arg:3>')</yellow>"
warning.config.type.long: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为长整型类型 (选项 '<arg:3>')</yellow>"
warning.config.type.float: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为浮点数类型 (选项 '<arg:3>')</yellow>" warning.config.type.float: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为浮点数类型 (选项 '<arg:3>')</yellow>"
warning.config.type.double: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度类型 (选项 '<arg:3>')</yellow>" warning.config.type.double: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度类型 (选项 '<arg:3>')</yellow>"
warning.config.type.quaternionf: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为四元数类型 (选项 '<arg:3>')</yellow>" warning.config.type.quaternionf: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为四元数类型 (选项 '<arg:3>')</yellow>"
@@ -213,6 +220,8 @@ warning.config.item.behavior.missing_type: "<yellow>在文件 <arg:0> 发现问
warning.config.item.behavior.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 使用了无效的行为类型 '<arg:2>'</yellow>" warning.config.item.behavior.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 使用了无效的行为类型 '<arg:2>'</yellow>"
warning.config.item.behavior.block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'block_item' 行为缺少必需的 'block' 参数</yellow>" warning.config.item.behavior.block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'block_item' 行为缺少必需的 'block' 参数</yellow>"
warning.config.item.behavior.wall_block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 缺少 'wall_block_item' 行为所需的 'block' 参数</yellow>" warning.config.item.behavior.wall_block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 缺少 'wall_block_item' 行为所需的 'block' 参数</yellow>"
warning.config.item.behavior.ceiling_block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 缺少 'ceiling_block_item' 行为所需的 'block' 参数</yellow>"
warning.config.item.behavior.ground_block.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 缺少 'ground_block_item' 行为所需的 'block' 参数</yellow>"
warning.config.item.behavior.furniture.missing_furniture: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'furniture_item' 行为缺少必需的 'furniture' 参数</yellow>" warning.config.item.behavior.furniture.missing_furniture: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'furniture_item' 行为缺少必需的 'furniture' 参数</yellow>"
warning.config.item.behavior.liquid_collision.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'liquid_collision_block_item' 行为缺少必需的 'block' 参数</yellow>" warning.config.item.behavior.liquid_collision.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'liquid_collision_block_item' 行为缺少必需的 'block' 参数</yellow>"
warning.config.item.behavior.double_high.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'double_high_block_item' 行为缺少必需的 'block' 参数</yellow>" warning.config.item.behavior.double_high.missing_block: "<yellow>在文件 <arg:0> 发现问题 - 物品 '<arg:1>' 的 'double_high_block_item' 行为缺少必需的 'block' 参数</yellow>"
@@ -357,6 +366,7 @@ warning.config.block.behavior.attached_stem.missing_stem: "<yellow>在文件 <ar
warning.config.block.behavior.chime.missing_sounds_projectile_hit: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'chime_block' 行为缺少必需的 'sounds.projectile-hit' 选项</yellow>" warning.config.block.behavior.chime.missing_sounds_projectile_hit: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'chime_block' 行为缺少必需的 'sounds.projectile-hit' 选项</yellow>"
warning.config.block.behavior.surface_spreading.missing_base_block: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'surface_spreading_block' 行为缺少必需的 'base-block' 选项</yellow>" warning.config.block.behavior.surface_spreading.missing_base_block: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'surface_spreading_block' 行为缺少必需的 'base-block' 选项</yellow>"
warning.config.block.behavior.snowy.missing_snowy: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'snowy_block' 行为缺少必需的 'snowy' 属性</yellow>" warning.config.block.behavior.snowy.missing_snowy: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'snowy_block' 行为缺少必需的 'snowy' 属性</yellow>"
warning.config.block.behavior.hangable.missing_hanging: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'hangable_block' 行为缺少必需的 'hanging' 属性</yellow>"
warning.config.model.generation.missing_parent: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'generation' 段落缺少必需的 'parent' 参数</yellow>" warning.config.model.generation.missing_parent: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'generation' 段落缺少必需的 'parent' 参数</yellow>"
warning.config.model.generation.conflict: "<yellow>在文件 <arg:0> 发现问题 - 无法为 '<arg:1>' 生成模型 存在多个配置尝试使用相同路径 '<arg:2>' 生成不同的 JSON 模型</yellow>" warning.config.model.generation.conflict: "<yellow>在文件 <arg:0> 发现问题 - 无法为 '<arg:1>' 生成模型 存在多个配置尝试使用相同路径 '<arg:2>' 生成不同的 JSON 模型</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 在 'generation.display' 区域使用了无效的 display 位置类型 '<arg:2>'. 可用展示类型: [<arg:3>]</yellow>" warning.config.model.generation.invalid_display_position: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 在 'generation.display' 区域使用了无效的 display 位置类型 '<arg:2>'. 可用展示类型: [<arg:3>]</yellow>"

View File

@@ -21,7 +21,7 @@ dependencies {
implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}")
implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}")
// S3 // S3
implementation("net.momirealms:craft-engine-s3:0.8") implementation("net.momirealms:craft-engine-s3:0.9")
// Util // Util
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
// Adventure // Adventure
@@ -69,6 +69,8 @@ dependencies {
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
// concurrentutil // concurrentutil
compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}") compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}")
// bucket4j
compileOnly("com.bucket4j:bucket4j_jdk17-core:${rootProject.properties["bucket4j_version"]}")
} }
java { 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.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.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.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") // bucket4j
} }
} }
publishing { publishing {
repositories { repositories {
maven { maven {
name = "releases"
url = uri("https://repo.momirealms.net/releases") url = uri("https://repo.momirealms.net/releases")
credentials(PasswordCredentials::class) { credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME") username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD") 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 { publications {
create<MavenPublication>("mavenJava") { create<MavenPublication>("mavenJava") {
@@ -139,5 +151,35 @@ publishing {
} }
} }
} }
create<MavenPublication>("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")
} }

View File

@@ -550,7 +550,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
} }
AutoStateGroup group = AutoStateGroup.byId(autoStateType); AutoStateGroup group = AutoStateGroup.byId(autoStateType);
if (group == null) { 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)); futureVisualStates.put(appearanceName, this.visualBlockStateAllocator.requestAutoState(autoStateId, group));
} else { } else {
@@ -704,7 +704,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
@NotNull @NotNull
private Map<String, Property<?>> parseBlockProperties(Map<String, Object> propertiesSection) { private Map<String, Property<?>> parseBlockProperties(Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>(); Map<String, Property<?>> properties = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) { for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
Property<?> property = Properties.fromMap(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey())); Property<?> property = Properties.fromMap(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()));
properties.put(entry.getKey(), property); properties.put(entry.getKey(), property);

View File

@@ -10,21 +10,21 @@ import java.util.function.Predicate;
public enum AutoStateGroup { public enum AutoStateGroup {
NON_TINTABLE_LEAVES(List.of("no_tint_leaves", "leaves_no_tint", "non_tintable_leaves"), 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") (w) -> !(boolean) w.getProperty("waterlogged")
), ),
WATERLOGGED_NON_TINTABLE_LEAVES( WATERLOGGED_NON_TINTABLE_LEAVES(
List.of("waterlogged_no_tint_leaves", "waterlogged_leaves_no_tint", "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") (w) -> w.getProperty("waterlogged")
), ),
TINTABLE_LEAVES("tintable_leaves", 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") (w) -> !(boolean) w.getProperty("waterlogged")
), ),
WATERLOGGED_TINTABLE_LEAVES( WATERLOGGED_TINTABLE_LEAVES(
"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") (w) -> w.getProperty("waterlogged")
), ),
LEAVES("leaves", LEAVES("leaves",

View File

@@ -73,7 +73,7 @@ public abstract class BlockBehavior {
superMethod.call(); superMethod.call();
} }
// 1.20+ BlockState state, LevelReader world, BlockPos pos // BlockState state, LevelReader world, BlockPos pos
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception { public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
return (boolean) superMethod.call(); return (boolean) superMethod.call();
} }

View File

@@ -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<Object> superMethod) throws Exception;
}

View File

@@ -132,7 +132,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
.item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item"))) .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")) .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)) .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")) .scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) .position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) .translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))

View File

@@ -26,6 +26,9 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
@NotNull @NotNull
public abstract Item<?> getItemInHand(InteractionHand hand); public abstract Item<?> getItemInHand(InteractionHand hand);
@NotNull
public abstract Item<?> getItemBySlot(int slot);
@Override @Override
public abstract Object platformPlayer(); public abstract Object platformPlayer();

View File

@@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
import org.ahocorasick.trie.Token; import org.ahocorasick.trie.Token;
import org.ahocorasick.trie.Trie; import org.ahocorasick.trie.Trie;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@@ -52,6 +53,8 @@ public abstract class AbstractFontManager implements FontManager {
protected Map<String, Emoji> emojiMapper; protected Map<String, Emoji> emojiMapper;
protected List<Emoji> emojiList; protected List<Emoji> emojiList;
protected List<String> allEmojiSuggestions; protected List<String> allEmojiSuggestions;
// Cached command suggestions
protected final List<Suggestion> cachedImagesSuggestions = new ArrayList<>();
public AbstractFontManager(CraftEngine plugin) { public AbstractFontManager(CraftEngine plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -95,6 +98,7 @@ public abstract class AbstractFontManager implements FontManager {
public void unload() { public void unload() {
this.fonts.clear(); this.fonts.clear();
this.images.clear(); this.images.clear();
this.cachedImagesSuggestions.clear();
this.illegalChars.clear(); this.illegalChars.clear();
this.emojis.clear(); this.emojis.clear();
this.networkTagTrie = null; this.networkTagTrie = null;
@@ -415,6 +419,12 @@ public abstract class AbstractFontManager implements FontManager {
return Optional.ofNullable(this.fonts.get(id)); return Optional.ofNullable(this.fonts.get(id));
} }
@Override
public Collection<Suggestion> cachedImagesSuggestions() {
return Collections.unmodifiableCollection(this.cachedImagesSuggestions);
}
private Font getOrCreateFont(Key key) { private Font getOrCreateFont(Key key) {
return this.fonts.computeIfAbsent(key, Font::new); 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.images.put(id, bitmapImage);
AbstractFontManager.this.cachedImagesSuggestions.add(Suggestion.suggestion(id.asString()));
}, () -> GsonHelper.get().toJson(section))); }, () -> GsonHelper.get().toJson(section)));
} }

View File

@@ -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.plugin.text.component.ComponentProvider;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
import net.momirealms.sparrow.nbt.Tag; import net.momirealms.sparrow.nbt.Tag;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -80,6 +81,8 @@ public interface FontManager extends Manageable {
Optional<Font> fontById(Key font); Optional<Font> fontById(Key font);
Collection<Suggestion> cachedImagesSuggestions();
int codepointByImageId(Key imageId, int x, int y); int codepointByImageId(Key imageId, int x, int y);
default int codepointByImageId(Key imageId) { default int codepointByImageId(Key imageId) {

View File

@@ -113,7 +113,7 @@ public interface ItemManager<T> extends Manageable, ModelGenerator {
Optional<Item<T>> c2s(Item<T> item); Optional<Item<T>> c2s(Item<T> item);
Optional<Item<T>> s2c(Item<T> item, Player player); Optional<Item<T>> s2c(Item<T> item, @Nullable Player player);
UniqueIdItem<T> uniqueEmptyItem(); UniqueIdItem<T> uniqueEmptyItem();

View File

@@ -47,6 +47,7 @@ public class ItemSettings {
Color dyeColor; Color dyeColor;
@Nullable @Nullable
Color fireworkColor; Color fireworkColor;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private ItemSettings() {} private ItemSettings() {}
@@ -108,6 +109,7 @@ public class ItemSettings {
newSettings.dyeColor = settings.dyeColor; newSettings.dyeColor = settings.dyeColor;
newSettings.fireworkColor = settings.fireworkColor; newSettings.fireworkColor = settings.fireworkColor;
newSettings.ingredientSubstitutes = settings.ingredientSubstitutes; newSettings.ingredientSubstitutes = settings.ingredientSubstitutes;
newSettings.customData = settings.customData;
return newSettings; return newSettings;
} }
@@ -123,73 +125,86 @@ public class ItemSettings {
return settings; return settings;
} }
@SuppressWarnings("unchecked")
public <T> T getCustomData(CustomDataType<T> type) {
return (T) this.customData.get(type);
}
public void clearCustomData() {
this.customData.clear();
}
public <T> void addCustomData(CustomDataType<T> key, T value) {
this.customData.put(key, value);
}
public ProjectileMeta projectileMeta() { public ProjectileMeta projectileMeta() {
return projectileMeta; return this.projectileMeta;
} }
public boolean disableVanillaBehavior() { public boolean disableVanillaBehavior() {
return disableVanillaBehavior; return this.disableVanillaBehavior;
} }
public Repairable repairable() { public Repairable repairable() {
return repairable; return this.repairable;
} }
public int fuelTime() { public int fuelTime() {
return fuelTime; return this.fuelTime;
} }
public boolean renameable() { public boolean renameable() {
return renameable; return this.renameable;
} }
public Set<Key> tags() { public Set<Key> tags() {
return tags; return this.tags;
} }
public Tristate dyeable() { public Tristate dyeable() {
return dyeable; return this.dyeable;
} }
public boolean canEnchant() { public boolean canEnchant() {
return canEnchant; return this.canEnchant;
} }
public List<AnvilRepairItem> repairItems() { public List<AnvilRepairItem> repairItems() {
return anvilRepairItems; return this.anvilRepairItems;
} }
public boolean respectRepairableComponent() { public boolean respectRepairableComponent() {
return respectRepairableComponent; return this.respectRepairableComponent;
} }
public List<Key> ingredientSubstitutes() { public List<Key> ingredientSubstitutes() {
return ingredientSubstitutes; return this.ingredientSubstitutes;
} }
@Nullable @Nullable
public FoodData foodData() { public FoodData foodData() {
return foodData; return this.foodData;
} }
@Nullable @Nullable
public Key consumeReplacement() { public Key consumeReplacement() {
return consumeReplacement; return this.consumeReplacement;
} }
@Nullable @Nullable
public CraftRemainder craftRemainder() { public CraftRemainder craftRemainder() {
return craftRemainder; return this.craftRemainder;
} }
@Nullable @Nullable
public Helmet helmet() { public Helmet helmet() {
return helmet; return this.helmet;
} }
@Nullable @Nullable
public ItemEquipment equipment() { public ItemEquipment equipment() {
return equipment; return this.equipment;
} }
@Nullable @Nullable
@@ -203,11 +218,11 @@ public class ItemSettings {
} }
public List<DamageSource> invulnerable() { public List<DamageSource> invulnerable() {
return invulnerable; return this.invulnerable;
} }
public float compostProbability() { public float compostProbability() {
return compostProbability; return this.compostProbability;
} }
public ItemSettings fireworkColor(Color color) { public ItemSettings fireworkColor(Color color) {
@@ -384,6 +399,9 @@ public class ItemSettings {
registerFactory("equippable", (value -> { registerFactory("equippable", (value -> {
Map<String, Object> args = MiscUtils.castToMap(value, false); Map<String, Object> args = MiscUtils.castToMap(value, false);
EquipmentData data = EquipmentData.fromMap(args); 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); ComponentBasedEquipment componentBasedEquipment = ComponentBasedEquipment.FACTORY.create(data.assetId(), args);
((AbstractItemManager<?>) CraftEngine.instance().itemManager()).addOrMergeEquipment(componentBasedEquipment); ((AbstractItemManager<?>) CraftEngine.instance().itemManager()).addOrMergeEquipment(componentBasedEquipment);
ItemEquipment itemEquipment = new ItemEquipment(Tristate.FALSE, data, 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()))); 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); FACTORIES.put(id, factory);
} }
} }

View File

@@ -18,7 +18,7 @@ public interface NetworkItemHandler<T> {
String NETWORK_OPERATION = "type"; String NETWORK_OPERATION = "type";
String NETWORK_VALUE = "value"; String NETWORK_VALUE = "value";
Optional<Item<T>> s2c(Item<T> itemStack, Player player); Optional<Item<T>> s2c(Item<T> itemStack, @Nullable Player player);
Optional<Item<T>> c2s(Item<T> itemStack); Optional<Item<T>> c2s(Item<T> itemStack);

View File

@@ -2,15 +2,17 @@ package net.momirealms.craftengine.core.item.equipment;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import java.util.Objects;
public abstract class AbstractEquipment implements Equipment { public abstract class AbstractEquipment implements Equipment {
protected final Key assetId; protected final Key assetId;
protected AbstractEquipment(Key assetId) { protected AbstractEquipment(Key assetId) {
this.assetId = assetId; this.assetId = Objects.requireNonNull(assetId, "asset-id cannot be null");
} }
@Override @Override
public Key assetId() { public Key assetId() {
return assetId; return this.assetId;
} }
} }

View File

@@ -143,5 +143,21 @@ public class ComponentBasedEquipment extends AbstractEquipment implements Suppli
return dyeData; return dyeData;
} }
} }
@Override
public @NotNull String toString() {
return "Layer{" +
"texture='" + texture + '\'' +
", data=" + data +
", usePlayerTexture=" + usePlayerTexture +
'}';
}
}
@Override
public String toString() {
return "ComponentBasedEquipment{" +
"layers=" + this.layers +
'}';
} }
} }

View File

@@ -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<T> extends AbstractSingleLootEntryContainer<T> {
public static final Factory<?> FACTORY = new Factory<>();
protected EmptyLoopEntryContainer(List<Condition<LootContext>> conditions, int weight, int quality) {
super(conditions, null, weight, quality);
}
@Override
protected void createItem(Consumer<Item<T>> lootConsumer, LootContext context) {}
@Override
public Key type() {
return LootEntryContainers.EMPTY;
}
public static class Factory<A> implements LootEntryContainerFactory<A> {
@SuppressWarnings("unchecked")
@Override
public LootEntryContainer<A> create(Map<String, Object> arguments) {
int weight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("weight", 1), "weight");
int quality = ResourceConfigUtils.getAsInt(arguments.getOrDefault("quality", 0), "quality");
List<Condition<LootContext>> conditions = Optional.ofNullable(arguments.get("conditions"))
.map(it -> LootConditions.fromMapList((List<Map<String, Object>>) it))
.orElse(Collections.emptyList());
return new EmptyLoopEntryContainer<>(conditions, weight, quality);
}
}
}

View File

@@ -18,6 +18,7 @@ public class LootEntryContainers {
public static final Key ITEM = Key.from("craftengine:item"); public static final Key ITEM = Key.from("craftengine:item");
public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_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 EXP = Key.from("craftengine:exp");
public static final Key EMPTY = Key.from("craftengine:empty");
static { static {
register(ALTERNATIVES, AlternativesLootEntryContainer.FACTORY); register(ALTERNATIVES, AlternativesLootEntryContainer.FACTORY);
@@ -25,6 +26,7 @@ public class LootEntryContainers {
register(ITEM, SingleItemLootEntryContainer.FACTORY); register(ITEM, SingleItemLootEntryContainer.FACTORY);
register(EXP, ExpLootEntryContainer.FACTORY); register(EXP, ExpLootEntryContainer.FACTORY);
register(FURNITURE_ITEM, FurnitureItemLootEntryContainer.FACTORY); register(FURNITURE_ITEM, FurnitureItemLootEntryContainer.FACTORY);
register(EMPTY, EmptyLoopEntryContainer.FACTORY);
} }
public static <T> void register(Key key, LootEntryContainerFactory<T> factory) { public static <T> void register(Key key, LootEntryContainerFactory<T> factory) {

View File

@@ -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.LocalizedException;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager; 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.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.SoundEvent; import net.momirealms.craftengine.core.sound.SoundEvent;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
@@ -1122,7 +1123,7 @@ public abstract class AbstractPackManager implements PackManager {
futures.add(CompletableFuture.runAsync(() -> { futures.add(CompletableFuture.runAsync(() -> {
try { try {
byte[] previousImageBytes = Files.readAllBytes(imagePath); byte[] previousImageBytes = Files.readAllBytes(imagePath);
byte[] optimized = optimizeImage(previousImageBytes); byte[] optimized = optimizeImage(imagePath, previousImageBytes);
previousBytes.addAndGet(previousImageBytes.length); previousBytes.addAndGet(previousImageBytes.length);
if (optimized.length < previousImageBytes.length) { if (optimized.length < previousImageBytes.length) {
afterBytes.addAndGet(optimized.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)) { try (ByteArrayInputStream is = new ByteArrayInputStream(previousImageBytes)) {
BufferedImage src = ImageIO.read(is); 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) { if (src.getType() == BufferedImage.TYPE_CUSTOM) {
return previousImageBytes; return previousImageBytes;
} }
@@ -1539,7 +1544,14 @@ public abstract class AbstractPackManager implements PackManager {
} }
} }
if (Config.fixTextureAtlas()) { 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 { } else {
TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString()); 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) { private void processComponentBasedEquipment(ComponentBasedEquipment componentBasedEquipment, Path generatedPackPath) {
Key assetId = componentBasedEquipment.assetId(); 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)) { if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4)) {
Path equipmentPath = generatedPackPath Path equipmentPath = generatedPackPath
.resolve("assets") .resolve("assets")

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.pack.host.impl; 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.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; 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.config.Config;
import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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"); boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token");
String protocol = arguments.getOrDefault("protocol", "http").toString(); String protocol = arguments.getOrDefault("protocol", "http").toString();
boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request"); boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request");
Map<String, Object> rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true);
int maxRequests = 5;
int resetInterval = 20_000; Bandwidth limit = null;
if (rateMap != null) { Map<String, Object> rateLimitingSection = ResourceConfigUtils.getAsMapOrNull(arguments.get("rate-limiting"), "rate-limiting");
maxRequests = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests"); long maxBandwidthUsage = 0L;
resetInterval = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval") * 1000; 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; return INSTANCE;
} }
} }

View File

@@ -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.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler; 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.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.*; 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.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*; 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.CharsetUtil;
import io.netty.util.concurrent.GlobalEventExecutor;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URLEncoder; import java.net.URLEncoder;
@@ -23,28 +32,35 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public class SelfHostHttpServer { public class SelfHostHttpServer {
private static SelfHostHttpServer instance; private static SelfHostHttpServer instance;
private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder() private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
.maximumSize(256) .maximumSize(1024)
.scheduler(Scheduler.systemScheduler()) .scheduler(Scheduler.systemScheduler())
.expireAfterWrite(1, TimeUnit.MINUTES) .expireAfterWrite(1, TimeUnit.MINUTES)
.build(); .build();
private final Cache<String, IpAccessRecord> ipAccessCache = Caffeine.newBuilder() private final Cache<String, Bucket> ipRateLimiters = Caffeine.newBuilder()
.maximumSize(256) .maximumSize(1024)
.scheduler(Scheduler.systemScheduler()) .scheduler(Scheduler.systemScheduler())
.expireAfterWrite(10, TimeUnit.MINUTES) .expireAfterAccess(5, TimeUnit.MINUTES)
.build(); .build();
private final AtomicLong totalRequests = new AtomicLong(); private final AtomicLong totalRequests = new AtomicLong();
private final AtomicLong blockedRequests = new AtomicLong(); private final AtomicLong blockedRequests = new AtomicLong();
private int rateLimit = 1; private Bandwidth limitPerIp = Bandwidth.builder()
private long rateLimitInterval = 1000; .capacity(1)
.refillGreedy(1, Duration.ofSeconds(1))
.initialTokens(1)
.build();
private String ip = "localhost"; private String ip = "localhost";
private int port = -1; private int port = -1;
private String protocol = "http"; private String protocol = "http";
@@ -52,6 +68,12 @@ public class SelfHostHttpServer {
private boolean denyNonMinecraft = true; private boolean denyNonMinecraft = true;
private boolean useToken; 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 byte[] resourcePackBytes;
private String packHash; private String packHash;
private UUID packUUID; private UUID packUUID;
@@ -72,17 +94,25 @@ public class SelfHostHttpServer {
String url, String url,
boolean denyNonMinecraft, boolean denyNonMinecraft,
String protocol, String protocol,
int maxRequests, Bandwidth limitPerIp,
int resetInterval, boolean token,
boolean token) { long globalUploadRateLimit,
long minDownloadSpeed) {
this.ip = ip; this.ip = ip;
this.url = url; this.url = url;
this.denyNonMinecraft = denyNonMinecraft; this.denyNonMinecraft = denyNonMinecraft;
this.protocol = protocol; this.protocol = protocol;
this.rateLimit = maxRequests; this.limitPerIp = limitPerIp;
this.rateLimitInterval = resetInterval;
this.useToken = token; 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) { if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port: " + port); throw new IllegalArgumentException("Invalid port: " + port);
} }
@@ -104,7 +134,17 @@ public class SelfHostHttpServer {
private void initializeServer() { private void initializeServer() {
bossGroup = new NioEventLoopGroup(1); bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(); 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(); ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) .channel(NioServerSocketChannel.class)
@@ -112,7 +152,9 @@ public class SelfHostHttpServer {
@Override @Override
protected void initChannel(SocketChannel ch) { protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline(); ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("trafficShaping", trafficShapingHandler);
pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(1048576)); pipeline.addLast(new HttpObjectAggregator(1048576));
pipeline.addLast(new RequestHandler()); pipeline.addLast(new RequestHandler());
} }
@@ -128,6 +170,17 @@ public class SelfHostHttpServer {
@ChannelHandler.Sharable @ChannelHandler.Sharable
private class RequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private class RequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
// 有人走了,其他人的速度上限提高
if (activeDownloadChannels.contains(ctx.channel())) {
activeDownloadChannels.remove(ctx.channel());
rebalanceBandwidth();
}
}
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
totalRequests.incrementAndGet(); totalRequests.incrementAndGet();
@@ -136,7 +189,7 @@ public class SelfHostHttpServer {
String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress()) String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress())
.getAddress().getHostAddress(); .getAddress().getHostAddress();
if (checkRateLimit(clientIp)) { if (!checkIpRateLimit(clientIp)) {
sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded"); sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded");
blockedRequests.incrementAndGet(); blockedRequests.incrementAndGet();
return; return;
@@ -159,6 +212,7 @@ public class SelfHostHttpServer {
} }
private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) { private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) {
// 使用一次性token
if (useToken) { if (useToken) {
String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null); String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null);
if (!validateToken(token)) { if (!validateToken(token)) {
@@ -168,6 +222,7 @@ public class SelfHostHttpServer {
} }
} }
// 不是Minecraft客户端
if (denyNonMinecraft) { if (denyNonMinecraft) {
String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT); String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT);
if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) {
@@ -177,22 +232,47 @@ public class SelfHostHttpServer {
} }
} }
// 没有资源包
if (resourcePackBytes == null) { if (resourcePackBytes == null) {
sendError(ctx, HttpResponseStatus.NOT_FOUND, "Resource pack missing"); sendError(ctx, HttpResponseStatus.NOT_FOUND, "Resource pack missing");
blockedRequests.incrementAndGet(); blockedRequests.incrementAndGet();
return; return;
} }
FullHttpResponse response = new DefaultFullHttpResponse( // 新人来了,所有人的速度上限降低
HttpVersion.HTTP_1_1, if (!activeDownloadChannels.contains(ctx.channel())) {
HttpResponseStatus.OK, activeDownloadChannels.add(ctx.channel());
Unpooled.wrappedBuffer(resourcePackBytes) rebalanceBandwidth();
); }
response.headers()
.set(HttpHeaderNames.CONTENT_TYPE, "application/zip")
.set(HttpHeaderNames.CONTENT_LENGTH, resourcePackBytes.length);
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) { private void handleMetrics(ChannelHandlerContext ctx) {
@@ -213,23 +293,11 @@ public class SelfHostHttpServer {
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} }
private boolean checkRateLimit(String clientIp) { private boolean checkIpRateLimit(String clientIp) {
IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); if (limitPerIp == null) return true;
long now = System.currentTimeMillis(); Bucket rateLimiter = ipRateLimiters.get(clientIp, k -> Bucket.builder().addLimit(limitPerIp).build());
assert rateLimiter != null;
if (record == null) { return rateLimiter.tryConsume(1);
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 validateToken(String token) { 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 @Nullable
public ResourcePackDownloadData generateOneTimeUrl() { public ResourcePackDownloadData generateOneTimeUrl() {
if (this.resourcePackBytes == null) return null; if (this.resourcePackBytes == null) return null;
@@ -275,6 +365,17 @@ public class SelfHostHttpServer {
} }
public void disable() { public void disable() {
// 释放流量整形资源
if (trafficShapingHandler != null) {
trafficShapingHandler.release();
trafficShapingHandler = null;
}
// 关闭专用线程池
if (virtualTrafficExecutor != null) {
virtualTrafficExecutor.shutdown();
virtualTrafficExecutor = null;
}
activeDownloadChannels.close();
if (serverChannel != null) { if (serverChannel != null) {
serverChannel.close().awaitUninterruptibly(); serverChannel.close().awaitUninterruptibly();
bossGroup.shutdownGracefully(); bossGroup.shutdownGracefully();
@@ -312,14 +413,4 @@ public class SelfHostHttpServer {
CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e); 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;
}
}
} }

View File

@@ -267,8 +267,11 @@ public abstract class CraftEngine implements Plugin {
this.vanillaLootManager.delayedInit(); this.vanillaLootManager.delayedInit();
// 注册脱离坐骑监听器 // 注册脱离坐骑监听器
this.seatManager.delayedInit(); this.seatManager.delayedInit();
// 注册世界加载相关监听器
this.worldManager.delayedInit(); if (!Config.delayConfigurationLoad()) {
// 注册世界加载相关监听器
this.worldManager.delayedInit();
}
// 延迟任务 // 延迟任务
this.beforeEnableTaskRegistry.executeTasks(); this.beforeEnableTaskRegistry.executeTasks();
@@ -310,6 +313,7 @@ public abstract class CraftEngine implements Plugin {
} else { } else {
try { try {
this.reloadPlugin(Runnable::run, Runnable::run, true); this.reloadPlugin(Runnable::run, Runnable::run, true);
this.worldManager.delayedInit();
} catch (Exception e) { } catch (Exception e) {
this.logger.severe("Failed to reload plugin on delayed enable stage", 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.LZ4,
Dependencies.EVALEX, Dependencies.EVALEX,
Dependencies.NETTY_HTTP, Dependencies.NETTY_HTTP,
Dependencies.JIMFS Dependencies.JIMFS,
Dependencies.BUCKET_4_J
); );
} }

View File

@@ -138,6 +138,8 @@ public class Config {
protected ColliderType furniture$collision_entity_type; protected ColliderType furniture$collision_entity_type;
protected boolean block$sound_system$enable; 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_break_check;
protected boolean block$simplify_adventure_place_check; protected boolean block$simplify_adventure_place_check;
protected boolean block$predict_breaking; protected boolean block$predict_breaking;
@@ -475,6 +477,8 @@ public class Config {
// block // block
block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); 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_break_check = config.getBoolean("block.simplify-adventure-break-check", false);
block$simplify_adventure_place_check = config.getBoolean("block.simplify-adventure-place-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); block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true);
@@ -675,6 +679,14 @@ public class Config {
return instance.block$sound_system$enable; 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() { public static boolean simplifyAdventureBreakCheck() {
return instance.block$simplify_adventure_break_check; return instance.block$simplify_adventure_break_check;
} }

View File

@@ -52,11 +52,11 @@ public class TemplateManagerImpl implements TemplateManager {
@Override @Override
public void parseObject(Pack pack, Path path, String node, Key id, Object obj) { 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"); throw new LocalizedResourceConfigException("warning.config.template.duplicate");
} }
// 预处理会将 string类型的键或值解析为ArgumentString以加速模板应用。所以处理后不可能存在String类型。 // 预处理会将 string类型的键或值解析为ArgumentString以加速模板应用。所以处理后不可能存在String类型。
templates.put(id, preprocessUnknownValue(obj)); TemplateManagerImpl.this.templates.put(id, preprocessUnknownValue(obj));
} }
} }

View File

@@ -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.Context;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; 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.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -42,7 +43,7 @@ public class HandCondition<CTX extends Context> implements Condition<CTX> {
try { try {
return new HandCondition<>(InteractionHand.valueOf(hand.toUpperCase(Locale.ENGLISH))); return new HandCondition<>(InteractionHand.valueOf(hand.toUpperCase(Locale.ENGLISH)));
} catch (IllegalArgumentException e) { } 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()));
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.plugin.context.function; 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.BlockStateWrapper;
import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.plugin.context.Condition; 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.Key;
import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils; 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.World;
import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -20,15 +21,28 @@ import java.util.Optional;
public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> { public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> {
private final String property; private final String property;
@Nullable
private final Map<String, String> rules;
@Nullable
private final NumberProvider inverse; private final NumberProvider inverse;
private final NumberProvider x; private final NumberProvider x;
private final NumberProvider y; private final NumberProvider y;
private final NumberProvider z; private final NumberProvider z;
private final NumberProvider updateFlags; private final NumberProvider updateFlags;
public CycleBlockPropertyFunction(List<Condition<CTX>> predicates, String property, NumberProvider inverse, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags) { public CycleBlockPropertyFunction(
List<Condition<CTX>> predicates,
String property,
@Nullable Map<String, String> rules,
@Nullable NumberProvider inverse,
NumberProvider x,
NumberProvider y,
NumberProvider z,
NumberProvider updateFlags
) {
super(predicates); super(predicates);
this.property = property; this.property = property;
this.rules = rules;
this.inverse = inverse; this.inverse = inverse;
this.x = x; this.x = x;
this.y = y; this.y = y;
@@ -44,11 +58,26 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); int x = MiscUtils.fastFloor(this.x.getDouble(ctx));
int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); int y = MiscUtils.fastFloor(this.y.getDouble(ctx));
int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); int z = MiscUtils.fastFloor(this.z.getDouble(ctx));
ExistingBlock blockAt = world.getBlock(x, y, z); BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx);
BlockStateWrapper wrapper = blockAt.blockState().cycleProperty(this.property, this.inverse.getInt(ctx) == 0);
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(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 @Override
public Key type() { public Key type() {
return CommonFunctions.CYCLE_BLOCK_PROPERTY; return CommonFunctions.CYCLE_BLOCK_PROPERTY;
@@ -62,8 +91,15 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
@Override @Override
public Function<CTX> create(Map<String, Object> arguments) { public Function<CTX> create(Map<String, Object> arguments) {
String property = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("property"), "warning.config.function.cycle_block_property.missing_property");
Map<String, String> 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), 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", "<arg:player.is_sneaking>")), NumberProviders.fromObject(arguments.getOrDefault("inverse", "<arg:player.is_sneaking>")),
NumberProviders.fromObject(arguments.getOrDefault("x", "<arg:position.x>")), NumberProviders.fromObject(arguments.getOrDefault("x", "<arg:position.x>")),
NumberProviders.fromObject(arguments.getOrDefault("y", "<arg:position.y>")), NumberProviders.fromObject(arguments.getOrDefault("y", "<arg:position.y>")),

View File

@@ -372,6 +372,13 @@ public class Dependencies {
List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) 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( public static final Dependency NETTY_HTTP = new Dependency(
"netty-codec-http", "netty-codec-http",
"io{}netty", "io{}netty",

View File

@@ -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);
}
}
}

View File

@@ -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_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_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_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");
} }

View File

@@ -37,7 +37,7 @@ public class TranslationManagerImpl implements TranslationManager {
private final Set<Locale> installed = ConcurrentHashMap.newKeySet(); private final Set<Locale> installed = ConcurrentHashMap.newKeySet();
private final Path translationsDirectory; private final Path translationsDirectory;
private final String langVersion; private final String langVersion;
private final String[] supportedLanguages; private final Set<String> supportedLanguages;
private final Map<String, String> translationFallback = new LinkedHashMap<>(); private final Map<String, String> translationFallback = new LinkedHashMap<>();
private Locale selectedLocale = DEFAULT_LOCALE; private Locale selectedLocale = DEFAULT_LOCALE;
private MiniMessageTranslationRegistry registry; private MiniMessageTranslationRegistry registry;
@@ -52,7 +52,7 @@ public class TranslationManagerImpl implements TranslationManager {
this.plugin = plugin; this.plugin = plugin;
this.translationsDirectory = this.plugin.dataFolderPath().resolve("translations"); this.translationsDirectory = this.plugin.dataFolderPath().resolve("translations");
this.langVersion = PluginProperties.getValue("lang-version"); 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.langParser = new LangParser();
this.translationParser = new TranslationParser(); this.translationParser = new TranslationParser();
Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions()));
@@ -201,7 +201,7 @@ public class TranslationManagerImpl implements TranslationManager {
Map<String, String> data = yaml.load(inputStream); Map<String, String> data = yaml.load(inputStream);
if (data == null) return FileVisitResult.CONTINUE; if (data == null) return FileVisitResult.CONTINUE;
String langVersion = data.getOrDefault("lang-version", ""); 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); data = updateLangFile(data, path);
} }
cachedFile = new CachedTranslation(data, lastModifiedTime, size); cachedFile = new CachedTranslation(data, lastModifiedTime, size);

View File

@@ -0,0 +1,4 @@
package net.momirealms.craftengine.core.util;
public class CustomDataType<T> {
}

View File

@@ -6,6 +6,8 @@ public interface LazyReference<T> {
T get(); T get();
boolean initialized();
static <T> LazyReference<T> lazyReference(final Supplier<T> supplier) { static <T> LazyReference<T> lazyReference(final Supplier<T> supplier) {
return new LazyReference<>() { return new LazyReference<>() {
private T value; private T value;
@@ -17,6 +19,11 @@ public interface LazyReference<T> {
} }
return this.value; return this.value;
} }
@Override
public boolean initialized() {
return this.value != null;
}
}; };
} }
} }

View File

@@ -131,7 +131,7 @@ public class PngOptimizer {
private byte[] tryNormal(BufferedImage src, boolean hasAlpha, boolean isGrayscale) throws IOException { private byte[] tryNormal(BufferedImage src, boolean hasAlpha, boolean isGrayscale) throws IOException {
byte[] bytes = generatePngData(src, hasAlpha, isGrayscale); 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); return zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes);
} }
@@ -177,7 +177,7 @@ public class PngOptimizer {
writeChunkPLTE(paletteOs, palette); writeChunkPLTE(paletteOs, palette);
} }
byte[] bytes = generatePaletteData(src, 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)); paletteOs.write(zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes));
return Pair.of(palette, paletteOs.toByteArray()); return Pair.of(palette, paletteOs.toByteArray());
} }

View File

@@ -134,7 +134,7 @@ public final class ResourceConfigUtils {
} }
case String s -> { case String s -> {
try { try {
return Integer.parseInt(s); return Integer.parseInt(s.replace("_", ""));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.type.int", e, s, option); 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") @SuppressWarnings("unchecked")
public static Map<String, Object> getAsMap(Object obj, String option) { public static Map<String, Object> getAsMap(Object obj, String option) {
if (obj instanceof Map<?, ?> map) { if (obj instanceof Map<?, ?> map) {

View File

@@ -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 +
'}';
}
}

View File

@@ -72,7 +72,7 @@ public class Vec3d implements Position {
public final boolean equals(Object o) { public final boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (!(o instanceof Vec3d vec3d)) return false; 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 @Override

View File

@@ -1,9 +1,9 @@
org.gradle.jvmargs=-Xmx1G org.gradle.jvmargs=-Xmx1G
# Project settings # Project settings
project_version=0.0.65.8 project_version=0.0.65.12.2
config_version=55 config_version=58
lang_version=38 lang_version=40
project_group=net.momirealms project_group=net.momirealms
latest_supported_version=1.21.10 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 cloud_minecraft_extras_version=2.0.0-beta.13
boosted_yaml_version=1.3.7 boosted_yaml_version=1.3.7
bstats_version=3.1.0 bstats_version=3.1.0
caffeine_version=3.2.2 caffeine_version=3.2.3
placeholder_api_version=2.11.6 placeholder_api_version=2.11.7
vault_version=1.7 vault_version=1.7
guava_version=33.5.0-jre guava_version=33.5.0-jre
lz4_version=1.8.0 lz4_version=1.8.0
geantyref_version=1.3.16 geantyref_version=1.3.16
zstd_version=1.5.7-4 zstd_version=1.5.7-6
commons_io_version=2.20.0 commons_io_version=2.21.0
commons_lang3_version=3.19.0 commons_lang3_version=3.20.0
sparrow_nbt_version=0.10.6 sparrow_nbt_version=0.10.6
sparrow_util_version=0.60 sparrow_util_version=0.65
fastutil_version=8.5.18 fastutil_version=8.5.18
netty_version=4.1.127.Final netty_version=4.1.128.Final
joml_version=1.10.8 joml_version=1.10.8
datafixerupper_version=8.0.16 datafixerupper_version=8.0.16
mojang_brigadier_version=1.0.18 mojang_brigadier_version=1.0.18
byte_buddy_version=1.17.8 byte_buddy_version=1.18.1
ahocorasick_version=0.6.3 ahocorasick_version=0.6.3
snake_yaml_version=2.5 snake_yaml_version=2.5
anti_grief_version=1.0.4 anti_grief_version=1.0.5
nms_helper_version=1.0.134 nms_helper_version=1.0.137
evalex_version=3.5.0 evalex_version=3.5.0
reactive_streams_version=1.0.4 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 amazon_awssdk_eventstream_version=1.0.1
jimfs_version=1.3.1 jimfs_version=1.3.1
authlib_version=7.0.60 authlib_version=7.0.60
concurrent_util_version=0.0.3 concurrent_util_version=0.0.3
bucket4j_version=8.15.0
# Proxy settings # Proxy settings
#systemProp.socks.proxyHost=127.0.0.1 #systemProp.socks.proxyHost=127.0.0.1