Compare commits

...

35 Commits

Author SHA1 Message Date
Helvetica Volubi b7bf1b5916 Update Luminol 2025-11-24 00:16:50 +08:00
Helvetica Volubi 4a8888c578 try fixes bugs caused by Bukkit.getScheduler().runTaskLater
we use (FoliaGlobalRegionScheduler.GlobalScheduledTask) Bukkit.getGlobalRegionScheduler().runDelayed to instead of Bukkit.getScheduler().runTaskLater, and we also provided a brand-new way to cancel task by task id
2025-11-22 01:57:16 +08:00
Helvetica Volubi 2c59a32afe [ci skip] Update context 2025-11-20 15:36:46 +08:00
Helvetica Volubi cc5382977d Update Luminol 2025-11-20 15:32:41 +08:00
xiaoxijun c07dde1c0b feat: CCE Update Suppression support and other dependences (#84)
add cce update suppression & prevent crashes caused by update suppres & dustTrapdoorReintroduced & instantBlockUpdater & old Block remove behaviour & do not reset placed block on exception & do not prevent block entity and entity crash at LevelChunk (#84)

* add cce update suppression & prevent crashes caused by update suppression

* no need for this transformed flag

* Add Redstone ignore upwards update

* fix up license

* move config

* instant Block Updater

* feat: Update Suppression!

* Leaves: Do not reset placed block on exception & Do not prevent block entity and entity crash at LevelChunk

---------

Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun>
Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
2025-11-19 00:04:34 +08:00
Helvetica Volubi 871a003a2e Update Luminol to fix open-gui bug 2025-11-17 17:54:27 +08:00
Helvetica Volubi d6a981a6e9 [ci skip] skip release and push in unexpect branch
See https://github.com/LuminolMC/Luminol/commit/ec746a66f0813f60eb59de11b8207f773fc4728c
2025-11-17 17:26:24 +08:00
Helvetica Volubi bc166a9fb6 [ci skip] refactor code 2025-11-16 21:22:06 +08:00
Helvetica Volubi feebf388f8 Add Replay Api (#86)
* feat: base replay api support

* fix an async bug
2025-11-16 21:05:01 +08:00
Helvetica Volubi dbcf764e76 fix ServuxStructuresProtocol (#89)
* Add LeaveHooks to fix bug caused by servux structures protocol

* add missing file

* add comment

* add license
2025-11-16 20:51:19 +08:00
Helvetica Volubi 735fe8855a try to fix a bug in fake player spawn 2025-11-16 18:53:16 +08:00
Helvetica Volubi 1b45a090f6 Update Luminol 2025-11-16 14:09:42 +08:00
Helvetica Volubi bb1335d390 [ci skip] Update Luminol
do not release yet, release at next commit
2025-11-15 15:38:52 +08:00
Helvetica Volubi 00743c841f Update Luminol 2025-11-14 22:27:06 +08:00
xiaoxijun bdb9c9dec4 Add Alternative block placement Protocol (#81)
* Add Alternative block placement Protocol

* fix: fix config error caused by config system

- TODO: fix config system read enum bug in luminol

* remove Leaves logger & use mojang logger

* update code from leaves

---------

Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun>
Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
2025-11-12 19:15:21 +08:00
Helvetica Volubi ac3653dda7 [ci skip]Update Gradle wrapper to version 9.2.0 2025-11-12 00:56:44 +08:00
Helvetica Volubi a08fd04751 Update Luminol to fix a sentry bug 2025-11-12 00:20:46 +08:00
Helvetica Volubi f5debe9ee1 [ci skip] Update Luminol 2025-11-10 17:27:08 +08:00
Helvetica Volubi 1a84d2e670 Update Luminol to fix a crash bug in config command system 2025-11-10 00:27:08 +08:00
Helvetica Volubi e96891275d [ci skip] update datapack command save 2025-11-09 02:38:51 +08:00
Helvetica Volubi 9e342c6683 Update Luminol 2025-11-06 00:12:13 +08:00
Helvetica Volubi 00f7f5ee2b fix: fixes #69 2025-11-01 02:53:48 +08:00
Helvetica Volubi 77b9582c4a Update Luminol 2025-10-31 22:23:17 +08:00
Helvetica Volubi 184048fd26 Update Luminol 2025-10-31 00:23:24 +08:00
Helvetica Volubi 7ccca03023 try to fix BBOR protocol 2025-10-29 02:09:22 +08:00
Helvetica Volubi b34fa8237e feat: provided void trade instead of infinite trade
fixes #77
2025-10-29 00:20:45 +08:00
Helvetica Volubi 42a4d18717 refactor: deprecated tickCount 2025-10-27 21:40:42 +08:00
Helvetica Volubi 2d8d7f5784 refactor: prepare to drop reuse tickCount 2025-10-27 21:11:51 +08:00
Helvetica Volubi bf509bcded feat: wool hopper counter(#27) 2025-10-27 19:59:03 +08:00
Helvetica Volubi ef6759a9ab [ci skip] Update Luminol to skip release by robot 2025-10-27 05:40:37 +00:00
Helvetica Volubi bc1483ea7a fix: try to fix Concurrent throw in protocol 2025-10-27 00:10:44 +08:00
Helvetica Volubi 5768a60ba8 fix: rewrite request block data method 2025-10-25 03:22:14 +08:00
Helvetica Volubi 05434f422c fix: fixes #63 2025-10-23 00:20:15 +08:00
Helvetica Volubi 21e9299352 Update code from Leaves 2025-10-22 11:34:52 +08:00
Helvetica Volubi 83e7718346 fix: try fix #63 2025-10-22 02:41:51 +08:00
97 changed files with 5330 additions and 495 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
name: Bug反馈 Bug report
description:
报告Bug,插件不兼容情况,以及其他相关问题。
Report issues with plugin incompatbility or other behavior related issues.
Report issues with plugin incompatibility or other behavior related issues.
labels:
- bug
- pending
@@ -41,7 +41,7 @@ body:
attributes:
label: 观测到的实际行为 Observed/Actual behavior
description: 你实际看到的。 What you actually saw.
placeholder: e.g. 使用 /tpsbar [NAME] 命令切换不了TPSBAR
placeholder: e.g. 使用 /tpsbar 命令切换不了TPSBAR
validations:
required: true
- type: textarea
+1 -1
View File
@@ -28,7 +28,7 @@ body:
validations:
required: true
- type: textarea
id: othre
id: other
attributes:
label: 其他 Other
description: 添加其他的信息。 Add any other context or screenshots about the feature request below.
+14 -3
View File
@@ -54,7 +54,7 @@ jobs:
run: ./gradlew --refresh-dependencies createMojmapPaperclipJar
- name: Upload Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ env.project_id_b }} CI Artifacts
path: lophine-server/build/libs/*-paperclip-*-mojmap.jar
@@ -84,8 +84,19 @@ jobs:
fi
echo "release_exists=${release_exists}" >> $GITHUB_ENV
- name: Check If Branch In Expectations
if: github.event_name != 'pull_request'
run: |
if [[ "$GITHUB_REF_NAME" == ver/* ]] || [[ "$GITHUB_REF_NAME" == exp/* ]] || [[ "$GITHUB_REF_NAME" == dev/* ]]; then
echo "Branch is in expected prefixes"
echo "unexpect=false" >> $GITHUB_ENV
else
echo "Branch is NOT in expected prefixes"
echo "unexpect=true" >> $GITHUB_ENV
fi
- name: Publish To Repo
if: (( github.event_name != 'pull_request' && env.flag_release == 'true' && env.release_exists != 'true' && !(inputs.force-push == '2')) || inputs.force-push == '1') && false # do not push to any repo
if: (( github.event_name != 'pull_request' && !( env.unexpect == 'true' ) && env.flag_release == 'true' && env.release_exists != 'true' && !( inputs.force-release == '2' )) || inputs.force-push == '1') && false # do not push to any repo
continue-on-error: true
run: ./gradlew --refresh-dependencies generateDevelopmentBundle publish -PpublishDevBundle=true
env:
@@ -93,7 +104,7 @@ jobs:
PRIVATE_MAVEN_REPO_USERNAME: ${{ secrets.PRIVATE_MAVEN_REPO_USERNAME }}
- name: Create Release - Pre Process
if: ( github.event_name != 'pull_request' && env.flag_release == 'true' && env.release_exists != 'true' && !(inputs.force-release == '2')) || inputs.force-release == '1'
if: ( github.event_name != 'pull_request' && !( env.unexpect == 'true' ) && env.flag_release == 'true' && env.release_exists != 'true' && !(inputs.force-release == '2')) || inputs.force-release == '1'
run: |
if [ ${{ inputs.comments }} == "" ]; then
echo "No comments provided"
+1 -1
View File
@@ -4,7 +4,7 @@ mcVersion=1.21.8
release=2
# 0 for skip release, 1 for pre-release, 2 for release
luminolRef=332099a920bc3e854ba1d37fc5651c0f922e32e8
luminolRef=774ad06b2b18e2ac1063a27e0f152484da210843
org.gradle.configuration-cache=true
org.gradle.caching=true
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -1,8 +1,11 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 30 Aug 2025 16:51:26 +0800
Subject: [PATCH] Add fakeplayer api
Subject: [PATCH] Leaves: Add fakeplayer api
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 85b778980e762835ac278528087e91b2e3effb43..89f00f4b6ff64305fa44ebb71f22a113c1296d26 100644
@@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 16 Nov 2025 15:50:28 +0800
Subject: [PATCH] Leaves: Replay Mod API
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
index 89f00f4b6ff64305fa44ebb71f22a113c1296d26..e9d20fe8b88aa63fb62effd430b5e22c8dbe1307 100644
--- a/src/main/java/org/bukkit/Bukkit.java
+++ b/src/main/java/org/bukkit/Bukkit.java
@@ -3063,4 +3063,10 @@ public final class Bukkit {
return server.getBotManager();
}
// Leaves end - Bot API
+
+ // Leaves start - Photographer API
+ public static @NotNull org.leavesmc.leaves.entity.photographer.PhotographerManager getPhotographerManager() {
+ return server.getPhotographerManager();
+ }
+ // Leaves end - Photographer API
}
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index c7ae463fb166750bbd0638a2ccf81753b293a381..4caadc8f9b328d978ece84c1480e32f24868531f 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2762,4 +2762,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
@NotNull org.leavesmc.leaves.entity.bot.BotManager getBotManager();
// Leaves end - Bot API
+
+ // Leaves start - Photographer API
+ @NotNull org.leavesmc.leaves.entity.photographer.PhotographerManager getPhotographerManager();
+ // Leaves end - Photographer API
}
@@ -0,0 +1,43 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.entity.photographer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
public interface Photographer extends Player {
@NotNull String getId();
void setRecordFile(@NotNull File file);
void stopRecording();
void stopRecording(boolean async);
void stopRecording(boolean async, boolean save);
void pauseRecording();
void resumeRecording();
void setFollowPlayer(@Nullable Player player);
}
@@ -0,0 +1,44 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.entity.photographer;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.replay.BukkitRecorderOption;
import java.util.Collection;
import java.util.UUID;
public interface PhotographerManager {
@Nullable Photographer getPhotographer(@NotNull UUID uuid);
@Nullable Photographer getPhotographer(@NotNull String id);
@Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location);
@Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption);
void removePhotographer(@NotNull String id);
void removePhotographer(@NotNull UUID uuid);
void removeAllPhotographers();
Collection<Photographer> getPhotographers();
}
@@ -0,0 +1,35 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
public class BukkitRecorderOption {
// public int recordDistance = -1;
public String serverName = "Lophine"; // Lophine - rebrand to Lophine to avoid confusion
public BukkitRecordWeather forceWeather = BukkitRecordWeather.NULL;
public int forceDayTime = -1;
public boolean ignoreChat = false;
// public boolean ignoreItem = false;
public enum BukkitRecordWeather {
CLEAR,
RAIN,
THUNDER,
NULL
}
}
@@ -5,7 +5,7 @@ Subject: [PATCH] Rebrand to Lophine
diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java
index d8066afd5f3f2ea9a5dff87d853562e22268785c..f8b2364fa162a41304a99a4993354e33528ab801 100644
index a5208f5426ea2e5d8f50c51fe17718ea1d63eef5..c80c578f1a3786bd7413c9a7af8ae125fe7975bb 100644
--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java
+++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java
@@ -21,6 +21,7 @@ public class ConfigManager {
@@ -5,7 +5,7 @@ Subject: [PATCH] Transformed Configs
diff --git a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandConfig.java b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandConfig.java
index ba360d57fd9ee2363c73863bc01b6592e59be4e5..86e6cc4c024ece5f45fb53b4cd341b1df2e8f0c7 100644
index 4051384e82dba0416838c3c9889a6e368367b601..a5535b479cdcca870d0c8be6ed6fbcb32f266ac2 100644
--- a/src/main/java/me/earthme/luminol/config/modules/experiment/CommandConfig.java
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandConfig.java
@@ -11,6 +11,7 @@ public class CommandConfig implements IConfigModule {
@@ -15,7 +15,7 @@ index ba360d57fd9ee2363c73863bc01b6592e59be4e5..86e6cc4c024ece5f45fb53b4cd341b1d
+ @TransformedConfig(name = "command_block_enabled", directory = {"experiment", "command"}, originInstance = "lophine")
@TransformedConfig(name = "enabled", directory = {"experiment", "force_enable_command_block_command_execution"})
@ConfigInfo(name = "enable_command_block", comments = """
Force to enable command blocks.
Force to enable command blocks.
diff --git a/src/main/java/me/earthme/luminol/config/modules/optimizations/PaperPacketLimiterConfig.java b/src/main/java/me/earthme/luminol/config/modules/optimizations/PaperPacketLimiterConfig.java
index 35e6014f04093050a9a62b895b43e5c5d8c6a28e..11abfb884f972fd73ca4c13a6e309de1bcd23fd5 100644
--- a/src/main/java/me/earthme/luminol/config/modules/optimizations/PaperPacketLimiterConfig.java
@@ -8,180 +8,69 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/d93e9766d3797d130
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
index 3a6314ed7f62c6473ce9ddc9deeee2a32b9d2893..23a0f8100eb3162331161635e8051981c4e34aed 100644
index 112dc08eb62c4c50fb4859f51a366cedf027ca00..b5f661fd20b382725ad117c56ce2c94ded32b1b7 100644
--- a/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
+++ b/src/main/java/org/leavesmc/leaves/util/ItemOverstackUtils.java
@@ -17,12 +17,173 @@
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.util;
+import fun.bm.lophine.config.modules.function.ContainerExpansionConfig;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.item.component.ItemContainerContents;
+import net.minecraft.world.level.block.ShulkerBoxBlock;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ItemEnchantments;
import net.minecraft.core.component.DataComponents;
@@ -135,9 +136,9 @@ public class ItemOverstackUtils {
// Only the used part is retained
public class ItemOverstackUtils {
+ private static final List<ItemUtil> overstackUtils = List.of(
+ new ShulkerBox()
+ );
+
+ public static int getItemStackMaxCount(ItemStack stack) {
+ int size;
+ for (ItemUtil util : overstackUtils) {
+ if ((size = util.getMaxServerStackCount(stack)) != -1) {
+ return size;
+ }
+ }
+ return stack.getMaxStackSize();
+ }
+
+ public static int getNetworkMaxCount(ItemStack stack) {
+ int size;
+ for (ItemUtil util : overstackUtils) {
+ if ((size = util.getMaxClientStackCount(stack)) != -1) {
+ return size;
+ }
+ }
+ return stack.getMaxStackSize();
+ }
+
+ public static boolean tryStackItems(ItemEntity self, ItemEntity other) {
+ for (ItemUtil util : overstackUtils) {
+ if (util.tryStackItems(self, other)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasOverstackingItem() {
+ return overstackUtils.stream().anyMatch(ItemUtil::isEnabled);
+ }
+
+ public static int getItemStackMaxCountReal(ItemStack stack) {
+ CompoundTag nbt = Optional.ofNullable(stack.get(DataComponents.CUSTOM_DATA)).orElse(CustomData.EMPTY).copyTag();
+ return nbt.getInt("Leaves.RealStackSize").orElse(stack.getMaxStackSize());
+ }
+
+ public static ItemStack encodeMaxStackSize(ItemStack itemStack) {
+ int realMaxStackSize = getItemStackMaxCountReal(itemStack);
+ int modifiedMaxStackSize = getNetworkMaxCount(itemStack);
+ if (itemStack.getMaxStackSize() != modifiedMaxStackSize) {
+ itemStack.set(DataComponents.MAX_STACK_SIZE, modifiedMaxStackSize);
+ CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ nbt.putInt("Leaves.RealStackSize", realMaxStackSize);
+ itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
+ }
+ return itemStack;
+ }
+
+ public static ItemStack decodeMaxStackSize(ItemStack itemStack) {
+ int realMaxStackSize = getItemStackMaxCountReal(itemStack);
+ if (itemStack.getMaxStackSize() != realMaxStackSize) {
+ itemStack.set(DataComponents.MAX_STACK_SIZE, realMaxStackSize);
+ CompoundTag nbt = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ nbt.remove("Leaves.RealStackSize");
+ if (nbt.isEmpty()) {
+ itemStack.remove(DataComponents.CUSTOM_DATA);
+ } else {
+ itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(nbt));
+ }
+ }
+ return itemStack;
+ }
+
public static float getItemStackSignalStrength(int maxStackSize, ItemStack itemStack) {
float result = (float) itemStack.getCount() / Math.min(maxStackSize, itemStack.getMaxStackSize());
return Math.clamp(result, 0f, 1f);
}
+
+ public static boolean isStackable(ItemStack itemStack) {
+ return getItemStackMaxCount(itemStack) > 1 && (!itemStack.isDamageableItem() || !itemStack.isDamaged());
+ }
+
+
+ private interface ItemUtil {
+ boolean isEnabled();
+
+ boolean tryStackItems(ItemEntity self, ItemEntity other);
+
+ // number -> modified count, -1 -> I don't care
+ int getMaxServerStackCount(ItemStack stack);
+
+ // number -> modified count, -1 -> I don't care
+ default int getMaxClientStackCount(ItemStack stack) {
+ return getMaxServerStackCount(stack);
+ }
+ }
+
+ private static class ShulkerBox implements ItemUtil {
+ public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
private static class ShulkerBox implements ItemUtil {
public static boolean shulkerBoxCheck(@NotNull ItemStack stack1, @NotNull ItemStack stack2) {
- /*if (LeavesConfig.modify.shulkerBox.sameNbtStackable) {
+ if (ContainerExpansionConfig.nbtShulkerStackable) {
+ return Objects.equals(stack1.getComponents(), stack2.getComponents());
return Objects.equals(stack1.getComponents(), stack2.getComponents());
- }*/ // Luminol
+ }
+ return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
+ }
+
+ public static boolean shulkerBoxNoItem(@NotNull ItemStack stack) {
+ return stack.getComponents().getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).stream().findAny().isEmpty();
+ }
+
+ @Override
+ public boolean isEnabled() {
return shulkerBoxNoItem(stack1) && shulkerBoxNoItem(stack2) && Objects.equals(stack1.getComponents(), stack2.getComponents());
}
@@ -147,16 +148,15 @@ public class ItemOverstackUtils {
@Override
public boolean isEnabled() {
- // return LeavesConfig.modify.shulkerBox.stackableShulkerBoxes > 1; // Luminol
- return false; // Luminol
+ return ContainerExpansionConfig.shulkerCount > 1;
+ }
+
+ @Override
+ public boolean tryStackItems(ItemEntity self, ItemEntity other) {
+ ItemStack selfStack = self.getItem();
+ if (!isEnabled() ||
}
@Override
public boolean tryStackItems(ItemEntity self, ItemEntity other) {
ItemStack selfStack = self.getItem();
if (!isEnabled() ||
- !(selfStack.getItem() instanceof net.minecraft.world.item.BlockItem blockItem) ||
- !(blockItem.getBlock() instanceof net.minecraft.world.level.block.ShulkerBoxBlock)
+ !(selfStack.getItem() instanceof BlockItem blockItem) ||
+ !(blockItem.getBlock() instanceof ShulkerBoxBlock)
+ ) {
+ return false;
+ }
+
+ ItemStack otherStack = other.getItem();
+ if (selfStack.getItem() == otherStack.getItem()
+ && shulkerBoxCheck(selfStack, otherStack)
) {
return false;
}
@@ -164,8 +164,8 @@ public class ItemOverstackUtils {
ItemStack otherStack = other.getItem();
if (selfStack.getItem() == otherStack.getItem()
&& shulkerBoxCheck(selfStack, otherStack)
- && selfStack.getCount() != /*org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.stackableShulkerBoxes*/ 1) { // Luminol
- int amount = Math.min(otherStack.getCount(), /*org.leavesmc.leaves.LeavesConfig.modify.shulkerBox.stackableShulkerBoxes*/1 - selfStack.getCount()); // Luminol
+ && selfStack.getCount() != ContainerExpansionConfig.shulkerCount) {
+ int amount = Math.min(otherStack.getCount(), ContainerExpansionConfig.shulkerCount - selfStack.getCount());
+
+ selfStack.grow(amount);
+ self.setItem(selfStack);
+
+ self.pickupDelay = Math.max(other.pickupDelay, self.pickupDelay);
+ self.age = Math.min(other.getAge(), self.age);
+
+ otherStack.shrink(amount);
+ if (otherStack.isEmpty()) {
+ other.discard();
+ } else {
+ other.setItem(otherStack);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int getMaxServerStackCount(ItemStack stack) {
+ if (isEnabled() && stack.getItem() instanceof BlockItem bi &&
selfStack.grow(amount);
self.setItem(selfStack);
@@ -187,9 +187,8 @@ public class ItemOverstackUtils {
@Override
public int getMaxServerStackCount(ItemStack stack) {
if (isEnabled() && stack.getItem() instanceof BlockItem bi &&
- bi.getBlock() instanceof ShulkerBoxBlock && (/*LeavesConfig.modify.shulkerBox.sameNbtStackable ||*/ shulkerBoxNoItem(stack))) { // Luminol
- // return LeavesConfig.modify.shulkerBox.stackableShulkerBoxes; // Luminol
- return 1; // Luminol
+ bi.getBlock() instanceof ShulkerBoxBlock && (ContainerExpansionConfig.nbtShulkerStackable || shulkerBoxNoItem(stack))) {
+ return ContainerExpansionConfig.shulkerCount;
+ }
+ return -1;
+ }
+ }
}
}
return -1;
}
@@ -1,60 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 7 Jul 2025 16:30:54 +0800
Subject: [PATCH] Add config to allow infinite trade
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
index 79242037e868a140513f032e74e8594c0c6ff41c..050e89c3e95c7c3ef7ad98f8ef03ab206241a7e2 100644
--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -490,8 +490,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
private void resendOffersToTradingPlayer() {
- MerchantOffers offers = this.getOffers();
+ // Lophine start - infinite trade
+ MerchantOffers offers = this.getOffers().copy();
Player tradingPlayer = this.getTradingPlayer();
+ if (fun.bm.lophine.config.modules.function.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 524288;
+ }
+ }
+ // Lophine end - infinite trade
if (tradingPlayer != null && !offers.isEmpty()) {
tradingPlayer.sendMerchantOffers(
tradingPlayer.containerMenu.containerId,
diff --git a/net/minecraft/world/item/trading/Merchant.java b/net/minecraft/world/item/trading/Merchant.java
index 6e2fa442ddcd4ef3c4fe789b9ebac9dd409c9de7..0c7318a0ed214204f5493576c8a7b02fbe51c00b 100644
--- a/net/minecraft/world/item/trading/Merchant.java
+++ b/net/minecraft/world/item/trading/Merchant.java
@@ -42,7 +42,14 @@ public interface Merchant {
new SimpleMenuProvider((containerId, inventory, player1) -> new MerchantMenu(containerId, inventory, this), displayName)
);
if (optionalInt.isPresent()) {
- MerchantOffers offers = this.getOffers();
+ // Lophine start - infinite trade
+ MerchantOffers offers = this.getOffers().copy();
+ if (fun.bm.lophine.config.modules.function.VillagerConfig.villagerInfiniteTrade) {
+ for (MerchantOffer merchantOffer : offers) {
+ merchantOffer.maxUses = 5242884;
+ }
+ }
+ // Lophine end - infinite trade
if (!offers.isEmpty()) {
player.sendMerchantOffers(optionalInt.getAsInt(), offers, level, this.getVillagerXp(), this.showProgressBar(), this.canRestock());
}
diff --git a/net/minecraft/world/item/trading/MerchantOffer.java b/net/minecraft/world/item/trading/MerchantOffer.java
index 64c99df8ff305fa28c75dc03fc5ef8c61634ad84..770f37d7158c7b4cec62783025f623a68c1f2457 100644
--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -196,7 +196,7 @@ public class MerchantOffer {
}
public boolean isOutOfStock() {
- return this.uses >= this.maxUses;
+ return (this.uses >= this.maxUses && !fun.bm.lophine.config.modules.function.VillagerConfig.villagerInfiniteTrade); // Lophine - infinite trade
}
public void setToOutOfStock() {
@@ -18,7 +18,7 @@ index 6868b915bf3deb85783a638d4441a15fea6da2dc..70740381c6501c1a518c52b24381edd1
double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
if (rangeY != -1) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 34d802060e096c5acfbd9a66fab96c0e8c792fe1..47ef63061737f3efdb0ce7ec71a8d9c920385bb0 100644
index 38092dcc3dedc6c855e5598126be47f892223547..a8b6e89035579557b59a20ddbe9b32943e6b7164 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
@@ -30,7 +30,7 @@ index 34d802060e096c5acfbd9a66fab96c0e8c792fe1..47ef63061737f3efdb0ce7ec71a8d9c9
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first snapTo
@@ -6480,4 +6480,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -6463,4 +6463,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// Paper end - Expose entity id counter
public boolean shouldTickHot() { return this.tickCount > 20 * 10 && this.isAlive(); } // KioCG
@@ -5,10 +5,10 @@ Subject: [PATCH] Add config to enable function command
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index af895859057ca303177ec3c8a07cf7534e62cca6..e0e9fcf3224f55f2ef31c8afca3154a5992f761b 100644
index 11697e65a7f7871920ca7bad37de07f78a3da1f8..25ccbfa12d57d540195ecc16aa54a144f8ecc0e8 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -206,7 +206,9 @@ public class Commands {
@@ -204,7 +204,9 @@ public class Commands {
FillCommand.register(this.dispatcher, context);
FillBiomeCommand.register(this.dispatcher, context);
ForceLoadCommand.register(this.dispatcher);
@@ -1,59 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 2 Jul 2025 23:36:25 +0800
Subject: [PATCH] Add config to enable datapack command
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index e05f75b6bbd48d6fb7379926d68d5f8ed6014be9..af895859057ca303177ec3c8a07cf7534e62cca6 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -192,7 +192,9 @@ public class Commands {
if(me.earthme.luminol.config.modules.experiment.CommandConfig.data) {
DataCommands.register(this.dispatcher); // Folia - region threading - TODO
}
- //DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
+ if (fun.bm.lophine.config.modules.experiment.CommandConfig.datapack) {
+ DataPackCommand.register(this.dispatcher, context); // Folia - region threading - TODO
+ }
//DebugCommand.register(this.dispatcher); // Folia - region threading - TODO
DefaultGameModeCommands.register(this.dispatcher);
//DialogCommand.register(this.dispatcher, context); // Folia - region threading - TODO
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 75027e73859c955a54034640f905cb5d91a6a2f4..b4b3ff86f7c4afb94fc4da5133746391a078a7fb 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2353,6 +2353,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
+ // Lophine start - region thread skip reload // TODO - add reload support
+ public void saveDatapacks(Collection<String> selectedIds) {
+ this.packRepository.setSelected(selectedIds, false); // Paper - add pendingReload flag to determine required pack loading - false as this is *after* a reload (see above)
+ WorldDataConfiguration worldDataConfiguration = new WorldDataConfiguration(
+ getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()
+ );
+ this.worldData.setDataConfiguration(worldDataConfiguration);
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
+
public CompletableFuture<Void> reloadResources(Collection<String> selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
// Paper end - Add ServerResourcesReloadedEvent
CompletableFuture<Void> completableFuture = CompletableFuture.<ImmutableList>supplyAsync(
diff --git a/net/minecraft/server/commands/ReloadCommand.java b/net/minecraft/server/commands/ReloadCommand.java
index b540a3c3a02f7242578a63477e34b9063e86f1d2..9f0adf157872e1c68cd7752a24eacdc636e8d17a 100644
--- a/net/minecraft/server/commands/ReloadCommand.java
+++ b/net/minecraft/server/commands/ReloadCommand.java
@@ -16,6 +16,12 @@ public class ReloadCommand {
private static final Logger LOGGER = LogUtils.getLogger();
public static void reloadPacks(Collection<String> selectedIds, CommandSourceStack source) {
+ // Lophine start - region thread skip reload // TODO - add reload support
+ if (true) {
+ source.getServer().saveDatapacks(selectedIds);
+ return;
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
source.getServer().reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally(throwable -> { // Paper - Add ServerResourcesReloadedEvent
LOGGER.warn("Failed to execute reload", throwable);
source.sendFailure(Component.translatable("commands.reload.failure"));
@@ -5,10 +5,10 @@ Subject: [PATCH] Add config to enable scoreboard command
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index e0e9fcf3224f55f2ef31c8afca3154a5992f761b..6633af3cff77bcd4bcc591c032d647b7a850dced 100644
index 25ccbfa12d57d540195ecc16aa54a144f8ecc0e8..22d211bee83e3ef34d6ac81dd89c0e73924bee90 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -231,7 +231,11 @@ public class Commands {
@@ -229,7 +229,11 @@ public class Commands {
//RotateCommand.register(this.dispatcher); // Folia - region threading - TODO later
SayCommand.register(this.dispatcher);
//ScheduleCommand.register(this.dispatcher); // Folia - region threading
@@ -97,10 +97,10 @@ index c485d5b3ba95f2a969bdf0dab9654ac284976f8d..b957585cf7cf7f845e20f59c7b355722
this.scheduler.regionFailed(this, false, thr);
// regionFailed will schedule a shutdown, so we should avoid letting this region tick further
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index 6633af3cff77bcd4bcc591c032d647b7a850dced..dc5a558c72a5a7ab8a650262c6a1a2dc5142b8fc 100644
index 22d211bee83e3ef34d6ac81dd89c0e73924bee90..1fb359073342a657e9e493403263b56be5b3393f 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -251,7 +251,11 @@ public class Commands {
@@ -249,7 +249,11 @@ public class Commands {
TeleportCommand.register(this.dispatcher);
TellRawCommand.register(this.dispatcher, context);
//TestCommand.register(this.dispatcher, context); // Folia - region threading
@@ -114,7 +114,7 @@ index 6633af3cff77bcd4bcc591c032d647b7a850dced..dc5a558c72a5a7ab8a650262c6a1a2dc
TitleCommand.register(this.dispatcher, context);
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index b4b3ff86f7c4afb94fc4da5133746391a078a7fb..b156b5c635f931a3d4abc0584591f90476d011a6 100644
index 75027e73859c955a54034640f905cb5d91a6a2f4..2f4970242c754cfba8ff5656a63af07d9d0a723c 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -267,7 +267,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -5,10 +5,10 @@ Subject: [PATCH] Add config to enable waypoint command & bar
diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
index dc5a558c72a5a7ab8a650262c6a1a2dc5142b8fc..9eee09ecc18abcc5972f65938ba8607dfeede026 100644
index 1fb359073342a657e9e493403263b56be5b3393f..451a685782da46f5683ea6707289bbaf3193771f 100644
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -259,7 +259,11 @@ public class Commands {
@@ -257,7 +257,11 @@ public class Commands {
TimeCommand.register(this.dispatcher);
TitleCommand.register(this.dispatcher, context);
//TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later
@@ -0,0 +1,94 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 2 Jul 2025 23:36:25 +0800
Subject: [PATCH] Fix datapack command save function
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 2f4970242c754cfba8ff5656a63af07d9d0a723c..b156b5c635f931a3d4abc0584591f90476d011a6 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -2353,6 +2353,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
}
+ // Lophine start - region thread skip reload // TODO - add reload support
+ public void saveDatapacks(Collection<String> selectedIds) {
+ this.packRepository.setSelected(selectedIds, false); // Paper - add pendingReload flag to determine required pack loading - false as this is *after* a reload (see above)
+ WorldDataConfiguration worldDataConfiguration = new WorldDataConfiguration(
+ getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()
+ );
+ this.worldData.setDataConfiguration(worldDataConfiguration);
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
+
public CompletableFuture<Void> reloadResources(Collection<String> selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
// Paper end - Add ServerResourcesReloadedEvent
CompletableFuture<Void> completableFuture = CompletableFuture.<ImmutableList>supplyAsync(
diff --git a/net/minecraft/server/commands/DataPackCommand.java b/net/minecraft/server/commands/DataPackCommand.java
index a5032b34752061d61fd9ddb4db828d35e403f5e7..3eaa9c85d06f6ecc8ed639d8befa861439390eb7 100644
--- a/net/minecraft/server/commands/DataPackCommand.java
+++ b/net/minecraft/server/commands/DataPackCommand.java
@@ -98,7 +98,7 @@ public class DataPackCommand {
dispatcher.register(
Commands.literal("datapack")
.requires(Commands.hasPermission(2))
- /*.then( // Luminol - Add back read-only datapack command
+ .then(
Commands.literal("enable")
.then(
Commands.argument("name", StringArgumentType.string())
@@ -160,11 +160,11 @@ public class DataPackCommand {
.suggests(SELECTED_PACKS)
.executes(commandContext -> disablePack(commandContext.getSource(), getPack(commandContext, "name", false)))
)
- )*/ // Luminol - Add back read-only datapack command
+ )
.then(
Commands.literal("list")
.executes(commandContext -> listPacks(commandContext.getSource()))
- // .then(Commands.literal("available").executes(commandContext -> listAvailablePacks(commandContext.getSource()))) // Luminol - Add back read-only datapack command
+ .then(Commands.literal("available").executes(commandContext -> listAvailablePacks(commandContext.getSource())))
.then(Commands.literal("enabled").executes(commandContext -> listEnabledPacks(commandContext.getSource())))
)
.then(
@@ -252,7 +252,7 @@ public class DataPackCommand {
}
private static int listPacks(CommandSourceStack source) {
- return listEnabledPacks(source) ;// + listAvailablePacks(source); // Luminol - Add back read-only datapack command
+ return listEnabledPacks(source) + listAvailablePacks(source);
}
private static int listAvailablePacks(CommandSourceStack source) {
@@ -280,7 +280,7 @@ public class DataPackCommand {
private static int listEnabledPacks(CommandSourceStack source) {
PackRepository packRepository = source.getServer().getPackRepository();
- // packRepository.reload(); // Luminol - Add back read-only datapack command
+ packRepository.reload();
Collection<? extends Pack> selectedPacks = packRepository.getSelectedPacks();
if (selectedPacks.isEmpty()) {
source.sendSuccess(() -> Component.translatable("commands.datapack.list.enabled.none"), false);
diff --git a/net/minecraft/server/commands/ReloadCommand.java b/net/minecraft/server/commands/ReloadCommand.java
index b540a3c3a02f7242578a63477e34b9063e86f1d2..bc999de32931558d0d8dc26264653dd63c1cad19 100644
--- a/net/minecraft/server/commands/ReloadCommand.java
+++ b/net/minecraft/server/commands/ReloadCommand.java
@@ -16,11 +16,17 @@ public class ReloadCommand {
private static final Logger LOGGER = LogUtils.getLogger();
public static void reloadPacks(Collection<String> selectedIds, CommandSourceStack source) {
+ // Lophine start - region thread skip reload // TODO - add reload support
+ source.getServer().saveDatapacks(selectedIds);
+ source.getSender().sendMessage("You need to restart server to load datapacks update.");
+ if (false) {
source.getServer().reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally(throwable -> { // Paper - Add ServerResourcesReloadedEvent
LOGGER.warn("Failed to execute reload", throwable);
source.sendFailure(Component.translatable("commands.reload.failure"));
return null;
});
+ }
+ // Lophine end - region thread skip reload // TODO - add reload support
}
private static Collection<String> discoverNewPacks(PackRepository packRepository, WorldData worldData, Collection<String> selectedIds) {
@@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 29 Oct 2025 00:09:09 +0800
Subject: [PATCH] Leaves: Configurable trading with the void
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 923aca94520f456a13d4e4eb23e1499b3dbc920f..0ee9e51c5abfd9269bcf0bb29c4b8394b27af02a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2901,7 +2901,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// Spigot start
if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
// Paper start - Fix merchant inventory not closing on entity removal
- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
+ if (!fun.bm.lophine.config.modules.function.OldFeatureConfig.villagerVoidTrade && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Leaves - Configurable trading with the void
merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
}
// Paper end - Fix merchant inventory not closing on entity removal
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..6c1f4f943a5be5f2e43a82cca52ca76a98226915 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -649,7 +649,7 @@ public abstract class PlayerList {
player.stopRiding();
rootVehicle.getPassengersAndSelf().forEach(entity -> {
// Paper start - Fix villager boat exploit
- if (entity instanceof net.minecraft.world.entity.npc.AbstractVillager villager) {
+ if (!fun.bm.lophine.config.modules.function.OldFeatureConfig.villagerVoidTrade && entity instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { // Leaves - Configurable trading with the void
final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer();
if (human != null) {
villager.setTradingPlayer(null);
diff --git a/net/minecraft/world/inventory/MerchantMenu.java b/net/minecraft/world/inventory/MerchantMenu.java
index d59f67ffe34201c63e3d9706a4434f33b6732edb..c635c7211bff9d190b7035b284dda162334fd5c2 100644
--- a/net/minecraft/world/inventory/MerchantMenu.java
+++ b/net/minecraft/world/inventory/MerchantMenu.java
@@ -74,6 +74,7 @@ public class MerchantMenu extends AbstractContainerMenu {
@Override
public boolean stillValid(Player player) {
+ if (fun.bm.lophine.config.modules.function.OldFeatureConfig.villagerVoidTrade) return this.trader.getTradingPlayer() == player; // Leaves - Configurable trading with the void
if (!checkReachable) return true; // Paper - checkReachable
return this.trader.stillValid(player);
}
@@ -1,63 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 28 Sep 2025 13:21:30 +0800
Subject: [PATCH] Rewrite tickCount support
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index b957585cf7cf7f845e20f59c7b355722880f3b4e..9b279d003eb9f780ed2723c98b28d9f4d6e3a281 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -412,6 +412,8 @@ public final class TickRegionScheduler {
}
// Luminol end - Add tick command support
+ MinecraftServer.getServer().handleTickCount(tickCount); // Lophine - reuse tick count
+
if (!this.tryMarkTicking()) {
if (!this.cancelled.get()) {
throw new IllegalStateException("Scheduled region should be acquirable");
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index b156b5c635f931a3d4abc0584591f90476d011a6..0c2b7aafa8ddee36d38fabd3561e6f97fce828f9 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -305,6 +305,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Folia start - regionised ticking
public final io.papermc.paper.threadedregions.RegionizedServer regionizedServer = new io.papermc.paper.threadedregions.RegionizedServer();
+ private int tickCount; // Lophine - reuse tick count
+ private final ThreadLocal<Integer> lastTickCount = ThreadLocal.withInitial(() -> 0); // Lophine - reuse tick count
@Override
public <V> CompletableFuture<V> submit(java.util.function.Supplier<V> task) {
@@ -2227,9 +2229,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return false;
}
+ // Lophine start - reuse tick count
public int getTickCount() {
- throw new UnsupportedOperationException(); // Folia - region threading
+ return this.tickCount;
+ }
+
+ public boolean checkTickCount(int period) {
+ return this.checkTickCount(period, this.lastTickCount.get());
+ }
+
+ public boolean checkTickCount(int period, int lastTickCount) {
+ if (this.tickCount % period == 0) {
+ return true;
+ }
+
+ int nextPeriodTick = ((lastTickCount / period) + 1) * period;
+ return nextPeriodTick < this.tickCount;
+ }
+
+ public void handleTickCount(int deltaTicks) {
+ this.lastTickCount.set(this.getTickCount());
+ this.tickCount += deltaTicks;
}
+ // Lophine end - reuse tick count
public int getSpawnProtectionRadius() {
return 16;
@@ -446,10 +446,10 @@ index 10b2cc20bbf3b70e4e09dbbe14b90506e6366158..1535bb8cec8692e8d26eaff090288f1b
}
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index aca3303a043144894ae20ddc43abc55790db44ad..55cbf9b4cc568fb9b418433f597ed6b64e4409b0 100644
index 3fd172f82216fa35c36e5f563819a6f01ffd4ea4..419bc939e19a306b33d4d387007c6acbae73fba3 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -148,7 +148,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -144,7 +144,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public void setItem(int index, ItemStack stack) {
this.unpackLootTable(null);
this.getItems().set(index, stack);
@@ -458,7 +458,7 @@ index aca3303a043144894ae20ddc43abc55790db44ad..55cbf9b4cc568fb9b418433f597ed6b6
}
@Override
@@ -770,9 +770,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -740,9 +740,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
if (item.isEmpty()) {
// Spigot start - SPIGOT-6693, SimpleContainer#setItem
ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
@@ -469,4 +469,4 @@ index aca3303a043144894ae20ddc43abc55790db44ad..55cbf9b4cc568fb9b418433f597ed6b6
+ stack = stack.split(Math.min(destination.getMaxStackSize(), stack.getMaxStackSize())); // Leaves - item over-stack util
}
// Spigot end
//IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading // Luminol - Use vanilla hopper logic
//IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading // Luminol - vanilla hopper
@@ -23,7 +23,7 @@ index d0d90a25a10bbecfffceee1992af88c60d14fd87..187e48e78e69c80b25710f193813335e
+ // Leaves end - servux
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index eb3aef1f5198592eda5dafe84b91271e861b4135..576dde41a4b48aac9079cca902f4371a31470af3 100644
index 0ee9e51c5abfd9269bcf0bb29c4b8394b27af02a..d1a799199a68771ae3130a30a59625587c11b0b0 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2368,6 +2368,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 16 Nov 2025 17:47:48 +0800
Subject: [PATCH] LeavesHooks
diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
index d9fd093f59987f471627ff20cfd2eb1a3c0dea64..78c6d425589b8147c33583ac46d6f21ec5dbefaa 100644
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -29,7 +29,7 @@ import net.minecraft.world.phys.AABB;
import java.util.List;
import java.util.function.Predicate;
-public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks {
+public class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks {
private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger();
@@ -8,7 +8,7 @@ As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18
Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE)
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 593dbcda4b497291eb1ecdc64b947a8d279f8be6..cb863a36fc67e993956ec3be655e6e4b9764c051 100644
index 6c1f4f943a5be5f2e43a82cca52ca76a98226915..8e2775e873c92beed6a0e4e8ce1304ab8ca6e8d1 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1087,6 +1087,10 @@ public abstract class PlayerList {
@@ -39,7 +39,7 @@ index cf72cb8b418239b56c755486b03a321672e16bc3..b69c06607ca900beeebf4892a978442d
String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
if (awtException != null) {
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index c93feb64b6088a4878fcdb6e7c733a5f37e9385b..79c911003c7b1c3d94e7e9d3974eeadcc16bde3a 100644
index 392d75548f7eb271a6320ac30eb301a61071d465..0e278f7b9ed85ce8c3cedd9154f817b30ff1e48a 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -33,10 +33,10 @@ index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..04ae8de63af0a8abe578f14c8ef85fd4
private DisconnectionDetails disconnectionDetails;
private boolean encrypted;
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 0c2b7aafa8ddee36d38fabd3561e6f97fce828f9..e5fc1969b8af692d785214731b3f8cc47df2aa78 100644
index b156b5c635f931a3d4abc0584591f90476d011a6..81726e5782d25ff35a2bea8820a62786e46148b1 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -349,6 +349,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -347,6 +347,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Folia end - regionised ticking
@@ -45,7 +45,7 @@ index 0c2b7aafa8ddee36d38fabd3561e6f97fce828f9..e5fc1969b8af692d785214731b3f8cc4
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
AtomicReference<S> atomicReference = new AtomicReference<>();
@@ -1040,6 +1042,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1038,6 +1040,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Folia end - region threading
public void stopServer() {
@@ -57,7 +57,7 @@ index 0c2b7aafa8ddee36d38fabd3561e6f97fce828f9..e5fc1969b8af692d785214731b3f8cc4
// Folia start - region threading
// halt scheduler
// don't wait, we may be on a scheduler thread
@@ -1590,7 +1597,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1588,7 +1595,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
int i = this.pauseWhileEmptySeconds() * 20;
this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
if (false && i > 0) { // Folia - region threading - this is complicated to implement, and even if done correctly is messy
@@ -66,7 +66,7 @@ index 0c2b7aafa8ddee36d38fabd3561e6f97fce828f9..e5fc1969b8af692d785214731b3f8cc4
this.emptyTicks++;
} else {
this.emptyTicks = 0;
@@ -1914,6 +1921,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1912,6 +1919,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public void tickConnection() {
this.getConnection().tick();
@@ -74,7 +74,7 @@ index 0c2b7aafa8ddee36d38fabd3561e6f97fce828f9..e5fc1969b8af692d785214731b3f8cc4
}
private void synchronizeTime(ServerLevel level) {
@@ -3005,6 +3013,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2983,6 +2991,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return 0;
}
@@ -219,7 +219,7 @@ index f2286b96b8f40b4588f817913c42ae7b4a92340f..e6c7bbb023000b9de90c1256274ff5ab
playerList.op(gameProfile);
i++;
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 79c911003c7b1c3d94e7e9d3974eeadcc16bde3a..328428f0ef8a2305f661c167e6fe1f604b0d6890 100644
index 0e278f7b9ed85ce8c3cedd9154f817b30ff1e48a..09d4b1d68c3f046ef304016a1afe7a3038dfae3e 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -162,6 +162,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -239,18 +239,9 @@ index 79c911003c7b1c3d94e7e9d3974eeadcc16bde3a..328428f0ef8a2305f661c167e6fe1f60
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
if (this.convertOldUsers()) {
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 70740381c6501c1a518c52b24381edd16792507f..5e31b499a894113b4be1982a1071348fcbaf9ded 100644
index 70740381c6501c1a518c52b24381edd16792507f..8ad6d5a2522cfbe316fec2c3b918e2e1b9af3d1b 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -134,7 +134,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public
private final String storageName;
//private final PlayerMap playerMap = new PlayerMap(); // Folia - region threading
- //public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading
+ public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading
private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap();
// Paper - rewrite chunk system
public int serverViewDistance;
@@ -1309,6 +1309,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
} else if (this.seenBy.remove(player.connection)) {
@@ -266,7 +257,7 @@ index 70740381c6501c1a518c52b24381edd16792507f..5e31b499a894113b4be1982a1071348f
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 576dde41a4b48aac9079cca902f4371a31470af3..952b92c55d7192791a19ecebe80894f6cf992049 100644
index 89d72f242d0fc224701bdcdf245d948415e7c820..5efe7d94a364cec1bd18c4d8d62c19075d52f3e8 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -207,6 +207,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -298,7 +289,7 @@ index 576dde41a4b48aac9079cca902f4371a31470af3..952b92c55d7192791a19ecebe80894f6
@Override
public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) {
Optional<Holder<PoiType>> optional = PoiTypes.forState(oldState);
@@ -2826,6 +2834,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -2827,6 +2835,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
if (entity instanceof ServerPlayer serverPlayer) {
ServerLevel.this.players.add(serverPlayer);
@@ -310,7 +301,7 @@ index 576dde41a4b48aac9079cca902f4371a31470af3..952b92c55d7192791a19ecebe80894f6
if (serverPlayer.isReceivingWaypoints()) {
ServerLevel.this.getWaypointManager().addPlayer(serverPlayer);
}
@@ -2913,6 +2926,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -2914,6 +2927,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
ServerLevel.this.getChunkSource().removeEntity(entity);
if (entity instanceof ServerPlayer serverPlayer) {
ServerLevel.this.players.remove(serverPlayer);
@@ -434,7 +425,7 @@ index 144a2644c15f276f02bb3be859dc5d05a677ac55..c6a89e8936cffa49b3de153ed3c40e3c
boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByName.get(string);
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index cb863a36fc67e993956ec3be655e6e4b9764c051..908ec29a6e285122d1a8a43dc547b74af2697442 100644
index 8e2775e873c92beed6a0e4e8ce1304ab8ca6e8d1..580f114fd99b017403c61e994d0c947d00e5115d 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -425,6 +425,19 @@ public abstract class PlayerList {
@@ -445,7 +436,7 @@ index cb863a36fc67e993956ec3be655e6e4b9764c051..908ec29a6e285122d1a8a43dc547b74a
+ if (fun.bm.lophine.config.modules.function.FakeplayerConfig.enable) {
+ org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName());
+ if (bot != null) {
+ this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false);
+ this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, true);
+ }
+ this.server.getBotList().bots.forEach(bot1 -> {
+ bot1.sendPlayerInfo(player);
@@ -548,7 +539,7 @@ index 52fd730998535ea071bfc99b7cc2c254b9b656d7..0abf6f0265fcb916f2c2c76fb1313bd1
for (WaypointTransmitter waypointTransmitter : this.waypoints) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 47ef63061737f3efdb0ce7ec71a8d9c920385bb0..674c4cc7443545f7e333d2bcec9e5fc98000ecae 100644
index a8b6e89035579557b59a20ddbe9b32943e6b7164..477241699752567df7695d33fa629fb1d5109e0a 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1225,7 +1225,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 29788fedd54b8411c9c8872b4d83b037874fb5f0..24af6821694768058dc0146220877ab70066b5f9 100644
index f8bc2679d9eeaa5d179ab70a4cc5c97522bcc674..a536dc4e09b22b69bcdf59871a9cccfcf8129395 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -754,6 +754,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 908ec29a6e285122d1a8a43dc547b74af2697442..1bf1731e43278b71423fee5d92401c48d6dabaca 100644
index 580f114fd99b017403c61e994d0c947d00e5115d..11c1eb6995ec3aa896815efa59477096c39089d1 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1247,6 +1247,7 @@ public abstract class PlayerList {
@@ -0,0 +1,95 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 27 Oct 2025 15:38:29 +0800
Subject: [PATCH] Leaves: Wool Hopper Counter
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: MIT
This patch is Powered by fabric-carpet(https://github.com/gnembon/fabric-carpet)
diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index 419bc939e19a306b33d4d387007c6acbae73fba3..96445438c5b4202e3fc93bece778ad0d7312d41c 100644
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -228,8 +228,30 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items
}
+ // Leaves start - Wool hopper counter
+ if (fun.bm.lophine.config.modules.function.WoolHopperCounterConfig.unlimitedSpeed && org.leavesmc.leaves.util.HopperCounter.isEnabled()) {
+ net.minecraft.world.item.DyeColor woolColor = org.leavesmc.leaves.util.WoolUtils.getWoolColorAtPosition(level, blockEntity.getBlockPos().relative(state.getValue(HopperBlock.FACING)));
+ if (woolColor != null) {
+ for (int i = 0; i < Short.MAX_VALUE; i++) {
+ flag |= suckInItems(level, blockEntity);
+ if (!flag) {
+ break;
+ } else {
+ woolHopperCounter(level, pos, state, HopperBlockEntity.getContainerAt(level, pos));
+ }
+ }
+ }
+ }
+ // Leaves end - Wool hopper counter
+
if (flag) {
blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
+ // Leaves start - Wool hopper counter
+ if (fun.bm.lophine.config.modules.function.WoolHopperCounterConfig.unlimitedSpeed && org.leavesmc.leaves.util.HopperCounter.isEnabled() && woolHopperCounter(level, pos, state, HopperBlockEntity.getContainerAt(level, pos))) {
+ blockEntity.setCooldown(0);
+ return true;
+ }
+ // Leaves end - Wool hopper counter
setChanged(level, pos, state);
// Leaves start - Lithium Sleeping Block Entity
if (me.earthme.luminol.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled
@@ -461,6 +483,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
// Paper end - Perf: Optimize Hoppers
private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) {
+ // Leaves start - hopper counter
+ if (org.leavesmc.leaves.util.HopperCounter.isEnabled()) {
+ if (woolHopperCounter(level, pos, level.getBlockState(pos), HopperBlockEntity.getContainerAt(level, pos))) {
+ return true;
+ }
+ }
+ // Leaves end - hopper counter
Container attachedContainer = me.earthme.luminol.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled ? blockEntity.getInsertInventory(level) : getAttachedContainer(level, pos, blockEntity); // Leaves - Lithium Sleeping Block Entity
if (attachedContainer == null) {
return false;
@@ -536,6 +565,26 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
}
+ // Leaves start - hopper counter
+ private static boolean woolHopperCounter(Level level, BlockPos blockPos, BlockState state, @Nullable Container container) {
+ if (container == null) {
+ return false;
+ }
+ net.minecraft.world.item.DyeColor woolColor = org.leavesmc.leaves.util.WoolUtils.getWoolColorAtPosition(level, blockPos.relative(state.getValue(HopperBlock.FACING)));
+ if (woolColor != null) {
+ for (int i = 0; i < container.getContainerSize(); ++i) {
+ if (!container.getItem(i).isEmpty()) {
+ ItemStack itemstack = container.getItem(i);
+ org.leavesmc.leaves.util.HopperCounter.getCounter(woolColor).add(level, itemstack);
+ container.setItem(i, ItemStack.EMPTY);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ // Leaves end - hopper counter
+
private static int[] getSlots(Container container, Direction direction) {
if (container instanceof WorldlyContainer worldlyContainer) {
return worldlyContainer.getSlotsForFace(direction);
@@ -680,6 +729,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
public static boolean addItem(Container container, ItemEntity item) {
+ if (fun.bm.lophine.config.modules.function.WoolHopperCounterConfig.unlimitedSpeed && org.leavesmc.leaves.util.HopperCounter.isEnabled() && item.isRemoved()) return false; // Leaves - Wool hopper counter
boolean flag = false;
// CraftBukkit start
if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers
@@ -0,0 +1,99 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Mon, 3 Feb 2025 13:51:26 +0800
Subject: [PATCH] Leaves: Alternative block placement Protocol
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: MIT
This patch is Powered by
carpet-extra(https://github.com/gnembon/carpet-extra)
MasaGadget(https://github.com/plusls/MasaGadget)
litematica(https://github.com/maruohon/litematica)
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 9be4bc9e78c22c12ba594b16dac6901023f0fd47..3d9c26bc8cc14e98310f557f351871b082e5023e 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2087,7 +2087,7 @@ public class ServerGamePacketListenerImpl
if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.level(), blockPos.getX() >> 4, blockPos.getZ() >> 4, 8) && this.player.canInteractWithBlock(blockPos, 1.0)) { // Folia - do not allow players to interact with blocks outside the current region
Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos));
double d = 1.0000001;
- if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) {
+ if (fun.bm.lophine.config.modules.function.protocol.AlternativeBlockPlacementProtocolConfig.needIgnoreDistance() ||Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { // Leaves - disableDistanceCheckForUseItem
Direction direction = hitResult.getDirection();
this.player.resetLastActionTime();
int maxY = this.player.level().getMaxY();
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
index 6db566adf2d0df1d26221eda04aa01738df6d3d2..8f112f382190d606a87f38da8bcdf8dd5f31d1c5 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -149,7 +149,7 @@ public class BlockItem extends Item {
@Nullable
protected BlockState getPlacementState(BlockPlaceContext context) {
- BlockState stateForPlacement = this.getBlock().getStateForPlacement(context);
+ BlockState stateForPlacement = this.getBlock().getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement
return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null;
}
diff --git a/net/minecraft/world/item/StandingAndWallBlockItem.java b/net/minecraft/world/item/StandingAndWallBlockItem.java
index 12c6c8aeec89a0a55633c62fe98f5a3aa75fd476..1f0e7c391d02b18e2c89700025713ec3d759f2ea 100644
--- a/net/minecraft/world/item/StandingAndWallBlockItem.java
+++ b/net/minecraft/world/item/StandingAndWallBlockItem.java
@@ -27,14 +27,14 @@ public class StandingAndWallBlockItem extends BlockItem {
@Nullable
@Override
protected BlockState getPlacementState(BlockPlaceContext context) {
- BlockState stateForPlacement = this.wallBlock.getStateForPlacement(context);
+ BlockState stateForPlacement = this.wallBlock.getRealStateForPlacement(context); // Leaves - alternativeBlockPlacement
BlockState blockState = null;
LevelReader level = context.getLevel();
BlockPos clickedPos = context.getClickedPos();
for (Direction direction : context.getNearestLookingDirections()) {
if (direction != this.attachmentDirection.getOpposite()) {
- BlockState blockState1 = direction == this.attachmentDirection ? this.getBlock().getStateForPlacement(context) : stateForPlacement;
+ BlockState blockState1 = direction == this.attachmentDirection ? this.getBlock().getRealStateForPlacement(context) : stateForPlacement; // Leaves - alternativeBlockPlacement
if (blockState1 != null && this.canPlace(level, blockState1, clickedPos)) {
blockState = blockState1;
break;
diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java
index 644b9fab1d6729bf3964ebee0d6e9c46f89b8c46..634bdb554dfe757c3c9f13a79c3f644d8be4e3ea 100644
--- a/net/minecraft/world/level/block/Block.java
+++ b/net/minecraft/world/level/block/Block.java
@@ -439,6 +439,33 @@ public class Block extends BlockBehaviour implements ItemLike {
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
}
+ // Leaves start - alternativeBlockPlacement
+ @Nullable
+ public BlockState getRealStateForPlacement(BlockPlaceContext ctx) {
+ BlockState vanillaState = this.getStateForPlacement(ctx);
+ switch (fun.bm.lophine.config.modules.function.protocol.AlternativeBlockPlacementProtocolConfig.alternativeBlockPlacement) {
+ case fun.bm.lophine.enums.EnumAlternativePlaceType.CARPET -> {
+ BlockState tryState = org.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacement(this, ctx);
+ if (tryState != null) {
+ return tryState;
+ }
+ }
+ case fun.bm.lophine.enums.EnumAlternativePlaceType.CARPET_FIX -> {
+ BlockState tryState = org.leavesmc.leaves.protocol.CarpetAlternativeBlockPlacement.alternativeBlockPlacementFix(this, ctx);
+ if (tryState != null) {
+ return tryState;
+ }
+ }
+ case fun.bm.lophine.enums.EnumAlternativePlaceType.LITEMATICA -> {
+ if (vanillaState != null) {
+ return org.leavesmc.leaves.protocol.LitematicaEasyPlaceProtocol.applyPlacementProtocol(vanillaState, ctx);
+ }
+ }
+ }
+ return vanillaState;
+ }
+ // Leaves end - alternativeBlockPlacement
+
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState();
@@ -0,0 +1,562 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 16 Nov 2025 15:48:12 +0800
Subject: [PATCH] Leaves: Replay Mod API
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves) & ReplayMod (https://github.com/ReplayMod)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
This patch is Powered by ReplayMod (https://github.com/ReplayMod)
diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java
index 8c005a7b9480da7c124c5bd5bfccd5ca83abbcb1..a814a67f321662f05e2c5f81666f75e128bf4ea7 100644
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -580,7 +580,7 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
@Override
public Collection<String> getOnlinePlayerNames() {
- return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().getName()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players
+ return this.entity instanceof ServerPlayer sourcePlayer && !(sourcePlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer) && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().getName()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players // Leaves - only real player
}
@Override
diff --git a/net/minecraft/commands/arguments/selector/EntitySelector.java b/net/minecraft/commands/arguments/selector/EntitySelector.java
index 514f8fbdeb776087608665c35de95294aadf5cf0..b75772897cabc3e7c59301d451685378fa55b6c3 100644
--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
+++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
@@ -128,11 +128,12 @@ public class EntitySelector {
return this.findPlayers(source);
} else if (this.playerName != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName);
+ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer
return playerByName == null ? List.of() : List.of(playerByName);
} else if (this.entityUUID != null) {
for (ServerLevel serverLevel : source.getServer().getAllLevels()) {
Entity entity = serverLevel.getEntity(this.entityUUID);
- if (entity != null) {
+ if (entity != null && !(entity instanceof org.leavesmc.leaves.replay.ServerPhotographer)) {
if (entity.getType().isEnabled(source.enabledFeatures())) {
return List.of(entity);
}
@@ -146,7 +147,7 @@ public class EntitySelector {
AABB absoluteAabb = this.getAbsoluteAabb(vec3);
if (this.currentEntity) {
Predicate<Entity> predicate = this.getPredicate(vec3, absoluteAabb, null);
- return source.getEntity() != null && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of();
+ return source.getEntity() != null && !(source.getEntity() instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of(); // Leaves - skip photographer
} else {
Predicate<Entity> predicate = this.getPredicate(vec3, absoluteAabb, source.enabledFeatures());
List<Entity> list = new ObjectArrayList<>();
@@ -157,6 +158,7 @@ public class EntitySelector {
this.addEntities(list, serverLevel1, absoluteAabb, predicate);
}
}
+ list.removeIf(entity -> entity instanceof org.leavesmc.leaves.replay.ServerPhotographer); // Leaves - skip photographer
return this.sortAndLimit(vec3, list);
}
@@ -192,9 +194,11 @@ public class EntitySelector {
this.checkPermissions(source);
if (this.playerName != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName);
+ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer
return playerByName == null ? List.of() : List.of(playerByName);
} else if (this.entityUUID != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayer(this.entityUUID);
+ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer
return playerByName == null ? List.of() : List.of(playerByName);
} else {
Vec3 vec3 = this.position.apply(source.getPosition());
@@ -206,11 +210,11 @@ public class EntitySelector {
int resultLimit = this.getResultLimit();
List<ServerPlayer> players;
if (this.isWorldLimited()) {
- players = source.getLevel().getPlayers(predicate, resultLimit);
+ players = source.getLevel().getPlayers((player -> !(player instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(player)), resultLimit); // Leaves - skip photographer
} else {
players = new ObjectArrayList<>();
- for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().getPlayers()) {
+ for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().realPlayers) { // Leaves - only real players
if (predicate.test(serverPlayer1)) {
players.add(serverPlayer1);
if (players.size() >= resultLimit) {
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 81726e5782d25ff35a2bea8820a62786e46148b1..3c94631f468f4ee1b46de9981c17171e5901d745 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1800,7 +1800,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
private ServerStatus.Players buildPlayerStatus() {
- List<ServerPlayer> players = new java.util.ArrayList<>(this.playerList.getPlayers()); // Folia - region threading
+ List<ServerPlayer> players = new java.util.ArrayList<>(this.playerList.realPlayers); // Folia - region threading
int maxPlayers = this.getMaxPlayers();
if (this.hidesOnlinePlayers()) {
return new ServerStatus.Players(maxPlayers, players.size(), List.of());
@@ -2001,7 +2001,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@Override
public int getPlayerCount() {
- return this.playerList.getPlayerCount();
+ return this.playerList.realPlayers.size(); // Leaves - only real player
}
@Override
@@ -2415,7 +2415,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (Thread.currentThread() != this.serverThread) return; // Paper
// Paper start - we don't need to save everything, just advancements
// this.getPlayerList().saveAll();
- for (final ServerPlayer player : this.getPlayerList().getPlayers()) {
+ for (final ServerPlayer player : this.getPlayerList().realPlayers) { // Leaves - only real players
player.getAdvancements().save();
}
// Paper end - we don't need to save everything, just advancements
@@ -2554,7 +2554,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (!playerList.isUsingWhitelist()) return; // Paper - whitelist not enabled
UserWhiteList whiteList = playerList.getWhiteList();
- for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
+ for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.realPlayers)) { // Leaves - only real player
if (!whiteList.isWhiteListed(serverPlayer.getGameProfile()) && !this.getPlayerList().isOp(serverPlayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
serverPlayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
}
diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java
index 5c0e338dc1b0eb5724d10a73d6fc7975f9d2e5e5..0e7e4246837353a5936a6388f23b6b4b2ec1d5f3 100644
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -168,7 +168,7 @@ public class PlayerAdvancements {
public boolean award(AdvancementHolder advancement, String criterionKey) {
// Leaves start - bot can't get advancement
- if (player instanceof org.leavesmc.leaves.bot.ServerBot) {
+ if (player instanceof org.leavesmc.leaves.bot.ServerBot || player instanceof org.leavesmc.leaves.replay.ServerPhotographer) { // Leaves - and photographer
return false;
}
// Leaves end - bot can't get advancement
diff --git a/net/minecraft/server/ServerScoreboard.java b/net/minecraft/server/ServerScoreboard.java
index fc1c377ad05edcefacd407671801ae605f3fc31a..5635f0940b4b70f27c8dfdc1aa666fcb7e26d55d 100644
--- a/net/minecraft/server/ServerScoreboard.java
+++ b/net/minecraft/server/ServerScoreboard.java
@@ -242,7 +242,7 @@ public class ServerScoreboard extends Scoreboard {
public void startTrackingObjective(Objective objective) {
List<Packet<?>> startTrackingPackets = this.getStartTrackingPackets(objective);
- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+ for (ServerPlayer serverPlayer : this.server.getPlayerList().realPlayers) { // Leaves - only real players
if (serverPlayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
for (Packet<?> packet : startTrackingPackets) {
serverPlayer.connection.send(packet);
@@ -268,7 +268,7 @@ public class ServerScoreboard extends Scoreboard {
public void stopTrackingObjective(Objective objective) {
List<Packet<?>> stopTrackingPackets = this.getStopTrackingPackets(objective);
- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+ for (ServerPlayer serverPlayer : this.server.getPlayerList().realPlayers) { // Leaves - only real players
if (serverPlayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
for (Packet<?> packet : stopTrackingPackets) {
serverPlayer.connection.send(packet);
diff --git a/net/minecraft/server/commands/DefaultGameModeCommands.java b/net/minecraft/server/commands/DefaultGameModeCommands.java
index c2ab47bb97c4fbbbc48a8ff0538ab8fd38532a88..2b7d11195f96e68a004e0e060e19d4004bb8a154 100644
--- a/net/minecraft/server/commands/DefaultGameModeCommands.java
+++ b/net/minecraft/server/commands/DefaultGameModeCommands.java
@@ -27,7 +27,7 @@ public class DefaultGameModeCommands {
server.setDefaultGameType(gamemode);
GameType forcedGameType = server.getForcedGameType();
if (forcedGameType != null) {
- for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+ for (ServerPlayer serverPlayer : server.getPlayerList().realPlayers) { // Leaves - only real players
serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { // Folia - region threading
// Paper start - Expand PlayerGameModeChangeEvent
org.bukkit.event.player.PlayerGameModeChangeEvent event = player.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); // Folia - region threading
diff --git a/net/minecraft/server/commands/ListPlayersCommand.java b/net/minecraft/server/commands/ListPlayersCommand.java
index c6ae34f91b3629990294fc5e69237a1e600ef038..2109b0a2d4099e64c34cd1c45b83f72654d3b615 100644
--- a/net/minecraft/server/commands/ListPlayersCommand.java
+++ b/net/minecraft/server/commands/ListPlayersCommand.java
@@ -33,7 +33,7 @@ public class ListPlayersCommand {
private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameExtractor) {
PlayerList playerList = source.getServer().getPlayerList();
// CraftBukkit start
- List<ServerPlayer> playersTemp = playerList.getPlayers();
+ List<ServerPlayer> playersTemp = playerList.realPlayers;
if (source.getBukkitSender() instanceof org.bukkit.entity.Player) {
org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender();
playersTemp = playersTemp.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList());
diff --git a/net/minecraft/server/commands/OpCommand.java b/net/minecraft/server/commands/OpCommand.java
index e6c7bbb023000b9de90c1256274ff5aba4a6478a..98f8804d58616592332e2a968282be6ad8903ea8 100644
--- a/net/minecraft/server/commands/OpCommand.java
+++ b/net/minecraft/server/commands/OpCommand.java
@@ -25,7 +25,7 @@ public class OpCommand {
(commandContext, suggestionsBuilder) -> {
PlayerList playerList = commandContext.getSource().getServer().getPlayerList();
return SharedSuggestionProvider.suggest(
- playerList.getPlayers()
+ playerList.realPlayers // Leaves - only real player
.stream()
.filter(serverPlayer -> !playerList.isOp(serverPlayer.getGameProfile()))
.map(serverPlayer -> serverPlayer.getGameProfile().getName()),
diff --git a/net/minecraft/server/commands/ParticleCommand.java b/net/minecraft/server/commands/ParticleCommand.java
index 33d96239f4b72a5587dc70f9602847a870d6d6a5..a83ce2cd112fca02cf3545f8c38e5cafae2c7c0e 100644
--- a/net/minecraft/server/commands/ParticleCommand.java
+++ b/net/minecraft/server/commands/ParticleCommand.java
@@ -36,7 +36,7 @@ public class ParticleCommand {
0.0F,
0,
false,
- commandContext.getSource().getServer().getPlayerList().getPlayers()
+ commandContext.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player
)
)
.then(
@@ -50,7 +50,7 @@ public class ParticleCommand {
0.0F,
0,
false,
- context1.getSource().getServer().getPlayerList().getPlayers()
+ context1.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player
)
)
.then(
@@ -68,7 +68,7 @@ public class ParticleCommand {
FloatArgumentType.getFloat(context1, "speed"),
IntegerArgumentType.getInteger(context1, "count"),
false,
- context1.getSource().getServer().getPlayerList().getPlayers()
+ context1.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player
)
)
.then(
@@ -82,7 +82,7 @@ public class ParticleCommand {
FloatArgumentType.getFloat(context1, "speed"),
IntegerArgumentType.getInteger(context1, "count"),
true,
- context1.getSource().getServer().getPlayerList().getPlayers()
+ context1.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player
)
)
.then(
@@ -112,7 +112,7 @@ public class ParticleCommand {
FloatArgumentType.getFloat(context1, "speed"),
IntegerArgumentType.getInteger(context1, "count"),
false,
- context1.getSource().getServer().getPlayerList().getPlayers()
+ context1.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player
)
)
.then(
diff --git a/net/minecraft/server/commands/TeamMsgCommand.java b/net/minecraft/server/commands/TeamMsgCommand.java
index 134d7b1a9d5a5a47ebf4aabff110dde914cd6fe1..894dd1d048904b8a775416ea6cb3112215c567bc 100644
--- a/net/minecraft/server/commands/TeamMsgCommand.java
+++ b/net/minecraft/server/commands/TeamMsgCommand.java
@@ -40,7 +40,7 @@ public class TeamMsgCommand {
} else {
List<ServerPlayer> list = commandSourceStack.getServer()
.getPlayerList()
- .getPlayers()
+ .realPlayers // Leaves - only real players
.stream()
.filter(player -> player == entityOrException || player.getTeam() == team)
.toList();
diff --git a/net/minecraft/server/commands/WhitelistCommand.java b/net/minecraft/server/commands/WhitelistCommand.java
index 763a51d9a0859296eb4ea52b6879d6c7703db673..25b4fcf79dbf3e227451b3321d8fd38bab55ffe9 100644
--- a/net/minecraft/server/commands/WhitelistCommand.java
+++ b/net/minecraft/server/commands/WhitelistCommand.java
@@ -43,7 +43,7 @@ public class WhitelistCommand {
(commandContext, suggestionsBuilder) -> {
PlayerList playerList = commandContext.getSource().getServer().getPlayerList();
return SharedSuggestionProvider.suggest(
- playerList.getPlayers()
+ playerList.realPlayers // Leaves - only real player
.stream()
.filter(serverPlayer -> !playerList.getWhiteList().isWhiteListed(serverPlayer.getGameProfile()))
.map(serverPlayer -> serverPlayer.getGameProfile().getName()),
diff --git a/net/minecraft/server/gui/PlayerListComponent.java b/net/minecraft/server/gui/PlayerListComponent.java
index f5ba0c9a4c3f9eaa38eeb689de915c25c7165433..24bbc32bc17802edbd9cc14310fe8141c0ad85b0 100644
--- a/net/minecraft/server/gui/PlayerListComponent.java
+++ b/net/minecraft/server/gui/PlayerListComponent.java
@@ -17,8 +17,8 @@ public class PlayerListComponent extends JList<String> {
if (this.tickCount++ % 20 == 0) {
Vector<String> list = new Vector<>();
- for (int i = 0; i < this.server.getPlayerList().getPlayers().size(); i++) {
- list.add(this.server.getPlayerList().getPlayers().get(i).getGameProfile().getName());
+ for (int i = 0; i < this.server.getPlayerList().realPlayers.size(); i++) { // Leaves - only real players
+ list.add(this.server.getPlayerList().realPlayers.get(i).getGameProfile().getName()); // Leaves - only real players
}
this.setListData(list);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 5efe7d94a364cec1bd18c4d8d62c19075d52f3e8..a4ee2bcdb31f889c7edbbc422636782c0d5d0af6 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2836,7 +2836,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (entity instanceof ServerPlayer serverPlayer) {
ServerLevel.this.players.add(serverPlayer);
// Leaves start - skip
- if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot)) {
+ if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot) && !(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // and photographer
ServerLevel.this.realPlayers.add(serverPlayer);
}
// Leaves end - skip
@@ -2928,7 +2928,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (entity instanceof ServerPlayer serverPlayer) {
ServerLevel.this.players.remove(serverPlayer);
// Leaves start - skip
- if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot)) {
+ if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot) && !(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // and photographer
ServerLevel.this.realPlayers.remove(serverPlayer);
}
// Leaves end - skip
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 4b2dba8b30b72fb3acc00957b3e19d6fc178c99e..afd607df83fda3de6f53f55ad305c8f69fe6ddb6 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -194,7 +194,7 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.slf4j.Logger;
public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
- private static final Logger LOGGER = LogUtils.getLogger();
+ protected static final Logger LOGGER = LogUtils.getLogger();
public static final long LAST_SAVE_ABSENT = Long.MIN_VALUE; public long lastSave = LAST_SAVE_ABSENT; // Paper // Folia - threaded regions - changed to nanoTime
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 11c1eb6995ec3aa896815efa59477096c39089d1..54e7ddf0b3949e02d2705ff7626010c947965938 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -130,6 +130,7 @@ public abstract class PlayerList {
private boolean allowCommandsForAllPlayers;
private static final boolean ALLOW_LOGOUTIVATOR = false;
private int sendAllPlayerInfoIn;
+ public final List<ServerPlayer> realPlayers = new java.util.concurrent.CopyOnWriteArrayList(); // Leaves - replay api
// CraftBukkit start
private org.bukkit.craftbukkit.CraftServer cserver;
@@ -213,6 +214,122 @@ public abstract class PlayerList {
abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
+ // Leaves start - replay mod api
+ public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver) {
+ player.isRealPlayer = true; // Paper
+ player.loginTime = System.currentTimeMillis(); // Paper
+
+ ServerLevel worldserver1 = worldserver;
+
+ player.setServerLevel(worldserver1);
+ player.spawnIn(worldserver1);
+ player.gameMode.setLevel((ServerLevel) player.level());
+
+ LevelData worlddata = worldserver1.getLevelData();
+
+ player.loadGameTypes(null);
+ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile, false));
+ GameRules gamerules = worldserver1.getGameRules();
+ boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN);
+ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
+ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
+
+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); // Paper - replace old player chunk management
+ player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
+ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
+ playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities()));
+ playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot()));
+ RecipeManager craftingmanager = this.server.getRecipeManager();
+ playerconnection.send(new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes()));
+
+ this.sendPlayerPermissionLevel(player);
+ player.getStats().markAllDirty();
+ player.getRecipeBook().sendInitialRecipeBook(player);
+ this.updateEntireScoreboard(worldserver1.getScoreboard(), player);
+ this.server.invalidateStatus();
+
+ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
+ ServerStatus serverping = this.server.getStatus();
+
+ if (serverping != null) {
+ player.sendServerStatus(serverping);
+ }
+
+ this.players.add(player);
+ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
+ this.playersByUUID.put(player.getUUID(), player);
+
+ player.supressTrackerForLogin = true;
+ worldserver1.addNewPlayer(player);
+ this.server.getCustomBossEvents().onPlayerConnect(player);
+ org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity();
+
+ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer);
+ if (!player.connection.isAcceptingMessages()) {
+ return;
+ }
+
+ // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
+
+ // Leaves start - bot support
+ if (fun.bm.lophine.config.modules.function.FakeplayerConfig.enable) {
+ org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName());
+ if (bot != null) {
+ this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false);
+ }
+ this.server.getBotList().bots.forEach(bot1 -> {
+ bot1.sendPlayerInfo(player);
+ bot1.sendFakeDataIfNeed(player, true);
+ }); // Leaves - render bot
+ }
+ // Leaves end - bot support
+
+ final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1);
+ final List<ServerPlayer> playerList = List.copyOf(this.players); // Lophine - copy to avoid ConcurrentModificationException
+ for (ServerPlayer serverPlayer : playerList) {
+ if (serverPlayer == player || !bukkitPlayer.canSee(serverPlayer.getBukkitEntity())) {
+ continue;
+ }
+
+ // Leaves start - skip photographer
+ if (serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer) {
+ continue;
+ }
+ // Leaves end - skip photographer
+
+ onlinePlayers.add(serverPlayer);
+ }
+ if (!onlinePlayers.isEmpty()) {
+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player));
+ }
+
+ player.sentListPacket = true;
+ player.supressTrackerForLogin = false;
+ player.level().getChunkSource().chunkMap.addEntity(player);
+
+ this.sendLevelInfo(player, worldserver1);
+
+ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) {
+ worldserver1.addNewPlayer(player);
+ this.server.getCustomBossEvents().onPlayerConnect(player);
+ }
+
+ worldserver1 = player.level();
+ for (MobEffectInstance mobeffect : player.getActiveEffects()) {
+ playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect, false));
+ }
+
+ if (player.isDeadOrDying()) {
+ net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
+ .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
+ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains),
+ worldserver1.getLightEngine(), null, null, false)
+ );
+ }
+ }
+ // Leaves end - replay mod api
+
public void loadSpawnForNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie, org.apache.commons.lang3.mutable.MutableObject<net.minecraft.util.ProblemReporter.ScopedCollector> scopedCollectorStore, org.apache.commons.lang3.mutable.MutableObject<ValueInput> data, org.apache.commons.lang3.mutable.MutableObject<String> lastKnownName, ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete) { // Folia - region threading - rewrite login process
player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
@@ -398,6 +515,7 @@ public abstract class PlayerList {
// player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
this.players.add(player);
+ this.realPlayers.add(player); // Leaves - replay api
this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
this.playersByUUID.put(player.getUUID(), player);
// this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below
@@ -601,6 +719,7 @@ public abstract class PlayerList {
}
protected void save(ServerPlayer player) {
+ if (player instanceof org.leavesmc.leaves.replay.ServerPhotographer) return; // Leaves - skip photographer
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking
this.playerIo.save(player);
@@ -615,6 +734,43 @@ public abstract class PlayerList {
}
}
+ // Leaves start - replay mod api
+ public void removePhotographer(org.leavesmc.leaves.replay.ServerPhotographer entityplayer) {
+ ServerLevel worldserver = entityplayer.level();
+
+ entityplayer.awardStat(Stats.LEAVE_GAME);
+
+ if (entityplayer.containerMenu != entityplayer.inventoryMenu) {
+ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT);
+ }
+
+ if (server.isSameThread()) entityplayer.doTick();
+
+ if (this.collideRuleTeamName != null) {
+ final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard();
+ final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName);
+ if (entityplayer.getTeam() == team && team != null) {
+ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team);
+ }
+ }
+
+ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
+ entityplayer.retireScheduler();
+ entityplayer.getAdvancements().stopListening();
+ this.players.remove(entityplayer);
+ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT));
+ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer);
+ UUID uuid = entityplayer.getUUID();
+ ServerPlayer entityplayer1 = this.playersByUUID.get(uuid);
+
+ if (entityplayer1 == entityplayer) {
+ this.playersByUUID.remove(uuid);
+ }
+
+ this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
+ }
+ // Leaves stop - replay mod api
+
public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player) { // CraftBukkit - return string // Paper - return Component
// Paper start - Fix kick event leave message not being sent
return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
@@ -688,6 +844,7 @@ public abstract class PlayerList {
player.retireScheduler(); // Paper - Folia schedulers
player.getAdvancements().stopListening();
this.players.remove(player);
+ this.realPlayers.remove(player); // Leaves - replay api
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
this.server.getCustomBossEvents().onPlayerDisconnect(player);
UUID uuid = player.getUUID();
@@ -1044,15 +1201,15 @@ public abstract class PlayerList {
}
public String[] getPlayerNamesArray() {
- List<ServerPlayer> players = new java.util.ArrayList<>(this.players); // Folia - region threading
- String[] strings = new String[this.players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support
+ List<ServerPlayer> players = new java.util.ArrayList<>(this.realPlayers); // Folia - region threading
+ String[] strings = new String[players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support, and skip photographer
- for (int i = 0; i < players.size(); i++) { // Folia - region threading
- strings[i] = players.get(i).getGameProfile().getName(); // Folia - region threading
+ for (int i = 0; i < players.size(); i++) { // Folia - region threading // Leaves - only real players
+ strings[i] = players.get(i).getGameProfile().getName(); // Folia - region threading // Leaves - only real players
}
// Leaves start - fakeplayer support
- for (int i = this.players.size(); i < strings.length; ++i) {
- strings[i] = this.server.getBotList().bots.get(i - this.players.size()).getGameProfile().getName();
+ for (int i = players.size(); i < strings.length; ++i) { // Leaves - only real players
+ strings[i] = this.server.getBotList().bots.get(i - players.size()).getGameProfile().getName(); // Leaves - only real players
}
// Leaves end - fakeplayer support
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index 12466e0294123a5922ee7694984d4d277a390ddd..205e2648569b7c18f488aa4d3d9ec944bb1babf4 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -257,7 +257,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
protected ItemStack useItem = ItemStack.EMPTY;
public int useItemRemaining;
protected int fallFlyTicks;
- private BlockPos lastPos;
+ protected BlockPos lastPos;
private Optional<BlockPos> lastClimbablePos = Optional.empty();
@Nullable
private DamageSource lastDamageSource;
@@ -0,0 +1,217 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 15 Nov 2025 01:06:20 +0800
Subject: [PATCH] Leaves: Catch update suppression crash
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/network/protocol/PacketUtils.java b/net/minecraft/network/protocol/PacketUtils.java
index 7280ca2250ade4166a1a883b205bbc89f4e00d1a..06268ac3420fc03a798a20bb617ba89a032df527 100644
--- a/net/minecraft/network/protocol/PacketUtils.java
+++ b/net/minecraft/network/protocol/PacketUtils.java
@@ -30,7 +30,20 @@ public class PacketUtils {
final int packetTimerId = profiler.getOrCreateTimerAndStart(() -> "Packet Handler: ".concat(io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(packet.getClass().getName()))); try { // Folia - profiler
packet.handle(processor);
} finally { profiler.stopTimer(packetTimerId); } // Folia - profiler
+ // Leaves start - update suppression crash fix
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ if (processor instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) {
+ exception.providePlayer(gamePacketListener.player);
+ }
+ exception.consume();
} catch (Exception var4) {
+ if (var4.getCause() instanceof org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ if (processor instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) {
+ exception.providePlayer(gamePacketListener.player);
+ }
+ exception.consume();
+ }
+ // Leaves end - update suppression crash fix
if (var4 instanceof ReportedException reportedException && reportedException.getCause() instanceof OutOfMemoryError) {
throw makeReportedException(var4, packet, processor);
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 3c94631f468f4ee1b46de9981c17171e5901d745..f7a2a19c0832ca90536d09de23f4ec9a7d4a1521 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1878,8 +1878,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
try {
profiler.startTimer(serverLevel.tickTimerId); try { // Folia - profiler
serverLevel.tick(hasTimeLeft, region); // Folia - region threading
+ // Leaves start - update suppression crash fix
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ exception.provideLevel(serverLevel);
+ exception.consume();
} finally { profiler.stopTimer(serverLevel.tickTimerId); } // Folia - profiler
} catch (Throwable var7) {
+ if (var7.getCause() instanceof org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ exception.provideLevel(serverLevel);
+ exception.consume();
+ }
+ // Leaves end - update suppression crash fix
CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world");
serverLevel.fillReportDetails(crashReport);
throw new ReportedException(crashReport);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index a4ee2bcdb31f889c7edbbc422636782c0d5d0af6..42bf4f0430a232a10cffb47d0d33f586afdc2a4d 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -840,7 +840,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// Kaiiju end
profilerFiller.push("tick");
- this.guardEntityTick(this::tickNonPassenger, entity);
+ // Lophine start - update suppression crash fix
+ if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
+ try {
+ this.tickNonPassenger(entity); // Lophine changed
+ // Leaves start - update suppression crash fix - for dragon dupe
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ exception.provideLevel(this);
+ exception.consume();
+ // Leaves end - update suppression crash fix - for dragon dupe
+ }
+ } else {
+ this.guardEntityTick(this::tickNonPassenger, entity);
+ }
+ // Lophine end - update suppression crash fix
profilerFiller.pop();
}
}
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index afd607df83fda3de6f53f55ad305c8f69fe6ddb6..e2f4b35c88c64a68cf7ef9fadf977e7b99e17c6e 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -1123,6 +1123,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
((org.bukkit.craftbukkit.CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick();
}
// CraftBukkit end
+ // Leaves start - update suppression crash fix
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ exception.providePlayer(this);
+ exception.provideLevel(this.level());
+ exception.consume();
+ // Leaves start - update suppression crash fix
} catch (Throwable var4) {
CrashReport crashReport = CrashReport.forThrowable(var4, "Ticking player");
CrashReportCategory crashReportCategory = crashReport.addCategory("Player being ticked");
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 477241699752567df7695d33fa629fb1d5109e0a..f63252c580c313ccf89304866cfc6503f4b26052 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1403,9 +1403,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.walkingStepSound(pos, state);
}
+ // Leaves start - update suppression crash fix
if (broadcastGameEvent) {
- this.level().gameEvent(GameEvent.STEP, this.position(), GameEvent.Context.of(this, state));
+ try {
+ this.level().gameEvent(net.minecraft.world.level.gameevent.GameEvent.STEP, this.position(), net.minecraft.world.level.gameevent.GameEvent.Context.of(this, state));
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException exception) {
+ exception.provideBlock(level, pos, state.getBlock());
+ if (this instanceof net.minecraft.server.level.ServerPlayer player) {
+ exception.providePlayer(player);
+ }
+ exception.consume();
+ }
}
+ // Leaves end - update suppression crash fix
return true;
} else {
diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java
index 49bac7af90b0a7c490141be6357563447783c6ca..a26cd86aedcaf7ab606f8543e08360cbe9f2d1e6 100644
--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java
+++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java
@@ -183,7 +183,17 @@ public class ShulkerBoxBlock extends BaseEntityBlock {
@Override
protected int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
- return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(level.getBlockEntity(pos));
+ // Leaves start - update suppression crash fix
+ try {
+ return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(level.getBlockEntity(pos));
+ } catch (ClassCastException ex) {
+ if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
+ throw new org.leavesmc.leaves.util.UpdateSuppressionException(pos, null, this, null, ex);
+ } else {
+ throw ex;
+ }
+ }
+ // Leaves end - update suppression crash fix
}
public static Block getBlockByColor(@Nullable DyeColor color) {
diff --git a/net/minecraft/world/level/block/state/StateHolder.java b/net/minecraft/world/level/block/state/StateHolder.java
index 9b9842462b210b0407279ee8a8e9ccecf3ead005..1057f3bd32ff4c8f93f09251341c243915593e4d 100644
--- a/net/minecraft/world/level/block/state/StateHolder.java
+++ b/net/minecraft/world/level/block/state/StateHolder.java
@@ -104,7 +104,16 @@ public abstract class StateHolder<O, S> implements ca.spottedleaf.moonrise.patch
if (ret != null) {
return ret;
}
- throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
+ // Leaves start - update suppression crash fix
+ IllegalArgumentException iae = new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
+ if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
+ org.leavesmc.leaves.util.UpdateSuppressionException exception = new org.leavesmc.leaves.util.UpdateSuppressionException(null, null, null, null, iae);
+ if (exception.getStackTrace()[1].getClassName().startsWith("net.minecraft")) {
+ throw exception;
+ }
+ }
+ throw iae;
+ // Leaves end - update suppression crash fix
// Paper end - optimise blockstate property access
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index a536dc4e09b22b69bcdf59871a9cccfcf8129395..a557045888b153bb046a25d276d811b9146a7874 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -378,7 +378,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
if (blockState == state) {
return null;
} else {
- Block block = state.getBlock();
+ Block block = state.getBlock(); try { // Leaves start - update suppression crash fix
this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, state);
this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, state);
this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(i, y, i2, state);
@@ -450,6 +450,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
this.markUnsaved();
return blockState;
}
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException e) { e.provideBlock(level, pos, block); throw e; } // Leaves - update suppression crash fix
}
}
}
diff --git a/net/minecraft/world/level/redstone/NeighborUpdater.java b/net/minecraft/world/level/redstone/NeighborUpdater.java
index 714e291b17856aacdac7db992fc18e6ae662f659..01285691a2e0bf72b94969e9045f7ab42a9161bf 100644
--- a/net/minecraft/world/level/redstone/NeighborUpdater.java
+++ b/net/minecraft/world/level/redstone/NeighborUpdater.java
@@ -63,9 +63,22 @@ public interface NeighborUpdater {
} finally { if (levelChunk != null) levelChunk.getChunkHot().stopTickingAndCount(); } // KioCG
// Spigot start
} catch (StackOverflowError ex) {
+ // Leaves start - update suppression crash fix
+ if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
+ throw new org.leavesmc.leaves.util.UpdateSuppressionException(pos, level, neighborBlock, null, ex);
+ }
level.lastPhysicsProblem = pos.immutable();
// Spigot end
} catch (Throwable var9) {
+ if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
+ if (var9 instanceof org.leavesmc.leaves.util.UpdateSuppressionException ue) {
+ ue.provideBlock(level, pos, neighborBlock);
+ throw ue;
+ } else {
+ throw new org.leavesmc.leaves.util.UpdateSuppressionException(pos, level, neighborBlock, null, var9);
+ }
+ }
+ // Leaves end - update suppression crash fix
CrashReport crashReport = CrashReport.forThrowable(var9, "Exception while updating neighbours");
CrashReportCategory crashReportCategory = crashReport.addCategory("Block being updated");
crashReportCategory.setDetail(
@@ -0,0 +1,24 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Bacteriawa <A3167717663@hotmail.com>
Date: Sat, 15 Nov 2025 01:12:39 +0800
Subject: [PATCH] Leaves: CCE update suppression
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java
index a26cd86aedcaf7ab606f8543e08360cbe9f2d1e6..27ccce4b4d8b4cb8be48bfe5d55366abe4ea3a38 100644
--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java
+++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java
@@ -185,7 +185,9 @@ public class ShulkerBoxBlock extends BaseEntityBlock {
protected int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
// Leaves start - update suppression crash fix
try {
- return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(level.getBlockEntity(pos));
+ return fun.bm.lophine.config.modules.experiment.RedStoneConfig.cce ?
+ AbstractContainerMenu.getRedstoneSignalFromContainer((net.minecraft.world.Container) level.getBlockEntity(pos)) : // Leaves - make cce happy(?)
+ AbstractContainerMenu.getRedstoneSignalFromBlockEntity(level.getBlockEntity(pos));
} catch (ClassCastException ex) {
if (fun.bm.lophine.config.modules.fixes.UpdateSuppressionCrashFixConfig.enabled) {
throw new org.leavesmc.leaves.util.UpdateSuppressionException(pos, null, this, null, ex);
@@ -0,0 +1,55 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Wed, 14 Jun 2023 12:07:07 +0800
Subject: [PATCH] Leaves: Redstone ignore upwards update
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/level/block/ComparatorBlock.java b/net/minecraft/world/level/block/ComparatorBlock.java
index afecf6bb6152b4d9dfee4cd8c5b1d798cbf723b3..a5bbe83b079276a3ab497e8a725713721db83aa1 100644
--- a/net/minecraft/world/level/block/ComparatorBlock.java
+++ b/net/minecraft/world/level/block/ComparatorBlock.java
@@ -58,6 +58,7 @@ public class ComparatorBlock extends DiodeBlock implements EntityBlock {
BlockState neighborState,
RandomSource random
) {
+ if (fun.bm.lophine.config.modules.experiment.RedStoneConfig.redstoneIgnoreUpwardsUpdate && direction == Direction.DOWN) return state; // Leaves - behavior to 1.20.1
return direction == Direction.DOWN && !this.canSurviveOn(level, neighborPos, neighborState)
? Blocks.AIR.defaultBlockState()
: super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
index 4b37335e91a51b9a58ce0bce94b61ab5afbc6486..555a00e402da3d9126925135d030b7bccd8e5327 100644
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -179,7 +179,7 @@ public class RedStoneWireBlock extends Block {
RandomSource random
) {
if (direction == Direction.DOWN) {
- return !this.canSurviveOn(level, neighborPos, neighborState) ? Blocks.AIR.defaultBlockState() : state;
+ return fun.bm.lophine.config.modules.experiment.RedStoneConfig.redstoneIgnoreUpwardsUpdate ? state : !this.canSurviveOn(level, neighborPos, neighborState) ? Blocks.AIR.defaultBlockState() : state; // Leaves - behavior to 1.19
} else if (direction == Direction.UP) {
return this.getConnectionState(level, state, pos);
} else {
@@ -239,7 +239,7 @@ public class RedStoneWireBlock extends Block {
BlockPos blockPos = pos.relative(direction);
BlockState blockState = level.getBlockState(blockPos);
if (nonNormalCubeAbove) {
- boolean flag = blockState.getBlock() instanceof TrapDoorBlock || this.canSurviveOn(level, blockPos, blockState);
+ boolean flag = (!fun.bm.lophine.config.modules.experiment.RedStoneConfig.redstoneIgnoreUpwardsUpdate && blockState.getBlock() instanceof TrapDoorBlock) || this.canSurviveOn(level, blockPos, blockState); // Leaves - behavior to 1.19
if (flag && shouldConnectTo(level.getBlockState(blockPos.above()))) {
if (blockState.isFaceSturdy(level, blockPos, direction.getOpposite())) {
return RedstoneSide.UP;
diff --git a/net/minecraft/world/level/block/RepeaterBlock.java b/net/minecraft/world/level/block/RepeaterBlock.java
index 1d45564253258eebdf10470dd7eefa644a4151ae..4b8a0f7353984d8997ab3b702227ae8d46863d7e 100644
--- a/net/minecraft/world/level/block/RepeaterBlock.java
+++ b/net/minecraft/world/level/block/RepeaterBlock.java
@@ -68,6 +68,7 @@ public class RepeaterBlock extends DiodeBlock {
BlockState neighborState,
RandomSource random
) {
+ if (fun.bm.lophine.config.modules.experiment.RedStoneConfig.redstoneIgnoreUpwardsUpdate && direction == Direction.DOWN) return state; // Leaves - behavior to 1.20.1
if (direction == Direction.DOWN && !this.canSurviveOn(level, neighborPos, neighborState)) {
return Blocks.AIR.defaultBlockState();
} else {
@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Mon, 17 Nov 2025 18:41:08 +0800
Subject: [PATCH] Instant Block Updater
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index d0de3b22f6cef6eb213b7ff688a514a67f84fbcf..3b39b8f7ccb278cf910e3cc4e00da29790465a81 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -524,7 +524,7 @@ public final class RegionizedWorldData {
this.world = world;
this.blockLevelTicks = new LevelTicks<>(world::isPositionTickingWithEntitiesLoaded, world, true);
this.fluidLevelTicks = new LevelTicks<>(world::isPositionTickingWithEntitiesLoaded, world, false);
- this.neighborUpdater = new CollectingNeighborUpdater(world, world.neighbourUpdateMax);
+ this.neighborUpdater = fun.bm.lophine.config.modules.experiment.RedStoneConfig.instantBlockUpdater ? new net.minecraft.world.level.redstone.InstantNeighborUpdater(world) : new CollectingNeighborUpdater(world, world.neighbourUpdateMax); // Lophine - Instant Block Updater
this.nearbyPlayers = new NearbyPlayers(world);
this.wireHandler = new alternate.current.wire.WireHandler(world);
this.turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((RedStoneWireBlock)Blocks.REDSTONE_WIRE);
@@ -0,0 +1,49 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 18 Nov 2025 00:24:32 +0800
Subject: [PATCH] Revert TrapDoorBlock changes form paperMC
diff --git a/net/minecraft/world/level/block/TrapDoorBlock.java b/net/minecraft/world/level/block/TrapDoorBlock.java
index a2140d22dc58aa2df1afbeb5170d04fd65739d55..ea79c471c03b6908710d5943f2ff378eaba4099e 100644
--- a/net/minecraft/world/level/block/TrapDoorBlock.java
+++ b/net/minecraft/world/level/block/TrapDoorBlock.java
@@ -128,37 +128,7 @@ public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleW
if (!level.isClientSide) {
boolean hasNeighborSignal = level.hasNeighborSignal(pos);
if (hasNeighborSignal != state.getValue(POWERED)) {
- // CraftBukkit start
- org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
- int power = bblock.getBlockPower();
- int oldPower = state.getValue(TrapDoorBlock.OPEN) ? 15 : 0;
-
- if (oldPower == 0 ^ power == 0 || neighborBlock.defaultBlockState().isSignalSource()) {
- org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bblock, oldPower, power);
- level.getCraftServer().getPluginManager().callEvent(eventRedstone);
- hasNeighborSignal = eventRedstone.getNewCurrent() > 0;
- }
- // CraftBukkit end
- // Paper start - break redstone on trapdoors early
- boolean open = state.getValue(TrapDoorBlock.OPEN) != hasNeighborSignal;
- // note: this must run before any state for this block/its neighbours are written to the world
- // we allow the redstone event to fire so that plugins can block
- if (hasNeighborSignal && open) { // if we are now powered and it caused the trap door to open
- // in this case, first check for the redstone on top first
- BlockPos abovePos = pos.above();
- BlockState above = level.getBlockState(abovePos);
- if (above.getBlock() instanceof RedStoneWireBlock) {
- level.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS);
- Block.popResource(level, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE));
- // now check that this didn't change our state
- if (level.getBlockState(pos) != state) {
- // our state was changed, so we cannot propagate this update
- return;
- }
- }
- }
- if (open) {
- // Paper end - break redstone on trapdoors early
+ if (state.getValue(OPEN) != hasNeighborSignal) {
state = state.setValue(OPEN, hasNeighborSignal);
this.playSound(null, level, pos, hasNeighborSignal);
}
@@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 18 Nov 2025 00:35:13 +0800
Subject: [PATCH] Leaves: Prevent loss of item drops due to update suppression
when breaking blocks
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc14e2e0225009cf9919072f7f/leaves-server/minecraft-patches/features/0090-Servux-Protocol.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
index ee7bdc3adbeb04cf6ca630282f31a21e52bdb215..58f6c3e83c80b4b419ba22da81a7880be5b5d551 100644
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -369,7 +369,18 @@ public class ServerPlayerGameMode {
this.level.getCurrentWorldData().captureDrops = new java.util.ArrayList<>(); // Folia - region threading
// CraftBukkit end
BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player);
- boolean flag = this.level.removeBlock(pos, false);
+ // Leaves start - Prevent loss of item drops due to update suppression when breaking blocks
+ boolean flag;
+ org.leavesmc.leaves.util.UpdateSuppressionException ex = null;
+ try {
+ flag = this.level.removeBlock(pos, false);
+ } catch (org.leavesmc.leaves.util.UpdateSuppressionException e) {
+ ex = e;
+ ex.provideBlock(level, pos, block);
+ ex.providePlayer(this.player);
+ flag = false;
+ }
+ // Leaves end - Prevent loss of item drops due to update suppression when breaking blocks
if (flag) {
block.destroy(this.level, pos, blockState1);
}
@@ -397,6 +408,7 @@ public class ServerPlayerGameMode {
if (event.isDropItems()) {
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world
}
+ if (ex != null) throw ex; // Leaves - Prevent loss of item drops due to update suppression when breaking blocks
// Drop event experience
if (flag) {
@@ -0,0 +1,807 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 18 Nov 2025 23:11:26 +0800
Subject: [PATCH] Leaves: Old Block remove behaviour
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/Containers.java b/net/minecraft/world/Containers.java
index da10ca5ef12be1a834adda9243c082dadde24ec0..f682930f765d04f0a278b0648c2773ab5d4d740e 100644
--- a/net/minecraft/world/Containers.java
+++ b/net/minecraft/world/Containers.java
@@ -51,4 +51,15 @@ public class Containers {
public static void updateNeighboursAfterDestroy(BlockState state, Level level, BlockPos pos) {
level.updateNeighbourForOutputSignal(pos, state.getBlock());
}
+
+ // Leaves start - behaviour 1.21.1-
+ public static void dropContentsOnDestroy(BlockState state, BlockState newState, Level level, BlockPos pos) {
+ if (!state.is(newState.getBlock())) {
+ if (level.getBlockEntity(pos) instanceof Container container) {
+ dropContents(level, pos, container);
+ level.updateNeighbourForOutputSignal(pos, state.getBlock());
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
}
diff --git a/net/minecraft/world/level/block/AbstractFurnaceBlock.java b/net/minecraft/world/level/block/AbstractFurnaceBlock.java
index d52eeff0d564a24bddb873750c81e3fe999f16f0..aa2597d825f93b0b0623ab7fae29e45de3f3ebcc 100644
--- a/net/minecraft/world/level/block/AbstractFurnaceBlock.java
+++ b/net/minecraft/world/level/block/AbstractFurnaceBlock.java
@@ -51,6 +51,26 @@ public abstract class AbstractFurnaceBlock extends BaseEntityBlock {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof AbstractFurnaceBlockEntity) {
+ if (level instanceof ServerLevel) {
+ Containers.dropContents(level, pos, (AbstractFurnaceBlockEntity)blockEntity);
+ ((AbstractFurnaceBlockEntity)blockEntity).getRecipesToAwardAndPopExperience((ServerLevel)level, net.minecraft.world.phys.Vec3.atCenterOf(pos));
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ level.updateNeighbourForOutputSignal(pos, this);
+ } else {
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/BarrelBlock.java b/net/minecraft/world/level/block/BarrelBlock.java
index f7820265f10b78674acd13e6204b9212fd27b038..700bd0f6a4f4fd6cd85b3e666fff1b3fd0a96b8e 100644
--- a/net/minecraft/world/level/block/BarrelBlock.java
+++ b/net/minecraft/world/level/block/BarrelBlock.java
@@ -49,6 +49,14 @@ public class BarrelBlock extends BaseEntityBlock {
return InteractionResult.SUCCESS;
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/BasePressurePlateBlock.java b/net/minecraft/world/level/block/BasePressurePlateBlock.java
index 42ee3f32fe44c1f0680c994a69201f7bd7792673..19214c236bca4e454e3bbe7dc50e00b66398c119 100644
--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
+++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
@@ -125,6 +125,19 @@ public abstract class BasePressurePlateBlock extends Block {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ if (this.getSignalForState(state) > 0) {
+ this.updateNeighbours(level, pos);
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston && this.getSignalForState(state) > 0) {
diff --git a/net/minecraft/world/level/block/BaseRailBlock.java b/net/minecraft/world/level/block/BaseRailBlock.java
index 5f99e18244501ed2d85be77b71f48cc93058cdb7..6bf50d9c3921b15f40b4fba273abeb4c2e0f30b3 100644
--- a/net/minecraft/world/level/block/BaseRailBlock.java
+++ b/net/minecraft/world/level/block/BaseRailBlock.java
@@ -121,6 +121,23 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving) {
+ super.onRemove(state, level, pos, newState, isMoving);
+ if (state.getValue(this.getShapeProperty()).isSlope()) {
+ level.updateNeighborsAt(pos.above(), this);
+ }
+
+ if (this.isStraight) {
+ level.updateNeighborsAt(pos, this);
+ level.updateNeighborsAt(pos.below(), this);
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston) {
diff --git a/net/minecraft/world/level/block/BrewingStandBlock.java b/net/minecraft/world/level/block/BrewingStandBlock.java
index f563f8ea63e67f808802baa4c6a700e803c045a0..0cc4479d9d3f6aea280c9f88997d141dc24917a9 100644
--- a/net/minecraft/world/level/block/BrewingStandBlock.java
+++ b/net/minecraft/world/level/block/BrewingStandBlock.java
@@ -78,6 +78,14 @@ public class BrewingStandBlock extends BaseEntityBlock {
level.addParticle(ParticleTypes.SMOKE, d, d1, d2, 0.0, 0.0, 0.0);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/ButtonBlock.java b/net/minecraft/world/level/block/ButtonBlock.java
index 66c589bc633d32ebf36b1ab55ba6250ca2ddd6f6..f96d3c7b7ec15973aca3aabe056608fd5efb856b 100644
--- a/net/minecraft/world/level/block/ButtonBlock.java
+++ b/net/minecraft/world/level/block/ButtonBlock.java
@@ -132,6 +132,19 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock {
return isOn ? this.type.buttonClickOn() : this.type.buttonClickOff();
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ if (state.getValue(POWERED)) {
+ this.updateNeighbours(state, level, pos);
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston && state.getValue(POWERED)) {
diff --git a/net/minecraft/world/level/block/CampfireBlock.java b/net/minecraft/world/level/block/CampfireBlock.java
index 028e2ad8bcb23b3f9f80a5ec551204bb2d7db1ae..c9e2becc0644de707e0bd251318813c50fc82be2 100644
--- a/net/minecraft/world/level/block/CampfireBlock.java
+++ b/net/minecraft/world/level/block/CampfireBlock.java
@@ -105,6 +105,20 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB
return InteractionResult.TRY_WITH_EMPTY_HAND;
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof CampfireBlockEntity) {
+ net.minecraft.world.Containers.dropContents(level, pos, ((CampfireBlockEntity)blockEntity).getItems());
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity, InsideBlockEffectApplier effectApplier) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
diff --git a/net/minecraft/world/level/block/ChestBlock.java b/net/minecraft/world/level/block/ChestBlock.java
index c4937d1b482e2ec60961bda62ad6cc155f0ce8f7..818b1a5df0e9a0566210e4b3512ad856515e385f 100644
--- a/net/minecraft/world/level/block/ChestBlock.java
+++ b/net/minecraft/world/level/block/ChestBlock.java
@@ -244,6 +244,14 @@ public class ChestBlock extends AbstractChestBlock<ChestBlockEntity> implements
return blockState.is(this) && blockState.getValue(TYPE) == ChestType.SINGLE ? blockState.getValue(FACING) : null;
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/ChiseledBookShelfBlock.java b/net/minecraft/world/level/block/ChiseledBookShelfBlock.java
index eb315a50a83dc7230d7ad66f4daeb0d632679941..7bccf15310f6851875bfd77d4c66f4ce863b65e1 100644
--- a/net/minecraft/world/level/block/ChiseledBookShelfBlock.java
+++ b/net/minecraft/world/level/block/ChiseledBookShelfBlock.java
@@ -180,6 +180,27 @@ public class ChiseledBookShelfBlock extends BaseEntityBlock {
SLOT_OCCUPIED_PROPERTIES.forEach(property -> builder.add(property));
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ if (!state.is(newState.getBlock())) {
+ if (level.getBlockEntity(pos) instanceof ChiseledBookShelfBlockEntity chiseledBookShelfBlockEntity && !chiseledBookShelfBlockEntity.isEmpty()) {
+ for (int i = 0; i < 6; i++) {
+ ItemStack item = chiseledBookShelfBlockEntity.getItem(i);
+ if (!item.isEmpty()) {
+ Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), item);
+ }
+ }
+
+ chiseledBookShelfBlockEntity.clearContent();
+ level.updateNeighbourForOutputSignal(pos, this);
+ }
+
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/CrafterBlock.java b/net/minecraft/world/level/block/CrafterBlock.java
index caf31218a1aff84367da126ee1dc46b8c8a1e5d1..59272e7223aff61d44c86776091ca74891839a45 100644
--- a/net/minecraft/world/level/block/CrafterBlock.java
+++ b/net/minecraft/world/level/block/CrafterBlock.java
@@ -129,6 +129,14 @@ public class CrafterBlock extends BaseEntityBlock {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/CreakingHeartBlock.java b/net/minecraft/world/level/block/CreakingHeartBlock.java
index 73885449a3316face50292756de2a3f298d3d111..333e18e4ead0bb6ef9681ce27ec156f79821af7c 100644
--- a/net/minecraft/world/level/block/CreakingHeartBlock.java
+++ b/net/minecraft/world/level/block/CreakingHeartBlock.java
@@ -152,6 +152,16 @@ public class CreakingHeartBlock extends BaseEntityBlock {
builder.add(AXIS, STATE, NATURAL);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ if (level.getBlockEntity(pos) instanceof CreakingHeartBlockEntity creakingHeartBlockEntity) {
+ creakingHeartBlockEntity.removeProtector(null);
+ }
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/DecoratedPotBlock.java b/net/minecraft/world/level/block/DecoratedPotBlock.java
index f1570f1f4f7e43aacbcffef8bcf8ef27d46899da..6afd1c618112234ce4940105582a4399e20ab34c 100644
--- a/net/minecraft/world/level/block/DecoratedPotBlock.java
+++ b/net/minecraft/world/level/block/DecoratedPotBlock.java
@@ -165,6 +165,14 @@ public class DecoratedPotBlock extends BaseEntityBlock implements SimpleWaterlog
return new DecoratedPotBlockEntity(pos, state);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java
index 0ab376b4207b695e92c324b8a5f9818d3f4accde..bcceb8f17294fb9f275ab6b3a28c75cb423394f2 100644
--- a/net/minecraft/world/level/block/DiodeBlock.java
+++ b/net/minecraft/world/level/block/DiodeBlock.java
@@ -176,6 +176,16 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock {
if (me.earthme.luminol.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this instanceof ComparatorBlock && !oldState.is(Blocks.COMPARATOR)) org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking.notifyNearbyBlockEntitiesAboutNewComparator(level, pos); // Leaves - Lithium Sleeping Block Entity
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ super.onRemove(state, level, pos, newState, isMoving);
+ this.updateNeighborsInFront(level, pos, state);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston) {
diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java
index 7ff6255fcd50344cd6ac7f8a67d61fd59c85e413..7a23d27ce76ae6b90d01c4dc7af416bb6b6516ea 100644
--- a/net/minecraft/world/level/block/DispenserBlock.java
+++ b/net/minecraft/world/level/block/DispenserBlock.java
@@ -147,6 +147,14 @@ public class DispenserBlock extends BaseEntityBlock {
return this.defaultBlockState().setValue(FACING, context.getNearestLookingDirection().getOpposite());
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java
index 912d1ba98809b4469e1468e4855bb1bc91bb4540..0f2b344b385b7726c3c23b68da8a2329180fe034 100644
--- a/net/minecraft/world/level/block/HopperBlock.java
+++ b/net/minecraft/world/level/block/HopperBlock.java
@@ -137,6 +137,14 @@ public class HopperBlock extends BaseEntityBlock implements org.leavesmc.leaves.
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ Containers.dropContentsOnDestroy(state, newState, level, pos);
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/JukeboxBlock.java b/net/minecraft/world/level/block/JukeboxBlock.java
index 56cf6528f0cd9b8528490d7cee9a1f0e54108ef9..955854e5b3a18b8f1441145554a97c5454a65d12 100644
--- a/net/minecraft/world/level/block/JukeboxBlock.java
+++ b/net/minecraft/world/level/block/JukeboxBlock.java
@@ -73,6 +73,19 @@ public class JukeboxBlock extends BaseEntityBlock {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ if (level.getBlockEntity(pos) instanceof JukeboxBlockEntity jukeboxBlockEntity) {
+ jukeboxBlockEntity.popOutTheItem();
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/LecternBlock.java b/net/minecraft/world/level/block/LecternBlock.java
index 5a9b601b7bf7e80b04ebd8f5c8b7d121031132c7..b714d3c7649384a8fdbe0a713e2193e241c80618 100644
--- a/net/minecraft/world/level/block/LecternBlock.java
+++ b/net/minecraft/world/level/block/LecternBlock.java
@@ -197,6 +197,36 @@ public class LecternBlock extends BaseEntityBlock {
changePowered(level, pos, state, false);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ if (state.getValue(HAS_BOOK)) {
+ this.popBook(state, level, pos);
+ }
+
+ if (state.getValue(POWERED)) {
+ updateBelow(level, pos, state);
+ }
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+
+ private void popBook(BlockState state, Level level, BlockPos pos) {
+ if (level.getBlockEntity(pos) instanceof LecternBlockEntity lecternBlockEntity) { // CraftBukkit - don't validate, type may be changed already // Leaves - the method with validate arg already removed by paper...
+ Direction direction = state.getValue(FACING);
+ ItemStack itemStack = lecternBlockEntity.getBook().copy();
+ if (itemStack.isEmpty()) return; // CraftBukkit - SPIGOT-5500
+ float f = 0.25F * direction.getStepX();
+ float f1 = 0.25F * direction.getStepZ();
+ net.minecraft.world.entity.item.ItemEntity itemEntity = new net.minecraft.world.entity.item.ItemEntity(level, pos.getX() + 0.5 + f, pos.getY() + 1, pos.getZ() + 0.5 + f1, itemStack);
+ itemEntity.setDefaultPickUpDelay();
+ level.addFreshEntity(itemEntity);
+ lecternBlockEntity.clearContent();
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (state.getValue(POWERED)) {
diff --git a/net/minecraft/world/level/block/LeverBlock.java b/net/minecraft/world/level/block/LeverBlock.java
index 76f2a29f37187344324d68941307d58e6343b6ae..3645ca9589aa1a74134180c9918a6f67bc64ce7a 100644
--- a/net/minecraft/world/level/block/LeverBlock.java
+++ b/net/minecraft/world/level/block/LeverBlock.java
@@ -125,6 +125,19 @@ public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ if (state.getValue(POWERED)) {
+ this.updateNeighbours(state, level, pos);
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston && state.getValue(POWERED)) {
diff --git a/net/minecraft/world/level/block/LightningRodBlock.java b/net/minecraft/world/level/block/LightningRodBlock.java
index d702d234111c2a34585f3532b5bb4bdb1501b82a..d9bedb99590a860a7ebb1d68861dc175ca3f4a2b 100644
--- a/net/minecraft/world/level/block/LightningRodBlock.java
+++ b/net/minecraft/world/level/block/LightningRodBlock.java
@@ -120,6 +120,18 @@ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBloc
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ if (!state.is(newState.getBlock())) {
+ if (state.getValue(POWERED)) {
+ this.updateNeighbours(state, level, pos);
+ }
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (state.getValue(POWERED)) {
diff --git a/net/minecraft/world/level/block/ObserverBlock.java b/net/minecraft/world/level/block/ObserverBlock.java
index bd2aa00ce8b78c16f6107064dd00bfbb072df0df..6db3a21bcc37ae79f82b19ee0a851f539d4a654d 100644
--- a/net/minecraft/world/level/block/ObserverBlock.java
+++ b/net/minecraft/world/level/block/ObserverBlock.java
@@ -127,6 +127,17 @@ public class ObserverBlock extends DirectionalBlock {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ if (state.getValue(POWERED) && level.getBlockTicks().hasScheduledTick(pos, this)) {
+ this.updateNeighborsInFront(level, pos, state.setValue(POWERED, Boolean.FALSE));
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (state.getValue(POWERED) && level.getBlockTicks().hasScheduledTick(pos, this)) {
diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
index 555a00e402da3d9126925135d030b7bccd8e5327..20cec51bcead34f95ed6195a7762cea7ce280914 100644
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -369,6 +369,27 @@ public class RedStoneWireBlock extends Block {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ super.onRemove(state, level, pos, newState, isMoving);
+ for (Direction direction : Direction.values()) {
+ level.updateNeighborsAt(pos.relative(direction), this);
+ }
+
+ // Paper start - optimize redstone - replace call to updatePowerStrength
+ if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
+ level.getWireHandler().onWireRemoved(pos, state); // Alternate Current
+ } else {
+ this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft
+ }
+ // Paper end - optimize redstone
+ this.updateNeighborsOfNeighboringWires(level, pos);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston) {
diff --git a/net/minecraft/world/level/block/RedstoneTorchBlock.java b/net/minecraft/world/level/block/RedstoneTorchBlock.java
index df4f1b18deb8bb4a04e740bbbeb6a89cb1ff8e71..7f165a4e2935cf4638b14379a0d1aca1b65ef84a 100644
--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java
+++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java
@@ -53,6 +53,15 @@ public class RedstoneTorchBlock extends BaseTorchBlock {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving) {
+ this.notifyNeighbors(level, pos, state);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston) {
diff --git a/net/minecraft/world/level/block/SculkSensorBlock.java b/net/minecraft/world/level/block/SculkSensorBlock.java
index f0101e88140f480c1a94f899744991f78c9e3756..fa9cb4c40a41eea7fd63a4513d0b0f39067de9ba 100644
--- a/net/minecraft/world/level/block/SculkSensorBlock.java
+++ b/net/minecraft/world/level/block/SculkSensorBlock.java
@@ -129,6 +129,18 @@ public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterlogg
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ if (!state.is(newState.getBlock())) {
+ if (getPhase(state) == SculkSensorPhase.ACTIVE) {
+ updateNeighbours(level, pos, state);
+ }
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (getPhase(state) == SculkSensorPhase.ACTIVE) {
diff --git a/net/minecraft/world/level/block/SculkShriekerBlock.java b/net/minecraft/world/level/block/SculkShriekerBlock.java
index 757f8453e147875ab9f14d9726bb734ef27447c9..d0558d0e33f3d6e25af2dd71650d723e8997dae6 100644
--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
+++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
@@ -68,6 +68,16 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo
super.stepOn(level, pos, state, entity);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ if (level instanceof ServerLevel serverLevel && state.getValue(SHRIEKING) && !state.is(newState.getBlock())) {
+ serverLevel.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent(sculkShrieker -> sculkShrieker.tryRespond(serverLevel));
+ }
+ super.onRemove(state, level, pos, newState, movedByPiston);
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (state.getValue(SHRIEKING)) {
diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java
index 27ccce4b4d8b4cb8be48bfe5d55366abe4ea3a38..2e6d462fb27ad1e538164934417ce07cc0e603c2 100644
--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java
+++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java
@@ -152,6 +152,19 @@ public class ShulkerBoxBlock extends BaseEntityBlock {
// Paper end - re-set loot table if it was cleared
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof ShulkerBoxBlockEntity) {
+ level.updateNeighbourForOutputSignal(pos, state.getBlock());
+ }
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
Containers.updateNeighboursAfterDestroy(state, level, pos);
diff --git a/net/minecraft/world/level/block/TripWireBlock.java b/net/minecraft/world/level/block/TripWireBlock.java
index c8f793d1cdeef7e3e0d5fceb45a4507d542e4b33..27f382f6c663b2954a6fb966ece05ba021fcd6b1 100644
--- a/net/minecraft/world/level/block/TripWireBlock.java
+++ b/net/minecraft/world/level/block/TripWireBlock.java
@@ -108,6 +108,16 @@ public class TripWireBlock extends Block {
}
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
+ if (!isMoving && !state.is(newState.getBlock())) {
+ this.updateSource(level, pos, state.setValue(POWERED, Boolean.TRUE));
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
diff --git a/net/minecraft/world/level/block/TripWireHookBlock.java b/net/minecraft/world/level/block/TripWireHookBlock.java
index 3f412b08e7d40d577efb7be291dd14826ba21f0e..30c65a0906646fc12cd842336ec4f3e2aaf31717 100644
--- a/net/minecraft/world/level/block/TripWireHookBlock.java
+++ b/net/minecraft/world/level/block/TripWireHookBlock.java
@@ -245,6 +245,25 @@ public class TripWireHookBlock extends Block {
level.updateNeighborsAt(pos.relative(opposite), block, orientation);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!isMoving && !state.is(newState.getBlock())) {
+ boolean attachedValue = state.getValue(ATTACHED);
+ boolean poweredValue = state.getValue(POWERED);
+ if (attachedValue || poweredValue) {
+ calculateState(level, pos, state, true, false, -1, null);
+ }
+
+ if (poweredValue) {
+ notifyNeighbors(this, level, pos, state.getValue(FACING));
+ }
+
+ super.onRemove(state, level, pos, newState, isMoving);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
if (!movedByPiston) {
diff --git a/net/minecraft/world/level/block/entity/BlockEntityType.java b/net/minecraft/world/level/block/entity/BlockEntityType.java
index 7b8631abf8adcdb889d412b30eca10faf044032a..13134dfe9f548b87fd6be4683bf3386c522aba6a 100644
--- a/net/minecraft/world/level/block/entity/BlockEntityType.java
+++ b/net/minecraft/world/level/block/entity/BlockEntityType.java
@@ -267,7 +267,7 @@ public class BlockEntityType<T extends BlockEntity> {
}
public boolean isValid(BlockState state) {
- return this.validBlocks.contains(state.getBlock());
+ return fun.bm.lophine.config.modules.experiment.RedStoneConfig.oldBlockRemoveBehaviour || this.validBlocks.contains(state.getBlock()); // Leaves - behaviour 1.21.1-
}
@Deprecated
diff --git a/net/minecraft/world/level/block/piston/MovingPistonBlock.java b/net/minecraft/world/level/block/piston/MovingPistonBlock.java
index 05bbc2e59384702439548a988e128a85f1adbe82..af516d5b6bd31de2c6a388ec8001126e2a351cf3 100644
--- a/net/minecraft/world/level/block/piston/MovingPistonBlock.java
+++ b/net/minecraft/world/level/block/piston/MovingPistonBlock.java
@@ -67,6 +67,18 @@ public class MovingPistonBlock extends BaseEntityBlock {
return createTickerHelper(blockEntityType, BlockEntityType.PISTON, PistonMovingBlockEntity::tick);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof PistonMovingBlockEntity) {
+ ((PistonMovingBlockEntity)blockEntity).finalTick();
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
public void destroy(LevelAccessor level, BlockPos pos, BlockState state) {
BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
diff --git a/net/minecraft/world/level/block/piston/PistonHeadBlock.java b/net/minecraft/world/level/block/piston/PistonHeadBlock.java
index 6c789e56f21f01252c21786cfeb48d88485b5636..e24a61a4e2dac0159d52f07c93ddf860f4bfb8f7 100644
--- a/net/minecraft/world/level/block/piston/PistonHeadBlock.java
+++ b/net/minecraft/world/level/block/piston/PistonHeadBlock.java
@@ -78,6 +78,19 @@ public class PistonHeadBlock extends DirectionalBlock {
return super.playerWillDestroy(level, pos, state, player);
}
+ // Leaves start - behaviour 1.21.1-
+ @Override
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
+ if (!state.is(newState.getBlock())) {
+ super.onRemove(state, level, pos, newState, isMoving);
+ BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
+ if (this.isFittingBase(state, level.getBlockState(blockPos))) {
+ level.destroyBlock(blockPos, true);
+ }
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
index e9749b66239d7562ecf22002bfbaa95df197050f..6e3841ed012eee16dcaa61f70edfc1d870374a56 100644
--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -172,6 +172,15 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea
org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot
}
+ // Leaves start - behaviour 1.21.1-
+ protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ org.spigotmc.AsyncCatcher.catchOp("block remove"); // Spigot
+ if (state.hasBlockEntity() && !state.is(newState.getBlock())) {
+ level.removeBlockEntity(pos);
+ }
+ }
+ // Leaves end - behaviour 1.21.1-
+
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
}
@@ -868,6 +877,12 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea
// CraftBukkit end
}
+ // Leaves start - behaviour 1.21.1-
+ public void onRemove(Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+ this.getBlock().onRemove(this.asState(), level, pos, newState, movedByPiston);
+ }
+ // Leaves end - behaviour 1.21.1-
+
public void affectNeighborsAfterRemoval(ServerLevel level, BlockPos pos, boolean movedByPiston) {
this.getBlock().affectNeighborsAfterRemoval(this.asState(), level, pos, movedByPiston);
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index a557045888b153bb046a25d276d811b9146a7874..568427a952a24012c17f64f62b6545a9d89c8c8a 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -401,20 +401,26 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
boolean flag = !blockState.is(block);
boolean flag1 = (flags & 64) != 0;
boolean flag2 = (flags & 256) == 0;
- if (flag && blockState.hasBlockEntity()) {
- if (!this.level.isClientSide && flag2) {
- BlockEntity blockEntity = this.level.getBlockEntity(pos);
- if (blockEntity != null) {
- blockEntity.preRemoveSideEffects(pos, blockState);
+ // Leaves start - behaviour 1.21.1-
+ if (!fun.bm.lophine.config.modules.experiment.RedStoneConfig.oldBlockRemoveBehaviour) {
+ if (flag && blockState.hasBlockEntity()) {
+ if (!this.level.isClientSide && flag2) {
+ BlockEntity blockEntity = this.level.getBlockEntity(pos);
+ if (blockEntity != null) {
+ blockEntity.preRemoveSideEffects(pos, blockState);
+ }
}
- }
- this.removeBlockEntity(pos);
- }
+ this.removeBlockEntity(pos);
+ }
- if ((flag || block instanceof BaseRailBlock) && this.level instanceof ServerLevel serverLevel && ((flags & 1) != 0 || flag1)) {
- blockState.affectNeighborsAfterRemoval(serverLevel, pos, flag1);
+ if ((flag || block instanceof BaseRailBlock) && this.level instanceof ServerLevel serverLevel && ((flags & 1) != 0 || flag1)) {
+ blockState.affectNeighborsAfterRemoval(serverLevel, pos, flag1);
+ }
+ } else {
+ blockState.onRemove(this.level, pos, state, flag1);
}
+ // Leaves end - behaviour 1.21.1-
if (!section.getBlockState(i, i1, i2).is(block)) {
return null;
@@ -0,0 +1,70 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 18 Nov 2025 23:26:27 +0800
Subject: [PATCH] Leaves: Do not reset placed block on exception & Do not
prevent block entity and entity crash at LevelChunk
Co-authored by: MC_XiaoHei <xiaohei.xor7@outlook.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
index 8f112f382190d606a87f38da8bcdf8dd5f31d1c5..f27915e43df8e627000cda1cb213a95300c98038 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -77,20 +77,22 @@ public class BlockItem extends Item {
BlockState blockState = level.getBlockState(clickedPos);
if (blockState.is(placementState.getBlock())) {
blockState = this.updateBlockStateFromTag(clickedPos, level, itemInHand, blockState);
+ // Leaves start - we do not need this
// Paper start - Reset placed block on exception
- try {
+ // try {
this.updateCustomBlockEntityTag(clickedPos, level, player, itemInHand, blockState);
updateBlockEntityComponents(level, clickedPos, itemInHand);
- } catch (Exception ex) {
- ((org.bukkit.craftbukkit.block.CraftBlockState) oldBukkitState).revertPlace();
- if (player instanceof ServerPlayer serverPlayer) {
- org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), ex);
- serverPlayer.getBukkitEntity().kickPlayer("Packet processing error");
- return InteractionResult.FAIL;
- }
- throw ex; // Rethrow exception if not placed by a player
- }
+ // } catch (Exception ex) {
+ // ((org.bukkit.craftbukkit.block.CraftBlockState) oldBukkitState).revertPlace();
+ // if (player instanceof ServerPlayer serverPlayer) {
+ // org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), ex);
+ // serverPlayer.getBukkitEntity().kickPlayer("Packet processing error");
+ // return InteractionResult.FAIL;
+ // }
+ // throw ex; // Rethrow exception if not placed by a player
+ // }
// Paper end - Reset placed block on exception
+ // Leaves end - we dot not need this
blockState.getBlock().setPlacedBy(level, clickedPos, blockState, player, itemInHand);
// CraftBukkit start
if (bukkitState != null) {
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 568427a952a24012c17f64f62b6545a9d89c8c8a..49a740bf17b2bed8010e347747b170b69ac26929 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -971,12 +971,14 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
profilerFiller.pop();
} catch (Throwable var5) {
+ // Leaves start - do not prevent here
// Paper start - Prevent block entity and entity crashes
- final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
- net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
- net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
- LevelChunk.this.removeBlockEntity(this.getPos());
+ // final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
+ // net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
+ // net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
+ // LevelChunk.this.removeBlockEntity(this.getPos());
// Paper end - Prevent block entity and entity crashes
+ // Leaves end - do not prevent here
}
}
}
@@ -0,0 +1,13 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 16 Nov 2025 17:47:42 +0800
Subject: [PATCH] LeavesHooks
diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
index e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a..86edd824ec42e550dd1419fd57d93a21fe2d648d 100644
--- a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
+++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
@@ -1 +1 @@
-ca.spottedleaf.moonrise.paper.PaperHooks
+org.leavesmc.leaves.region.LeavesHooks
@@ -0,0 +1,106 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 16 Nov 2025 15:55:47 +0800
Subject: [PATCH] Leaves: Replay Mod API
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
index e1f456b31ffda9370b63e51f343847d6f9f62263..8a0f85241cc1530aacccc8b6ea24b9dec86fad66 100644
--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
@@ -57,7 +57,7 @@ class PaperEventManager {
}
// Leaves start - skip bot
- if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.bot.Bot) {
+ if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && (playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.bot.Bot || playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.photographer.Photographer)) { // Leaves - and photographer
return;
}
// Leaves end - skip bot
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0a96721797b9bbc643f68677bbb1d10c3c89e815..7e2d870dc8b77c58270542f8916b9c9b88588809 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -317,6 +317,7 @@ public final class CraftServer implements Server {
public io.papermc.paper.SparksFly spark; // Paper - spark // Luminol - Force disable builtin spark
private final ServerConfiguration serverConfig = new PaperServerConfiguration();
private final org.leavesmc.leaves.entity.bot.CraftBotManager botManager; // Leaves
+ private final org.leavesmc.leaves.entity.photographer.CraftPhotographerManager photographerManager = new org.leavesmc.leaves.entity.photographer.CraftPhotographerManager(); // Leaves
// Paper start - Folia region threading API
private final io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler(); // Folia - region threading
@@ -411,7 +412,7 @@ public final class CraftServer implements Server {
public CraftServer(DedicatedServer console, PlayerList playerList) {
this.console = console;
this.playerList = (DedicatedPlayerList) playerList;
- this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function<ServerPlayer, CraftPlayer>() {
+ this.playerView = Collections.unmodifiableList(Lists.transform(playerList.realPlayers, new Function<ServerPlayer, CraftPlayer>() { // Leaves - replay api
@Override
public CraftPlayer apply(ServerPlayer player) {
return player.getBukkitEntity();
@@ -3333,4 +3334,11 @@ public final class CraftServer implements Server {
}
}
// Folia end - region TPS API
+
+ // Leaves start - replay mod api
+ @Override
+ public org.leavesmc.leaves.entity.photographer.CraftPhotographerManager getPhotographerManager() {
+ return photographerManager;
+ }
+ // Leaves end - replay mod api
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 8eb5d014d9ed688ffebaffb4ce0bb40896961a04..634bd74449c6991ad72330d10f42ddbb7f59c164 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -127,6 +127,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
if (entity instanceof org.leavesmc.leaves.bot.ServerBot bot) { return new org.leavesmc.leaves.entity.bot.CraftBot(server, bot); }
+ if (entity instanceof org.leavesmc.leaves.replay.ServerPhotographer photographer) { return new org.leavesmc.leaves.entity.photographer.CraftPhotographer(server, photographer); }
// Special case complex part, since there is no extra entity type for them
if (entity instanceof EnderDragonPart complexPart) {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 7883b7973570b5f275a03c6aac4d3b4392e7fa6e..18d066c605f8027d9b1170b64ef1428fb969631b 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2214,7 +2214,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa
@Override
public boolean canSee(Player player) {
- return this.canSee((org.bukkit.entity.Entity) player);
+ return !(player instanceof org.leavesmc.leaves.entity.photographer.Photographer) && this.canSee((org.bukkit.entity.Entity) player); // Leaves - skip photographer
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 679529d94c16960527e5d76adb61c7e536f55d3e..503e11640bd2b6f7c854747723d26fe9cd67f490 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -338,7 +338,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex);
}
- MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> {
+ net.minecraft.server.MinecraftServer.getServer().getPlayerList().realPlayers.forEach(player -> { // Leaves - only real players
player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements());
player.getAdvancements().flushDirty(player, false);
});
diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java
index 25aae550dcdcef2df268d0dd99bdcc9bbd49fcf8..83af50de50a03e164d572f3c3466b6d0b42ed138 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java
@@ -23,7 +23,7 @@ public class LazyPlayerSet extends LazyHashSet<Player> {
}
public static Set<Player> makePlayerSet(final MinecraftServer server) {
- List<ServerPlayer> players = server.getPlayerList().players;
+ List<ServerPlayer> players = server.getPlayerList().realPlayers; // Leaves - only real players
Set<Player> reference = new HashSet<>(players.size());
for (ServerPlayer player : players) {
reference.add(player.getBukkitEntity());
@@ -0,0 +1,101 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sat, 22 Nov 2025 01:43:26 +0800
Subject: [PATCH] add cancel task by task id in FoliaGlobalRegionScheduler
diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java
index d306f911757a4d556c82c0070d4837db87afc497..7c9d3e4c399b2ea246a76b4550ba12e45e75078e 100644
--- a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java
+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java
@@ -17,6 +17,8 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
private long tickCount = 0L;
private final Object stateLock = new Object();
private final Long2ObjectOpenHashMap<List<GlobalScheduledTask>> tasksByDeadline = new Long2ObjectOpenHashMap<>();
+ private int lastTaskId = 1; // Lophine - add task id
+ private final Object stateLockForTaskId = new Object(); // Lophine - add task id
public void tick() {
final List<GlobalScheduledTask> run;
@@ -65,7 +67,7 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
}
- final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task);
+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task, this.getNextTaskId()); // Lophine - add task id
this.scheduleInternal(ret, delayTicks);
@@ -92,7 +94,7 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
}
- final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task);
+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task, this.getNextTaskId()); // Lophine - add task id
this.scheduleInternal(ret, initialDelayTicks);
@@ -134,7 +136,33 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
}
}
- private final class GlobalScheduledTask implements ScheduledTask, Runnable {
+ // Lophine start - add cancel task by task id
+ public void cancelTask(final int taskId) {
+ synchronized (this.stateLock) {
+ for (final List<GlobalScheduledTask> tasks : this.tasksByDeadline.values()) {
+ for (final GlobalScheduledTask task : tasks) {
+ if (task.getTaskId() == taskId) {
+ task.cancel();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private int getNextTaskId() {
+ synchronized (this.stateLockForTaskId) {
+ if (this.lastTaskId == Integer.MAX_VALUE) {
+ this.lastTaskId = 1;
+ } else {
+ ++this.lastTaskId;
+ }
+ }
+ return this.lastTaskId;
+ }
+ // Lophine end - add cancel task by task id
+
+ public final class GlobalScheduledTask implements ScheduledTask, Runnable { // Lophine - private -> public
private static final int STATE_IDLE = 0;
private static final int STATE_EXECUTING = 1;
@@ -146,13 +174,15 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
private final long repeatDelay; // in ticks
private Consumer<ScheduledTask> run;
private volatile int state;
+ private int taskId; // Lophine - add task id
private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(GlobalScheduledTask.class, "state", int.class);
- private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer<ScheduledTask> run) {
+ private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer<ScheduledTask> run, int taskId) { // Lophine - add task id
this.plugin = plugin;
this.repeatDelay = repeatDelay;
this.run = run;
+ this.taskId = taskId; // Lophine - add task id
}
private final int getStateVolatile() {
@@ -263,5 +293,11 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
}
}
}
+
+ // Lophine start - add task id
+ public int getTaskId() {
+ return taskId;
+ }
+ // Lophine end - add task id
}
}
@@ -0,0 +1,8 @@
package fun.bm.lophine;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
public class LophineLogger {
public static final Logger LOGGER = LogUtils.getClassLogger(); // only provided for some Logger can not be initialized
}
@@ -0,0 +1,41 @@
package fun.bm.lophine.command.counter;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.command.counter.sub.DisplayCommand;
import fun.bm.lophine.command.counter.sub.ResetCommand;
import fun.bm.lophine.command.counter.sub.ToggleCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.command.RootNode;
import org.leavesmc.leaves.util.HopperCounter;
public class CounterCommand extends RootNode {
private final String PERM_BASE;
public CounterCommand() {
super("counter", "lophine.commands.counter");
this.PERM_BASE = "lophine.commands.counter";
children(
new ToggleCommand(this),
new ResetCommand(this),
new DisplayCommand(this)
);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
context.getSender().sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Hopper Counter: ", NamedTextColor.GRAY),
Component.text(HopperCounter.isEnabled(), HopperCounter.isEnabled() ? NamedTextColor.AQUA : NamedTextColor.GRAY)
));
return true;
}
public boolean hasPermission(@NotNull CommandSender sender, String... subcommand) {
return hasPermission(PERM_BASE, sender, subcommand);
}
}
@@ -0,0 +1,24 @@
package fun.bm.lophine.command.counter;
import net.minecraft.commands.CommandSourceStack;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.LiteralNode;
public class CounterSubCommand extends LiteralNode {
protected final CounterCommand parent;
protected CounterSubCommand(String name, CounterCommand parent) {
super(name);
this.parent = parent;
}
@Override
public boolean requires(@NotNull CommandSourceStack source) {
return hasPermission(source.getSender());
}
protected boolean hasPermission(CommandSender sender) {
return parent.hasPermission(sender, this.name);
}
}
@@ -0,0 +1,91 @@
package fun.bm.lophine.command.counter.sub;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import fun.bm.lophine.command.counter.CounterCommand;
import fun.bm.lophine.command.counter.CounterSubCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.DyeColor;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.util.HopperCounter;
import java.util.concurrent.CompletableFuture;
public class DisplayCommand extends CounterSubCommand {
public DisplayCommand(CounterCommand parent) {
super("display", parent);
children(
DyeColorArg::new
);
}
public static void displayCounter(CommandContext context, @NotNull HopperCounter counter, boolean realTime) {
for (Component component : counter.format(MinecraftServer.getServer(), context.getSource().getLevel(), realTime)) {
context.getSender().sendMessage(component);
}
}
private static class DyeColorArg extends ArgumentNode<String> {
protected DyeColorArg() {
super("color", StringArgumentType.string());
children(
TimeArg::new
);
}
@Override
protected boolean execute(@NotNull CommandContext context) {
String color0 = context.getArgument(DyeColorArg.class);
DyeColor color = DyeColor.byName(color0, null);
if (color == null) return true;
HopperCounter counter = HopperCounter.getCounter(color);
displayCounter(context, counter, false);
return true;
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) {
String path = context.getArgumentOrDefault(DyeColorArg.class, "");
for (DyeColor value : DyeColor.values()) {
String color = value.getName();
if (color.startsWith(path)) {
builder.suggest(color);
}
}
return builder.buildFuture();
}
private static class TimeArg extends ArgumentNode<String> {
protected TimeArg() {
super("time", StringArgumentType.string());
}
@Override
protected boolean execute(@NotNull CommandContext context) {
String color0 = context.getArgument(DyeColorArg.class);
DyeColor color = DyeColor.byName(color0, null);
if (color == null) return true;
HopperCounter counter = HopperCounter.getCounter(color);
String timeType = context.getArgument(TimeArg.class);
switch (timeType) {
case "realtime" -> displayCounter(context, counter, true);
case "gametick" -> displayCounter(context, counter, false);
default ->
context.getSender().sendMessage(Component.text("Invalid time type: " + timeType, NamedTextColor.RED));
}
return true;
}
protected CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) {
builder.suggest("realtime");
builder.suggest("gametick");
return builder.buildFuture();
}
}
}
}
@@ -0,0 +1,75 @@
package fun.bm.lophine.command.counter.sub;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import fun.bm.lophine.command.counter.CounterCommand;
import fun.bm.lophine.command.counter.CounterSubCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.TextColor;
import net.minecraft.world.item.DyeColor;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.util.HopperCounter;
import java.util.concurrent.CompletableFuture;
public class ResetCommand extends CounterSubCommand {
public ResetCommand(CounterCommand parent) {
super("reset", parent);
children(
DyeColorArg::new
);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
HopperCounter.resetAll(context.getSource().getLevel(), false);
context.getSender().sendMessage(Component.text("Restarted all counters."));
return true;
}
private static class DyeColorArg extends ArgumentNode<String> {
protected DyeColorArg() {
super("color", StringArgumentType.string());
}
@Override
protected boolean execute(@NotNull CommandContext context) {
String color0 = context.getArgument(DyeColorArg.class);
if (color0.equals("all")) {
HopperCounter.resetAll(context.getSource().getLevel(), false);
context.getSender().sendMessage(Component.text("Restarted all counters."));
return true;
}
DyeColor color = DyeColor.byName(color0, null);
if (color == null) return true;
HopperCounter counter = HopperCounter.getCounter(color);
counter.reset(context.getSource().getLevel());
context.getSender().sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Restarted "),
Component.text(color.getName(), TextColor.color(color.getTextColor())),
Component.text(" counter.")
));
return true;
}
@Override
protected CompletableFuture<Suggestions> getSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) {
String path = context.getArgumentOrDefault(DyeColorArg.class, "");
if ("all".startsWith(path)) {
builder.suggest("all");
}
for (DyeColor value : DyeColor.values()) {
String color = value.getName();
if (color.startsWith(path)) {
builder.suggest(color);
}
}
return builder.buildFuture();
}
}
}
@@ -0,0 +1,55 @@
package fun.bm.lophine.command.counter.sub;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import fun.bm.lophine.command.counter.CounterCommand;
import fun.bm.lophine.command.counter.CounterSubCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.ArgumentNode;
import org.leavesmc.leaves.command.CommandContext;
import org.leavesmc.leaves.util.HopperCounter;
public class ToggleCommand extends CounterSubCommand {
public ToggleCommand(CounterCommand parent) {
super("toggle", parent);
children(
BooleanArg::new
);
}
@Override
protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException {
if (!HopperCounter.isEnabled()) {
HopperCounter.setEnabled(true);
context.getSender().sendMessage(Component.text("Hopper Counter now is enabled.", NamedTextColor.AQUA));
} else {
HopperCounter.setEnabled(false);
context.getSender().sendMessage(Component.text("Hopper Counter now is disabled.", NamedTextColor.RED));
}
return true;
}
private static class BooleanArg extends ArgumentNode<Boolean> {
protected BooleanArg() {
super("enabled", BoolArgumentType.bool());
}
@Override
protected boolean execute(@NotNull CommandContext context) {
boolean enabled = context.getArgument(BooleanArg.class);
if (enabled == HopperCounter.isEnabled()) {
context.getSender().sendMessage(Component.text("Hopper Counter is already " + (enabled ? "enabled" : "disabled") + ".", NamedTextColor.GRAY));
} else {
HopperCounter.setEnabled(enabled);
if (enabled) {
context.getSender().sendMessage(Component.text("Hopper Counter now is enabled.", NamedTextColor.AQUA));
} else {
context.getSender().sendMessage(Component.text("Hopper Counter now is disabled.", NamedTextColor.RED));
}
}
return true;
}
}
}
@@ -13,12 +13,6 @@ public class CommandConfig implements IConfigModule {
Allow to use tick command""")
public static boolean tick = false;
@ConfigInfo(name = "datapack_command_enabled", comments =
"""
Allow to use datapack command
--- datapack hot-update unsupported, please restart server""")
public static boolean datapack = false;
@ConfigInfo(name = "function_command_enabled", comments =
"""
Allow to use function command""")
@@ -0,0 +1,34 @@
package fun.bm.lophine.config.modules.experiment;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.HotReloadUnsupported;
import me.earthme.luminol.config.flags.TransformedConfig;
import me.earthme.luminol.enums.EnumConfigCategory;
/*
* This is a config module for redstone in experimental level
* If we think configs from here is stable for future, we will move them to function module directory
*/
@ConfigClassInfo(category = EnumConfigCategory.EXPERIMENT, name = "redstone")
public class RedStoneConfig implements IConfigModule {
@TransformedConfig(name = "enabled", directory = {"experiment", "redstone-ignore-upwards-update"})
@ConfigInfo(name = "redstone-ignore-upwards-update", comments = """
Should the pre-1.20 mechanism be reintroduced:
Redstone dust does not connect to adjacent redstone dust on trapdoors that are open
Pre-1.20.2 mechanism: Redstone dust, redstone repeaters, and redstone comparators do not check for attachment when receiving status updates from below""")
public static boolean redstoneIgnoreUpwardsUpdate = false;
@TransformedConfig(name = "enabled", directory = {"experiment", "cce-update-suppression"})
@ConfigInfo(name = "cce-update-suppression", comments = """
Is it permissible to use ClassCastException for update suppression?""")
public static boolean cce = false;
@HotReloadUnsupported
@ConfigInfo(name = "instant-block-updater")
public static boolean instantBlockUpdater = false;
@ConfigInfo(name = "old-block-remove-behaviour")
public static boolean oldBlockRemoveBehaviour = false;
}
@@ -0,0 +1,13 @@
package fun.bm.lophine.config.modules.fixes;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FIXES, name = "update-suppression-crash-fix")
public class UpdateSuppressionCrashFixConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Should crash caused by update suppression be prevented?""")
public static boolean enabled = true;
}
@@ -5,10 +5,12 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.bot.BotCommand;
import java.util.List;
import java.util.Set;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "fakeplayer")
public class FakeplayerConfig implements IConfigModule {
@@ -91,7 +93,7 @@ public class FakeplayerConfig implements IConfigModule {
}
@Override
public void onLoaded(CommentedFileConfig configInstance) {
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (enable) {
command = new BotCommand();
command.register();
@@ -24,4 +24,12 @@ public class OldFeatureConfig implements IConfigModule {
@TransformedConfig(name = "give_bad_omen_when_kill_raid_captain", directory = {"misc", "revert_raid_changes"}, transformComments = false)
@ConfigInfo(name = "old_raid_behavior")
public static boolean oldRaidBehavior = false;
@TransformedConfig(name = "villager-infinite-trade", directory = {"function", "villager"}, transformComments = false)
@TransformedConfig(name = "villager-infinite-trade", directory = {"misc", "villager"}, transformComments = false)
@TransformedConfig(name = "villager-infinite-trade", directory = {"misc", "villager-config"}, transformComments = false)
@ConfigInfo(name = "villager-void-trade", comments =
"""
Allow villager void trade.""")
public static boolean villagerVoidTrade = false;
}
@@ -1,19 +0,0 @@
package fun.bm.lophine.config.modules.function;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.config.flags.TransformedConfig;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.MISC, name = "villager")
public class VillagerConfig implements IConfigModule {
@TransformedConfig(name = "villager-infinite-trade", directory = {"misc", "villager"})
@TransformedConfig(name = "villager-infinite-trade", directory = {"misc", "villager-config"})
@ConfigInfo(name = "villager-infinite-trade", comments =
"""
Allow villager infinite trade (limit of 524288 times)
---- we won't edit saved data, only edit in send data to client.
(after we fixed void trade, this config will drop to void trade)""")
public static boolean villagerInfiniteTrade = false;
}
@@ -0,0 +1,31 @@
package fun.bm.lophine.config.modules.function;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import fun.bm.lophine.command.counter.CounterCommand;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "wool-hopper-counter")
public class WoolHopperCounterConfig implements IConfigModule {
@ConfigInfo(name = "enabled")
public static boolean enabled = false;
@ConfigInfo(name = "unlimited-speed")
public static boolean unlimitedSpeed = false;
@Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> exs) {
if (enabled) new CounterCommand().register();
}
@Override
public void onUnloaded(CommentedFileConfig configInstance) {
Bukkit.getCommandMap().getKnownCommands().remove("luminol:tpsbar");
}
}
@@ -0,0 +1,23 @@
package fun.bm.lophine.config.modules.function.protocol;
import fun.bm.lophine.enums.EnumAlternativePlaceType;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "alternative_block_placement", directory = {"protocol"})
public class AlternativeBlockPlacementProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Specify the precise placement protocol type
NONE Disable precise placement protocol
CARPET Precise placement protocol version 2
CARPET_FIX Enhanced precise placement protocol version 2 (requires MasaGadget installed on client)
LITEMATICA Precise placement protocol version 3""")
public static EnumAlternativePlaceType alternativeBlockPlacement = EnumAlternativePlaceType.NONE;
public static boolean needIgnoreDistance() {
return alternativeBlockPlacement != EnumAlternativePlaceType.NONE;
}
}
@@ -5,8 +5,11 @@ import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol;
import java.util.Set;
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "syncmatica", directory = {"protocol"})
public class SyncmaticaProtocolConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
@@ -19,7 +22,7 @@ public class SyncmaticaProtocolConfig implements IConfigModule {
Maximum Projection File Size (in bytes)""")
public static int quotaLimit = 40000000;
public void onLoaded(CommentedFileConfig configInstance) {
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> e) {
SyncmaticaProtocol.init(enabled);
}
}
@@ -8,6 +8,7 @@ import me.earthme.luminol.enums.EnumConfigCategory;
@ConfigClassInfo(category = EnumConfigCategory.REMOVED, name = "removed_config")
public class RemovedConfig implements IConfigModule {
@TransformedConfig(name = "datapack_command_enabled", directory = {"experiment", "command"}, transform = false)
@TransformedConfig(name = "disable_end_crystal_check", directory = {"fixes", "end_crystal"}, transform = false)
@TransformedConfig(name = "disable_end_crystal_check", directory = {"misc", "end_crystal"}, transform = false)
@TransformedConfig(name = "allow_skip_cooldown", directory = {"misc", "revert_raid_changes"}, transform = false)
@@ -0,0 +1,8 @@
package fun.bm.lophine.enums;
public enum EnumAlternativePlaceType {
NONE,
CARPET,
CARPET_FIX,
LITEMATICA
}
@@ -25,6 +25,7 @@ import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.threadedregions.RegionizedServer;
import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.minecraft.nbt.CompoundTag;
@@ -213,6 +214,18 @@ public class BotList {
return bot;
}
/*
* return true if async
*/
public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved, boolean async) {
if (async && !TickThread.isTickThreadFor(bot.level(), bot.getX(), bot.getZ())) {
bot.getBukkitEntity().taskScheduler.schedule((Entity unused) -> this.removeBot(bot, reason, remover, saved), null, 1L);
return true;
}
this.removeBot(bot, reason, remover, saved);
return false;
}
public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) {
return this.removeBot(bot, reason, remover, saved, this.dataStorage);
}
@@ -226,7 +239,7 @@ public class BotList {
}
if (bot.removeTaskId != -1) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
((FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).cancelTask(bot.removeTaskId);
bot.removeTaskId = -1;
}
@@ -304,12 +317,12 @@ public class BotList {
AtomicInteger received = new AtomicInteger();
for (ServerBot bot : this.bots) {
bot.resume = FakeplayerConfig.canResident;
if (TickThread.isTickThreadFor(bot)) {
if (TickThread.isTickThreadFor(bot.level(), bot.getX(), bot.getZ())) {
this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, FakeplayerConfig.canResident);
} else {
finished = false;
check.getAndIncrement();
removeBot(bot, check, received, new AtomicInteger());
this.removeBot(bot, check, received, new AtomicInteger());
}
}
return finished;
@@ -321,13 +334,17 @@ public class BotList {
BotList.LOGGER.info("Try to remove bot {} located in [{}]{},{},{} too many times!", bot.getName().getString(), bot.level().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ());
}
counter.getAndIncrement();
this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, FakeplayerConfig.canResident);
received.getAndIncrement();
try {
this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, FakeplayerConfig.canResident);
received.getAndIncrement();
} catch (Exception e) {
this.removeBot(bot, check, received, counter);
}
if (received.get() >= check.get()) {
this.forceShutdown = true;
MinecraftServer.getServer().stopServer();
}
}, (Entity unused) -> removeBot(bot, check, received, counter), 1L);
}, null, 1L);
}
public void loadBotInfo() {
@@ -154,7 +154,7 @@ public class ServerBot extends ServerPlayer {
this.notSleepTicks++;
}
if (FakeplayerConfig.regenAmount > 0.0 && getServer().checkTickCount(20)) {
if (FakeplayerConfig.regenAmount > 0.0 && this.tickCount % 20 == 0) {
float regenAmount = (float) (FakeplayerConfig.regenAmount * 20);
this.setHealth(Math.min(this.getHealth() + regenAmount, this.getMaxHealth()));
}
@@ -464,16 +464,16 @@ public class ServerBot extends ServerPlayer {
}
public void sendFakeData(ServerPlayerConnection playerConnection, boolean login) {
ChunkMap.TrackedEntity entityTracker = this.level().getChunkSource().chunkMap.entityMap.get(this.getId());
ChunkMap.TrackedEntity entityTracker = this.moonrise$getTrackedEntity();
if (entityTracker == null) {
LOGGER.warn("Fakeplayer cant get entity tracker for " + this.getId());
LOGGER.warn("Fakeplayer cant get entity tracker for {}", this.getId());
return;
}
playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity));
if (login) {
Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10);
Bukkit.getGlobalRegionScheduler().runDelayed(MinecraftInternalPlugin.INSTANCE, (unused) -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10);
} else {
playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f)));
}
@@ -499,7 +499,7 @@ public class ServerBot extends ServerPlayer {
Component defaultMessage = this.getCombatTracker().getDeathMessage();
BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag);
this.getServer().server.getPluginManager().callEvent(event);
MinecraftServer.getServer().server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
if (this.getHealth() <= 0) {
@@ -20,6 +20,7 @@ package org.leavesmc.leaves.command.bot.subcommands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler;
import net.minecraft.world.entity.LivingEntity;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@@ -102,12 +103,12 @@ public class RemoveCommand extends BotSubcommand {
boolean isReschedule = bot.removeTaskId != -1;
if (isReschedule) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
((FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).cancelTask(bot.removeTaskId);
}
bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> {
bot.removeTaskId = ((FoliaGlobalRegionScheduler.GlobalScheduledTask) Bukkit.getGlobalRegionScheduler().runDelayed(MinecraftInternalPlugin.INSTANCE, (unused) -> {
bot.removeTaskId = -1;
removeBot(bot, sender);
}, removeTimeSeconds * 20L).getTaskId();
}, removeTimeSeconds * 20L)).getTaskId();
sender.sendMessage(join(spaces(),
text("Bot", GRAY),
@@ -0,0 +1,90 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.entity.photographer;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.replay.ServerPhotographer;
import java.io.File;
public class CraftPhotographer extends CraftPlayer implements Photographer {
public CraftPhotographer(CraftServer server, ServerPhotographer entity) {
super(server, entity);
}
@Override
public void stopRecording() {
this.stopRecording(true);
}
@Override
public void stopRecording(boolean async) {
this.stopRecording(async, true);
}
@Override
public void stopRecording(boolean async, boolean save) {
this.getHandle().remove(async, save);
}
@Override
public void pauseRecording() {
this.getHandle().pauseRecording();
}
@Override
public void resumeRecording() {
this.getHandle().resumeRecording();
}
@Override
public void setRecordFile(@NotNull File file) {
this.getHandle().setSaveFile(file);
}
@Override
public void setFollowPlayer(@Nullable Player player) {
ServerPlayer serverPlayer = player != null ? ((CraftPlayer) player).getHandle() : null;
this.getHandle().setFollowPlayer(serverPlayer);
}
@Override
public @NotNull String getId() {
return this.getHandle().createState.id;
}
@Override
public ServerPhotographer getHandle() {
return (ServerPhotographer) entity;
}
public void setHandle(final ServerPhotographer entity) {
super.setHandle(entity);
}
@Override
public String toString() {
return "CraftPhotographer{" + "name=" + getName() + '}';
}
}
@@ -0,0 +1,99 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.entity.photographer;
import com.google.common.collect.Lists;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.replay.BukkitRecorderOption;
import org.leavesmc.leaves.replay.RecorderOption;
import org.leavesmc.leaves.replay.ServerPhotographer;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
public class CraftPhotographerManager implements PhotographerManager {
private final Collection<Photographer> photographerViews = Collections.unmodifiableList(Lists.transform(ServerPhotographer.getPhotographers(), ServerPhotographer::getBukkitPlayer));
@Override
public @Nullable Photographer getPhotographer(@NotNull UUID uuid) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid);
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer getPhotographer(@NotNull String id) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(id);
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location) {
ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createDefaultOption()).createSync();
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption) {
ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createFromBukkit(recorderOption)).createSync();
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public void removePhotographer(@NotNull String id) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(id);
if (photographer != null) {
photographer.remove(true);
}
}
@Override
public void removePhotographer(@NotNull UUID uuid) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid);
if (photographer != null) {
photographer.remove(true);
}
}
@Override
public void removeAllPhotographers() {
for (ServerPhotographer photographer : ServerPhotographer.getPhotographers()) {
photographer.remove(true);
}
}
@Override
public Collection<Photographer> getPhotographers() {
return photographerViews;
}
}
@@ -30,7 +30,11 @@ import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@LeavesProtocol.Register(namespace = "appleskin")
public class AppleSkinProtocol implements LeavesProtocol {
@@ -43,11 +47,11 @@ public class AppleSkinProtocol implements LeavesProtocol {
private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F;
private static final Map<ServerPlayer, Float> previousSaturationLevels = new HashMap<>();
private static final Map<ServerPlayer, Float> previousExhaustionLevels = new HashMap<>();
private static final Map<ServerPlayer, Boolean> previousNaturalRegeneration = new HashMap<>();
private static final Map<ServerPlayer, Float> previousSaturationLevels = new ConcurrentHashMap<>();
private static final Map<ServerPlayer, Float> previousExhaustionLevels = new ConcurrentHashMap<>();
private static final Map<ServerPlayer, Boolean> previousNaturalRegeneration = new ConcurrentHashMap<>();
private static final Map<UUID, Set<String>> subscribedChannels = new HashMap<>();
private static final Map<UUID, Set<String>> subscribedChannels = new ConcurrentHashMap<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
@@ -55,7 +55,7 @@ public class BBORProtocol implements LeavesProtocol {
private static final ResourceLocation STRUCTURE_LIST_SYNC = id("structure_list_sync_v1");
// call
private static final Map<Integer, ServerPlayer> players = new ConcurrentHashMap<>();
private static final Map<Integer, Set<BBoundingBox>> playerBoundingBoxesCache = new HashMap<>();
private static final Map<Integer, Set<BBoundingBox>> playerBoundingBoxesCache = new ConcurrentHashMap<>();
private static final Map<ResourceLocation, Map<BBoundingBox, Set<BBoundingBox>>> dimensionCache = new ConcurrentHashMap<>();
private static boolean initialized = false;
@@ -125,22 +125,24 @@ public class BBORProtocol implements LeavesProtocol {
Map<BBoundingBox, Set<BBoundingBox>> cache = getOrCreateCache(dimensionID);
for (var entry : structures.entrySet()) {
StructureStart structureStart = entry.getValue();
if (structureStart == null) {
return;
if (structureStart == null || !structureStart.isValid()) {
continue;
}
String type = "structure:" + entry.getKey();
BoundingBox bb = structureStart.getBoundingBox();
BBoundingBox boundingBox = buildStructure(bb, type);
if (cache.containsKey(boundingBox)) {
return;
continue;
}
Set<BBoundingBox> structureBoundingBoxes = new HashSet<>();
for (StructurePiece structureComponent : structureStart.getPieces()) {
structureBoundingBoxes.add(buildStructure(structureComponent.getBoundingBox(), type));
if (!structureStart.getPieces().isEmpty()) {
for (StructurePiece structureComponent : structureStart.getPieces()) {
structureBoundingBoxes.add(buildStructure(structureComponent.getBoundingBox(), type));
}
cache.put(boundingBox, structureBoundingBoxes);
}
cache.put(boundingBox, structureBoundingBoxes);
}
}
@@ -0,0 +1,170 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.piston.PistonBaseBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.*;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.Objects;
public class CarpetAlternativeBlockPlacement {
@Nullable
public static BlockState alternativeBlockPlacement(@NotNull Block block, @NotNull BlockPlaceContext context) {
Vec3 hitPos = context.getClickLocation();
BlockPos blockPos = context.getClickedPos();
double relativeHitX = hitPos.x - blockPos.getX();
BlockState state = block.getStateForPlacement(context);
Player player = context.getPlayer();
if (relativeHitX < 2 || state == null || player == null) {
return null;
}
EnumProperty<Direction> directionProp = getFirstDirectionProperty(state);
int protocolValue = ((int) relativeHitX - 2) / 2;
if (directionProp != null) {
Direction origFacing = state.getValue(directionProp);
Direction facing = origFacing;
int facingIndex = protocolValue & 0xF;
if (facingIndex == 6) {
facing = facing.getOpposite();
} else if (facingIndex <= 5) {
facing = Direction.from3DDataValue(facingIndex);
}
if (!directionProp.getPossibleValues().contains(facing)) {
facing = player.getDirection().getOpposite();
}
if (facing != origFacing && directionProp.getPossibleValues().contains(facing)) {
if (state.getBlock() instanceof BedBlock) {
BlockPos headPos = blockPos.relative(facing);
if (!context.getLevel().getBlockState(headPos).canBeReplaced(context)) {
return null;
}
}
state = state.setValue(directionProp, facing);
}
} else if (state.hasProperty(BlockStateProperties.AXIS)) {
Direction.Axis axis = Direction.Axis.VALUES[protocolValue % 3];
state = state.setValue(BlockStateProperties.AXIS, axis);
}
protocolValue &= 0xFFFFFFF0;
if (protocolValue >= 16) {
if (block instanceof RepeaterBlock) {
Integer delay = (protocolValue / 16);
if (RepeaterBlock.DELAY.getPossibleValues().contains(delay)) {
state = state.setValue(RepeaterBlock.DELAY, delay);
}
} else if (protocolValue == 16) {
if (block instanceof ComparatorBlock) {
state = state.setValue(ComparatorBlock.MODE, ComparatorMode.SUBTRACT);
} else if (state.hasProperty(BlockStateProperties.HALF) && state.getValue(BlockStateProperties.HALF) == Half.BOTTOM) {
state = state.setValue(BlockStateProperties.HALF, Half.TOP);
} else if (state.hasProperty(BlockStateProperties.SLAB_TYPE) && state.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.BOTTOM) {
state = state.setValue(BlockStateProperties.SLAB_TYPE, SlabType.TOP);
}
}
}
return state;
}
@Nullable
public static BlockState alternativeBlockPlacementFix(Block block, @NotNull BlockPlaceContext context) {
Direction facing;
Vec3 vec3d = context.getClickLocation();
BlockPos pos = context.getClickedPos();
double hitX = vec3d.x - pos.getX();
if (hitX < 2) {
return null;
}
int code = (int) (hitX - 2) / 2;
Player placer = Objects.requireNonNull(context.getPlayer());
Level world = context.getLevel();
if (block instanceof GlazedTerracottaBlock) {
facing = Direction.from3DDataValue(code);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(GlazedTerracottaBlock.FACING, facing);
} else if (block instanceof ObserverBlock) {
return block.defaultBlockState().setValue(ObserverBlock.FACING, Direction.from3DDataValue(code)).setValue(ObserverBlock.POWERED, true);
} else if (block instanceof RepeaterBlock) {
facing = Direction.from3DDataValue(code % 16);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(RepeaterBlock.FACING, facing).setValue(RepeaterBlock.DELAY, Mth.clamp(code / 16, 1, 4)).setValue(RepeaterBlock.LOCKED, Boolean.FALSE);
} else if (block instanceof TrapDoorBlock) {
facing = Direction.from3DDataValue(code % 16);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(TrapDoorBlock.FACING, facing).setValue(TrapDoorBlock.OPEN, Boolean.FALSE).setValue(TrapDoorBlock.HALF, (code >= 16) ? Half.TOP : Half.BOTTOM).setValue(TrapDoorBlock.OPEN, world.hasNeighborSignal(pos));
} else if (block instanceof ComparatorBlock) {
facing = Direction.from3DDataValue(code % 16);
if ((facing == Direction.UP) || (facing == Direction.DOWN)) {
facing = placer.getDirection().getOpposite();
}
ComparatorMode m = (hitX >= 16) ? ComparatorMode.SUBTRACT : ComparatorMode.COMPARE;
return block.defaultBlockState().setValue(ComparatorBlock.FACING, facing).setValue(ComparatorBlock.POWERED, Boolean.FALSE).setValue(ComparatorBlock.MODE, m);
} else if (block instanceof DispenserBlock) {
return block.defaultBlockState().setValue(DispenserBlock.FACING, Direction.from3DDataValue(code)).setValue(DispenserBlock.TRIGGERED, Boolean.FALSE);
} else if (block instanceof PistonBaseBlock) {
return block.defaultBlockState().setValue(DirectionalBlock.FACING, Direction.from3DDataValue(code)).setValue(PistonBaseBlock.EXTENDED, Boolean.FALSE);
} else if (block instanceof StairBlock) {
return Objects.requireNonNull(block.getStateForPlacement(context)).setValue(StairBlock.FACING, Direction.from3DDataValue(code % 16)).setValue(StairBlock.HALF, (hitX >= 16) ? Half.TOP : Half.BOTTOM);
}
return null;
}
@SuppressWarnings("unchecked")
@Nullable
public static EnumProperty<Direction> getFirstDirectionProperty(@NotNull BlockState state) {
for (Property<?> prop : state.getProperties()) {
if (prop instanceof EnumProperty<?> enumProperty) {
if (enumProperty.getValueClass().equals(Direction.class)) {
return (EnumProperty<Direction>) enumProperty;
}
}
}
return null;
}
}
@@ -0,0 +1,243 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.protocol;
import com.google.common.collect.ImmutableSet;
import com.mojang.logging.LogUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BedBlock;
import net.minecraft.world.level.block.RepeaterBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class LitematicaEasyPlaceProtocol {
private static final Logger LOGGER = LogUtils.getClassLogger();
public static final ImmutableSet<Property<?>> WHITELISTED_PROPERTIES = ImmutableSet.of(
BlockStateProperties.INVERTED,
BlockStateProperties.OPEN,
BlockStateProperties.BELL_ATTACHMENT,
BlockStateProperties.AXIS,
BlockStateProperties.HALF,
BlockStateProperties.ATTACH_FACE,
BlockStateProperties.CHEST_TYPE,
BlockStateProperties.MODE_COMPARATOR,
BlockStateProperties.DOOR_HINGE,
BlockStateProperties.FACING,
BlockStateProperties.FACING_HOPPER,
BlockStateProperties.HORIZONTAL_FACING,
BlockStateProperties.ORIENTATION,
BlockStateProperties.RAIL_SHAPE,
BlockStateProperties.RAIL_SHAPE_STRAIGHT,
BlockStateProperties.SLAB_TYPE,
BlockStateProperties.STAIRS_SHAPE,
BlockStateProperties.BITES,
BlockStateProperties.DELAY,
BlockStateProperties.NOTE,
BlockStateProperties.ROTATION_16
);
public static BlockState applyPlacementProtocol(BlockState state, BlockPlaceContext context) {
return applyPlacementProtocolV3(state, UseContext.from(context, context.getHand()));
}
@SuppressWarnings("unchecked")
@Nullable
private static <T extends Comparable<T>> BlockState applyPlacementProtocolV3(BlockState state, @NotNull UseContext context) {
int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2;
BlockState oldState = state;
if (protocolValue < 0) {
return oldState;
}
EnumProperty<Direction> property = CarpetAlternativeBlockPlacement.getFirstDirectionProperty(state);
if (property != null && property != BlockStateProperties.VERTICAL_DIRECTION) {
state = applyDirectionProperty(state, context, property, protocolValue);
if (state == null) {
return null;
}
if (state.canSurvive(context.getWorld(), context.getPos())) {
oldState = state;
} else {
state = oldState;
}
protocolValue >>>= 3;
}
protocolValue >>>= 1;
List<Property<?>> propList = new ArrayList<>(state.getBlock().getStateDefinition().getProperties());
propList.sort(Comparator.comparing(Property::getName));
try {
for (Property<?> p : propList) {
if (property != null && property.equals(p)) {
continue;
}
if (!WHITELISTED_PROPERTIES.contains(p)) {
continue;
}
Property<T> prop = (Property<T>) p;
List<T> list = new ArrayList<>(prop.getPossibleValues());
list.sort(Comparable::compareTo);
int requiredBits = Mth.log2(Mth.smallestEncompassingPowerOfTwo(list.size()));
int bitMask = ~(0xFFFFFFFF << requiredBits);
int valueIndex = protocolValue & bitMask;
if (valueIndex < list.size()) {
T value = list.get(valueIndex);
if (!state.getValue(prop).equals(value) && value != SlabType.DOUBLE) {
state = state.setValue(prop, value);
if (state.canSurvive(context.getWorld(), context.getPos())) {
oldState = state;
} else {
state = oldState;
}
}
protocolValue >>>= requiredBits;
}
}
} catch (Exception e) {
LOGGER.warn("Exception trying to apply placement protocol value", e);
}
if (state.getBlock() instanceof RepeaterBlock repeaterBlock) {
state = state.setValue(RepeaterBlock.LOCKED, repeaterBlock.isLocked(context.getWorld(), context.getPos(), state));
}
if (state.canSurvive(context.getWorld(), context.getPos())) {
return state;
} else {
return null;
}
}
private static BlockState applyDirectionProperty(BlockState state, UseContext context, EnumProperty<Direction> property, int protocolValue) {
Direction facingOrig = state.getValue(property);
Direction facing = facingOrig;
int decodedFacingIndex = (protocolValue & 0xF) >> 1;
if (decodedFacingIndex == 6) {
facing = facing.getOpposite();
} else if (decodedFacingIndex <= 5) {
facing = Direction.from3DDataValue(decodedFacingIndex);
if (!property.getPossibleValues().contains(facing)) {
facing = context.getEntity().getDirection().getOpposite();
}
}
if (facing != facingOrig && property.getPossibleValues().contains(facing)) {
if (state.getBlock() instanceof BedBlock) {
BlockPos headPos = context.pos.relative(facing);
BlockPlaceContext ctx = context.getItemPlacementContext();
if (ctx == null || !context.getWorld().getBlockState(headPos).canBeReplaced(ctx)) {
return null;
}
}
state = state.setValue(property, facing);
}
return state;
}
public static class UseContext {
private final Level world;
private final BlockPos pos;
private final Direction side;
private final Vec3 hitVec;
private final LivingEntity entity;
private final InteractionHand hand;
@Nullable
private final BlockPlaceContext itemPlacementContext;
private UseContext(Level world, BlockPos pos, Direction side, Vec3 hitVec, LivingEntity entity, InteractionHand hand, @Nullable BlockPlaceContext itemPlacementContext) {
this.world = world;
this.pos = pos;
this.side = side;
this.hitVec = hitVec;
this.entity = entity;
this.hand = hand;
this.itemPlacementContext = itemPlacementContext;
}
@NotNull
public static UseContext from(@NotNull BlockPlaceContext ctx, InteractionHand hand) {
Vec3 pos = ctx.getClickLocation();
return new UseContext(ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace(), new Vec3(pos.x, pos.y, pos.z), ctx.getPlayer(), hand, ctx);
}
public Level getWorld() {
return this.world;
}
public BlockPos getPos() {
return this.pos;
}
public Direction getSide() {
return this.side;
}
public Vec3 getHitVec() {
return this.hitVec;
}
public LivingEntity getEntity() {
return this.entity;
}
public InteractionHand getHand() {
return this.hand;
}
@Nullable
public BlockPlaceContext getItemPlacementContext() {
return this.itemPlacementContext;
}
}
}
@@ -24,7 +24,6 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.protocol.core.invoker.*;
import org.slf4j.Logger;
@@ -66,6 +65,8 @@ public class LeavesProtocolManager {
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadServer>> RELOAD_SERVER = new ArrayList<>();
private static final List<EmptyInvokerHolder<ProtocolHandler.ReloadDataPack>> RELOAD_DATAPACK = new ArrayList<>();
private static long lastAcceptTime = 0;
@SuppressWarnings("unchecked")
public static void init() {
for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
@@ -253,8 +254,11 @@ public class LeavesProtocolManager {
}
public static void handleTick() {
long currentTime = System.currentTimeMillis() / 50;
if (currentTime == lastAcceptTime) return;
lastAcceptTime = currentTime;
for (var tickerInfo : TICKERS) {
if (MinecraftServer.getServer().checkTickCount(tickerInfo.owner().tickerInterval(tickerInfo.handler().tickerId()))) {
if (currentTime % tickerInfo.owner().tickerInterval(tickerInfo.handler().tickerId()) == 0) {
tickerInfo.invoke();
}
}
@@ -17,6 +17,7 @@
package org.leavesmc.leaves.protocol.jade;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.protocol.JadeProtocolConfig;
import net.minecraft.core.BlockPos;
@@ -26,6 +27,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.AgeableMob;
@@ -150,12 +152,16 @@ public class JadeProtocol implements LeavesProtocol {
return;
}
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
enabledPlayers.add(player);
synchronized (enabledPlayers) {
enabledPlayers.add(player);
}
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLeave(ServerPlayer player) {
enabledPlayers.remove(player);
synchronized (enabledPlayers) {
enabledPlayers.remove(player);
}
}
@ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class)
@@ -196,6 +202,7 @@ public class JadeProtocol implements LeavesProtocol {
@ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class)
public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) {
ServerLevel level = player.level();
player.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> {
BlockAccessor accessor = payload.data().unpack(player);
if (accessor == null) {
@@ -204,7 +211,7 @@ public class JadeProtocol implements LeavesProtocol {
BlockPos pos = accessor.getPosition();
Block block = accessor.getBlock();
BlockEntity blockEntity = accessor.getBlockEntity();
BlockEntity blockEntity = TickThread.isTickThreadFor(level, pos) ? accessor.getBlockEntity() : null;
double maxDistance = Mth.square(player.blockInteractionRange() + 21);
if (pos.distSqr(player.blockPosition()) > maxDistance || !accessor.getLevel().isLoaded(pos)) {
return;
@@ -242,8 +249,10 @@ public class JadeProtocol implements LeavesProtocol {
@ProtocolHandler.ReloadServer
public static void onServerReload() {
rebuildShearableBlocks();
for (ServerPlayer player : enabledPlayers) {
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
synchronized (enabledPlayers) {
for (ServerPlayer player : enabledPlayers) {
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
}
}
}
@@ -72,12 +72,12 @@ public class ItemCollector<T> {
return null;
}
long currentVersion = iterator.getVersion(container);
long gameTime = request.getLevel().getServer().getTickCount();
long gameTimeReforged = System.currentTimeMillis() / 50;
if (mergedResult != null && iterator.isFinished()) {
if (version == currentVersion) {
return mergedResult; // content not changed
}
if (lastTimeFinished + 5 > gameTime) {
if (lastTimeFinished + 5 > gameTimeReforged) {
return mergedResult; // avoid update too frequently
}
iterator.reset();
@@ -104,7 +104,7 @@ public class ItemCollector<T> {
mergedResult = groups;
lastTimeIsEmpty = mergedResult.getFirst().views.isEmpty();
version = currentVersion;
lastTimeFinished = gameTime;
lastTimeFinished = gameTimeReforged;
items.clear();
}
return groups;
@@ -98,13 +98,13 @@ public class REIServerProtocol implements LeavesProtocol {
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
private static int minecraftRecipeVer = 0;
private static int nextReiRecipeVer = -1;
private static long minecraftRecipeVer = 0;
private static long nextReiRecipeVer = -1;
private static ImmutableList<CustomPacketPayload> cachedPayloads;
@ProtocolHandler.ReloadDataPack
public static void onRecipeReload() {
minecraftRecipeVer = MinecraftServer.getServer().getTickCount();
minecraftRecipeVer = System.currentTimeMillis() / 50;
}
@Contract("_ -> new")
@@ -138,7 +138,7 @@ public class REIServerProtocol implements LeavesProtocol {
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static void reloadRecipe(int reiRecipeVer) {
private static void reloadRecipe(long reiRecipeVer) {
ImmutableList.Builder<Display> builder = ImmutableList.builder();
MinecraftServer server = MinecraftServer.getServer();
RecipeMap recipeMap = server.getRecipeManager().recipes;
@@ -56,11 +56,12 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
private static final List<ServerPlayer> players = Collections.synchronizedList(new ArrayList<>());
private static final int updateInterval = 80;
private static final ConcurrentHashMap<ServerPlayer, List<DataLogger.Type>> loggerPlayers = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<DataLogger.Type, DataLogger<?>> LOGGERS = new ConcurrentHashMap<>();
private static final Map<ServerPlayer, List<DataLogger.Type>> loggerPlayers = new ConcurrentHashMap<>();
private static final Map<DataLogger.Type, DataLogger<?>> LOGGERS = new ConcurrentHashMap<>();
private static final Table<DataLogger.Type, ServerPlayer, Tag> DATA = HashBasedTable.create();
public static boolean refreshSpawnMetadata = false;
private long lastAcceptTime = 0;
@ProtocolHandler.Init
private static void initializeLoggers() {
@@ -238,9 +239,12 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
return;
}
MinecraftServer server = MinecraftServer.getServer();
long currentTime = System.currentTimeMillis() / 50;
if (currentTime == lastAcceptTime) return;
lastAcceptTime = currentTime;
if (server.checkTickCount(ServuxProtocolConfig.hudUpdateInterval)) {
if (currentTime % ServuxProtocolConfig.hudUpdateInterval == 0) {
MinecraftServer server = MinecraftServer.getServer();
LOGGERS.forEach((type, logger) -> {
if (!isLoggerTypeEnabled(type)) {
return;
@@ -255,10 +259,6 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
}
loggerPlayers.forEach((player, list) -> {
if (list.isEmpty()) {
return;
}
CompoundTag nbt = new CompoundTag();
for (DataLogger.Type type : list) {
Tag data = DATA.get(type, player);
@@ -267,7 +267,9 @@ public class ServuxHudDataProtocol implements LeavesProtocol {
DATA.remove(type, player);
}
}
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_DATA_LOGGER_TICK, nbt));
if (!nbt.isEmpty()) {
sendPacket(player, new HudDataPayload(HudDataPayloadType.PACKET_S2C_DATA_LOGGER_TICK, nbt));
}
});
}
@@ -84,7 +84,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
@ProtocolHandler.Ticker
public static void tick() {
MinecraftServer server = MinecraftServer.getServer();
int tickCounter = server.getTickCount();
long tickCounter = System.currentTimeMillis() / 50;
retainDistance = server.getPlayerList().getViewDistance() + 2;
for (ServerPlayer player : players.values()) {
// TODO DimensionChange
@@ -95,11 +95,11 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
public static void onStartedWatchingChunk(ServerPlayer player, LevelChunk chunk) {
MinecraftServer server = player.getServer();
if (players.containsKey(player.getId()) && server != null) {
addChunkTimeoutIfHasReferences(player.getUUID(), chunk, server.getTickCount());
addChunkTimeoutIfHasReferences(player.getUUID(), chunk, System.currentTimeMillis() / 50);
}
}
private static void addChunkTimeoutIfHasReferences(final UUID uuid, LevelChunk chunk, final int tickCounter) {
private static void addChunkTimeoutIfHasReferences(final UUID uuid, LevelChunk chunk, final long tickCounter) {
final ChunkPos pos = chunk.getPos();
if (chunkHasStructureReferences(pos.x, pos.z, chunk.getLevel())) {
@@ -135,7 +135,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
MinecraftServer server = MinecraftServer.getServer();
sendMetaData(player);
initialSyncStructures(player, player.moonrise$getViewDistanceHolder().getViewDistances().sendViewDistance() + 2, server.getTickCount());
initialSyncStructures(player, player.moonrise$getViewDistanceHolder().getViewDistances().sendViewDistance() + 2, System.currentTimeMillis() / 50);
}
private static void sendMetaData(ServerPlayer player) {
@@ -149,7 +149,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
sendPacket(player, new StructuresPayload(StructuresPayloadType.PACKET_S2C_METADATA, tag));
}
public static void initialSyncStructures(ServerPlayer player, int chunkRadius, int tickCounter) {
public static void initialSyncStructures(ServerPlayer player, int chunkRadius, long tickCounter) {
UUID uuid = player.getUUID();
ChunkPos center = player.getLastSectionPos().chunk();
Map<Structure, LongSet> references = getStructureReferences(player.level(), center, chunkRadius);
@@ -195,7 +195,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
}
}
public static void sendStructures(ServerPlayer player, Map<Structure, LongSet> references, int tickCounter) {
public static void sendStructures(ServerPlayer player, Map<Structure, LongSet> references, long tickCounter) {
ServerLevel world = player.level();
Map<ChunkPos, StructureStart> starts = getStructureStarts(world, references);
@@ -254,7 +254,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
return starts;
}
public static void refreshTrackedChunks(ServerPlayer player, int tickCounter) {
public static void refreshTrackedChunks(ServerPlayer player, long tickCounter) {
UUID uuid = player.getUUID();
Map<ChunkPos, Timeout> map = timeouts.get(uuid);
@@ -263,7 +263,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
}
}
public static void sendAndRefreshExpiredStructures(ServerPlayer player, Map<ChunkPos, Timeout> map, int tickCounter) {
public static void sendAndRefreshExpiredStructures(ServerPlayer player, Map<ChunkPos, Timeout> map, long tickCounter) {
Set<ChunkPos> positionsToUpdate = new HashSet<>();
for (Map.Entry<ChunkPos, Timeout> entry : map.entrySet()) {
@@ -303,7 +303,7 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
return Math.abs(pos.x - center.x) > retainDistance || Math.abs(pos.z - center.z) > retainDistance;
}
public static void addOrRefreshTimeouts(final UUID uuid, final Map<Structure, LongSet> references, final int tickCounter) {
public static void addOrRefreshTimeouts(final UUID uuid, final Map<Structure, LongSet> references, final long tickCounter) {
Map<ChunkPos, Timeout> map = timeouts.computeIfAbsent(uuid, (u) -> new HashMap<>());
for (LongSet chunks : references.values()) {
@@ -402,18 +402,17 @@ public class ServuxStructuresProtocol implements LeavesProtocol {
}
public static class Timeout {
private int lastSync;
private long lastSync;
public Timeout(int currentTick) {
public Timeout(long currentTick) {
this.lastSync = currentTick;
}
public boolean needsUpdate(int currentTick, int timeout) {
if (timeout == -1 || currentTick - this.lastSync >= timeout) return true;
return MinecraftServer.getServer().checkTickCount(timeout);
public boolean needsUpdate(int timeout, long currentTick) {
return timeout == -1 || currentTick - this.lastSync >= timeout;
}
public void setLastSync(int tickCounter) {
public void setLastSync(long tickCounter) {
this.lastSync = tickCounter;
}
}
@@ -37,6 +37,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@LeavesProtocol.Register(namespace = "syncmatica")
public class CommunicationManager implements LeavesProtocol {
@@ -46,8 +47,8 @@ public class CommunicationManager implements LeavesProtocol {
protected static final Map<UUID, Exchange> modifyState = new HashMap<>();
protected static final Rotation[] rotOrdinals = Rotation.values();
protected static final Mirror[] mirOrdinals = Mirror.values();
private static final Map<UUID, List<ServerPlacement>> downloadingFile = new HashMap<>();
private static final Map<ExchangeTarget, ServerPlayer> playerMap = new HashMap<>();
private static final Map<UUID, List<ServerPlacement>> downloadingFile = new ConcurrentHashMap<>();
private static final Map<ExchangeTarget, ServerPlayer> playerMap = new ConcurrentHashMap<>();
public CommunicationManager() {
}
@@ -0,0 +1,37 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.region;
import ca.spottedleaf.moonrise.paper.PaperHooks;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.chunk.LevelChunk;
public final class LeavesHooks extends PaperHooks {
@Override
public String getBrand() {
return "Lophine"; // Lophine - rebrand
}
@Override
public void onChunkWatch(ServerLevel world, LevelChunk chunk, ServerPlayer player) {
super.onChunkWatch(world, chunk, player);
org.leavesmc.leaves.protocol.servux.ServuxStructuresProtocol.onStartedWatchingChunk(player, chunk); // servux
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.Checksum;
public class DigestOutputStream extends OutputStream {
private final Checksum sum;
private final OutputStream out;
public DigestOutputStream(OutputStream out, Checksum sum) {
this.out = out;
this.sum = sum;
}
@Override
public void close() throws IOException {
out.close();
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void write(int b) throws IOException {
sum.update(b);
out.write(b);
}
@Override
public void write(byte @NotNull [] b) throws IOException {
sum.update(b);
out.write(b);
}
@Override
public void write(byte @NotNull [] b, int off, int len) throws IOException {
sum.update(b, off, len);
out.write(b, off, len);
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class RecordMetaData {
public static final int CURRENT_FILE_FORMAT_VERSION = 14;
public boolean singleplayer = false;
public String serverName = "Leaves";
public int duration = 0;
public long date;
public String mcversion;
public String fileFormat = "MCPR";
public int fileFormatVersion;
public int protocol;
public String generator;
public int selfId = -1;
public Set<UUID> players = new HashSet<>();
}
@@ -0,0 +1,277 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.DynamicOps;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.local.LocalChannel;
import net.minecraft.SharedConstants;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.RegistrySynchronization;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.protocol.BundlePacket;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.*;
import net.minecraft.network.protocol.common.custom.BrandPayload;
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket;
import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks;
import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.packs.repository.KnownPack;
import net.minecraft.tags.TagNetworkSerialization;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.PositionMoveRotation;
import net.minecraft.world.flag.FeatureFlags;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
public class Recorder extends Connection {
public static final Logger LOGGER = LogUtils.getClassLogger();
public final ExecutorService saveService = Executors.newSingleThreadExecutor();
private final ReplayFile replayFile;
private final ServerPhotographer photographer;
private final RecorderOption recorderOption;
private final RecordMetaData metaData;
private final AtomicBoolean isSaving = new AtomicBoolean(false);
private boolean stopped = false;
private boolean paused = false;
private boolean resumeOnNextPacket = true;
private long startTime;
private long lastPacket;
private long timeShift = 0;
private boolean isSaved;
private ConnectionProtocol state = ConnectionProtocol.LOGIN;
public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException {
super(PacketFlow.CLIENTBOUND);
this.photographer = photographer;
this.recorderOption = recorderOption;
this.metaData = new RecordMetaData();
this.replayFile = new ReplayFile(replayFile, saveService);
this.channel = new LocalChannel();
}
public void start() {
startTime = System.currentTimeMillis();
metaData.singleplayer = false;
metaData.serverName = recorderOption.serverName;
metaData.date = startTime;
metaData.mcversion = SharedConstants.getCurrentVersion().name();
// TODO start event
this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN);
this.startConfiguration();
savePacket(ClientboundPlayerPositionPacket.of(photographer.getId(), PositionMoveRotation.of(photographer), Collections.emptySet()));
if (recorderOption.forceWeather != null) {
setWeather(recorderOption.forceWeather);
}
}
public void startConfiguration() {
this.state = ConnectionProtocol.CONFIGURATION;
MinecraftServer server = MinecraftServer.getServer();
this.savePacket(new ClientboundCustomPayloadPacket(new BrandPayload(server.getServerModName())), ConnectionProtocol.CONFIGURATION);
this.savePacket(new ClientboundServerLinksPacket(server.serverLinks().untrust()), ConnectionProtocol.CONFIGURATION);
this.savePacket(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(server.getWorldData().enabledFeatures())), ConnectionProtocol.CONFIGURATION);
List<KnownPack> knownPackslist = server.getResourceManager().listPacks().flatMap((iresourcepack) -> iresourcepack.location().knownPackInfo().stream()).toList();
this.savePacket(new ClientboundSelectKnownPacks(knownPackslist), ConnectionProtocol.CONFIGURATION);
server.getServerResourcePack().ifPresent((info) -> this.savePacket(new ClientboundResourcePackPushPacket(
info.id(), info.url(), info.hash(), info.isRequired(), Optional.ofNullable(info.prompt())
)));
LayeredRegistryAccess<RegistryLayer> layeredregistryaccess = server.registries();
DynamicOps<Tag> dynamicOps = layeredregistryaccess.compositeAccess().createSerializationContext(NbtOps.INSTANCE);
RegistrySynchronization.packRegistries(dynamicOps, layeredregistryaccess.getAccessFrom(RegistryLayer.WORLDGEN), Set.copyOf(knownPackslist),
(key, entries) ->
this.savePacket(new ClientboundRegistryDataPacket(key, entries), ConnectionProtocol.CONFIGURATION)
);
this.savePacket(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(layeredregistryaccess)), ConnectionProtocol.CONFIGURATION);
this.savePacket(ClientboundFinishConfigurationPacket.INSTANCE, ConnectionProtocol.CONFIGURATION);
state = ConnectionProtocol.PLAY;
}
@Override
public void flushChannel() {
}
public void stop() {
stopped = true;
}
public void pauseRecording() {
resumeOnNextPacket = false;
paused = true;
}
public void resumeRecording() {
resumeOnNextPacket = true;
}
public void setWeather(RecorderOption.RecordWeather weather) {
weather.getPackets().forEach(this::savePacket);
}
public long getRecordedTime() {
final long base = System.currentTimeMillis() - startTime;
return base - timeShift;
}
private synchronized long getCurrentTimeAndUpdate() {
long now = getRecordedTime();
if (paused) {
if (resumeOnNextPacket) {
paused = false;
}
timeShift += now - lastPacket;
return lastPacket;
}
return lastPacket = now;
}
@Override
public boolean isConnected() {
return true;
}
@Override
public void send(@NotNull Packet<?> packet, @Nullable ChannelFutureListener callbacks, boolean flush) {
if (stopped) {
return;
}
switch (packet) {
case BundlePacket<?> packet1 -> {
packet1.subPackets().forEach(subPacket -> send(subPacket, null));
return;
}
case ClientboundAddEntityPacket packet1 -> {
if (packet1.getType() == EntityType.PLAYER) {
metaData.players.add(packet1.getUUID());
saveMetadata();
}
}
case ClientboundDisconnectPacket ignored -> {
return;
}
case ClientboundTrackedWaypointPacket ignored -> {
return;
}
default -> {
}
}
if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) {
packet = new ClientboundSetTimePacket(packet1.dayTime(), recorderOption.forceDayTime, false);
}
if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) {
ClientboundGameEventPacket.Type type = packet1.getEvent();
if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) {
return;
}
}
if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) {
return;
}
savePacket(packet);
}
private void saveMetadata() {
saveService.execute(() -> {
try {
replayFile.saveMetaData(metaData);
} catch (IOException e) {
LOGGER.error("Error saving metadata", e);
}
});
}
private void savePacket(Packet<?> packet) {
this.savePacket(packet, state);
}
private void savePacket(Packet<?> packet, final ConnectionProtocol protocol) {
final long timestamp = getCurrentTimeAndUpdate();
try {
replayFile.savePacket(timestamp, packet, protocol);
} catch (Exception e) {
LOGGER.error("Error saving packet on thread {}. Are you using some plugin that modify data asynchronously?", Thread.currentThread(), e);
}
}
public boolean isSaved() {
return isSaved;
}
public CompletableFuture<Void> saveRecording(File dest, boolean save) {
if (!isSaving.compareAndSet(false, true)) {
LOGGER.error("saveRecording() called twice");
return CompletableFuture.failedFuture(new IllegalStateException("saveRecording() called twice"));
}
isSaved = true;
metaData.duration = (int) lastPacket;
return CompletableFuture.runAsync(() -> {
try {
replayFile.saveMetaData(metaData);
if (save) {
replayFile.closeAndSave(dest);
} else {
replayFile.closeNotSave();
}
} catch (IOException e) {
throw new CompletionException(e);
}
}, saveService);
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class RecorderOption {
public int recordDistance = -1;
public String serverName = "Leaves";
public RecordWeather forceWeather = null;
public int forceDayTime = -1;
public boolean ignoreChat = false;
public boolean ignoreItem = false;
@NotNull
@Contract(" -> new")
public static RecorderOption createDefaultOption() {
return new RecorderOption();
}
@NotNull
public static RecorderOption createFromBukkit(@NotNull BukkitRecorderOption bukkitRecorderOption) {
RecorderOption recorderOption = new RecorderOption();
// recorderOption.recordDistance = bukkitRecorderOption.recordDistance;
// recorderOption.ignoreItem = bukkitRecorderOption.ignoreItem;
recorderOption.serverName = bukkitRecorderOption.serverName;
recorderOption.ignoreChat = bukkitRecorderOption.ignoreChat;
recorderOption.forceDayTime = bukkitRecorderOption.forceDayTime;
recorderOption.forceWeather = switch (bukkitRecorderOption.forceWeather) {
case RAIN -> RecordWeather.RAIN;
case CLEAR -> RecordWeather.CLEAR;
case THUNDER -> RecordWeather.THUNDER;
case NULL -> null;
};
return recorderOption;
}
public enum RecordWeather {
CLEAR(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)),
RAIN(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)),
THUNDER(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 1));
private final List<Packet<?>> packets;
RecordWeather(Packet<?>... packets) {
this.packets = List.of(packets);
}
public List<Packet<?>> getPackets() {
return packets;
}
}
}
@@ -0,0 +1,217 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.SharedConstants;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.ProtocolInfo;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.configuration.ConfigurationProtocols;
import net.minecraft.network.protocol.game.GameProtocols;
import net.minecraft.network.protocol.login.LoginProtocols;
import net.minecraft.network.protocol.status.StatusProtocols;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.util.UUIDSerializer;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.leavesmc.leaves.replay.Recorder.LOGGER;
public class ReplayFile {
private static final String RECORDING_FILE = "recording.tmcpr";
private static final String RECORDING_FILE_CRC32 = "recording.tmcpr.crc32";
private static final String MARKER_FILE = "markers.json";
private static final String META_FILE = "metaData.json";
private static final Gson MARKER_GSON = new GsonBuilder().registerTypeAdapter(ReplayMarker.class, new ReplayMarker.Serializer()).create();
private static final Gson META_GSON = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDSerializer()).create();
private final File tmpDir;
private final DataOutputStream packetStream;
private final CRC32 crc32 = new CRC32();
private final File markerFile;
private final File metaFile;
private final Map<ConnectionProtocol, ProtocolInfo<?>> protocols;
private final ExecutorService saveService;
public ReplayFile(@NotNull File name, ExecutorService saveService) throws IOException {
this.saveService = saveService;
this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp");
if (tmpDir.exists()) {
if (!ReplayFile.deleteDir(tmpDir)) {
throw new IOException("Recording file " + name + " already exists!");
}
}
if (!tmpDir.mkdirs()) {
throw new IOException("Failed to create temp directory for recording " + tmpDir);
}
File packetFile = new File(tmpDir, RECORDING_FILE);
this.metaFile = new File(tmpDir, META_FILE);
this.markerFile = new File(tmpDir, MARKER_FILE);
this.packetStream = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(packetFile)), crc32));
this.protocols = Map.of(
ConnectionProtocol.STATUS, StatusProtocols.CLIENTBOUND,
ConnectionProtocol.LOGIN, LoginProtocols.CLIENTBOUND,
ConnectionProtocol.CONFIGURATION, ConfigurationProtocols.CLIENTBOUND,
ConnectionProtocol.PLAY, GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess()))
);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private byte @NotNull [] getPacketBytes(Packet packet, ConnectionProtocol state) {
ProtocolInfo<?> protocol = this.protocols.get(state);
if (protocol == null) {
throw new IllegalArgumentException("Unknown protocol state " + state);
}
ByteBuf buf = Unpooled.buffer();
protocol.codec().encode(buf, packet);
buf.readerIndex(0);
byte[] ret = ByteBufUtil.getBytes(buf);
buf.release();
return ret;
}
public void saveMarkers(List<ReplayMarker> markers) throws IOException {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(markerFile), StandardCharsets.UTF_8)) {
writer.write(MARKER_GSON.toJson(markers));
}
}
public void saveMetaData(@NotNull RecordMetaData data) throws IOException {
data.fileFormat = "MCPR";
data.fileFormatVersion = RecordMetaData.CURRENT_FILE_FORMAT_VERSION;
data.protocol = SharedConstants.getCurrentVersion().protocolVersion();
data.generator = ProtocolUtils.buildProtocolVersion("replay");
try (Writer writer = new OutputStreamWriter(new FileOutputStream(metaFile), StandardCharsets.UTF_8)) {
writer.write(META_GSON.toJson(data));
}
}
public void savePacket(long timestamp, Packet<?> packet, ConnectionProtocol protocol) {
byte[] data = getPacketBytes(packet, protocol);
saveService.execute(() -> {
try {
packetStream.writeInt((int) timestamp);
packetStream.writeInt(data.length);
packetStream.write(data);
} catch (Exception e) {
LOGGER.error("Error saving packet", e);
}
});
}
public synchronized void closeAndSave(File file) throws IOException {
packetStream.close();
String[] files = tmpDir.list();
if (files == null) {
return;
}
try (ZipOutputStream os = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
for (String fileName : files) {
os.putNextEntry(new ZipEntry(fileName));
File f = new File(tmpDir, fileName);
copy(new FileInputStream(f), os);
}
os.putNextEntry(new ZipEntry(RECORDING_FILE_CRC32));
Writer writer = new OutputStreamWriter(os);
writer.write(Long.toString(crc32.getValue()));
writer.flush();
}
for (String fileName : files) {
File f = new File(tmpDir, fileName);
Files.delete(f.toPath());
}
Files.delete(tmpDir.toPath());
}
public synchronized void closeNotSave() throws IOException {
packetStream.close();
String[] files = tmpDir.list();
if (files == null) {
return;
}
for (String fileName : files) {
File f = new File(tmpDir, fileName);
Files.delete(f.toPath());
}
Files.delete(tmpDir.toPath());
}
private void copy(@NotNull InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > -1) {
out.write(buffer, 0, len);
}
in.close();
}
private static boolean deleteDir(File dir) {
if (dir == null || !dir.exists()) {
return false;
}
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDir(file);
} else {
if (!file.delete()) {
return false;
}
}
}
}
return dir.delete();
}
}
@@ -0,0 +1,56 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import com.google.gson.*;
import java.lang.reflect.Type;
public class ReplayMarker {
public int time;
public String name;
public double x = 0;
public double y = 0;
public double z = 0;
public float phi = 0;
public float theta = 0;
public float varphi = 0;
public static class Serializer implements JsonSerializer<ReplayMarker> {
@Override
public JsonElement serialize(ReplayMarker src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject ret = new JsonObject();
JsonObject value = new JsonObject();
JsonObject position = new JsonObject();
ret.add("realTimestamp", new JsonPrimitive(src.time));
ret.add("value", value);
value.add("name", new JsonPrimitive(src.name));
value.add("position", position);
position.add("x", new JsonPrimitive(src.x));
position.add("y", new JsonPrimitive(src.y));
position.add("z", new JsonPrimitive(src.z));
position.add("yaw", new JsonPrimitive(src.phi));
position.add("pitch", new JsonPrimitive(src.theta));
position.add("roll", new JsonPrimitive(src.varphi));
return ret;
}
}
}
@@ -0,0 +1,258 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.mojang.authlib.GameProfile;
import io.papermc.paper.threadedregions.RegionizedServer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.ServerStatsCounter;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.CraftWorld;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.BotStatsCounter;
import org.leavesmc.leaves.entity.photographer.CraftPhotographer;
import org.leavesmc.leaves.entity.photographer.Photographer;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
public class ServerPhotographer extends ServerPlayer {
private static final List<ServerPhotographer> photographers = new CopyOnWriteArrayList<>();
public PhotographerCreateState createState;
private ServerPlayer followPlayer;
private Recorder recorder;
private File saveFile;
private Vec3 lastPosVec3;
private final ServerStatsCounter stats;
private ServerPhotographer(MinecraftServer server, ServerLevel world, GameProfile profile) {
super(server, world, profile, ClientInformation.createDefault());
this.gameMode = new ServerPhotographerGameMode(this);
this.followPlayer = null;
this.stats = new BotStatsCounter(server);
this.lastPosVec3 = this.position();
}
public static ServerPhotographer createPhotographer(@NotNull PhotographerCreateState state) throws IOException {
if (!isCreateLegal(state.id)) {
throw new IllegalArgumentException(state.id + " is a invalid photographer id");
}
MinecraftServer server = MinecraftServer.getServer();
ServerLevel world = ((CraftWorld) state.loc.getWorld()).getHandle();
GameProfile profile = new GameProfile(UUID.randomUUID(), state.id);
ServerPhotographer photographer = new ServerPhotographer(server, world, profile);
photographer.absSnapTo(state.loc.x(), state.loc.y(), state.loc.z(), state.loc.getYaw(), state.loc.getPitch());
photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id));
photographer.saveFile = new File("replay", state.id + ".mcpr");
photographer.createState = state;
photographer.recorder.start();
if (TickThread.isTickThreadFor(world, state.loc.x(), state.loc.z())) {
placePhotographer(server, photographer, world, state);
} else {
RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
world, net.minecraft.util.Mth.floor(state.loc.getX()) >> 4, net.minecraft.util.Mth.floor(state.loc.getZ()) >> 4,
() -> placePhotographer(server, photographer, world, state),
ca.spottedleaf.concurrentutil.util.Priority.HIGHER);
}
photographers.add(photographer);
// TODO record distance
return photographer;
}
private static void placePhotographer(MinecraftServer server, ServerPhotographer photographer, ServerLevel world, @NotNull PhotographerCreateState state) {
server.getPlayerList().placeNewPhotographer(photographer.recorder, photographer, world);
photographer.level().chunkSource.move(photographer);
photographer.setInvisible(true);
LOGGER.info("Photographer {} created", state.id);
}
@Override
public void tick() {
this.lastPos = this.blockPosition();
super.tick();
if (this.tickCount % 10 == 0) {
connection.resetPosition();
this.level().chunkSource.move(this);
}
if (this.followPlayer != null) {
if (this.getCamera() == this || this.getCamera().level() != this.level()) {
this.getBukkitPlayer().teleportAsync(this.getCamera().getBukkitEntity().getLocation());
this.setCamera(followPlayer);
}
if (lastPosVec3.distanceToSqr(this.position()) > 1024D) {
this.getBukkitPlayer().teleportAsync(this.getCamera().getBukkitEntity().getLocation());
}
}
lastPosVec3 = this.position();
}
@Override
public void die(@NotNull DamageSource damageSource) {
super.die(damageSource);
remove(true);
}
@Override
public boolean isInvulnerableTo(@NotNull ServerLevel world, @NotNull DamageSource damageSource) {
return true;
}
@Override
public boolean hurtServer(@NotNull ServerLevel world, @NotNull DamageSource source, float amount) {
return false;
}
@Override
public void setHealth(float health) {
}
@NotNull
@Override
public ServerStatsCounter getStats() {
return stats;
}
public void remove(boolean async) {
this.remove(async, true);
}
public void remove(boolean async, boolean save) {
super.remove(RemovalReason.KILLED);
photographers.remove(this);
this.recorder.stop();
this.getServer().getPlayerList().removePhotographer(this);
LOGGER.info("Photographer {} removed", createState.id);
if (!recorder.isSaved()) {
CompletableFuture<Void> future = recorder.saveRecording(saveFile, save);
if (!async) {
future.join();
}
}
}
public void setFollowPlayer(ServerPlayer followPlayer) {
this.setCamera(followPlayer);
this.followPlayer = followPlayer;
}
public ServerPlayer getFollowPlayer() {
return followPlayer;
}
public void setSaveFile(File saveFile) {
this.saveFile = saveFile;
}
public void pauseRecording() {
this.recorder.pauseRecording();
}
public void resumeRecording() {
this.recorder.resumeRecording();
}
public static ServerPhotographer getPhotographer(String id) {
for (ServerPhotographer photographer : photographers) {
if (photographer.createState.id.equals(id)) {
return photographer;
}
}
return null;
}
public static ServerPhotographer getPhotographer(UUID uuid) {
for (ServerPhotographer photographer : photographers) {
if (photographer.getUUID().equals(uuid)) {
return photographer;
}
}
return null;
}
public static List<ServerPhotographer> getPhotographers() {
return photographers;
}
public Photographer getBukkitPlayer() {
return getBukkitEntity();
}
@Override
@NotNull
public CraftPhotographer getBukkitEntity() {
return (CraftPhotographer) super.getBukkitEntity();
}
public static boolean isCreateLegal(@NotNull String name) {
if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
return false;
}
return Bukkit.getPlayerExact(name) == null && ServerPhotographer.getPhotographer(name) == null;
}
public static class PhotographerCreateState {
public RecorderOption option;
public Location loc;
public final String id;
public PhotographerCreateState(Location loc, String id, RecorderOption option) {
this.loc = loc;
this.id = id;
this.option = option;
}
public ServerPhotographer createSync() {
try {
return createPhotographer(this);
} catch (IOException e) {
LOGGER.error("Error happened when create photographer: ", e);
}
return null;
}
}
}
@@ -0,0 +1,52 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.replay;
import net.kyori.adventure.text.Component;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.level.GameType;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ServerPhotographerGameMode extends ServerPlayerGameMode {
public ServerPhotographerGameMode(ServerPhotographer photographer) {
super(photographer);
super.setGameModeForPlayer(GameType.SPECTATOR, null);
}
@Override
public boolean changeGameModeForPlayer(@NotNull GameType gameMode) {
return false;
}
@Nullable
@Override
public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) {
return null;
}
@Override
protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) {
}
@Override
public void tick() {
}
}
@@ -0,0 +1,351 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.util;
import fun.bm.lophine.config.modules.function.WoolHopperCounterConfig;
import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.context.ContextMap;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.display.RecipeDisplay;
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.AbstractBannerBlock;
import net.minecraft.world.level.block.BeaconBeamBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.MapColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Map.entry;
// Powered by fabric-carpet(https://github.com/gnembon/fabric-carpet)
public class HopperCounter {
private static boolean enabled = false;
private static final Map<DyeColor, HopperCounter> COUNTERS;
static {
EnumMap<DyeColor, HopperCounter> counterMap = new EnumMap<>(DyeColor.class);
for (DyeColor color : DyeColor.values()) {
counterMap.put(color, new HopperCounter(color));
}
COUNTERS = Collections.unmodifiableMap(counterMap);
}
public final DyeColor color;
private final TextComponent coloredName;
private final Object2LongMap<Item> counter = new Object2LongLinkedOpenHashMap<>();
private long startTick;
private long startMillis;
private HopperCounter(DyeColor color) {
this.startTick = -1;
this.color = color;
this.coloredName = Component.text(color.getName(), TextColor.color(color.getTextColor()));
}
public void add(Level level, ItemStack stack) {
if (startTick < 0) {
startTick = level.getGameTime();
startMillis = System.currentTimeMillis();
}
Item item = stack.getItem();
counter.put(item, counter.getLong(item) + stack.getCount());
}
public void reset(Level level) {
counter.clear();
startTick = level.getGameTime();
startMillis = System.currentTimeMillis();
}
public static void resetAll(Level level, boolean fresh) {
for (HopperCounter counter : COUNTERS.values()) {
counter.reset(level);
if (fresh) {
counter.startTick = -1;
}
}
}
public List<Component> format(MinecraftServer server, Level level, boolean realTime) {
long ticks = Math.max(realTime ? (System.currentTimeMillis() - startMillis) / 50 : level.getGameTime() - startTick, -1);
if (startTick < 0 || ticks == -1) {
return Collections.singletonList(Component.text().append(coloredName, Component.text(" hasn't started counting yet")).build());
}
long total = getTotalItems();
if (total <= 0) {
return Collections.singletonList(Component.text()
.append(Component.text("No items for "), coloredName)
.append(Component.text(" yet ("), Component.text(String.format("%.2f ", ticks / (20.0 * 60.0)), Style.style(TextDecoration.BOLD)))
.append(Component.text("min"), Component.text(realTime ? " - real time" : ""), Component.text(")"))
.build());
}
List<Component> items = new ArrayList<>();
items.add(Component.text()
.append(Component.text("Items for "), coloredName, Component.text(" "))
.append(Component.text("("), Component.text(String.format("%.2f ", ticks * 1.0 / (20 * 60)), Style.style(TextDecoration.BOLD)))
.append(Component.text("min"), Component.text(realTime ? " - real time" : ""), Component.text("), "))
.append(Component.text("total: "), Component.text(total, Style.style(TextDecoration.BOLD)), Component.text(", "))
.append(Component.text("("), Component.text(String.format("%.1f", total * 1.0 * (20 * 60 * 60) / ticks), Style.style(TextDecoration.BOLD)))
.append(Component.text("/h):"))
.build());
items.addAll(counter.object2LongEntrySet().stream().sorted((e, f) -> Long.compare(f.getLongValue(), e.getLongValue())).map(entry -> {
Item item = entry.getKey();
Component name = Component.translatable(item.getDescriptionId());
TextColor textColor = guessColor(server, item);
if (textColor != null) {
name = name.style(name.style().merge(Style.style(textColor)));
} else {
name = name.style(name.style().merge(Style.style(TextDecoration.ITALIC)));
}
long count = entry.getLongValue();
return Component.text()
.append(Component.text("- ", NamedTextColor.GRAY))
.append(name)
.append(Component.text(": ", NamedTextColor.GRAY))
.append(Component.text(count, Style.style(TextDecoration.BOLD)), Component.text(", ", NamedTextColor.GRAY))
.append(Component.text(String.format("%.1f", count * (20.0 * 60.0 * 60.0) / ticks), Style.style(TextDecoration.BOLD)))
.append(Component.text("/h"))
.build();
}).toList());
return items;
}
private static final Map<Item, Block> DEFAULTS = Map.<Item, Block>ofEntries(
entry(Items.DANDELION, Blocks.YELLOW_WOOL),
entry(Items.POPPY, Blocks.RED_WOOL),
entry(Items.BLUE_ORCHID, Blocks.LIGHT_BLUE_WOOL),
entry(Items.ALLIUM, Blocks.MAGENTA_WOOL),
entry(Items.AZURE_BLUET, Blocks.SNOW_BLOCK),
entry(Items.RED_TULIP, Blocks.RED_WOOL),
entry(Items.ORANGE_TULIP, Blocks.ORANGE_WOOL),
entry(Items.WHITE_TULIP, Blocks.SNOW_BLOCK),
entry(Items.PINK_TULIP, Blocks.PINK_WOOL),
entry(Items.OXEYE_DAISY, Blocks.SNOW_BLOCK),
entry(Items.CORNFLOWER, Blocks.BLUE_WOOL),
entry(Items.WITHER_ROSE, Blocks.BLACK_WOOL),
entry(Items.LILY_OF_THE_VALLEY, Blocks.WHITE_WOOL),
entry(Items.BROWN_MUSHROOM, Blocks.BROWN_MUSHROOM_BLOCK),
entry(Items.RED_MUSHROOM, Blocks.RED_MUSHROOM_BLOCK),
entry(Items.STICK, Blocks.OAK_PLANKS),
entry(Items.GOLD_INGOT, Blocks.GOLD_BLOCK),
entry(Items.IRON_INGOT, Blocks.IRON_BLOCK),
entry(Items.DIAMOND, Blocks.DIAMOND_BLOCK),
entry(Items.NETHERITE_INGOT, Blocks.NETHERITE_BLOCK),
entry(Items.SUNFLOWER, Blocks.YELLOW_WOOL),
entry(Items.LILAC, Blocks.MAGENTA_WOOL),
entry(Items.ROSE_BUSH, Blocks.RED_WOOL),
entry(Items.PEONY, Blocks.PINK_WOOL),
entry(Items.CARROT, Blocks.ORANGE_WOOL),
entry(Items.APPLE, Blocks.RED_WOOL),
entry(Items.WHEAT, Blocks.HAY_BLOCK),
entry(Items.PORKCHOP, Blocks.PINK_WOOL),
entry(Items.RABBIT, Blocks.PINK_WOOL),
entry(Items.CHICKEN, Blocks.WHITE_TERRACOTTA),
entry(Items.BEEF, Blocks.NETHERRACK),
entry(Items.ENCHANTED_GOLDEN_APPLE, Blocks.GOLD_BLOCK),
entry(Items.COD, Blocks.WHITE_TERRACOTTA),
entry(Items.SALMON, Blocks.ACACIA_PLANKS),
entry(Items.ROTTEN_FLESH, Blocks.BROWN_WOOL),
entry(Items.PUFFERFISH, Blocks.YELLOW_TERRACOTTA),
entry(Items.TROPICAL_FISH, Blocks.ORANGE_WOOL),
entry(Items.POTATO, Blocks.WHITE_TERRACOTTA),
entry(Items.MUTTON, Blocks.RED_WOOL),
entry(Items.BEETROOT, Blocks.NETHERRACK),
entry(Items.MELON_SLICE, Blocks.MELON),
entry(Items.POISONOUS_POTATO, Blocks.SLIME_BLOCK),
entry(Items.SPIDER_EYE, Blocks.NETHERRACK),
entry(Items.GUNPOWDER, Blocks.GRAY_WOOL),
entry(Items.TURTLE_SCUTE, Blocks.LIME_WOOL),
entry(Items.ARMADILLO_SCUTE, Blocks.ANCIENT_DEBRIS),
entry(Items.FEATHER, Blocks.WHITE_WOOL),
entry(Items.FLINT, Blocks.BLACK_WOOL),
entry(Items.LEATHER, Blocks.SPRUCE_PLANKS),
entry(Items.GLOWSTONE_DUST, Blocks.GLOWSTONE),
entry(Items.PAPER, Blocks.WHITE_WOOL),
entry(Items.BRICK, Blocks.BRICKS),
entry(Items.INK_SAC, Blocks.BLACK_WOOL),
entry(Items.SNOWBALL, Blocks.SNOW_BLOCK),
entry(Items.WATER_BUCKET, Blocks.WATER),
entry(Items.LAVA_BUCKET, Blocks.LAVA),
entry(Items.MILK_BUCKET, Blocks.WHITE_WOOL),
entry(Items.CLAY_BALL, Blocks.CLAY),
entry(Items.COCOA_BEANS, Blocks.COCOA),
entry(Items.BONE, Blocks.BONE_BLOCK),
entry(Items.COD_BUCKET, Blocks.BROWN_TERRACOTTA),
entry(Items.PUFFERFISH_BUCKET, Blocks.YELLOW_TERRACOTTA),
entry(Items.SALMON_BUCKET, Blocks.PINK_TERRACOTTA),
entry(Items.TROPICAL_FISH_BUCKET, Blocks.ORANGE_TERRACOTTA),
entry(Items.SUGAR, Blocks.WHITE_WOOL),
entry(Items.BLAZE_POWDER, Blocks.GOLD_BLOCK),
entry(Items.ENDER_PEARL, Blocks.WARPED_PLANKS),
entry(Items.NETHER_STAR, Blocks.DIAMOND_BLOCK),
entry(Items.PRISMARINE_CRYSTALS, Blocks.SEA_LANTERN),
entry(Items.PRISMARINE_SHARD, Blocks.PRISMARINE),
entry(Items.RABBIT_HIDE, Blocks.OAK_PLANKS),
entry(Items.CHORUS_FRUIT, Blocks.PURPUR_BLOCK),
entry(Items.SHULKER_SHELL, Blocks.SHULKER_BOX),
entry(Items.NAUTILUS_SHELL, Blocks.BONE_BLOCK),
entry(Items.HEART_OF_THE_SEA, Blocks.CONDUIT),
entry(Items.HONEYCOMB, Blocks.HONEYCOMB_BLOCK),
entry(Items.NAME_TAG, Blocks.BONE_BLOCK),
entry(Items.TOTEM_OF_UNDYING, Blocks.YELLOW_TERRACOTTA),
entry(Items.TRIDENT, Blocks.PRISMARINE),
entry(Items.GHAST_TEAR, Blocks.WHITE_WOOL),
entry(Items.PHANTOM_MEMBRANE, Blocks.BONE_BLOCK),
entry(Items.EGG, Blocks.BONE_BLOCK),
entry(Items.COPPER_INGOT, Blocks.COPPER_BLOCK),
entry(Items.AMETHYST_SHARD, Blocks.AMETHYST_BLOCK)
);
@SuppressWarnings("deprecation")
@Nullable
public static TextColor guessColor(@NotNull MinecraftServer server, Item item) {
RegistryAccess registryAccess = server.registryAccess();
TextColor direct = fromItem(item, registryAccess);
if (direct != null) {
return direct;
}
ResourceLocation id = registryAccess.lookupOrThrow(Registries.ITEM).getKey(item);
if (id == null) {
return null;
}
for (Recipe<?> recipe : getRecipesForOutput(server.getRecipeManager(), id, server.overworld())) {
for (Ingredient ingredient : recipe.placementInfo().ingredients()) {
Optional<Holder<Item>> match = ingredient.items().filter(stack -> fromItem(stack.value(), registryAccess) != null).findFirst();
if (match.isPresent()) {
return fromItem(match.get().value(), registryAccess);
}
}
}
return null;
}
@NotNull
public static List<Recipe<?>> getRecipesForOutput(@NotNull RecipeManager recipeManager, ResourceLocation id, Level level) {
List<Recipe<?>> results = new ArrayList<>();
ContextMap context = SlotDisplayContext.fromLevel(level);
recipeManager.getRecipes().forEach(recipe -> {
for (RecipeDisplay recipeDisplay : recipe.value().display()) {
recipeDisplay.result().resolveForStacks(context).forEach(stack -> {
if (BuiltInRegistries.ITEM.wrapAsHolder(stack.getItem()).unwrapKey().map(ResourceKey::location).orElseThrow(IllegalStateException::new).equals(id)) {
results.add(recipe.value());
}
});
}
});
return results;
}
@Nullable
public static TextColor fromItem(Item item, RegistryAccess registryAccess) {
if (DEFAULTS.containsKey(item)) {
return TextColor.color(appropriateColor(DEFAULTS.get(item).defaultMapColor().col));
}
if (item instanceof DyeItem dye) {
return TextColor.color(appropriateColor(dye.getDyeColor().getMapColor().col));
}
Block block = null;
final Registry<Item> itemRegistry = registryAccess.lookupOrThrow(Registries.ITEM);
final Registry<Block> blockRegistry = registryAccess.lookupOrThrow(Registries.BLOCK);
ResourceLocation id = itemRegistry.getKey(item);
if (item instanceof BlockItem blockItem) {
block = blockItem.getBlock();
} else if (blockRegistry.getOptional(id).isPresent()) {
block = blockRegistry.getValue(id);
}
if (block != null) {
if (block instanceof AbstractBannerBlock) {
return TextColor.color(appropriateColor(((AbstractBannerBlock) block).getColor().getMapColor().col));
} else if (block instanceof BeaconBeamBlock) {
return TextColor.color(appropriateColor(((BeaconBeamBlock) block).getColor().getMapColor().col));
}
return TextColor.color(appropriateColor(block.defaultMapColor().col));
}
return null;
}
public static int appropriateColor(int color) {
if (color == 0) {
return MapColor.SNOW.col;
}
int r = (color >> 16 & 255);
int g = (color >> 8 & 255);
int b = (color & 255);
if (r < 70) {
r = 70;
}
if (g < 70) {
g = 70;
}
if (b < 70) {
b = 70;
}
return (r << 16) + (g << 8) + b;
}
public long getTotalItems() {
return counter.isEmpty() ? 0 : counter.values().longStream().sum();
}
public static HopperCounter getCounter(DyeColor color) {
return COUNTERS.get(color);
}
public static void setEnabled(boolean is) {
enabled = is;
}
public static boolean isEnabled() {
return WoolHopperCounterConfig.enabled && enabled;
}
}
@@ -0,0 +1,34 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.util;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Type;
import java.util.UUID;
public class UUIDSerializer implements JsonSerializer<UUID> {
@Override
public JsonElement serialize(@NotNull UUID src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
@@ -17,7 +17,7 @@
package org.leavesmc.leaves.util;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.LophineLogger;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
@@ -30,7 +30,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.event.player.UpdateSuppressionEvent;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +40,6 @@ public class UpdateSuppressionException extends RuntimeException {
private @Nullable Block source;
private @Nullable ServerPlayer player;
private final @NotNull Throwable throwable;
private static final Logger LOGGER = LogUtils.getLogger();
public UpdateSuppressionException(
@Nullable BlockPos pos,
@@ -87,7 +85,7 @@ public class UpdateSuppressionException extends RuntimeException {
public void consume() {
submitEvent();
LOGGER.info(getMessage());
LophineLogger.LOGGER.info(getMessage());
}
private void submitEvent() {
@@ -0,0 +1,55 @@
/*
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
*
* Leaves is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Leaves is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
*/
package org.leavesmc.leaves.util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Map;
import static java.util.Map.entry;
public class WoolUtils {
private static final Map<Block, DyeColor> WOOL_BLOCK_TO_DYE = Map.ofEntries(
entry(Blocks.WHITE_WOOL, DyeColor.WHITE),
entry(Blocks.ORANGE_WOOL, DyeColor.ORANGE),
entry(Blocks.MAGENTA_WOOL, DyeColor.MAGENTA),
entry(Blocks.LIGHT_BLUE_WOOL, DyeColor.LIGHT_BLUE),
entry(Blocks.YELLOW_WOOL, DyeColor.YELLOW),
entry(Blocks.LIME_WOOL, DyeColor.LIME),
entry(Blocks.PINK_WOOL, DyeColor.PINK),
entry(Blocks.GRAY_WOOL, DyeColor.GRAY),
entry(Blocks.LIGHT_GRAY_WOOL, DyeColor.LIGHT_GRAY),
entry(Blocks.CYAN_WOOL, DyeColor.CYAN),
entry(Blocks.PURPLE_WOOL, DyeColor.PURPLE),
entry(Blocks.BLUE_WOOL, DyeColor.BLUE),
entry(Blocks.BROWN_WOOL, DyeColor.BROWN),
entry(Blocks.GREEN_WOOL, DyeColor.GREEN),
entry(Blocks.RED_WOOL, DyeColor.RED),
entry(Blocks.BLACK_WOOL, DyeColor.BLACK)
);
public static DyeColor getWoolColorAtPosition(Level worldIn, BlockPos pos) {
BlockState state = worldIn.getBlockState(pos);
return WOOL_BLOCK_TO_DYE.get(state.getBlock());
}
}