9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +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
!/gradle/wrapper/*.jar
.vscode/settings.json
/runPaper/

View File

@@ -111,12 +111,21 @@ tasks {
publishing {
repositories {
maven {
name = "releases"
url = uri("https://repo.momirealms.net/releases")
credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD")
}
}
maven {
name = "snapshot"
url = uri("https://repo.momirealms.net/snapshots")
credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD")
}
}
}
publications {
create<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) {
new ImageExpansion(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.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j")
}
}

View File

@@ -1,8 +1,12 @@
import net.minecrell.pluginyml.paper.PaperPluginDescription
import xyz.jpenilla.runpaper.task.RunServer
import xyz.jpenilla.runtask.pluginsapi.DownloadPluginsSpec
import java.net.URI
plugins {
id("com.gradleup.shadow") version "9.2.2"
id("de.eldoria.plugin-yml.paper") version "0.7.1"
id("xyz.jpenilla.run-paper") version "3.0.2"
}
repositories {
@@ -168,5 +172,62 @@ tasks {
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j")
}
}
/**
* Register Run Dev Server Tasks
*/
listOf(
"1.21.10",
"1.21.8",
"1.21.5",
"1.21.4",
"1.21.2",
"1.21.1",
"1.20.6",
"1.20.4",
"1.20.2",
"1.20.1",
).forEach {
registerPaperTask(it)
}
fun registerPaperTask(
version: String,
dirName: String = version,
javaVersion : Int = 21,
serverJar: File? = null,
downloadPlugins: Action<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) {
org.bukkit.block.Block block = event.getBlock();
Object blockState = BlockStateUtils.getBlockState(block);
@@ -109,7 +109,7 @@ public final class BlockEventListener implements Listener {
WorldPosition position = new WorldPosition(world, location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5);
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND);
if (!ItemUtils.isEmpty(itemInHand)) {
if (!event.isCancelled() && !ItemUtils.isEmpty(itemInHand)) {
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isPresent()) {
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
@@ -129,41 +129,49 @@ public final class BlockEventListener implements Listener {
}
if (!BlockStateUtils.isVanillaBlock(stateId)) {
ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId);
ImmutableBlockState state = this.manager.getImmutableBlockStateUnsafe(stateId);
if (!state.isEmpty()) {
// double check adventure mode to prevent dupe
if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) {
return;
}
if (!event.isCancelled()) {
// double check adventure mode to prevent dupe
if (!FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) {
return;
}
// trigger api event
CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state);
boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent);
if (isCancelled) {
event.setCancelled(true);
return;
}
// trigger api event
CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(serverPlayer, location, block, state);
boolean isCancelled = EventUtils.fireAndCheckCancel(customBreakEvent);
if (isCancelled) {
event.setCancelled(true);
return;
}
// execute functions
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block))
.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state)
.withParameter(DirectContextParameters.EVENT, cancellable)
.withParameter(DirectContextParameters.POSITION, position)
.withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand)
);
state.owner().value().execute(context, EventTrigger.BREAK);
if (cancellable.isCancelled()) {
return;
}
// execute functions
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block))
.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state)
.withParameter(DirectContextParameters.EVENT, cancellable)
.withParameter(DirectContextParameters.POSITION, position)
.withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, ItemUtils.isEmpty(itemInHand) ? null : itemInHand)
);
state.owner().value().execute(context, EventTrigger.BREAK);
if (cancellable.isCancelled()) {
return;
}
// play sound
serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK);
// play sound
serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK);
}
// Restore sounds in cancelled events
else {
if (Config.processCancelledBreak()) {
serverPlayer.playSound(position, state.settings().sounds().breakSound(), SoundSource.BLOCK);
}
}
}
} else {
// override vanilla block loots
if (player.getGameMode() != GameMode.CREATIVE) {
if (!event.isCancelled() && player.getGameMode() != GameMode.CREATIVE) {
this.plugin.vanillaLootManager().getBlockLoot(stateId).ifPresent(it -> {
if (!event.isDropItems()) {
return;
@@ -185,7 +193,7 @@ public final class BlockEventListener implements Listener {
});
}
// sound system
if (Config.enableSoundSystem()) {
if (Config.enableSoundSystem() && (!event.isCancelled() || Config.processCancelledBreak())) {
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$breakSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
@@ -223,7 +231,7 @@ public final class BlockEventListener implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
@EventHandler(priority = EventPriority.LOW)
public void onStep(GenericGameEvent event) {
if (event.getEvent() != GameEvent.STEP) return;
Entity entity = event.getEntity();
@@ -242,11 +250,14 @@ public final class BlockEventListener implements Listener {
.withParameter(DirectContextParameters.BLOCK, new BukkitExistingBlock(block))
.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state)
), EventTrigger.STEP);
if (cancellable.isCancelled()) {
if (cancellable.isCancelled() && !Config.processCancelledStep()) {
return;
}
player.playSound(location, state.settings().sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.settings().sounds().stepSound().volume().get(), state.settings().sounds().stepSound().pitch().get());
} else if (Config.enableSoundSystem()) {
if (event.isCancelled() && !Config.processCancelledStep()) {
return;
}
Object soundType = FastNMS.INSTANCE.method$BlockBehaviour$BlockStateBase$getSoundType(blockState);
Object soundEvent = FastNMS.INSTANCE.field$SoundType$stepSound(soundType);
Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);

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

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.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.HorizontalDirection;
@@ -20,7 +21,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class AttachedStemBlockBehavior extends BukkitBlockBehavior {
public class AttachedStemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty;
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 SURFACE_SPREADING_BLOCK = Key.from("craftengine:surface_spreading_block");
public static final Key SNOWY_BLOCK = Key.from("craftengine:snowy_block");
public static final Key HANGABLE_BLOCK = Key.from("craftengine:hangable_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -90,5 +91,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SEAT_BLOCK, SeatBlockBehavior.FACTORY);
register(SURFACE_SPREADING_BLOCK, SurfaceSpreadingBlockBehavior.FACTORY);
register(SNOWY_BLOCK, SnowyBlockBehavior.FACTORY);
register(HANGABLE_BLOCK, HangableBlockBehavior.FACTORY);
}
}

View File

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

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.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -121,14 +122,15 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
}
@Override
public void performBoneMeal(Object thisBlock, Object[] args) throws Exception {
public void performBoneMeal(Object thisBlock, Object[] args) {
this.performBoneMeal(args[0], args[2], args[3]);
}
@Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode())
Player player = context.getPlayer();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
return InteractionResult.PASS;
if (isMaxAge(state))
return InteractionResult.PASS;
@@ -144,7 +146,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
sendSwing = true;
}
if (sendSwing) {
context.getPlayer().swingHand(context.getHand());
player.swingHand(context.getHand());
}
return InteractionResult.SUCCESS;
}

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.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.properties.type.DoorHinge;
import net.momirealms.craftengine.core.block.properties.type.DoubleBlockHalf;
@@ -46,7 +47,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode")
public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior {
public class DoorBlockBehavior extends AbstractCanSurviveBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<DoubleBlockHalf> halfProperty;
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.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.BooleanProperty;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -30,7 +31,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class FenceBlockBehavior extends BukkitBlockBehavior {
public class FenceBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final BooleanProperty northProperty;
private final BooleanProperty eastProperty;

View File

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -40,7 +41,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode")
public class FenceGateBlockBehavior extends BukkitBlockBehavior {
public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty;
private final Property<Boolean> inWallProperty;
@@ -144,6 +145,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked")
private void playerToggle(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
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.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -83,7 +84,8 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
@Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode())
Player player = context.getPlayer();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
return InteractionResult.PASS;
BlockPos pos = context.getClickedPos();
BukkitExistingBlock upper = (BukkitExistingBlock) context.getLevel().getBlock(pos.x(), pos.y() + 1, pos.z());
@@ -102,7 +104,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
sendSwing = true;
}
if (sendSwing) {
context.getPlayer().swingHand(context.getHand());
player.swingHand(context.getHand());
}
return InteractionResult.SUCCESS;
}

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) {
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");
int actual = distance.possibleValues().get(distance.possibleValues().size() - 1);
int actual = distance.possibleValues().getLast();
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.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -148,7 +149,8 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
@Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Item<?> item = context.getItem();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode())
Player player = context.getPlayer();
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
return InteractionResult.PASS;
boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject();
@@ -162,7 +164,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
sendSwing = true;
}
if (sendSwing) {
context.getPlayer().swingHand(context.getHand());
player.swingHand(context.getHand());
}
return InteractionResult.SUCCESS;
}

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

