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