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