View File

@@ -46,7 +46,7 @@ public class StackableBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked")
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer();
if (player.isSecondaryUseActive()) {
if (player == null || player.isSecondaryUseActive()) {
return InteractionResult.PASS;
}
Item<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.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.*;
@@ -21,7 +22,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class StemBlockBehavior extends BukkitBlockBehavior {
public class StemBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final IntegerProperty ageProperty;
private final Key fruit;

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.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.properties.type.SingleBlockHalf;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -40,7 +41,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
@SuppressWarnings("DuplicatedCode")
public class TrapDoorBlockBehavior extends BukkitBlockBehavior {
public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<SingleBlockHalf> halfProperty;
private final Property<HorizontalDirection> facingProperty;
@@ -117,6 +118,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior {
@SuppressWarnings("unchecked")
private void playerToggle(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer();
if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
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.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.FallOnBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.PlaceLiquidBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.*;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
@@ -18,7 +15,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior {
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior, IsPathFindableBlockBehavior {
private final AbstractBlockBehavior[] behaviors;
public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List<AbstractBlockBehavior> behaviors) {
@@ -237,12 +234,18 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
@Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
boolean processed = false;
for (AbstractBlockBehavior behavior : this.behaviors) {
if (!behavior.isPathFindable(thisBlock, args, superMethod)) {
return false;
if (behavior instanceof IsPathFindableBlockBehavior pathFindableBlockBehavior) {
if (!pathFindableBlockBehavior.isPathFindable(thisBlock, args, superMethod)) {
return false;
} else {
processed = true;
}
}
}
return (boolean) superMethod.call();
if (!processed) return (boolean) superMethod.call();
return true;
}
@Override

View File

@@ -84,7 +84,15 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null;
}
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || !previous.config.position.equals(this.position));
Vector3f translation = previous.config.translation;
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
return null;
}
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId,
previous.config.yRot != this.yRot ||
previous.config.xRot != this.xRot ||
!previous.config.position.equals(this.position)
);
}
@Override

View File

@@ -72,7 +72,15 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
return null;
}
return new TextDisplayBlockEntityElement(this, pos, previous.entityId, previous.config.yRot != this.yRot || previous.config.xRot != this.xRot || !previous.config.position.equals(this.position));
Vector3f translation = previous.config.translation;
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
return null;
}
return new TextDisplayBlockEntityElement(this, pos, previous.entityId,
previous.config.yRot != this.yRot ||
previous.config.xRot != this.xRot ||
!previous.config.position.equals(this.position)
);
}
@Override

View File

@@ -115,7 +115,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
@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();
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.Tag;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -119,7 +120,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler<ItemSt
}
@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;
// 处理收纳袋

View File

@@ -19,6 +19,7 @@ import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.StringTag;
import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -110,7 +111,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler<ItemSt
}
@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;
// 处理收纳袋

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

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));
}
@Override
public InteractionResult place(BlockPlaceContext context) {
if (context.getClickedFace().stepY() != 0) {
return InteractionResult.PASS;

View File

@@ -33,6 +33,7 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new ReloadCommand(this, plugin),
new GetItemCommand(this, plugin),
new GiveItemCommand(this, plugin),
new ClearItemCommand(this, plugin),
new ItemBrowserPlayerCommand(this, plugin),
new ItemBrowserAdminCommand(this, plugin),
new SearchRecipePlayerCommand(this, plugin),
@@ -63,8 +64,9 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new SendResourcePackCommand(this, plugin),
new DebugSaveDefaultResourcesCommand(this, plugin),
new DebugCleanCacheCommand(this, plugin),
new DebugGenerateInternalAssetsCommand(this, plugin)
// new OverrideGiveCommand(this, plugin)
new DebugGenerateInternalAssetsCommand(this, plugin),
new DebugCustomModelDataCommand(this, plugin),
new DebugImageCommand(this, plugin)
));
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
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
public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) {
if (Config.disableItemOperations()) return;
BukkitServerPlayer player = (BukkitServerPlayer) user;
if (!player.isOnline()) return;
MutableBoolean changed = new MutableBoolean(false);
FriendlyByteBuf buf = event.getBuffer();
BukkitItemManager itemManager = BukkitItemManager.instance();
BukkitServerPlayer player = (BukkitServerPlayer) user;
Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf.source());
List<RecipeBookEntry<ItemStack>> entries = buf.readCollection(ArrayList::new, byteBuf -> {
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());
}
@NotNull
@Override
public Item<ItemStack> getItemBySlot(int slot) {
PlayerInventory inventory = platformPlayer().getInventory();
return BukkitItemManager.instance().wrap(inventory.getItem(slot));
}
@Override
public World world() {
return new BukkitWorld(platformPlayer().getWorld());

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.bukkit.sound;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
@@ -9,13 +11,15 @@ import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.sound.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.JukeboxSong;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class BukkitSoundManager extends AbstractSoundManager {
@@ -25,6 +29,65 @@ public class BukkitSoundManager extends AbstractSoundManager {
Object resourceLocation = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent);
VANILLA_SOUND_EVENTS.add(KeyUtils.resourceLocationToKey(resourceLocation));
}
this.registerSongs(this.loadLastRegisteredSongs());
}
@Override
public void disable() {
this.saveLastRegisteredSongs(super.songs);
super.disable();
}
private void saveLastRegisteredSongs(Map<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

View File

@@ -51,4 +51,8 @@ public final class ItemStackUtils {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
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
- /ce item give
clear_item:
enable: true
permission: ce.command.admin.clear_item
usage:
- /craftengine item clear
- /ce item clear
item_browser_player:
enable: true
permission: ce.command.player.item_browser
@@ -230,6 +237,20 @@ debug_clean_cache:
- /craftengine debug clean-cache
- /ce debug clean-cache
debug_custom_model_data:
enable: true
permission: ce.command.debug.custom_model_data
usage:
- /craftengine debug custom-model-data
- /ce debug custom-model-data
debug_image:
enable: true
permission: ce.command.debug.image
usage:
- /craftengine debug image
- /ce debug image
debug_generate_internal_assets:
enable: false
permission: ce.command.debug.generate_internal_assets

View File

@@ -193,11 +193,17 @@ resource-pack:
ip: "localhost"
port: 8163
protocol: "http"
# Blocks all requests from non-Minecraft clients.
deny-non-minecraft-request: true
# Generates a single-use, time-limited download link for each player.
one-time-token: true
rate-limit:
max-requests: 10
reset-interval: 30
rate-limiting:
# Maximum bandwidth per second to prevent server instability for other players during resource pack downloads
max-bandwidth-per-second: 5_000_000 # 5MB/s
# Minimum guaranteed download speed per player to ensure acceptable download performance during concurrent downloads
min-download-speed-per-player: 50_000 # 50KB/s
# Prevent a single IP from sending too many resource pack download requests in a short time period
qps-per-ip: 5/60 # 5 requests per 60 seconds
item:
# [Premium Exclusive]
@@ -261,6 +267,10 @@ block:
# Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience.
sound-system:
enable: true
# Should we process events that were canceled by other plugins to restore sounds?
process-cancelled-events:
step: true
break: true
# Adventure mode requires correct tools to break custom blocks.
# Vanilla clients cannot recognize custom block IDs (e.g., craftengine:custom_100).
#
@@ -542,6 +552,12 @@ chunk-system:
remove: []
convert: {}
#client-optimization:
# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure.
# entity-culling:
# enable: false
# whitelist-entities: []
# Enables or disables debug mode
debug:
common: false

View File

@@ -35,3 +35,4 @@ amazon-sdk-s3=${amazon_awssdk_version}
amazon-sdk-eventstream=${amazon_awssdk_eventstream_version}
evalex=${evalex_version}
jimfs=${jimfs_version}
bucket4j=${bucket4j_version}

View File

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

View File

@@ -127,6 +127,70 @@ translations:
emoji.tip: 使用<yellow>'<arg:keyword>'</yellow>来发送表情'<arg:emoji>'
emoji.time: '<bold>当前时间: <papi:player_world_time_12></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).
# Some other plugins support displaying block names using lang components.
# This might be useful for the client-side, but it's not mandatory.
@@ -203,3 +267,39 @@ lang:
block_name:default:hami_melon_stem: 哈密瓜茎
block_name:default:attached_hami_melon_stem: 哈密瓜茎
block_name:default:magma_plant: 岩浆植物
fr_fr:
block_name:default:chinese_lantern: Lanterne Chinoise
block_name:default:netherite_anvil: Enclume en Netherite
block_name:default:topaz_ore: Minerai de Topaze
block_name:default:deepslate_topaz_ore: Minerai de Topaze en Schiste Sombre
block_name:default:palm_log: Bois de Palmier
block_name:default:stripped_palm_log: Bois de Palmier Écorcé
block_name:default:palm_wood: Bois de Palmier
block_name:default:stripped_palm_wood: Bois de Palmier Écorcé
block_name:default:palm_planks: Planches de Palmier
block_name:default:palm_sapling: Pousse de Palmier
block_name:default:palm_leaves: Feuilles de Palmier
block_name:default:palm_trapdoor: Trappe en Palmier
block_name:default:palm_door: Porte en Palmier
block_name:default:palm_fence_gate: Portail de Barrière en Palmier
block_name:default:palm_slab: Dalle en Palmier
block_name:default:palm_stairs: Escalier en Palmier
block_name:default:palm_button: Bouton en Palmier
block_name:default:palm_fence: Barrière en Palmier
block_name:default:fairy_flower: Fleur Fée
block_name:default:reed: Roseau
block_name:default:flame_cane: Canne de Flamme
block_name:default:ender_pearl_flower: Fleur de Perle du Néant
block_name:default:gunpowder_block: Bloc de Poudre à Canon
block_name:default:solid_gunpowder_block: Bloc de Poudre à Canon Solide
block_name:default:copper_coil: Bobine de Cuivre
block_name:default:chessboard_block: Bloc Damier
block_name:default:safe_block: Coffre-Fort
block_name:default:sleeper_sofa: Canapé
block_name:default:sofa: Canapé
block_name:default:amethyst_torch: Torche en Améthyste
block_name:default:amethyst_wall_torch: Torche en Améthyste
block_name:default:hami_melon: Melon de Hami
block_name:default:hami_melon_stem: Tige de Melon de Hami
block_name:default:attached_hami_melon_stem: Tige de Melon de Hami
block_name:default:magma_plant: Plante de Magma

View File

@@ -842,6 +842,7 @@ block-state-mappings:
#### Leaves ####
# The 'distance' and 'persistent' properties are used under the hood to optimize how leaves decay, but visually? They look exactly the same.
# These are some of the few block types that actually support transparent textures.
# tintable leaves
oak_leaves[distance=1,persistent=true,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true]
oak_leaves[distance=1,persistent=true,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false]
oak_leaves[distance=1,persistent=false,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true]
@@ -868,58 +869,6 @@ block-state-mappings:
oak_leaves[distance=6,persistent=false,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false]
oak_leaves[distance=7,persistent=false,waterlogged=true]: oak_leaves[distance=7,persistent=true,waterlogged=true]
oak_leaves[distance=7,persistent=false,waterlogged=false]: oak_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=1,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=1,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=1,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=1,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=2,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=2,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=2,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=2,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=3,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=3,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=3,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=3,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=4,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=4,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=4,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=4,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=5,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=5,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=5,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=5,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=6,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=6,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=6,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=6,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
spruce_leaves[distance=7,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
spruce_leaves[distance=7,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=1,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=1,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=1,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=1,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=2,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=2,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=2,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=2,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=3,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=3,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=3,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=3,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=4,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=4,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=4,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=4,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=5,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=5,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=5,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=5,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=6,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=6,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=6,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=6,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
birch_leaves[distance=7,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
birch_leaves[distance=7,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
jungle_leaves[distance=1,persistent=true,waterlogged=true]: jungle_leaves[distance=7,persistent=true,waterlogged=true]
jungle_leaves[distance=1,persistent=true,waterlogged=false]: jungle_leaves[distance=7,persistent=true,waterlogged=false]
jungle_leaves[distance=1,persistent=false,waterlogged=true]: jungle_leaves[distance=7,persistent=true,waterlogged=true]
@@ -972,32 +921,6 @@ block-state-mappings:
acacia_leaves[distance=6,persistent=false,waterlogged=false]: acacia_leaves[distance=7,persistent=true,waterlogged=false]
acacia_leaves[distance=7,persistent=false,waterlogged=true]: acacia_leaves[distance=7,persistent=true,waterlogged=true]
acacia_leaves[distance=7,persistent=false,waterlogged=false]: acacia_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=1,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=1,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=1,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=1,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=2,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=2,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=2,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=2,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=3,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=3,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=3,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=3,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=4,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=4,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=4,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=4,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=5,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=5,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=5,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=5,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=6,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=6,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=6,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=6,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=7,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=7,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
dark_oak_leaves[distance=1,persistent=true,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true]
dark_oak_leaves[distance=1,persistent=true,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false]
dark_oak_leaves[distance=1,persistent=false,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true]
@@ -1024,33 +947,6 @@ block-state-mappings:
dark_oak_leaves[distance=6,persistent=false,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false]
dark_oak_leaves[distance=7,persistent=false,waterlogged=true]: dark_oak_leaves[distance=7,persistent=true,waterlogged=true]
dark_oak_leaves[distance=7,persistent=false,waterlogged=false]: dark_oak_leaves[distance=7,persistent=true,waterlogged=false]
$$>=1.21.4#leaves:
pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
mangrove_leaves[distance=1,persistent=true,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true]
mangrove_leaves[distance=1,persistent=true,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false]
mangrove_leaves[distance=1,persistent=false,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true]
@@ -1077,6 +973,7 @@ block-state-mappings:
mangrove_leaves[distance=6,persistent=false,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false]
mangrove_leaves[distance=7,persistent=false,waterlogged=true]: mangrove_leaves[distance=7,persistent=true,waterlogged=true]
mangrove_leaves[distance=7,persistent=false,waterlogged=false]: mangrove_leaves[distance=7,persistent=true,waterlogged=false]
# non tintable leaves
azalea_leaves[distance=1,persistent=true,waterlogged=true]: azalea_leaves[distance=7,persistent=true,waterlogged=true]
azalea_leaves[distance=1,persistent=true,waterlogged=false]: azalea_leaves[distance=7,persistent=true,waterlogged=false]
azalea_leaves[distance=1,persistent=false,waterlogged=true]: azalea_leaves[distance=7,persistent=true,waterlogged=true]
@@ -1129,6 +1026,113 @@ block-state-mappings:
flowering_azalea_leaves[distance=6,persistent=false,waterlogged=false]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false]
flowering_azalea_leaves[distance=7,persistent=false,waterlogged=true]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=true]
flowering_azalea_leaves[distance=7,persistent=false,waterlogged=false]: flowering_azalea_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=1,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=1,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=1,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=1,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=2,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=2,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=2,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=2,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=3,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=3,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=3,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=3,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=4,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=4,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=4,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=4,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=5,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=5,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=5,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=5,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=6,persistent=true,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=6,persistent=true,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=6,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=6,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
cherry_leaves[distance=7,persistent=false,waterlogged=true]: cherry_leaves[distance=7,persistent=true,waterlogged=true]
cherry_leaves[distance=7,persistent=false,waterlogged=false]: cherry_leaves[distance=7,persistent=true,waterlogged=false]
$$>=1.21.4#leaves:
pale_oak_leaves[distance=1,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=1,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=1,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=1,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=2,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=2,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=2,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=2,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=3,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=3,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=3,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=3,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=4,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=4,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=4,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=4,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=5,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=5,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=5,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=5,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=6,persistent=true,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=6,persistent=true,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=6,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=6,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
pale_oak_leaves[distance=7,persistent=false,waterlogged=true]: pale_oak_leaves[distance=7,persistent=true,waterlogged=true]
pale_oak_leaves[distance=7,persistent=false,waterlogged=false]: pale_oak_leaves[distance=7,persistent=true,waterlogged=false]
## Spruce leaves always use color #619961
# spruce_leaves[distance=1,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=1,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=1,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=1,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=2,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=2,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=2,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=2,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=3,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=3,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=3,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=3,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=4,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=4,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=4,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=4,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=5,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=5,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=5,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=5,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=6,persistent=true,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=6,persistent=true,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=6,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=6,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
# spruce_leaves[distance=7,persistent=false,waterlogged=true]: spruce_leaves[distance=7,persistent=true,waterlogged=true]
# spruce_leaves[distance=7,persistent=false,waterlogged=false]: spruce_leaves[distance=7,persistent=true,waterlogged=false]
## Birch leaves always use color #80a755
# birch_leaves[distance=1,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=1,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=1,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=1,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=2,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=2,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=2,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=2,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=3,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=3,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=3,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=3,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=4,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=4,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=4,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=4,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=5,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=5,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=5,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=5,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=6,persistent=true,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=6,persistent=true,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=6,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=6,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
# birch_leaves[distance=7,persistent=false,waterlogged=true]: birch_leaves[distance=7,persistent=true,waterlogged=true]
# birch_leaves[distance=7,persistent=false,waterlogged=false]: birch_leaves[distance=7,persistent=true,waterlogged=false]
#### Tripwire ####
# Tripwires actually have 128 different states, but we're keeping just two of them to match vanilla's visual styles.

View File

@@ -38,3 +38,16 @@ translations:
internal.cooking_info: Rezeptinformationen
internal.cooking_info.0: 'Zeit: <arg:cooking_time> Ticks'
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.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.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_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>"
@@ -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.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.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.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>"
@@ -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.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.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.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>"
@@ -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.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.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.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>"

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.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.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_usage.not_found: "<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.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.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.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>"
@@ -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.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.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.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>"
@@ -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.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.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.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>"

View File

@@ -21,7 +21,7 @@ dependencies {
implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}")
implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}")
// S3
implementation("net.momirealms:craft-engine-s3:0.8")
implementation("net.momirealms:craft-engine-s3:0.9")
// Util
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
// Adventure
@@ -69,6 +69,8 @@ dependencies {
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
// concurrentutil
compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}")
// bucket4j
compileOnly("com.bucket4j:bucket4j_jdk17-core:${rootProject.properties["bucket4j_version"]}")
}
java {
@@ -107,18 +109,28 @@ tasks {
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy")
relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") // bucket4j
}
}
publishing {
repositories {
maven {
name = "releases"
url = uri("https://repo.momirealms.net/releases")
credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD")
}
}
maven {
name = "snapshot"
url = uri("https://repo.momirealms.net/snapshots")
credentials(PasswordCredentials::class) {
username = System.getenv("REPO_USERNAME")
password = System.getenv("REPO_PASSWORD")
}
}
}
publications {
create<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);
if (group == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values()));
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateType, EnumUtils.toString(AutoStateGroup.values()));
}
futureVisualStates.put(appearanceName, this.visualBlockStateAllocator.requestAutoState(autoStateId, group));
} else {
@@ -704,7 +704,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
@NotNull
private Map<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()) {
Property<?> property = Properties.fromMap(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()));
properties.put(entry.getKey(), property);

View File

@@ -10,21 +10,21 @@ import java.util.function.Predicate;
public enum AutoStateGroup {
NON_TINTABLE_LEAVES(List.of("no_tint_leaves", "leaves_no_tint", "non_tintable_leaves"),
Set.of(BlockKeys.SPRUCE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES),
(w) -> !(boolean) w.getProperty("waterlogged")
),
WATERLOGGED_NON_TINTABLE_LEAVES(
List.of("waterlogged_no_tint_leaves", "waterlogged_leaves_no_tint", "waterlogged_non_tintable_leaves"),
Set.of(BlockKeys.SPRUCE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES),
Set.of(BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES),
(w) -> w.getProperty("waterlogged")
),
TINTABLE_LEAVES("tintable_leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES),
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES),
(w) -> !(boolean) w.getProperty("waterlogged")
),
WATERLOGGED_TINTABLE_LEAVES(
"waterlogged_tintable_leaves",
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES),
Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES),
(w) -> w.getProperty("waterlogged")
),
LEAVES("leaves",

View File

@@ -73,7 +73,7 @@ public abstract class BlockBehavior {
superMethod.call();
}
// 1.20+ BlockState state, LevelReader world, BlockPos pos
// BlockState state, LevelReader world, BlockPos pos
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
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")))
.applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color"))
.billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED))
.transform(ResourceConfigUtils.getOrDefault(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
.transform(ResourceConfigUtils.getOrDefault(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))

View File

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

View File

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

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.util.*;
import net.momirealms.sparrow.nbt.Tag;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -80,6 +81,8 @@ public interface FontManager extends Manageable {
Optional<Font> fontById(Key font);
Collection<Suggestion> cachedImagesSuggestions();
int codepointByImageId(Key imageId, int x, int y);
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>> s2c(Item<T> item, Player player);
Optional<Item<T>> s2c(Item<T> item, @Nullable Player player);
UniqueIdItem<T> uniqueEmptyItem();

View File

@@ -47,6 +47,7 @@ public class ItemSettings {
Color dyeColor;
@Nullable
Color fireworkColor;
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
private ItemSettings() {}
@@ -108,6 +109,7 @@ public class ItemSettings {
newSettings.dyeColor = settings.dyeColor;
newSettings.fireworkColor = settings.fireworkColor;
newSettings.ingredientSubstitutes = settings.ingredientSubstitutes;
newSettings.customData = settings.customData;
return newSettings;
}
@@ -123,73 +125,86 @@ public class ItemSettings {
return settings;
}
@SuppressWarnings("unchecked")
public <T> 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() {
return projectileMeta;
return this.projectileMeta;
}
public boolean disableVanillaBehavior() {
return disableVanillaBehavior;
return this.disableVanillaBehavior;
}
public Repairable repairable() {
return repairable;
return this.repairable;
}
public int fuelTime() {
return fuelTime;
return this.fuelTime;
}
public boolean renameable() {
return renameable;
return this.renameable;
}
public Set<Key> tags() {
return tags;
return this.tags;
}
public Tristate dyeable() {
return dyeable;
return this.dyeable;
}
public boolean canEnchant() {
return canEnchant;
return this.canEnchant;
}
public List<AnvilRepairItem> repairItems() {
return anvilRepairItems;
return this.anvilRepairItems;
}
public boolean respectRepairableComponent() {
return respectRepairableComponent;
return this.respectRepairableComponent;
}
public List<Key> ingredientSubstitutes() {
return ingredientSubstitutes;
return this.ingredientSubstitutes;
}
@Nullable
public FoodData foodData() {
return foodData;
return this.foodData;
}
@Nullable
public Key consumeReplacement() {
return consumeReplacement;
return this.consumeReplacement;
}
@Nullable
public CraftRemainder craftRemainder() {
return craftRemainder;
return this.craftRemainder;
}
@Nullable
public Helmet helmet() {
return helmet;
return this.helmet;
}
@Nullable
public ItemEquipment equipment() {
return equipment;
return this.equipment;
}
@Nullable
@@ -203,11 +218,11 @@ public class ItemSettings {
}
public List<DamageSource> invulnerable() {
return invulnerable;
return this.invulnerable;
}
public float compostProbability() {
return compostProbability;
return this.compostProbability;
}
public ItemSettings fireworkColor(Color color) {
@@ -384,6 +399,9 @@ public class ItemSettings {
registerFactory("equippable", (value -> {
Map<String, Object> args = MiscUtils.castToMap(value, false);
EquipmentData data = EquipmentData.fromMap(args);
if (data.assetId() == null) {
throw new IllegalArgumentException("Please move 'equippable' option to 'data' section.");
}
ComponentBasedEquipment componentBasedEquipment = ComponentBasedEquipment.FACTORY.create(data.assetId(), args);
((AbstractItemManager<?>) CraftEngine.instance().itemManager()).addOrMergeEquipment(componentBasedEquipment);
ItemEquipment itemEquipment = new ItemEquipment(Tristate.FALSE, data, componentBasedEquipment);
@@ -482,7 +500,7 @@ public class ItemSettings {
registerFactory("ingredient-substitute", (value -> settings -> settings.ingredientSubstitutes(MiscUtils.getAsStringList(value).stream().map(Key::of).toList())));
}
private static void registerFactory(String id, ItemSettings.Modifier.Factory factory) {
public static void registerFactory(String id, ItemSettings.Modifier.Factory factory) {
FACTORIES.put(id, factory);
}
}

View File

@@ -18,7 +18,7 @@ public interface NetworkItemHandler<T> {
String NETWORK_OPERATION = "type";
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);

View File

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

View File

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

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 FURNITURE_ITEM = Key.from("craftengine:furniture_item");
public static final Key EXP = Key.from("craftengine:exp");
public static final Key EMPTY = Key.from("craftengine:empty");
static {
register(ALTERNATIVES, AlternativesLootEntryContainer.FACTORY);
@@ -25,6 +26,7 @@ public class LootEntryContainers {
register(ITEM, SingleItemLootEntryContainer.FACTORY);
register(EXP, ExpLootEntryContainer.FACTORY);
register(FURNITURE_ITEM, FurnitureItemLootEntryContainer.FACTORY);
register(EMPTY, EmptyLoopEntryContainer.FACTORY);
}
public static <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.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.sound.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.SoundEvent;
import net.momirealms.craftengine.core.util.*;
@@ -1122,7 +1123,7 @@ public abstract class AbstractPackManager implements PackManager {
futures.add(CompletableFuture.runAsync(() -> {
try {
byte[] previousImageBytes = Files.readAllBytes(imagePath);
byte[] optimized = optimizeImage(previousImageBytes);
byte[] optimized = optimizeImage(imagePath, previousImageBytes);
previousBytes.addAndGet(previousImageBytes.length);
if (optimized.length < previousImageBytes.length) {
afterBytes.addAndGet(optimized.length);
@@ -1190,9 +1191,13 @@ public abstract class AbstractPackManager implements PackManager {
}
}
private byte[] optimizeImage(byte[] previousImageBytes) throws IOException {
private byte[] optimizeImage(Path imagePath, byte[] previousImageBytes) throws IOException {
try (ByteArrayInputStream is = new ByteArrayInputStream(previousImageBytes)) {
BufferedImage src = ImageIO.read(is);
if (src == null) {
Debugger.RESOURCE_PACK.debug(() -> "Cannot read image " + imagePath.toString());
return previousImageBytes;
}
if (src.getType() == BufferedImage.TYPE_CUSTOM) {
return previousImageBytes;
}
@@ -1539,7 +1544,14 @@ public abstract class AbstractPackManager implements PackManager {
}
}
if (Config.fixTextureAtlas()) {
texturesToFix.add(key);
String imagePath = "assets/" + key.namespace() + "/textures/" + key.value() + ".png";
for (Path rootPath : rootPaths) {
if (Files.exists(rootPath.resolve(imagePath))) {
texturesToFix.add(key);
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_model_texture", entry.getValue().stream().distinct().toList().toString(), imagePath);
} else {
TranslationManager.instance().log("warning.config.resource_pack.generation.texture_not_in_atlas", key.toString());
}
@@ -1903,6 +1915,11 @@ public abstract class AbstractPackManager implements PackManager {
private void processComponentBasedEquipment(ComponentBasedEquipment componentBasedEquipment, Path generatedPackPath) {
Key assetId = componentBasedEquipment.assetId();
if (assetId == null) {
this.plugin.logger().severe("Asset id is null for equipment " + componentBasedEquipment);
return;
}
if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4)) {
Path equipmentPath = generatedPackPath
.resolve("assets")

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.pack.host.impl;
import io.github.bucket4j.Bandwidth;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
@@ -8,10 +9,10 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -76,14 +77,28 @@ public class SelfHost implements ResourcePackHost {
boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token");
String protocol = arguments.getOrDefault("protocol", "http").toString();
boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request");
Map<String, Object> rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true);
int maxRequests = 5;
int resetInterval = 20_000;
if (rateMap != null) {
maxRequests = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests");
resetInterval = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval") * 1000;
Bandwidth limit = null;
Map<String, Object> rateLimitingSection = ResourceConfigUtils.getAsMapOrNull(arguments.get("rate-limiting"), "rate-limiting");
long maxBandwidthUsage = 0L;
long minDownloadSpeed = 50_000L;
if (rateLimitingSection != null) {
if (rateLimitingSection.containsKey("qps-per-ip")) {
String qps = rateLimitingSection.get("qps-per-ip").toString();
String[] split = qps.split("/", 2);
if (split.length == 1) split = new String[]{split[0], "1"};
int maxRequests = ResourceConfigUtils.getAsInt(split[0], "qps-per-ip");
int resetInterval = ResourceConfigUtils.getAsInt(split[1], "qps-per-ip");
limit = Bandwidth.builder()
.capacity(maxRequests)
.refillGreedy(maxRequests, Duration.ofSeconds(resetInterval))
.build();
}
maxBandwidthUsage = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("max-bandwidth-per-second", 0), "max-bandwidth");
minDownloadSpeed = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("min-download-speed-per-player", 50_000), "min-download-speed-per-player");
}
selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, maxRequests, resetInterval, oneTimeToken);
selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed);
return INSTANCE;
}
}

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.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedStream;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.GlobalEventExecutor;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
@@ -23,28 +32,35 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class SelfHostHttpServer {
private static SelfHostHttpServer instance;
private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
.maximumSize(256)
.maximumSize(1024)
.scheduler(Scheduler.systemScheduler())
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
private final Cache<String, IpAccessRecord> ipAccessCache = Caffeine.newBuilder()
.maximumSize(256)
private final Cache<String, Bucket> ipRateLimiters = Caffeine.newBuilder()
.maximumSize(1024)
.scheduler(Scheduler.systemScheduler())
.expireAfterWrite(10, TimeUnit.MINUTES)
.expireAfterAccess(5, TimeUnit.MINUTES)
.build();
private final AtomicLong totalRequests = new AtomicLong();
private final AtomicLong blockedRequests = new AtomicLong();
private int rateLimit = 1;
private long rateLimitInterval = 1000;
private Bandwidth limitPerIp = Bandwidth.builder()
.capacity(1)
.refillGreedy(1, Duration.ofSeconds(1))
.initialTokens(1)
.build();
private String ip = "localhost";
private int port = -1;
private String protocol = "http";
@@ -52,6 +68,12 @@ public class SelfHostHttpServer {
private boolean denyNonMinecraft = true;
private boolean useToken;
private long globalUploadRateLimit = 0;
private long minDownloadSpeed = 50_000;
private GlobalChannelTrafficShapingHandler trafficShapingHandler;
private ScheduledExecutorService virtualTrafficExecutor;
private final ChannelGroup activeDownloadChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private byte[] resourcePackBytes;
private String packHash;
private UUID packUUID;
@@ -72,17 +94,25 @@ public class SelfHostHttpServer {
String url,
boolean denyNonMinecraft,
String protocol,
int maxRequests,
int resetInterval,
boolean token) {
Bandwidth limitPerIp,
boolean token,
long globalUploadRateLimit,
long minDownloadSpeed) {
this.ip = ip;
this.url = url;
this.denyNonMinecraft = denyNonMinecraft;
this.protocol = protocol;
this.rateLimit = maxRequests;
this.rateLimitInterval = resetInterval;
this.limitPerIp = limitPerIp;
this.useToken = token;
if (this.globalUploadRateLimit != globalUploadRateLimit || this.minDownloadSpeed != minDownloadSpeed) {
this.globalUploadRateLimit = globalUploadRateLimit;
this.minDownloadSpeed = minDownloadSpeed;
if (this.trafficShapingHandler != null) {
long initSize = globalUploadRateLimit <= 0 ? 0 : Math.max(minDownloadSpeed, globalUploadRateLimit);
this.trafficShapingHandler.setWriteLimit(initSize);
this.trafficShapingHandler.setWriteChannelLimit(initSize);
}
}
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port: " + port);
}
@@ -104,7 +134,17 @@ public class SelfHostHttpServer {
private void initializeServer() {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
virtualTrafficExecutor = Executors.newScheduledThreadPool(1, Thread.ofVirtual().factory());
long initSize = globalUploadRateLimit <= 0 ? 0 : Math.max(minDownloadSpeed, globalUploadRateLimit);
trafficShapingHandler = new GlobalChannelTrafficShapingHandler(
virtualTrafficExecutor,
initSize,
0, // 全局读取不限
initSize, // 默认单通道和总体一致
0, // 单通道读取不限
100, // checkInterval (ms)
10_000 // maxTime (ms)
);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
@@ -112,7 +152,9 @@ public class SelfHostHttpServer {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("trafficShaping", trafficShapingHandler);
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(1048576));
pipeline.addLast(new RequestHandler());
}
@@ -128,6 +170,17 @@ public class SelfHostHttpServer {
@ChannelHandler.Sharable
private class RequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
// 有人走了,其他人的速度上限提高
if (activeDownloadChannels.contains(ctx.channel())) {
activeDownloadChannels.remove(ctx.channel());
rebalanceBandwidth();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
totalRequests.incrementAndGet();
@@ -136,7 +189,7 @@ public class SelfHostHttpServer {
String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress())
.getAddress().getHostAddress();
if (checkRateLimit(clientIp)) {
if (!checkIpRateLimit(clientIp)) {
sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded");
blockedRequests.incrementAndGet();
return;
@@ -159,6 +212,7 @@ public class SelfHostHttpServer {
}
private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) {
// 使用一次性token
if (useToken) {
String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null);
if (!validateToken(token)) {
@@ -168,6 +222,7 @@ public class SelfHostHttpServer {
}
}
// 不是Minecraft客户端
if (denyNonMinecraft) {
String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT);
if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) {
@@ -177,22 +232,47 @@ public class SelfHostHttpServer {
}
}
// 没有资源包
if (resourcePackBytes == null) {
sendError(ctx, HttpResponseStatus.NOT_FOUND, "Resource pack missing");
blockedRequests.incrementAndGet();
return;
}
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(resourcePackBytes)
);
response.headers()
.set(HttpHeaderNames.CONTENT_TYPE, "application/zip")
.set(HttpHeaderNames.CONTENT_LENGTH, resourcePackBytes.length);
// 新人来了,所有人的速度上限降低
if (!activeDownloadChannels.contains(ctx.channel())) {
activeDownloadChannels.add(ctx.channel());
rebalanceBandwidth();
}
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
// 告诉客户端资源包大小
long fileLength = resourcePackBytes.length;
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
HttpUtil.setContentLength(response, fileLength);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/zip");
boolean keepAlive = HttpUtil.isKeepAlive(request);
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.write(response);
// 发送分段资源包
ChunkedStream chunkedStream = new ChunkedStream(new ByteArrayInputStream(resourcePackBytes), 8192);
HttpChunkedInput httpChunkedInput = new HttpChunkedInput(chunkedStream);
ChannelFuture sendFileFuture = ctx.writeAndFlush(httpChunkedInput);
if (!keepAlive) {
sendFileFuture.addListener(ChannelFutureListener.CLOSE);
}
// 监听下载完成(成功或失败),以便在下载结束后(如果不关闭连接)也能移除计数
// 注意:如果是 Keep-Alive连接不会断但下载结束了。
// 为了精确控制,可以在这里监听 operationComplete
sendFileFuture.addListener((ChannelFutureListener) future -> {
if (activeDownloadChannels.contains(ctx.channel())) {
activeDownloadChannels.remove(ctx.channel());
rebalanceBandwidth();
}
});
}
private void handleMetrics(ChannelHandlerContext ctx) {
@@ -213,23 +293,11 @@ public class SelfHostHttpServer {
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private boolean checkRateLimit(String clientIp) {
IpAccessRecord record = ipAccessCache.getIfPresent(clientIp);
long now = System.currentTimeMillis();
if (record == null) {
record = new IpAccessRecord(now, 1);
ipAccessCache.put(clientIp, record);
return false;
}
if (now - record.lastAccessTime > rateLimitInterval) {
record.lastAccessTime = now;
record.accessCount = 1;
return false;
}
return ++record.accessCount > rateLimit;
private boolean checkIpRateLimit(String clientIp) {
if (limitPerIp == null) return true;
Bucket rateLimiter = ipRateLimiters.get(clientIp, k -> Bucket.builder().addLimit(limitPerIp).build());
assert rateLimiter != null;
return rateLimiter.tryConsume(1);
}
private boolean validateToken(String token) {
@@ -257,6 +325,28 @@ public class SelfHostHttpServer {
}
}
private synchronized void rebalanceBandwidth() {
if (globalUploadRateLimit == 0) {
trafficShapingHandler.setWriteChannelLimit(0);
return;
}
int activeCount = activeDownloadChannels.size();
if (activeCount == 0) {
trafficShapingHandler.setWriteChannelLimit(globalUploadRateLimit);
return;
}
// 计算平均带宽:全局总量 / 当前人数
long fairRate = globalUploadRateLimit / activeCount;
// 确保不低于最小保障速率(可选,防止除法导致过小)
fairRate = Math.max(fairRate, this.minDownloadSpeed);
// 更新 Handler 配置
trafficShapingHandler.setWriteChannelLimit(fairRate);
}
@Nullable
public ResourcePackDownloadData generateOneTimeUrl() {
if (this.resourcePackBytes == null) return null;
@@ -275,6 +365,17 @@ public class SelfHostHttpServer {
}
public void disable() {
// 释放流量整形资源
if (trafficShapingHandler != null) {
trafficShapingHandler.release();
trafficShapingHandler = null;
}
// 关闭专用线程池
if (virtualTrafficExecutor != null) {
virtualTrafficExecutor.shutdown();
virtualTrafficExecutor = null;
}
activeDownloadChannels.close();
if (serverChannel != null) {
serverChannel.close().awaitUninterruptibly();
bossGroup.shutdownGracefully();
@@ -312,14 +413,4 @@ public class SelfHostHttpServer {
CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e);
}
}
private static class IpAccessRecord {
long lastAccessTime;
int accessCount;
IpAccessRecord(long lastAccessTime, int accessCount) {
this.lastAccessTime = lastAccessTime;
this.accessCount = accessCount;
}
}
}

View File

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

View File

@@ -138,6 +138,8 @@ public class Config {
protected ColliderType furniture$collision_entity_type;
protected boolean block$sound_system$enable;
protected boolean block$sound_system$process_cancelled_events$step;
protected boolean block$sound_system$process_cancelled_events$break;
protected boolean block$simplify_adventure_break_check;
protected boolean block$simplify_adventure_place_check;
protected boolean block$predict_breaking;
@@ -475,6 +477,8 @@ public class Config {
// block
block$sound_system$enable = config.getBoolean("block.sound-system.enable", true);
block$sound_system$process_cancelled_events$step = config.getBoolean("block.sound-system.process-cancelled-events.step", true);
block$sound_system$process_cancelled_events$break = config.getBoolean("block.sound-system.process-cancelled-events.break", true);
block$simplify_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", false);
block$simplify_adventure_place_check = config.getBoolean("block.simplify-adventure-place-check", false);
block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true);
@@ -675,6 +679,14 @@ public class Config {
return instance.block$sound_system$enable;
}
public static boolean processCancelledStep() {
return instance.block$sound_system$process_cancelled_events$step;
}
public static boolean processCancelledBreak() {
return instance.block$sound_system$process_cancelled_events$break;
}
public static boolean simplifyAdventureBreakCheck() {
return instance.block$simplify_adventure_break_check;
}

View File

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

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

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.plugin.context.function;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.plugin.context.Condition;
@@ -10,9 +11,9 @@ import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextPar
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.ExistingBlock;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@@ -20,15 +21,28 @@ import java.util.Optional;
public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> {
private final String property;
@Nullable
private final Map<String, String> rules;
@Nullable
private final NumberProvider inverse;
private final NumberProvider x;
private final NumberProvider y;
private final NumberProvider z;
private final NumberProvider updateFlags;
public CycleBlockPropertyFunction(List<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);
this.property = property;
this.rules = rules;
this.inverse = inverse;
this.x = x;
this.y = y;
@@ -44,11 +58,26 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
int x = MiscUtils.fastFloor(this.x.getDouble(ctx));
int y = MiscUtils.fastFloor(this.y.getDouble(ctx));
int z = MiscUtils.fastFloor(this.z.getDouble(ctx));
ExistingBlock blockAt = world.getBlock(x, y, z);
BlockStateWrapper wrapper = blockAt.blockState().cycleProperty(this.property, this.inverse.getInt(ctx) == 0);
BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx);
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx));
}
private BlockStateWrapper updateBlockState(BlockStateWrapper wrapper, CTX ctx) {
boolean inverse = this.inverse != null && this.inverse.getInt(ctx) == 0;
if (this.rules == null) {
return wrapper.cycleProperty(this.property, inverse);
}
Object value = wrapper.getProperty(this.property);
if (value == null) {
return wrapper.cycleProperty(this.property, inverse);
}
String mapValue = this.rules.get(value.toString());
if (mapValue == null) {
return wrapper.cycleProperty(this.property, inverse);
}
return wrapper.withProperty(this.property, mapValue);
}
@Override
public Key type() {
return CommonFunctions.CYCLE_BLOCK_PROPERTY;
@@ -62,8 +91,15 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
@Override
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),
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("x", "<arg:position.x>")),
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"))
);
public static final Dependency BUCKET_4_J = new Dependency(
"bucket4j",
"com{}bucket4j",
"bucket4j_jdk17-core",
List.of(Relocation.of("bucket4j", "io{}github{}bucket4j"))
);
public static final Dependency NETTY_HTTP = new Dependency(
"netty-codec-http",
"io{}netty",

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_SUCCESS = Component.translatable().key("command.locale.set.success");
TranslatableComponent.Builder COMMAND_LOCALE_UNSET_SUCCESS = Component.translatable().key("command.locale.unset.success");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_SUCCESS_SINGLE = Component.translatable().key("command.item.clear.success.single");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_SUCCESS_MULTIPLE = Component.translatable().key("command.item.clear.success.multiple");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_SINGLE = Component.translatable().key("command.item.clear.failed.single");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_MULTIPLE = Component.translatable().key("command.item.clear.failed.multiple");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_SINGLE = Component.translatable().key("command.item.clear.test.single");
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_MULTIPLE = Component.translatable().key("command.item.clear.test.multiple");
}

View File

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

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();
boolean initialized();
static <T> LazyReference<T> lazyReference(final Supplier<T> supplier) {
return new LazyReference<>() {
private T value;
@@ -17,6 +19,11 @@ public interface LazyReference<T> {
}
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 {
byte[] bytes = generatePngData(src, hasAlpha, isGrayscale);
int zopfli = Config.zopfliIterations();
int zopfli = Config.optimizeTexture() ? Config.zopfliIterations() : 0;
return zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes);
}
@@ -177,7 +177,7 @@ public class PngOptimizer {
writeChunkPLTE(paletteOs, palette);
}
byte[] bytes = generatePaletteData(src, palette);
int zopfli = Config.zopfliIterations();
int zopfli = Config.optimizeTexture() ? Config.zopfliIterations() : 0;
paletteOs.write(zopfli > 0 ? compressImageZopfli(bytes, zopfli) : compressImageStandard(bytes));
return Pair.of(palette, paletteOs.toByteArray());
}

View File

@@ -134,7 +134,7 @@ public final class ResourceConfigUtils {
}
case String s -> {
try {
return Integer.parseInt(s);
return Integer.parseInt(s.replace("_", ""));
} catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.type.int", e, s, option);
}
@@ -218,6 +218,30 @@ public final class ResourceConfigUtils {
}
}
public static long getAsLong(Object o, String option) {
switch (o) {
case null -> {
return 0;
}
case Long l -> {
return l;
}
case Number number -> {
return number.longValue();
}
case String s -> {
try {
return Long.parseLong(s.replace("_", ""));
} catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.type.long", e, s, option);
}
}
default -> {
throw new LocalizedResourceConfigException("warning.config.type.long", o.toString(), option);
}
}
}
@SuppressWarnings("unchecked")
public static Map<String, Object> getAsMap(Object obj, String option) {
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) {
if (this == o) return true;
if (!(o instanceof Vec3d vec3d)) return false;
return Double.compare(x, vec3d.x) == 0 && Double.compare(y, vec3d.y) == 0 && Double.compare(z, vec3d.z) == 0;
return this.x == vec3d.x && this.y == vec3d.y && this.z == vec3d.z;
}
@Override

View File

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