Compare commits

..
Author SHA1 Message Date
Nassim Jahnke 4d854e66b8 Disable MC-163962 fix by default
It changes restocking behavior pretty drastically compared to Vanilla, keep it as a config option for those that want it

Fixes #12761
2025-06-30 11:39:07 +02:00
Jake Potrebic 57c202e015 Allow adventure nbt codec to parse all tags 2025-06-28 13:05:31 -07:00
FlorianMichael bd79e20c66 [ci skip] PluginManager#getPlugin and PluginManager#isPluginEnabled are case-insensitive (#12723) 2025-06-26 21:17:20 +02:00
Shane Freeder 35b2c6ece4 Use dropped item for stats info (#12747)
We are already using the dropped stack to determine the type, we
might as well also use it for the count, given that plugins can already
mutate the type, might as well let them mess with the amount.
2025-06-26 19:44:29 +01:00
Nassim Jahnke e382e6872c Fix 0 yield on explosion events
Fixes #12745

That cooked is mildly cooked and should be fixed differently, e.g. a custom loot context param, but reverting the recent change in this line works for now
2025-06-26 16:58:05 +02:00
Nassim Jahnke 4e1a2555be Update try catch for command handling
Fixes #12729
2025-06-25 22:42:38 +02:00
Nassim Jahnke 7ec3174a33 Jump out of experimental phase 2025-06-25 22:14:31 +02:00
Lulu13022002 c9e89f49b0 Expose arrow velocity in EntityShootBowEvent for mobs (#12688) 2025-06-25 22:11:18 +02:00
Pedro aa6cd74c03 Remove unnecesary item check for ServerboundPlayerActionPacket RELEASE_USE_ITEM (#12668) 2025-06-25 22:09:11 +02:00
Owen ea10fa4a79 Don't mutate the position of Items for MC-4 Fix (#12702) 2025-06-25 21:51:26 +02:00
Spottedleaf 692e93a91f Fix MC-299110
When converting entity/poi data (SimpleRegionStorage),
we need to insert the new data version so that force upgrading
will write the data back with the correct version.
2025-06-25 12:25:45 -07:00
Kezz bee287927c Adventure 4.23.0 (#12690) 2025-06-25 21:16:18 +02:00
Owen 0caf75f839 Fix #12721 (#12725)
* Fix #12721

Track when knots are created and only fire the event then

* Use mutable boolean + check if actually spawned
2025-06-25 14:16:03 -04:00
Owen d7510efc16 Fix #12722 (#12726)
Use our getRegistryAccess to null check against world
2025-06-24 15:26:37 -04:00
Spottedleaf aa6ee44a7a Re-add global region scheduler tick erroneously removed in last commit 2025-06-24 08:14:29 -07:00
Spottedleaf 7c90c7c497 Optimise EntityScheduler ticking
The vast majority of the time, there are no tasks scheduled to
the EntityScheduler. We can avoid iterating the entire entity list
by tracking which schedulers have any tasks scheduled.
2025-06-24 07:19:33 -07:00
Spottedleaf 2f083acbed Ensure player entity schedulers are ticked when they are dead
If the player dies, then they are removed from the world and
as a result are not present in the world entity map.

To guarantee that the player entity scheduler is ticked, we can
tick all schedulers for players in the server player list, and
then skip all players we find in the world entity map.

This problem is not present on Folia since Folia must guarantee
that the player remains in the world.
2025-06-24 05:12:58 -07:00
Spottedleaf 38c1ddb52a Add and use FeatureHooks.getAllEntities
The ServerLevel#getAllEntities function only returns entities which
are accessible. FeatureHooks#getAllEntities will return all
entities, whether or not they are accessible.

Use the new hook in the EntityCommand, which allows server admins
to inspect entities in unloaded chunks.

Use the hook as well for ticking the EntityScheduler. This fixes
an issue whether unloaded entities did not have their scheduler ticked.
2025-06-24 04:55:58 -07:00
Spottedleaf 46b4b0b8d5 Improve keepalive ping system
Send more keepalives, record all transactions within the last minute.
We send more keepalives so that the latency calculation is more
accurate. Since we send more keepalives, we track all pending
keepalives in case multiple end up in flight.

Additionally, replace the latency calculation with a true
average over the last 5 seconds of keepalive transactions.
2025-06-24 04:23:08 -07:00
Spottedleaf d0e808f44c Move player to correct position in vehicle move packet handler
We add the change to guarantee that the player position moves
in-sync with the vehicle's. As a result, it should be teleporting
the player using the new position of the vehicle rather than the
old.
2025-06-24 02:09:39 -07:00
wiicart e454fef40e Add support for private constructors in plugin main classes (#12652) 2025-06-23 22:50:59 -04:00
58 changed files with 860 additions and 342 deletions

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@ java {
val annotationsVersion = "26.0.2"
// Keep in sync with paper-server adventure-text-serializer-ansi dep
val adventureVersion = "4.21.0"
val adventureVersion = "4.23.0"
val bungeeCordChatVersion = "1.21-R0.2-deprecated+build.21"
val slf4jVersion = "2.0.16"
val log4jVersion = "2.24.1"
@@ -4,6 +4,8 @@ import com.destroystokyo.paper.util.SneakyThrow;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InaccessibleObjectException;
/**
* An <b>internal</b> utility type that holds logic for loading a provider-like type from a classloaders.
@@ -56,7 +58,14 @@ public final class ProviderUtil {
throw new ClassCastException("class '%s' does not extend '%s'".formatted(clazz, classType));
}
clazzInstance = pluginClass.getDeclaredConstructor().newInstance();
final Constructor<? extends T> constructor = pluginClass.getDeclaredConstructor();
try {
constructor.setAccessible(true); // Allow non-public constructors
} catch (final InaccessibleObjectException | SecurityException ex) {
throw new RuntimeException("Inaccessible constructor");
}
clazzInstance = constructor.newInstance();
} catch (final IllegalAccessException exception) {
throw new RuntimeException("No public constructor");
} catch (final InstantiationException exception) {
@@ -37,7 +37,7 @@ public enum SoundCategory implements Sound.Source.Provider {
case PLAYERS -> Sound.Source.PLAYER;
case AMBIENT -> Sound.Source.AMBIENT;
case VOICE -> Sound.Source.VOICE;
case UI -> throw new UnsupportedOperationException("Waiting on adventure release for the UI sound source"); // todo adventure
case UI -> Sound.Source.UI;
};
}
}
@@ -46,6 +46,10 @@ public interface Fireball extends Projectile, Explosive {
* The acceleration gets applied to the velocity every tick, depending on
* the specific type of the fireball a damping / drag factor is applied so
* that the velocity does not grow into infinity.
* <br>
* <b>Note:</b> that the client may not respect non-default acceleration
* power and will therefore mispredict the location of the fireball, causing
* visual stutter.
*
* @param acceleration the acceleration
*/
@@ -29,7 +29,7 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager
/**
* Checks if the given plugin is loaded and returns it when applicable
* <p>
* Please note that the name of the plugin is case-sensitive
* Please note that the name of the plugin is case-insensitive
*
* @param name Name of the plugin to check
* @return Plugin if it exists, otherwise null
@@ -48,7 +48,7 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager
/**
* Checks if the given plugin is enabled or not
* <p>
* Please note that the name of the plugin is case-sensitive.
* Please note that the name of the plugin is case-insensitive.
*
* @param name Name of the plugin to check
* @return true if the plugin is enabled, otherwise false
@@ -464,7 +464,7 @@ public final class SimplePluginManager implements PluginManager {
/**
* Checks if the given plugin is loaded and returns it when applicable
* <p>
* Please note that the name of the plugin is case-sensitive
* Please note that the name of the plugin is case-insensitive
*
* @param name Name of the plugin to check
* @return Plugin if it exists, otherwise null
@@ -486,7 +486,7 @@ public final class SimplePluginManager implements PluginManager {
/**
* Checks if the given plugin is enabled or not
* <p>
* Please note that the name of the plugin is case-sensitive.
* Please note that the name of the plugin is case-insensitive.
*
* @param name Name of the plugin to check
* @return true if the plugin is enabled, otherwise false
@@ -6,6 +6,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -91,13 +92,20 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm
try {
pluginConstructor = pluginClass.getDeclaredConstructor();
} catch (NoSuchMethodException ex) {
throw new InvalidPluginException("main class `" + description.getMain() + "' must have a public no-args constructor", ex);
throw new InvalidPluginException("main class `" + description.getMain() + "' must have a no-args constructor", ex);
}
try {
// Support non-public constructors
pluginConstructor.setAccessible(true);
} catch (InaccessibleObjectException | SecurityException ex) {
throw new InvalidPluginException("main class `" + description.getMain() + "' constructor inaccessible", ex);
}
try {
plugin = pluginConstructor.newInstance();
} catch (IllegalAccessException ex) {
throw new InvalidPluginException("main class `" + description.getMain() + "' constructor must be public", ex);
throw new InvalidPluginException("main class `" + description.getMain() + "' constructor inaccessible", ex);
} catch (InstantiationException ex) {
throw new InvalidPluginException("main class `" + description.getMain() + "' must not be abstract", ex);
} catch (IllegalArgumentException ex) {
+2 -2
View File
@@ -134,7 +134,7 @@ dependencies {
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
implementation("net.minecrell:terminalconsoleappender:1.3.0")
implementation("net.kyori:adventure-text-serializer-ansi:4.21.0") // Keep in sync with adventureVersion from Paper-API build file
implementation("net.kyori:adventure-text-serializer-ansi:4.23.0") // Keep in sync with adventureVersion from Paper-API build file
runtimeConfiguration(sourceSets.main.map { it.runtimeClasspath })
/*
@@ -381,7 +381,7 @@ fill {
version(paperweight.minecraftVersion)
build {
channel = BuildChannel.ALPHA
channel = BuildChannel.STABLE
downloads {
register("server:default") {
@@ -484,7 +484,7 @@ index c70a58f5f633fa8e255f74c42f5e87c96b7b013a..ec20a5a6d7c8f65abda528fec36bec7b
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 7c7b9872a2cb16d842901a86e7e4b0f723588ae0..c8ce2ccfd5e5e8bdc7dc10ed7b72ca8d43444b4e 100644
index 75f81a6bc156a6455a616b8de0d7701fd2255a2d..b3d951670b3a097d04cfe347d7df496b1d0a0e09 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -409,6 +409,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -517,7 +517,7 @@ index 7c7b9872a2cb16d842901a86e7e4b0f723588ae0..c8ce2ccfd5e5e8bdc7dc10ed7b72ca8d
SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
@@ -995,6 +1011,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -984,6 +1000,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
} else {
if (type == MoverType.PISTON) {
@@ -528,7 +528,7 @@ index 7c7b9872a2cb16d842901a86e7e4b0f723588ae0..c8ce2ccfd5e5e8bdc7dc10ed7b72ca8d
movement = this.limitPistonMovement(movement);
if (movement.equals(Vec3.ZERO)) {
return;
@@ -1008,6 +1028,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -997,6 +1017,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.stuckSpeedMultiplier = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
}
@@ -661,10 +661,10 @@ index 3f780276be6766ef253c50212e06fd93a96b0caa..7e70c7bee633c54497d1cd2854dd60f4
}
}
diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
index 548d7c8dc517da6c4db86b11f579ae63e6db56cf..a29860af4c37b2b45df49f9ba18f7e38921dfb02 100644
index fca31bbab8e7830933ceffcf992ff56ccc84414c..51804b611f469f2ab53e455e8c633b867b00cc88 100644
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -121,6 +121,29 @@ public class ItemEntity extends Entity implements TraceableEntity {
@@ -129,6 +129,29 @@ public class ItemEntity extends Entity implements TraceableEntity {
return 0.04;
}
@@ -5,10 +5,10 @@ Subject: [PATCH] Anti-Xray
diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
index 55cc0dec4a88baea17f160e95d5d8316e0bb7a50..338dc0fb07cdba5f7350cca332fa3e942c622bfb 100644
index 811a8e5141f2061a185b53b63d951646141c0c7d..33d3eb510c5844e72bbc382bd24641aae080962d 100644
--- a/io/papermc/paper/FeatureHooks.java
+++ b/io/papermc/paper/FeatureHooks.java
@@ -40,20 +40,25 @@ public final class FeatureHooks {
@@ -45,20 +45,25 @@ public final class FeatureHooks {
}
public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
@@ -14,17 +14,17 @@ movement will load only the chunk the player enters anyways and avoids loading
massive amounts of surrounding chunks due to large AABB lookups.
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 7453ddb09f349b7836f966573e4933646a75cba6..58eda0d6426f30cda604f4120f1ddb012316c108 100644
index 23dfc87db1d5e90099270627197abc0f787a4393..27a01fd28ea565221768f31df02f0a2ddf242fce 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -229,6 +229,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// Paper end - Share random for entities to make them more random
@@ -230,6 +230,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public @Nullable org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Paper - Folia schedulers - volatile
+ public boolean collisionLoadChunks = false; // Paper
private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
if (this.bukkitEntity == null) {
diff --git a/net/minecraft/world/level/BlockCollisions.java b/net/minecraft/world/level/BlockCollisions.java
index ed6e4f9fd0c7ad1219e66bc1cb4038191dd6edd8..45a20dbb935b12d429153463dba5d6fd3385dd7a 100644
--- a/net/minecraft/world/level/BlockCollisions.java
@@ -32481,7 +32481,7 @@ index 0000000000000000000000000000000000000000..b028017b9c44821a8a313a04e0b10f5d
+ }
+}
diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
index 0c611598a6912b692a7639301811ee557e2304f1..6f65564f6a6e99d6549de9a23a031b1f4a8a9798 100644
index e4f0653c575c33b1ef8160b6c88e29ee9fb44508..b6a34796d33f1593301c3a67a013fa7e812cb329 100644
--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -211,6 +211,43 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -32542,7 +32542,7 @@ index 6536dc08c80170f5679acedd65cd2b9f6ad3fb3a..294cd15a796ad25823c8ccf98fbfae46
return structureTemplate.save(new CompoundTag());
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 794a01ee70a2a30d91550f5265f774ba73828cf9..c716521fb1497dc8a22d827ddb50fc1cc21a05f4 100644
index 2d597e50dcd957bd566c4da384fac5f36b5362f7..75aba65cbe1a943f21c7464ff9465e64f63e8e5b 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -305,6 +305,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -32585,10 +32585,10 @@ index 8c1417c659ea0e079e99b9bfa79e1cf6ba9b712b..a8a32edea080f32fd25c9e009d4efa41
if (stopBelowZero) {
chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN).toString());
diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
index 6be673172548c1382c7402ec4e1ec6ef51f702d3..18750f1ea3101b6c0ab0b8e33c304eb7fa1ed04d 100644
index 6be673172548c1382c7402ec4e1ec6ef51f702d3..49be43ac896d60587511a97445c53c10c587a341 100644
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
@@ -32,13 +32,30 @@ public class SimpleRegionStorage implements AutoCloseable {
@@ -32,13 +32,32 @@ public class SimpleRegionStorage implements AutoCloseable {
return this.worker.store(chunkPos, data);
}
@@ -32609,7 +32609,9 @@ index 6be673172548c1382c7402ec4e1ec6ef51f702d3..18750f1ea3101b6c0ab0b8e33c304eb7
- return this.dataFixType.updateToCurrentVersion(this.fixerUpper, tag, dataVersion);
+ // Paper start - rewrite data conversion system
+ final int dataVer = NbtUtils.getDataVersion(tag, version);
+ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), tag, dataVer, ca.spottedleaf.dataconverter.minecraft.util.Version.getCurrentVersion());
+ final CompoundTag ret = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), tag, dataVer, ca.spottedleaf.dataconverter.minecraft.util.Version.getCurrentVersion());
+ NbtUtils.addCurrentDataVersion(ret); // Fix MC-299110
+ return ret;
+ // Paper end - rewrite data conversion system
}
@@ -23061,7 +23061,7 @@ index 0000000000000000000000000000000000000000..f1f72a051083b61273202cb4e67ecb11
+ private SaveUtil() {}
+}
diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47bbe5e481 100644
index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a6b3e66a3 100644
--- a/io/papermc/paper/FeatureHooks.java
+++ b/io/papermc/paper/FeatureHooks.java
@@ -1,6 +1,9 @@
@@ -23074,8 +23074,13 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
@@ -31,12 +34,16 @@ import org.bukkit.World;
public final class FeatureHooks {
@@ -32,16 +35,20 @@ public final class FeatureHooks {
// this includes non-accessible entities
public static Iterable<Entity> getAllEntities(final net.minecraft.server.level.ServerLevel world) {
- return ((net.minecraft.world.level.entity.LevelEntityGetterAdapter<Entity>)world.getEntities()).sectionStorage.getAllEntities();
+ return ((ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup)world.getEntities()).getAllMapped(); // Paper - rewrite chunk system
}
public static void setPlayerChunkUnloadDelay(final long ticks) {
+ ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.setUnloadDelay(ticks); // Paper - rewrite chunk system
@@ -23091,7 +23096,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47
}
public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
@@ -62,111 +69,58 @@ public final class FeatureHooks {
@@ -67,111 +74,58 @@ public final class FeatureHooks {
}
public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
@@ -23224,7 +23229,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47
org.bukkit.Chunk chunk = null;
for (net.minecraft.server.level.Ticket ticket : tickets) {
@@ -186,15 +140,15 @@ public final class FeatureHooks {
@@ -191,15 +145,15 @@ public final class FeatureHooks {
}
public static int getViewDistance(net.minecraft.server.level.ServerLevel world) {
@@ -23243,7 +23248,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47
}
public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
@@ -212,35 +166,31 @@ public final class FeatureHooks {
@@ -217,35 +171,31 @@ public final class FeatureHooks {
}
public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
@@ -23862,7 +23867,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f
thread1 -> {
DedicatedServer dedicatedServer1 = new DedicatedServer(
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index f7362a979126c5c0a581c05c0b623cf40b8f0ebd..338ef549efe82c250c74365c1c1071986920c8c9 100644
index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a718d12b8 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -24054,7 +24059,7 @@ index f7362a979126c5c0a581c05c0b623cf40b8f0ebd..338ef549efe82c250c74365c1c107198
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
@@ -2469,6 +2557,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -2478,6 +2566,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
@@ -26910,11 +26915,11 @@ index 92a39927d7bd9af3ab67c2595d8ed435a2da6a69..7c9a2eed4441f816723562e0012f918d
}
}
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 5f8ce58e8af0590f0f79ff7122ad8a5ac96a5d9b..50bde815b917c14bc4fb9163bd9df45bfa63ef47 100644
index 283f240511898e416b83ec4dcccd4901889b80bb..3d5a8163a7dd78a195b77c4aaebdee2dc3dee64b 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -122,6 +122,11 @@ public class ServerEntity {
// Paper end - Improve AbstractHurtingProjectile syncing
@@ -104,6 +104,11 @@ public class ServerEntity {
}
public void sendChanges() {
+ // Paper start - optimise collisions
@@ -27686,7 +27691,7 @@ index 49008b4cbaead8a66a93d2b0d4b50b335a6c3eed..f9c96bbdc54e68b9216b7f8662bfae03
}
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 69dbff3f62e3a9ba7090156380f842bb44deebf8..c0b74d408340101bc3aac4cb4b7232c5cc78b08a 100644
index 6f7f92cc43c56a7453b289f205502d089474ef6d..b390ba657b8b880e431c84e9dd948ac9c84af2fd 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -193,7 +193,7 @@ import net.minecraft.world.scores.Team;
@@ -28723,7 +28728,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
+ // Paper end - block counting
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b487416c18ea6 100644
index 06d07f93e42edcfdd7d69df0b52efe2a58e7dfc1..da880f52920b1101f23ef94f3fd0dbdea218c373 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;
@@ -28974,7 +28979,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
@@ -1389,35 +1495,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1378,35 +1484,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return distance;
}
@@ -29076,7 +29081,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
}
private static float[] collectCandidateStepUpHeights(AABB box, List<VoxelShape> colliders, float deltaY, float maxUpStep) {
@@ -2691,21 +2839,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -2680,21 +2828,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public boolean isInWall() {
@@ -29198,7 +29203,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
}
public InteractionResult interact(Player player, InteractionHand hand) {
@@ -4302,15 +4539,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4291,15 +4528,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public Iterable<Entity> getIndirectPassengers() {
@@ -29224,7 +29229,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
}
public int countPlayerPassengers() {
@@ -4453,77 +4692,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4442,77 +4681,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return Mth.lerp(partialTick, this.yRotO, this.yRot);
}
@@ -29415,7 +29420,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
public boolean touchingUnloadedChunk() {
AABB aabb = this.getBoundingBox().inflate(1.0);
@@ -4678,6 +4976,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4667,6 +4965,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29431,7 +29436,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
if (!checkPosition(this, x, y, z)) {
return;
}
@@ -4839,6 +5146,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4818,6 +5125,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Override
public final void setRemoved(Entity.RemovalReason removalReason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause cause) { // CraftBukkit - add Bukkit remove cause
@@ -29444,7 +29449,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit
final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
if (this.removalReason == null) {
@@ -4849,7 +5162,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4828,7 +5141,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.stopRiding();
}
@@ -29453,7 +29458,7 @@ index f185440ef2d2340f1b7e8db85ea2287478edf6b8..c6bc9b1bf6780c0fa4819e7b773b4874
this.levelCallback.onRemove(removalReason);
this.onRemoval(removalReason);
// Paper start - Folia schedulers
@@ -4883,7 +5196,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4862,7 +5175,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public boolean shouldBeSaved() {
return (this.removalReason == null || this.removalReason.shouldSave())
&& !this.isPassenger()
@@ -31627,7 +31632,7 @@ index 96e8dfb1ff24954656470925a1fc6280fe5e09d9..be6f37f91569c659c609e5e8d38671ca
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
index c846d5d47c6488b11930b858da946e636e025294..d4c02b9bb9bfc10484a79ede35985ba35c99bada 100644
index 1c81dc30aabb354d18290d42dfc419d9b1581fbd..834e27ef2f7b342b074ff9e1e390e02f3ca1c399 100644
--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -413,7 +413,7 @@ public abstract class BlockBehaviour implements FeatureElement {
@@ -34312,7 +34317,7 @@ index 15417fab103feec3c1f7d5bd5b332e89d3ace3f5..2e6263d8b466e0f61bc72eb818044734
}
}
diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
index 18750f1ea3101b6c0ab0b8e33c304eb7fa1ed04d..bf708ff89ea1f2c7279e48c41c4f44abc77ceebb 100644
index 49be43ac896d60587511a97445c53c10c587a341..1b070cf7e3612dbcb170cf5d954eba5f5b3c777c 100644
--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
@@ -14,7 +14,7 @@ import net.minecraft.util.datafix.DataFixTypes;
@@ -60,21 +60,24 @@ index 61692c07dfb75ca0c19f603aafc96c0817861107..56a1d081a28e8b38384cfca732b10346
} else if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 50bde815b917c14bc4fb9163bd9df45bfa63ef47..dec11820b80094656e89e7384c4b515948280121 100644
index 3d5a8163a7dd78a195b77c4aaebdee2dc3dee64b..e96d4dee14c05f2fa329bfb1588ec795d4e3d730 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -104,8 +104,9 @@ public class ServerEntity {
@@ -103,6 +103,13 @@ public class ServerEntity {
this.trackedDataValues = entity.getEntityData().getNonDefaultValues();
}
// Paper start - fix desync when a player is added to the tracker
+ // Paper start - fix desync when a player is added to the tracker
+ private boolean forceStateResync;
public void onPlayerAdd() {
- // TODO - IMPLEMENT STUBBED METHOD FROM MOONRISE
+ public void onPlayerAdd() {
+ this.forceStateResync = true;
}
// Paper end - fix desync when a player is added to the tracker
// Paper start - Improve AbstractHurtingProjectile syncing
@@ -159,7 +160,7 @@ public class ServerEntity {
+ }
+ // Paper end - fix desync when a player is added to the tracker
+
public void sendChanges() {
// Paper start - optimise collisions
if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
@@ -141,7 +148,7 @@ public class ServerEntity {
this.sendDirtyEntityData();
}
@@ -83,7 +86,7 @@ index 50bde815b917c14bc4fb9163bd9df45bfa63ef47..dec11820b80094656e89e7384c4b5159
byte b = Mth.packDegrees(this.entity.getYRot());
byte b1 = Mth.packDegrees(this.entity.getXRot());
boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
@@ -194,7 +195,7 @@ public class ServerEntity {
@@ -176,7 +183,7 @@ public class ServerEntity {
long l1 = this.positionCodec.encodeY(vec3);
long l2 = this.positionCodec.encodeZ(vec3);
boolean flag5 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
@@ -92,7 +95,7 @@ index 50bde815b917c14bc4fb9163bd9df45bfa63ef47..dec11820b80094656e89e7384c4b5159
|| flag5
|| this.teleportDelay > 400
|| this.wasRiding
@@ -263,6 +264,7 @@ public class ServerEntity {
@@ -245,6 +252,7 @@ public class ServerEntity {
}
this.entity.hasImpulse = false;
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
+ }
+}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 39581095ccc69d113d954ed835bdfa32d25b5489..ce7d72e02797da1ae408d0d310420e30aaa0ce28 100644
index cff9d761dfee8a90b19fb2f3e678f99a39fc000c..0a260fdf6b198a8ab52e60bf6db2fb5eab719c48 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1709,6 +1709,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -1718,6 +1718,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
serverLevel.updateLagCompensationTick(); // Paper - lag compensation
@@ -0,0 +1,204 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 24 Jun 2025 03:41:38 -0700
Subject: [PATCH] Improve keepalive ping system
Send more keepalives, record all transactions within the last minute.
We send more keepalives so that the latency calculation is more
accurate. Since we send more keepalives, we track all pending
keepalives in case multiple end up in flight.
Additionally, replace the latency calculation with a true
average over the last 5 seconds of keepalive transactions.
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 53f038e1b5e7a13a08a0c925c8bd3f8a40868195..f3eca351021c37b64315872d075bd0a84aeee267 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -461,6 +461,70 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
return this.viewDistanceHolder;
}
// Paper end - rewrite chunk system
+ // Paper start - improve keepalives
+ public long lastKeepAliveTx = System.nanoTime();
+ public static final record KeepAliveResponse(long txTimeNS, long rxTimeNS) {
+ public long latencyNS() {
+ return this.rxTimeNS - this.txTimeNS;
+ }
+ }
+ public static final record PendingKeepAlive(long txTimeNS, long challengeId) {}
+
+ public final ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<PendingKeepAlive> pendingKeepAlives = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>();
+
+ public final PingCalculator pingCalculator1m = new PingCalculator(java.util.concurrent.TimeUnit.MINUTES.toNanos(1L));
+ public final PingCalculator pingCalculator5s = new PingCalculator(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L));
+
+ public static final class PingCalculator {
+
+ private final long intervalNS;
+ private final ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<KeepAliveResponse> responses = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>();
+
+ private long timeSumNS;
+ private int timeSumCount;
+ private volatile long lastAverageNS;
+
+ public PingCalculator(long intervalNS) {
+ this.intervalNS = intervalNS;
+ }
+
+ public void update(KeepAliveResponse response) {
+ long currTime = response.txTimeNS;
+
+ this.responses.add(response);
+
+ ++this.timeSumCount;
+ this.timeSumNS += response.latencyNS();
+
+ // remove out-of-window times
+ KeepAliveResponse removed;
+ while ((removed = this.responses.pollIf((ka) -> (currTime - ka.txTimeNS) > this.intervalNS)) != null) {
+ --this.timeSumCount;
+ this.timeSumNS -= removed.latencyNS();
+ }
+
+ this.lastAverageNS = this.timeSumNS / (long)this.timeSumCount;
+ }
+
+ public int getAvgLatencyMS() {
+ return (int)java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(this.getAvgLatencyNS());
+ }
+
+ public long getAvgLatencyNS() {
+ return this.lastAverageNS;
+ }
+
+ public it.unimi.dsi.fastutil.longs.LongArrayList getAllNS() {
+ it.unimi.dsi.fastutil.longs.LongArrayList ret = new it.unimi.dsi.fastutil.longs.LongArrayList();
+
+ for (KeepAliveResponse response : this.responses) {
+ ret.add(response.latencyNS());
+ }
+
+ return ret;
+ }
+ }
+ // Paper end - improve keepalives
public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
super(level, gameProfile);
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ecb382e369 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -38,12 +38,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
protected final MinecraftServer server;
public final Connection connection; // Paper
private final boolean transferred;
- private long keepAliveTime;
- private boolean keepAlivePending;
- private long keepAliveChallenge;
+ //private long keepAliveTime; // Paper - improve keepalives
+ //private boolean keepAlivePending; // Paper - improve keepalives
+ //private long keepAliveChallenge; // Paper - improve keepalives
private long closedListenerTime;
private boolean closed = false;
- private int latency;
+ private volatile int latency; // Paper - improve keepalives - make volatile
private volatile boolean suspendFlushingOnServerThread = false;
// CraftBukkit start
protected final net.minecraft.server.level.ServerPlayer player;
@@ -57,7 +57,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit
this.server = server;
this.connection = connection;
- this.keepAliveTime = Util.getMillis();
+ //this.keepAliveTime = Util.getMillis(); // Paper - improve keepalives
this.latency = cookie.latency();
this.transferred = cookie.transferred();
// CraftBukkit start - add fields and methods
@@ -120,13 +120,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@Override
public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
- if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) {
- int i = (int)(Util.getMillis() - this.keepAliveTime);
- this.latency = (this.latency * 3 + i) / 4;
- this.keepAlivePending = false;
- } else if (!this.isSingleplayerOwner()) {
- this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
+ // Paper start - improve keepalives
+ long now = System.nanoTime();
+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pending = this.player.pendingKeepAlives.peek();
+ if (pending != null && pending.challengeId() == packet.getId()) {
+ this.player.pendingKeepAlives.remove(pending);
+
+ net.minecraft.server.level.ServerPlayer.KeepAliveResponse response = new net.minecraft.server.level.ServerPlayer.KeepAliveResponse(pending.txTimeNS(), now);
+
+ this.player.pingCalculator1m.update(response);
+ this.player.pingCalculator5s.update(response);
+
+ this.latency = this.player.pingCalculator5s.getAvgLatencyMS();
+ return;
+ }
+
+ for (java.util.Iterator<net.minecraft.server.level.ServerPlayer.PendingKeepAlive> itr = this.player.pendingKeepAlives.iterator(); itr.hasNext();) {
+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive ka = itr.next();
+ if (ka.challengeId() == packet.getId()) {
+ itr.remove();
+
+ if (!this.processedDisconnect) {
+ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") out-of-order!");
+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT);
+ return;
+ }
+ break;
+ }
}
+
+ if (!this.processedDisconnect) {
+ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") without matching challenge!");
+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT);
+ return;
+ }
+ // Paper end - improve keepalives
}
@Override
@@ -247,20 +275,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
protected void keepConnectionAlive() {
Profiler.get().push("keepAlive");
long millis = Util.getMillis();
- // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
- // This should effectively place the keepalive handling back to "as it was" before 1.12.2
- final long elapsedTime = millis - this.keepAliveTime;
- if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // use vanilla's 15000L between keep alive packets
- if (this.keepAlivePending) {
- if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected
- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
- }
- // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings
- } else if (this.checkIfClosed(millis)) {
- this.keepAlivePending = true;
- this.keepAliveTime = millis;
- this.keepAliveChallenge = millis;
- this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge));
+ // Paper start - improve keepalives
+ if (this.checkIfClosed(millis) && !this.processedDisconnect) {
+ long currTime = System.nanoTime();
+
+ if ((currTime - this.player.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) {
+ this.player.lastKeepAliveTx = currTime;
+
+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pka = new net.minecraft.server.level.ServerPlayer.PendingKeepAlive(currTime, millis);
+ this.player.pendingKeepAlives.add(pka);
+ this.send(new ClientboundKeepAlivePacket(pka.challengeId()));
+ }
+
+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive oldest = this.player.pendingKeepAlives.peek();
+ if (oldest != null && (currTime - oldest.txTimeNS()) > java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(KEEPALIVE_LIMIT)) {
+ LOGGER.warn(this.player.getScoreboardName() + " was kicked due to keepalive timeout!");
+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT);
+ // Paper end - improve keepalives
}
}
@@ -0,0 +1,84 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 24 Jun 2025 07:05:51 -0700
Subject: [PATCH] Optimise EntityScheduler ticking
The vast majority of the time, there are no tasks scheduled to
the EntityScheduler. We can avoid iterating the entire entity list
by tracking which schedulers have any tasks scheduled.
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af94916135e84 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
@@ -96,6 +96,7 @@ public final class ServerEntityLookup extends EntityLookup {
if (entity instanceof ThrownEnderpearl enderpearl) {
this.addEnderPearl(CoordinateUtils.getChunkKey(enderpearl.chunkPosition()));
}
+ entity.registerScheduler(); // Paper - optimise Folia entity scheduler
}
@Override
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3aee82cc9 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1654,33 +1654,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
+ public final io.papermc.paper.threadedregions.EntityScheduler.EntitySchedulerTickList entitySchedulerTickList = new io.papermc.paper.threadedregions.EntityScheduler.EntitySchedulerTickList(); // Paper - optimise Folia entity scheduler
+
protected void tickChildren(BooleanSupplier hasTimeLeft) {
ProfilerFiller profilerFiller = Profiler.get();
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
- // Paper start - Folia scheduler API
- ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
- for (ServerPlayer player : this.playerList.players) {
- if (!this.playerList.players.contains(player)) {
+ // Paper start - optimise Folia entity scheduler
+ ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler)org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
+ for (io.papermc.paper.threadedregions.EntityScheduler scheduler : this.entitySchedulerTickList.getAllSchedulers()) {
+ if (scheduler.isRetired()) {
continue;
}
- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = player.getBukkitEntityRaw();
- if (bukkit != null) {
- bukkit.taskScheduler.executeTick();
- }
+
+ scheduler.executeTick();
}
- getAllLevels().forEach(level -> {
- for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) {
- if (entity.isRemoved() || entity instanceof ServerPlayer) {
- continue;
- }
- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
- if (bukkit != null) {
- bukkit.taskScheduler.executeTick();
- }
- }
- });
- // Paper end - Folia scheduler API
+ // Paper end - optimise Folia entity scheduler
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
profilerFiller.push("commandFunctions");
this.getFunctions().tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index da880f52920b1101f23ef94f3fd0dbdea218c373..3d2c0a4d3a1f9d3e5cc6cd0cdb988ae1205de821 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5165,6 +5165,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.getBukkitEntity().taskScheduler.retire();
}
// Paper end - Folia schedulers
+ // Paper start - optimise Folia entity scheduler
+ public final void registerScheduler() {
+ this.getBukkitEntity().taskScheduler.registerTo(net.minecraft.server.MinecraftServer.getServer().entitySchedulerTickList);
+ }
+ // Paper end - optimise Folia entity scheduler
@Override
public void setLevelCallback(EntityInLevelCallback levelCallback) {
@@ -1,6 +1,6 @@
--- /dev/null
+++ b/io/papermc/paper/FeatureHooks.java
@@ -1,0 +_,241 @@
@@ -1,0 +_,246 @@
+package io.papermc.paper;
+
+import io.papermc.paper.command.PaperSubcommand;
@@ -33,6 +33,11 @@
+
+public final class FeatureHooks {
+
+ // this includes non-accessible entities
+ public static Iterable<Entity> getAllEntities(final net.minecraft.server.level.ServerLevel world) {
+ return ((net.minecraft.world.level.entity.LevelEntityGetterAdapter<Entity>)world.getEntities()).sectionStorage.getAllEntities();
+ }
+
+ public static void setPlayerChunkUnloadDelay(final long ticks) {
+ }
+
@@ -76,11 +76,15 @@
CommandSourceStack commandSourceStack = parseResults.getContext().getSource();
Profiler.get().push(() -> "/" + command);
ContextChain<CommandSourceStack> contextChain = finishParsing(parseResults, command, commandSourceStack);
@@ -313,9 +_,10 @@
@@ -312,10 +_,13 @@
)
);
}
} catch (Exception var12) {
+ if (throwCommandError) throw var12; // Paper
- } catch (Exception var12) {
+ // Paper start
+ } catch (Throwable var12) { // always gracefully handle it, no matter how bad:tm:
+ if (throwCommandError) throw var12; // rethrow directly if requested
+ // Paper end
MutableComponent mutableComponent = Component.literal(var12.getMessage() == null ? var12.getClass().getName() : var12.getMessage());
- if (LOGGER.isDebugEnabled()) {
- LOGGER.error("Command exception: /{}", command, var12);
@@ -1,6 +1,6 @@
--- a/net/minecraft/network/protocol/game/VecDeltaCodec.java
+++ b/net/minecraft/network/protocol/game/VecDeltaCodec.java
@@ -5,16 +_,16 @@
@@ -5,7 +_,7 @@
public class VecDeltaCodec {
private static final double TRUNCATION_STEPS = 4096.0;
@@ -9,14 +9,3 @@
@VisibleForTesting
static long encode(double value) {
- return Math.round(value * 4096.0);
+ return Math.round(value * 4096.0); // Paper - Fix MC-4; diff on change
}
@VisibleForTesting
static double decode(long value) {
- return value / 4096.0;
+ return value / 4096.0; // Paper - Fix MC-4; diff on change
}
public Vec3 decode(long x, long y, long z) {
@@ -984,16 +984,25 @@
ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
@@ -1040,17 +_,66 @@
@@ -1040,17 +_,75 @@
protected void tickChildren(BooleanSupplier hasTimeLeft) {
ProfilerFiller profilerFiller = Profiler.get();
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
+ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
+ // Paper start - Folia scheduler API
+ ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
+ for (ServerPlayer player : this.playerList.players) {
+ if (!this.playerList.players.contains(player)) {
+ continue;
+ }
+ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = player.getBukkitEntityRaw();
+ if (bukkit != null) {
+ bukkit.taskScheduler.executeTick();
+ }
+ }
+ getAllLevels().forEach(level -> {
+ for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) {
+ if (entity.isRemoved()) {
+ for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) {
+ if (entity.isRemoved() || entity instanceof ServerPlayer) {
+ continue;
+ }
+ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
@@ -28,30 +28,7 @@
this.level = level;
this.broadcast = broadcast;
this.entity = entity;
@@ -98,21 +_,45 @@
this.trackedDataValues = entity.getEntityData().getNonDefaultValues();
}
+ // Paper start - fix desync when a player is added to the tracker
+ public void onPlayerAdd() {
+ // TODO - IMPLEMENT STUBBED METHOD FROM MOONRISE
+ }
+ // Paper end - fix desync when a player is added to the tracker
+ // Paper start - Improve AbstractHurtingProjectile syncing
+ // This can be removed when velocity is no longer downsized when sending it over the protocol.
+ // We create a packet that only updates the velocity on the client.
+ private static final Set<net.minecraft.world.entity.Relative> RELATIVES = net.minecraft.world.entity.Relative.union(Set.of(net.minecraft.world.entity.Relative.X, net.minecraft.world.entity.Relative.Y, net.minecraft.world.entity.Relative.Z), net.minecraft.world.entity.Relative.ROTATION);
+ public Packet<ClientGamePacketListener> getAccurateVelocityPacket() {
+ return new net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket(this.entity.getId(),
+ new net.minecraft.world.entity.PositionMoveRotation(net.minecraft.world.phys.Vec3.ZERO, this.lastSentMovement, 0, 0),
+ RELATIVES,
+ this.wasOnGround
+ );
+ }
+ // Paper end - Improve AbstractHurtingProjectile syncing
+
public void sendChanges() {
List<Entity> passengers = this.entity.getPassengers();
@@ -103,16 +_,22 @@
if (!passengers.equals(this.lastPassengers)) {
List<UUID> list = this.mountedOrDismounted(passengers).map(Entity::getUUID).toList();
this.broadcastWithIgnore.accept(new ClientboundSetPassengersPacket(this.entity), list);
@@ -93,15 +70,6 @@
Packet<?> packet = null;
boolean flag2 = flag1 || this.tickCount % 60 == 0;
boolean flag3 = false;
@@ -188,7 +_,7 @@
.accept(
new ClientboundBundlePacket(
List.of(
- new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement),
+ this.getAccurateVelocityPacket(), // Paper - Improve AbstractHurtingProjectile syncing
new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower)
)
)
@@ -227,6 +_,25 @@
this.tickCount++;
@@ -163,20 +131,6 @@
}
if (!this.entity.getPassengers().isEmpty()) {
@@ -326,6 +_,13 @@
if (this.entity instanceof Leashable leashable && leashable.isLeashed()) {
consumer.accept(new ClientboundSetEntityLinkPacket(this.entity, leashable.getLeashHolder()));
}
+ // Paper start - Improve AbstractHurtingProjectile syncing
+ // We need to send our more accurate velocity and our acceleration power
+ if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) {
+ consumer.accept(new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower));
+ consumer.accept(this.getAccurateVelocityPacket());
+ }
+ // Paper end - Improve AbstractHurtingProjectile syncing
}
public Vec3 getPositionBase() {
@@ -359,6 +_,11 @@
if (this.entity instanceof LivingEntity) {
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
@@ -1334,7 +1334,8 @@
if (traceItem) {
- ItemStack itemStack = itemEntity != null ? itemEntity.getItem() : ItemStack.EMPTY;
if (!itemStack.isEmpty()) {
this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), droppedItem.getCount());
- this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), droppedItem.getCount());
+ this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), itemStack.getCount()); // Paper - use size from dropped item
this.awardStat(Stats.DROP);
}
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -29,30 +_,67 @@
@@ -29,14 +_,14 @@
import net.minecraft.util.profiling.Profiler;
import org.slf4j.Logger;
@@ -15,11 +15,9 @@
- protected final Connection connection;
+ public final Connection connection; // Paper
private final boolean transferred;
- private long keepAliveTime;
+ private long keepAliveTime = Util.getMillis(); // Paper
private long keepAliveTime;
private boolean keepAlivePending;
private long keepAliveChallenge;
private long closedListenerTime;
@@ -45,14 +_,51 @@
private boolean closed = false;
private int latency;
private volatile boolean suspendFlushingOnServerThread = false;
@@ -295,7 +295,7 @@
rootVehicle.absSnapTo(d, d1, d2, f, f1);
+ // CraftBukkit start - fire PlayerMoveEvent TODO: this should be removed.
+ this.player.absSnapTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // Paper - TODO: This breaks alot of stuff
+ this.player.absSnapTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // Paper - TODO: This breaks alot of stuff
+ org.bukkit.entity.Player player = this.getCraftPlayer();
+ if (!this.hasMoved) {
+ this.lastPosX = prevX;
@@ -1146,8 +1146,7 @@
return;
case RELEASE_USE_ITEM:
- this.player.releaseUsingItem();
+ if (this.player.getUseItem() == this.player.getItemInHand(this.player.getUsedItemHand())) this.player.releaseUsingItem(); // Paper - validate use item before processing release
this.player.releaseUsingItem();
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes.
return;
case START_DESTROY_BLOCK:
@@ -2610,7 +2609,7 @@
if (!this.receivedMovementThisTick) {
this.player.setKnownMovement(Vec3.ZERO);
}
@@ -2078,4 +_,17 @@
@@ -2078,4 +_,29 @@
interface EntityInteraction {
InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand);
}
@@ -2627,4 +2626,16 @@
+ return event;
+ }
+ // Paper end - Add fail move event
+
+ // Paper start - Implement click callbacks with custom click action
+ @Override
+ public void handleCustomClickAction(final net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket packet) {
+ super.handleCustomClickAction(packet);
+ if (packet.id().equals(io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CLICK_CALLBACK_RESOURCE_LOCATION)) {
+ packet.payload().ifPresent(tag ->
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.tryRunCallback(this.player.getBukkitEntity(), tag)
+ );
+ }
+ }
+ // Paper end - Implement click callbacks with custom click action
}
@@ -85,7 +85,7 @@
+ // Paper end - Share random for entities to make them more random
+ public @Nullable org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
+
+ private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
+ private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Paper - Folia schedulers - volatile
+
+ public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
+ if (this.bukkitEntity == null) {
@@ -227,7 +227,7 @@
}
protected abstract void defineSynchedData(SynchedEntityData.Builder builder);
@@ -384,6 +_,59 @@
@@ -384,6 +_,48 @@
return this.entityData;
}
@@ -271,17 +271,6 @@
+
+ to.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(this.id, values));
+ }
+
+ // This method forces the entity position to be resynced the next tick, using the exact position.
+ public void resendPossiblyDesyncedPosition() {
+ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level();
+ net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId());
+ if (tracker == null) {
+ return;
+ }
+ final net.minecraft.server.level.ServerEntity serverEntity = tracker.serverEntity;
+ serverEntity.onPlayerAdd();
+ }
+ // Paper end
+
@Override
@@ -1797,7 +1786,7 @@
}
public void addDeltaMovement(Vec3 addend) {
@@ -3661,9 +_,45 @@
@@ -3661,9 +_,35 @@
return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale);
}
@@ -1826,16 +1815,6 @@
+ return;
+ }
+ // Paper end - Block invalid positions and bounding box
+ // Paper start - Fix MC-4
+ if (this instanceof ItemEntity) {
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) {
+ // encode/decode from VecDeltaCodec todo computation changed?
+ x = Mth.lfloor(x * 4096.0) * (1 / 4096.0);
+ y = Mth.lfloor(y * 4096.0) * (1 / 4096.0);
+ z = Mth.lfloor(z * 4096.0) * (1 / 4096.0);
+ }
+ }
+ // Paper end - Fix MC-4
if (this.position.x != x || this.position.y != y || this.position.z != z) {
+ synchronized (this.posLock) { // Paper - detailed watchdog information
this.position = new Vec3(x, y, z);
@@ -8,14 +8,6 @@
public ItemFrame(EntityType<? extends ItemFrame> entityType, Level level) {
super(entityType, level);
@@ -92,6 +_,7 @@
this.xRotO = this.getXRot();
this.yRotO = this.getYRot();
this.recalculateBoundingBox();
+ this.resendPossiblyDesyncedPosition(); // Paper - Fix MC-297817
}
@Override
@@ -102,6 +_,12 @@
@Override
@@ -27,3 +27,25 @@
}
}
@@ -120,7 +_,12 @@
return this.level().getBlockState(this.pos).is(BlockTags.FENCES);
}
+ // Paper start - Track if a knot was created
public static LeashFenceKnotEntity getOrCreateKnot(Level level, BlockPos pos) {
+ return getOrCreateKnot(level, pos, null);
+ }
+ public static LeashFenceKnotEntity getOrCreateKnot(Level level, BlockPos pos, @Nullable org.apache.commons.lang3.mutable.MutableBoolean created) {
+ // Paper end - Track if a knot was created
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
@@ -134,7 +_,7 @@
}
LeashFenceKnotEntity leashFenceKnotEntity1 = new LeashFenceKnotEntity(level, pos);
- level.addFreshEntity(leashFenceKnotEntity1);
+ if (level.addFreshEntity(leashFenceKnotEntity1) && created != null) { created.setTrue(); } // Paper - Track if a knot was created
return leashFenceKnotEntity1;
}
@@ -24,6 +24,21 @@
}
public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack, double deltaX, double deltaY, double deltaZ) {
@@ -79,6 +_,14 @@
this.bobOffs = other.bobOffs;
}
+ // Paper start - Require item entities to send their location precisely (Fixes MC-4)
+ {
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.sendFullPosForItemEntities) {
+ this.setRequiresPrecisePosition(true);
+ }
+ }
+ // Paper end - Require item entities to send their location precisely (Fixes MC-4)
+
@Override
public boolean dampensVibrations() {
return this.getItem().is(ItemTags.DAMPENS_VIBRATIONS);
@@ -116,7 +_,7 @@
@Override
public void tick() {
@@ -43,23 +43,36 @@
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
LocalDate localDate = LocalDate.now();
int i = localDate.get(ChronoField.DAY_OF_MONTH);
@@ -196,9 +_,19 @@
@@ -188,7 +_,8 @@
@Override
public void performRangedAttack(LivingEntity target, float distanceFactor) {
- ItemStack itemInHand = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW));
+ net.minecraft.world.InteractionHand hand = ProjectileUtil.getWeaponHoldingHand(this, Items.BOW); // Paper - call EntityShootBowEvent
+ ItemStack itemInHand = this.getItemInHand(hand); // Paper - call EntityShootBowEvent
ItemStack projectile = this.getProjectile(itemInHand);
AbstractArrow arrow = this.getArrow(projectile, distanceFactor, itemInHand);
double d = target.getX() - this.getX();
@@ -196,9 +_,21 @@
double d2 = target.getZ() - this.getZ();
double squareRoot = Math.sqrt(d * d + d2 * d2);
if (this.level() instanceof ServerLevel serverLevel) {
+ // CraftBukkit start
+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), arrow.getPickupItem(), arrow, net.minecraft.world.InteractionHand.MAIN_HAND, distanceFactor, true); // Paper - improve entity shoot bow event, add arrow stack to event
- Projectile.spawnProjectileUsingShoot(
+ Projectile.Delayed<AbstractArrow> delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed
arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
);
+
+ // Paper start - call EntityShootBowEvent
+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, itemInHand, arrow.getPickupItem(), arrow, hand, distanceFactor, true);
+ if (event.isCancelled()) {
+ event.getProjectile().remove();
+ return;
+ }
+
+ if (event.getProjectile() == arrow.getBukkitEntity()) {
+ // CraftBukkit end
Projectile.spawnProjectileUsingShoot(
arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
);
+ } // CraftBukkit
+ delayedEntity.spawn();
+ }
+ // Paper end - call EntityShootBowEvent
}
this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
@@ -1,22 +1,35 @@
--- a/net/minecraft/world/entity/monster/Illusioner.java
+++ b/net/minecraft/world/entity/monster/Illusioner.java
@@ -180,9 +_,19 @@
@@ -172,7 +_,8 @@
@Override
public void performRangedAttack(LivingEntity target, float distanceFactor) {
- ItemStack itemInHand = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW));
+ net.minecraft.world.InteractionHand hand = ProjectileUtil.getWeaponHoldingHand(this, Items.BOW); // Paper - call EntityShootBowEvent
+ ItemStack itemInHand = this.getItemInHand(hand); // Paper - call EntityShootBowEvent
ItemStack projectile = this.getProjectile(itemInHand);
AbstractArrow mobArrow = ProjectileUtil.getMobArrow(this, projectile, distanceFactor, itemInHand);
double d = target.getX() - this.getX();
@@ -180,9 +_,21 @@
double d2 = target.getZ() - this.getZ();
double squareRoot = Math.sqrt(d * d + d2 * d2);
if (this.level() instanceof ServerLevel serverLevel) {
+ // Paper start - EntityShootBowEvent
+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), mobArrow.getPickupItem(), mobArrow, target.getUsedItemHand(), distanceFactor, true);
- Projectile.spawnProjectileUsingShoot(
+ Projectile.Delayed<AbstractArrow> delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed
mobArrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
);
+
+ // Paper start - call EntityShootBowEvent
+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, itemInHand, mobArrow.getPickupItem(), mobArrow, hand, distanceFactor, true);
+ if (event.isCancelled()) {
+ event.getProjectile().remove();
+ return;
+ }
+
+ if (event.getProjectile() == mobArrow.getBukkitEntity()) {
Projectile.spawnProjectileUsingShoot(
mobArrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
);
+ delayedEntity.spawn();
+ }
+ // Paper end - EntityShootBowEvent
+ // Paper end - call EntityShootBowEvent
}
this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/item/LeadItem.java
+++ b/net/minecraft/world/item/LeadItem.java
@@ -26,24 +_,43 @@
@@ -26,24 +_,46 @@
if (blockState.is(BlockTags.FENCES)) {
Player player = context.getPlayer();
if (!level.isClientSide && player != null) {
@@ -22,15 +22,19 @@
+ for (java.util.Iterator<Leashable> iterator = list.iterator(); iterator.hasNext();) { // Paper - use iterator to remove
+ Leashable leashable = iterator.next(); // Paper - use iterator to remove
if (leashFenceKnotEntity == null) {
leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos);
- leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos);
+ // CraftBukkit start - fire HangingPlaceEvent
+ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(interactionHand);
+ org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) leashFenceKnotEntity.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.block.BlockFace.SELF, hand);
+ level.getCraftServer().getPluginManager().callEvent(event);
+ org.apache.commons.lang3.mutable.MutableBoolean created = new org.apache.commons.lang3.mutable.MutableBoolean(false);
+ leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos, created);
+ if (created.booleanValue()) {
+ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(interactionHand);
+ org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) leashFenceKnotEntity.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.block.BlockFace.SELF, hand);
+ level.getCraftServer().getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ leashFenceKnotEntity.discard(null); // CraftBukkit - add Bukkit remove cause
+ return InteractionResult.PASS;
+ if (event.isCancelled()) {
+ leashFenceKnotEntity.discard(null); // CraftBukkit - add Bukkit remove cause
+ return InteractionResult.PASS;
+ }
+ }
+ // CraftBukkit end
leashFenceKnotEntity.playPlacementSound();
@@ -63,12 +63,11 @@
);
}
@@ -124,7 +_,7 @@
}
@@ -125,6 +_,7 @@
public void updateDemand() {
- this.demand = this.demand + this.uses - (this.maxUses - this.uses);
+ this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962
this.demand = this.demand + this.uses - (this.maxUses - this.uses);
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.preventNegativeVillagerDemand) this.demand = Math.max(0, this.demand); // Paper - Fix MC-163962
}
public ItemStack assemble() {
@@ -30,7 +30,7 @@
- builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
+ // CraftBukkit start - add yield
+ if (explosion instanceof net.minecraft.world.level.ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) {
+ builder.withParameter(LootContextParams.EXPLOSION_RADIUS, serverExplosion.yield == 0 ? 0 : 1.0F / serverExplosion.yield);
+ builder.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield);
+ // CraftBukkit end
}
@@ -0,0 +1,15 @@
--- a/net/minecraft/world/level/entity/EntitySection.java
+++ b/net/minecraft/world/level/entity/EntitySection.java
@@ -19,6 +_,12 @@
this.storage = new ClassInstanceMultiMap<>(entityClazz);
}
+ // Paper start - support retrieving all entities, regardless of whether they are accessible
+ public void getEntities(java.util.List<T> into) {
+ into.addAll(this.storage);
+ }
+ // Paper end - support retrieving all entities, regardless of whether they are accessible
+
public void add(T entity) {
this.storage.add(entity);
}
@@ -0,0 +1,19 @@
--- a/net/minecraft/world/level/entity/EntitySectionStorage.java
+++ b/net/minecraft/world/level/entity/EntitySectionStorage.java
@@ -34,6 +_,16 @@
this.intialSectionVisibility = initialSectionVisibility;
}
+ // Paper start - support retrieving all entities, regardless of whether they are accessible
+ public Iterable<T> getAllEntities() {
+ java.util.List<T> ret = new java.util.ArrayList<>();
+ for (EntitySection<T> section : this.sections.values()) {
+ section.getEntities(ret);
+ }
+ return ret;
+ }
+ // Paper end - support retrieving all entities, regardless of whether they are accessible
+
public void forEachAccessibleNonEmptySection(AABB boundingBox, AbortableIterationConsumer<EntitySection<T>> consumer) {
int sectionPosCoord = SectionPos.posToSectionCoord(boundingBox.minX - 2.0);
int sectionPosCoord1 = SectionPos.posToSectionCoord(boundingBox.minY - 4.0);
@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
+++ b/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java
@@ -8,7 +_,7 @@
public class LevelEntityGetterAdapter<T extends EntityAccess> implements LevelEntityGetter<T> {
private final EntityLookup<T> visibleEntities;
- private final EntitySectionStorage<T> sectionStorage;
+ public final EntitySectionStorage<T> sectionStorage; // Paper - public
public LevelEntityGetterAdapter(EntityLookup<T> visibleEntities, EntitySectionStorage<T> sectionStorage) {
this.visibleEntities = visibleEntities;
@@ -1,5 +1,6 @@
package io.papermc.paper.adventure;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
@@ -14,6 +15,7 @@ import java.util.function.Function;
import java.util.function.Predicate;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.BlockNBTComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.EntityNBTComponent;
@@ -37,6 +39,9 @@ import net.kyori.adventure.text.format.TextDecoration;
import net.minecraft.commands.arguments.selector.SelectorPattern;
import net.minecraft.core.UUIDUtil;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagParser;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.chat.contents.KeybindContents;
@@ -44,6 +49,7 @@ import net.minecraft.network.chat.contents.ScoreContents;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.Item;
@@ -63,7 +69,14 @@ import static net.kyori.adventure.text.TranslationArgument.numeric;
@DefaultQualifier(NonNull.class)
public final class AdventureCodecs {
public static final Codec<BinaryTagHolder> BINARY_TAG_HOLDER_CODEC = ExtraCodecs.NBT.flatComapMap(tag -> BinaryTagHolder.encode(tag, PaperAdventure.NBT_CODEC), api -> {
try {
final Tag tag = api.get(PaperAdventure.NBT_CODEC);
return DataResult.success(tag);
} catch (CommandSyntaxException e) {
return DataResult.error(e::getMessage);
}
});
public static final Codec<Component> COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec);
public static final StreamCodec<RegistryFriendlyByteBuf, Component> STREAM_COMPONENT_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(COMPONENT_CODEC);
@@ -89,27 +102,37 @@ public final class AdventureCodecs {
return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key");
}, Key::asString);
static final Function<ClickEvent, String> TEXT_PAYLOAD_EXTRACTOR = a -> ((ClickEvent.Payload.Text) a.payload()).value();
/*
* Click
*/
static final MapCodec<ClickEvent> OPEN_URL_CODEC = mapCodec((instance) -> instance.group(
ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> URI.create(!a.value().contains("://") ? "https://" + a.value() : a.value()))
ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> {
final String url = ((ClickEvent.Payload.Text) a.payload()).value();
return URI.create(!url.contains("://") ? "https://" + url : url);
}
)
).apply(instance, (url) -> ClickEvent.openUrl(url.toString())));
static final MapCodec<ClickEvent> OPEN_FILE_CODEC = mapCodec((instance) -> instance.group(
Codec.STRING.fieldOf("path").forGetter(ClickEvent::value)
Codec.STRING.fieldOf("path").forGetter(TEXT_PAYLOAD_EXTRACTOR)
).apply(instance, ClickEvent::openFile));
static final MapCodec<ClickEvent> RUN_COMMAND_CODEC = mapCodec((instance) -> instance.group(
ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value)
ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR)
).apply(instance, ClickEvent::runCommand));
static final MapCodec<ClickEvent> SUGGEST_COMMAND_CODEC = mapCodec((instance) -> instance.group(
ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value)
ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR)
).apply(instance, ClickEvent::suggestCommand));
static final MapCodec<ClickEvent> CHANGE_PAGE_CODEC = mapCodec((instance) -> instance.group(
ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> Integer.parseInt(a.value()))
ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> ((ClickEvent.Payload.Int) a.payload()).integer())
).apply(instance, ClickEvent::changePage));
static final MapCodec<ClickEvent> COPY_TO_CLIPBOARD_CODEC = mapCodec((instance) -> instance.group(
Codec.STRING.fieldOf("value").forGetter(ClickEvent::value)
Codec.STRING.fieldOf("value").forGetter(TEXT_PAYLOAD_EXTRACTOR)
).apply(instance, ClickEvent::copyToClipboard));
static final MapCodec<ClickEvent> CUSTOM_CODEC = mapCodec((instance) -> instance.group(
KEY_CODEC.fieldOf("id").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).key()),
BINARY_TAG_HOLDER_CODEC.fieldOf("payload").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).nbt())
).apply(instance, ClickEvent::custom));
static final ClickEventType OPEN_URL_CLICK_EVENT_TYPE = new ClickEventType(OPEN_URL_CODEC, "open_url");
static final ClickEventType OPEN_FILE_CLICK_EVENT_TYPE = new ClickEventType(OPEN_FILE_CODEC, "open_file");
@@ -117,7 +140,8 @@ public final class AdventureCodecs {
static final ClickEventType SUGGEST_COMMAND_CLICK_EVENT_TYPE = new ClickEventType(SUGGEST_COMMAND_CODEC, "suggest_command");
static final ClickEventType CHANGE_PAGE_CLICK_EVENT_TYPE = new ClickEventType(CHANGE_PAGE_CODEC, "change_page");
static final ClickEventType COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE = new ClickEventType(COPY_TO_CLIPBOARD_CODEC, "copy_to_clipboard");
static final Codec<ClickEventType> CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE});
static final ClickEventType CUSTOM_CLICK_EVENT_TYPE = new ClickEventType(CUSTOM_CODEC, "custom");
static final Codec<ClickEventType> CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE});
record ClickEventType(MapCodec<ClickEvent> codec, String id) implements StringRepresentable {
@Override
@@ -126,23 +150,17 @@ public final class AdventureCodecs {
}
}
private static final Function<ClickEvent, ClickEventType> GET_CLICK_EVENT_TYPE = he -> {
if (he.action() == ClickEvent.Action.OPEN_URL) {
return OPEN_URL_CLICK_EVENT_TYPE;
} else if (he.action() == ClickEvent.Action.OPEN_FILE) {
return OPEN_FILE_CLICK_EVENT_TYPE;
} else if (he.action() == ClickEvent.Action.RUN_COMMAND) {
return RUN_COMMAND_CLICK_EVENT_TYPE;
} else if (he.action() == ClickEvent.Action.SUGGEST_COMMAND) {
return SUGGEST_COMMAND_CLICK_EVENT_TYPE;
} else if (he.action() == ClickEvent.Action.CHANGE_PAGE) {
return CHANGE_PAGE_CLICK_EVENT_TYPE;
} else if (he.action() == ClickEvent.Action.COPY_TO_CLIPBOARD) {
return COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE;
} else {
throw new IllegalStateException();
}
};
private static final Function<ClickEvent, ClickEventType> GET_CLICK_EVENT_TYPE =
he -> switch (he.action()) {
case OPEN_URL -> OPEN_URL_CLICK_EVENT_TYPE;
case OPEN_FILE -> OPEN_FILE_CLICK_EVENT_TYPE;
case RUN_COMMAND -> RUN_COMMAND_CLICK_EVENT_TYPE;
case SUGGEST_COMMAND -> SUGGEST_COMMAND_CLICK_EVENT_TYPE;
case CHANGE_PAGE -> CHANGE_PAGE_CLICK_EVENT_TYPE;
case COPY_TO_CLIPBOARD -> COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE;
case SHOW_DIALOG -> throw new UnsupportedOperationException(); // todo: dialog codec with dialog "api"
case CUSTOM -> CUSTOM_CLICK_EVENT_TYPE;
};
static final Codec<ClickEvent> CLICK_EVENT_CODEC = CLICK_EVENT_TYPE_CODEC.dispatch("action", GET_CLICK_EVENT_TYPE, ClickEventType::codec);
@@ -35,8 +35,6 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import net.kyori.adventure.util.Codec;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
@@ -78,16 +76,16 @@ import static java.util.Objects.requireNonNull;
public final class PaperAdventure {
private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s");
public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder()
.nestingLimit(30) // todo: should this be configurable? a system property or config value?
.complexMapper(TranslatableComponent.class, (translatable, consumer) -> {
if (!Language.getInstance().has(translatable.key())) {
for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry registry && registry.contains(translatable.key())) {
consumer.accept(GlobalTranslator.render(translatable, Locale.US));
return;
}
final Language language = Language.getInstance();
final @Nullable String fallback = translatable.fallback();
if (!language.has(translatable.key()) && (fallback == null || !language.has(fallback))) {
if (GlobalTranslator.translator().canTranslate(translatable.key(), Locale.US)) {
consumer.accept(GlobalTranslator.render(translatable, Locale.US));
return;
}
}
final @Nullable String fallback = translatable.fallback();
final @NotNull String translated = Language.getInstance().getOrDefault(translatable.key(), fallback != null ? fallback : translatable.key());
final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated);
@@ -130,10 +128,11 @@ public final class PaperAdventure {
@Deprecated
public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build();
public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build();
private static final TagParser<Tag> NBT_PARSER = TagParser.create(NbtOps.INSTANCE);
public static final Codec<Tag, String, CommandSyntaxException, RuntimeException> NBT_CODEC = new Codec<>() {
@Override
public @NotNull Tag decode(final @NotNull String encoded) throws CommandSyntaxException {
return TagParser.parseCompoundFully(encoded);
return NBT_PARSER.parseFully(encoded);
}
@Override
@@ -379,6 +378,7 @@ public final class PaperAdventure {
case PLAYER -> SoundSource.PLAYERS;
case AMBIENT -> SoundSource.AMBIENT;
case VOICE -> SoundSource.VOICE;
case UI -> SoundSource.UI;
};
}
@@ -1,24 +1,33 @@
package io.papermc.paper.adventure.providers;
import io.papermc.paper.adventure.PaperAdventure;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@SuppressWarnings("UnstableApiUsage") // permitted provider
public class ClickCallbackProviderImpl implements ClickCallback.Provider {
private static final Key CLICK_CALLBACK_KEY = Key.key("paper", "click_callback");
private static final String ID_KEY = "id";
public static final ResourceLocation CLICK_CALLBACK_RESOURCE_LOCATION = PaperAdventure.asVanilla(CLICK_CALLBACK_KEY);
public static final CallbackManager CALLBACK_MANAGER = new CallbackManager();
@Override
public @NotNull ClickEvent create(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options) {
return ClickEvent.runCommand("/paper:callback " + CALLBACK_MANAGER.addCallback(callback, options));
final CompoundTag tag = new CompoundTag();
tag.putString(ID_KEY, CALLBACK_MANAGER.addCallback(callback, options).toString());
return ClickEvent.custom(CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag));
}
public static final class CallbackManager {
@@ -48,12 +57,21 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider {
}
}
public void runCallback(final @NotNull Audience audience, final UUID id) {
final StoredCallback callback = this.callbacks.get(id);
if (callback != null && callback.valid()) { //TODO Message if expired/invalid?
callback.takeUse();
callback.callback.accept(audience);
}
public void tryRunCallback(final @NotNull Audience audience, final Tag tag) {
tag.asCompound().flatMap(t -> t.getString(ID_KEY)).ifPresent(s -> {
final UUID id;
try {
id = UUID.fromString(s);
} catch (final IllegalArgumentException ignored) {
return;
}
final StoredCallback callback = this.callbacks.get(id);
if (callback != null && callback.valid()) {
callback.takeUse();
callback.callback.accept(audience);
}
});
}
}
@@ -1,35 +0,0 @@
package io.papermc.paper.command;
import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import java.util.UUID;
@DefaultQualifier(NonNull.class)
public class CallbackCommand extends Command {
protected CallbackCommand(final String name) {
super(name);
this.description = "ClickEvent callback";
this.usageMessage = "/callback <uuid>";
}
@Override
public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
if (args.length != 1) {
return false;
}
final UUID id;
try {
id = UUID.fromString(args[0]);
} catch (final IllegalArgumentException ignored) {
return false;
}
ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id);
return true;
}
}
@@ -23,7 +23,6 @@ public final class PaperCommands {
public static void registerCommands(final MinecraftServer server) {
COMMANDS.put("paper", new PaperCommand("paper"));
COMMANDS.put("callback", new CallbackCommand("callback"));
COMMANDS.put("mspt", new MSPTCommand("mspt"));
COMMANDS.forEach((s, command) -> {
@@ -1,6 +1,7 @@
package io.papermc.paper.command.subcommands;
import com.google.common.collect.Maps;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.command.CommandUtil;
import io.papermc.paper.command.PaperSubcommand;
import java.util.Collections;
@@ -9,7 +10,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
@@ -102,7 +102,7 @@ public final class EntityCommand implements PaperSubcommand {
ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
Map<ResourceLocation, Integer> nonEntityTicking = Maps.newHashMap();
ServerChunkCache chunkProviderServer = world.getChunkSource();
world.getAllEntities().forEach(e -> {
FeatureHooks.getAllEntities(world).forEach(e -> {
ResourceLocation key = EntityType.getKey(e.getType());
MutablePair<Integer, Map<ChunkPos, Integer>> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap()));
@@ -343,7 +343,7 @@ public class GlobalConfiguration extends ConfigurationPart {
}
}
public int maxJoinsPerTick = 5;
public boolean fixEntityPositionDesync = true;
public boolean sendFullPosForItemEntities = false;
public boolean loadPermissionsYmlBeforePlugins = true;
@Constraints.Min(4)
public int regionFileCacheSize = 256;
@@ -357,6 +357,8 @@ public class GlobalConfiguration extends ConfigurationPart {
@Comment("Defines how many orbs groups can exist in an area.")
@Constraints.Min(1)
public IntOr.Default xpOrbGroupsPerArea = IntOr.Default.USE_DEFAULT;
@Comment("See Fix MC-163962; prevent villager demand from going negative.")
public boolean preventNegativeVillagerDemand = false;
}
public BlockUpdates blockUpdates;
@@ -82,6 +82,7 @@ interface RemovedConfigurations {
path("unsupported-settings", "allow-grindstone-overstacking"),
path("unsupported-settings", "allow-tripwire-disarming-exploits"),
path("commands", "fix-target-selector-tag-completion"),
path("misc", "fix-entity-position-desync")
};
}
@@ -1,6 +1,7 @@
package io.papermc.paper.threadedregions;
import ca.spottedleaf.concurrentutil.util.Validate;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.TickThread;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.world.entity.Entity;
@@ -9,6 +10,8 @@ import org.bukkit.craftbukkit.entity.CraftEntity;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Consumer;
/**
@@ -43,6 +46,8 @@ public final class EntityScheduler {
private static final long RETIRED_TICK_COUNT = -1L;
private final Object stateLock = new Object();
private final Long2ObjectOpenHashMap<List<ScheduledTask>> oneTimeDelayed = new Long2ObjectOpenHashMap<>();
private EntitySchedulerTickList scheduledList;
private boolean insideScheduledList;
private final ArrayDeque<ScheduledTask> currentlyExecuting = new ArrayDeque<>();
@@ -50,6 +55,51 @@ public final class EntityScheduler {
this.entity = Validate.notNull(entity);
}
// must own state lock
private boolean hasTasks() {
return !this.currentlyExecuting.isEmpty() || !this.oneTimeDelayed.isEmpty();
}
public void registerTo(final EntitySchedulerTickList newTickList) {
synchronized (this.stateLock) {
final EntitySchedulerTickList prevList = this.scheduledList;
if (prevList == newTickList) {
return;
}
this.scheduledList = newTickList;
// make sure tasks scheduled before registration can be ticked
if (prevList == null && this.hasTasks()) {
this.insideScheduledList = true;
}
// transfer to new list
if (this.insideScheduledList) {
if (prevList != null) {
prevList.remove(this);
}
if (newTickList != null) {
newTickList.add(this);
} else {
// retired
this.insideScheduledList = false;
}
}
}
}
/**
* Returns whether this scheduler is retired.
*
* <p>
* Note: This should only be invoked on the owning thread for the entity.
* </p>
* @return whether this scheduler is retired.
*/
public boolean isRetired() {
return this.tickCount == RETIRED_TICK_COUNT;
}
/**
* Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback
* on all currently scheduled tasks.
@@ -66,6 +116,7 @@ public final class EntityScheduler {
throw new IllegalStateException("Already retired");
}
this.tickCount = RETIRED_TICK_COUNT;
this.registerTo(null);
}
final Entity thisEntity = this.entity.getHandleRaw();
@@ -127,6 +178,11 @@ public final class EntityScheduler {
this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> {
return new ArrayList<>();
}).add(task);
if (!this.insideScheduledList && this.scheduledList != null) {
this.scheduledList.add(this);
this.insideScheduledList = true;
}
}
return true;
@@ -147,6 +203,12 @@ public final class EntityScheduler {
throw new IllegalStateException("Ticking retired scheduler");
}
++this.tickCount;
if (this.scheduledList != null && !this.hasTasks()) {
this.scheduledList.remove(this);
this.insideScheduledList = false;
}
if (this.oneTimeDelayed.isEmpty()) {
toRun = null;
} else {
@@ -178,4 +240,34 @@ public final class EntityScheduler {
}
}
}
public static final class EntitySchedulerTickList {
private static final EntityScheduler[] ENTITY_SCHEDULER_ARRAY = new EntityScheduler[0];
private final ReferenceList<EntityScheduler> entitySchedulers = new ReferenceList<>(ENTITY_SCHEDULER_ARRAY);
public boolean add(final EntityScheduler scheduler) {
synchronized (this) {
return this.entitySchedulers.add(scheduler);
}
}
public void remove(final EntityScheduler scheduler) {
synchronized (this) {
this.entitySchedulers.remove(scheduler);
}
}
public EntityScheduler[] getAllSchedulers() {
EntityScheduler[] ret = new EntityScheduler[this.entitySchedulers.size()];
synchronized (this) {
if (ret.length != this.entitySchedulers.size()) {
ret = new EntityScheduler[this.entitySchedulers.size()];
}
System.arraycopy(this.entitySchedulers.getRawDataUnchecked(), 0, ret, 0, this.entitySchedulers.size());
return ret;
}
}
}
}
@@ -29,6 +29,7 @@ import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.kyori.adventure.pointer.PointersSupplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
@@ -157,6 +158,10 @@ import org.jetbrains.annotations.Nullable;
public class CraftWorld extends CraftRegionAccessor implements World {
public static final int CUSTOM_DIMENSION_OFFSET = 10;
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
private static final PointersSupplier<World> POINTERS_SUPPLIER = PointersSupplier.<World>builder()
.resolving(net.kyori.adventure.identity.Identity.NAME, World::getName)
.resolving(net.kyori.adventure.identity.Identity.UUID, World::getUID)
.build();
private final ServerLevel world;
private WorldBorder worldBorder;
@@ -168,7 +173,6 @@ public class CraftWorld extends CraftRegionAccessor implements World {
private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this);
private final Object2IntOpenHashMap<SpawnCategory> spawnCategoryLimit = new Object2IntOpenHashMap<>();
private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY);
private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
// Paper start - void damage configuration
private boolean voidDamageEnabled;
private float voidDamageAmount;
@@ -2481,14 +2485,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
// Paper start - implement pointers
@Override
public net.kyori.adventure.pointer.Pointers pointers() {
if (this.adventure$pointers == null) {
this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder()
.withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName)
.withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUID)
.build();
}
return this.adventure$pointers;
return POINTERS_SUPPLIER.view(this);
}
// Paper end
}
@@ -74,7 +74,7 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
this.loadData(state.getSnapshotNBT());
}
private RegistryAccess getRegistryAccess() {
public RegistryAccess getRegistryAccess() {
LevelAccessor worldHandle = this.getWorldHandle();
return (worldHandle != null) ? worldHandle.registryAccess() : CraftRegistry.getMinecraftRegistry();
}
@@ -50,7 +50,7 @@ public class CraftCreatureSpawner extends CraftBlockEntityState<SpawnerBlockEnti
}
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(() -> "spawner@" + getLocation(), LOGGER)) {
ValueInput valueInput = TagValueInput.create(scopedCollector, this.getInternalWorld().registryAccess(), spawnData.entityToSpawn());
ValueInput valueInput = TagValueInput.create(scopedCollector, this.getRegistryAccess(), spawnData.entityToSpawn());
Optional<net.minecraft.world.entity.EntityType<?>> type = net.minecraft.world.entity.EntityType.by(valueInput);
return type.map(CraftEntityType::minecraftToBukkit).orElse(null);
}
@@ -182,7 +182,7 @@ public class CraftCreatureSpawner extends CraftBlockEntityState<SpawnerBlockEnti
return null;
}
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(() -> "spawner@" + getLocation(), LOGGER)) {
ValueInput valueInput = TagValueInput.create(scopedCollector, this.getInternalWorld().registryAccess(), spawnData.getEntityToSpawn());
ValueInput valueInput = TagValueInput.create(scopedCollector, this.getRegistryAccess(), spawnData.getEntityToSpawn());
Optional<net.minecraft.world.entity.EntityType<?>> type = net.minecraft.world.entity.EntityType.by(valueInput);
return type.map(CraftEntityType::minecraftToBukkit).map(CraftEntityType::bukkitToString).orElse(null);
@@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.command;
import java.util.Set;
import java.util.UUID;
import net.kyori.adventure.pointer.PointersSupplier;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
@@ -12,8 +13,12 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
public abstract class ServerCommandSender implements CommandSender {
private static final PointersSupplier<ServerCommandSender> POINTERS_SUPPLIER = PointersSupplier.<ServerCommandSender>builder()
.resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, ServerCommandSender::name)
.resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, serverCommandSender -> serverCommandSender::permissionValue)
.build();
public final PermissibleBase perm;
private net.kyori.adventure.pointer.Pointers adventure$pointers;
protected ServerCommandSender() {
this.perm = new PermissibleBase(this);
@@ -126,13 +131,6 @@ public abstract class ServerCommandSender implements CommandSender {
@Override
public net.kyori.adventure.pointer.Pointers pointers() {
if (this.adventure$pointers == null) {
this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder()
.withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name)
.withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue)
.build();
}
return this.adventure$pointers;
return POINTERS_SUPPLIER.view(this);
}
}
@@ -14,6 +14,7 @@ import java.util.Set;
import java.util.UUID;
import io.papermc.paper.entity.LookAnchor;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.util.TriState;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
@@ -85,13 +86,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
private static PermissibleBase perm;
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
static final PointersSupplier<org.bukkit.entity.Entity> POINTERS_SUPPLIER = PointersSupplier.<org.bukkit.entity.Entity>builder()
.resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, org.bukkit.entity.Entity::name)
.resolving(net.kyori.adventure.identity.Identity.UUID, org.bukkit.entity.Entity::getUniqueId)
.resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, entity1 -> entity1::permissionValue)
.build();
protected final CraftServer server;
protected Entity entity;
private final EntityType entityType;
private EntityDamageEvent lastDamageEvent;
private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY);
protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
// Paper start - Folia shedulers
public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this);
private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this);
@@ -200,7 +205,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
// Paper end
this.entity.setDeltaMovement(CraftVector.toVec3(velocity));
this.entity.hasImpulse = true;
this.entity.hurtMarked = true;
}
/**
@@ -670,15 +675,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
@Override
public net.kyori.adventure.pointer.Pointers pointers() {
if (this.adventure$pointers == null) {
this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder()
.withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name)
.withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId)
.withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue)
.build();
}
return this.adventure$pointers;
return POINTERS_SUPPLIER.view(this);
}
@Override
@@ -62,10 +62,9 @@ public class CraftFireball extends AbstractProjectile implements Fireball {
public void setAcceleration(@NotNull Vector acceleration) {
Preconditions.checkArgument(acceleration != null, "Vector acceleration cannot be null");
// SPIGOT-6993: AbstractHurtingProjectile#assignDirectionalMovement will normalize the given values
// Set the acceleration power in order to properly sync the movement to the client
double length = acceleration.length();
this.getHandle().accelerationPower = length;
this.getHandle().assignDirectionalMovement(CraftVector.toVec3(acceleration), length);
// Note: Because of MC-80142 the fireball will stutter on the client when setting the power to something other than 0 or the normalized vector * 0.1
this.getHandle().assignDirectionalMovement(CraftVector.toVec3(acceleration), acceleration.length());
this.update(); // SPIGOT-6579
}
@NotNull
@@ -22,6 +22,35 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame {
return (net.minecraft.world.entity.decoration.ItemFrame) this.entity;
}
@Override
public boolean setFacingDirection(BlockFace face, boolean force) {
HangingEntity hanging = this.getHandle();
Direction oldDir = hanging.getDirection();
Direction newDir = CraftBlock.blockFaceToNotch(face);
Preconditions.checkArgument(newDir != null, "%s is not a valid facing direction", face);
this.getHandle().setDirection(newDir);
if (!force && !this.getHandle().generation && !hanging.survives()) {
hanging.setDirection(oldDir);
return false;
}
this.update();
return true;
}
@Override
protected void update() {
super.update();
// update redstone
if (!this.getHandle().generation) {
this.getHandle().level().updateNeighbourForOutputSignal(this.getHandle().getPos(), Blocks.AIR);
}
}
@Override
public void setItem(org.bukkit.inventory.ItemStack item) {
this.setItem(item, true);
@@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import net.minecraft.core.RegistryAccess;
import net.minecraft.util.ProblemReporter;
import net.minecraft.util.RandomSource;
import net.minecraft.util.random.WeightedList;
@@ -186,6 +187,11 @@ public class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMin
return this.getHandle().level();
}
@Override
public RegistryAccess getRegistryAccess() {
return this.getHandle().registryAccess();
}
@Override
public net.minecraft.core.BlockPos getInternalPosition() {
return this.getHandle().blockPosition();
@@ -44,4 +44,14 @@ public class CraftPainting extends CraftHanging implements Painting {
//this.update(); Paper - Don't resent entity on art update
return true;
}
@Override
public boolean setFacingDirection(BlockFace face, boolean force) {
if (super.setFacingDirection(face, force)) {
this.update();
return true;
}
return false;
}
}
@@ -44,6 +44,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.util.TriState;
import net.md_5.bungee.api.chat.BaseComponent;
import net.minecraft.advancements.AdvancementProgress;
@@ -213,6 +215,12 @@ import org.jspecify.annotations.Nullable;
@DelegateDeserialization(CraftOfflinePlayer.class)
public class CraftPlayer extends CraftHumanEntity implements Player {
private static final PointersSupplier<Player> POINTERS_SUPPLIER = PointersSupplier.<Player>builder()
.parent(CraftEntity.POINTERS_SUPPLIER)
.resolving(Identity.NAME, Player::getName)
.resolving(Identity.DISPLAY_NAME, Player::displayName)
.resolving(Identity.LOCALE, Player::locale)
.build();
private long firstPlayed = 0;
private long lastPlayed = 0;
@@ -3283,17 +3291,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override
public net.kyori.adventure.pointer.Pointers pointers() {
if (this.adventure$pointers == null) {
this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder()
.withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::displayName)
.withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName)
.withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId)
.withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue)
.withDynamic(net.kyori.adventure.identity.Identity.LOCALE, this::locale)
.build();
}
return this.adventure$pointers;
return POINTERS_SUPPLIER.view(this);
}
@Override
@@ -34,6 +34,8 @@ public interface PaperSharedSpawnerLogic extends Spawner {
Level getInternalWorld();
RegistryAccess getRegistryAccess();
BlockPos getInternalPosition();
default boolean isActivated() {
return this.getSpawner().isNearPlayer(this.getInternalWorld(), this.getInternalPosition());
@@ -11,11 +11,14 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.BlockNBTComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
@@ -106,23 +109,35 @@ class AdventureCodecsTest {
}
@ParameterizedTest(name = PARAMETERIZED_NAME)
@EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE"})
@EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE", "SHOW_DIALOG", "CUSTOM"})
void testClickEvent(final ClickEvent.Action action) {
final ClickEvent event = ClickEvent.clickEvent(action, action.name().equals("OPEN_URL") ? "https://google.com" : "1337");
final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow();
final ClickEvent event = switch (action) {
case OPEN_URL -> openUrl("https://google.com");
case RUN_COMMAND -> ClickEvent.runCommand("/say hello");
case SUGGEST_COMMAND -> suggestCommand("/suggest hello");
case CHANGE_PAGE -> ClickEvent.changePage(2);
case COPY_TO_CLIPBOARD -> ClickEvent.copyToClipboard("clipboard content");
case CUSTOM -> ClickEvent.custom(key("test"), BinaryTagHolder.binaryTagHolder("3"));
case SHOW_DIALOG, OPEN_FILE -> throw new IllegalArgumentException();
};
final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow(() -> new RuntimeException("Failed to encode ClickEvent: " + event));
final net.minecraft.network.chat.ClickEvent nms = net.minecraft.network.chat.ClickEvent.CODEC.decode(NbtOps.INSTANCE, result).result().orElseThrow().getFirst();
assertEquals(event.action().toString(), nms.action().getSerializedName());
switch (nms) {
case net.minecraft.network.chat.ClickEvent.OpenUrl(java.net.URI uri) ->
assertEquals(event.value(), uri.toString());
case net.minecraft.network.chat.ClickEvent.OpenUrl(URI uri) ->
assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), uri.toString());
case net.minecraft.network.chat.ClickEvent.SuggestCommand(String command) ->
assertEquals(event.value(), command);
assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command);
case net.minecraft.network.chat.ClickEvent.RunCommand(String command) ->
assertEquals(event.value(), command);
assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command);
case net.minecraft.network.chat.ClickEvent.CopyToClipboard(String value) ->
assertEquals(event.value(), value);
assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), value);
case net.minecraft.network.chat.ClickEvent.ChangePage(int page) ->
assertEquals(event.value(), String.valueOf(page));
assertEquals(((ClickEvent.Payload.Int) event.payload()).integer(), page);
case net.minecraft.network.chat.ClickEvent.Custom(ResourceLocation id, Optional<Tag> payload) -> {
assertEquals(((ClickEvent.Payload.Custom) event.payload()).key().toString(), id.toString());
assertEquals(((ClickEvent.Payload.Custom) event.payload()).nbt(), payload.orElseThrow().asString());
}
default -> throw new AssertionError("Unexpected ClickEvent type: " + nms.getClass());
}
}
@@ -294,10 +309,10 @@ class AdventureCodecsTest {
.clickEvent(openUrl("https://github.com"))
.build(),
style()
.hoverEvent(HoverEvent.showEntity(HoverEvent.ShowEntity.showEntity(
Key.key(Key.MINECRAFT_NAMESPACE, "pig"),
.hoverEvent(showEntity(HoverEvent.ShowEntity.showEntity(
key(Key.MINECRAFT_NAMESPACE, "pig"),
UUID.randomUUID(),
Component.text("Dolores", TextColor.color(0x0a1ab9))
text("Dolores", color(0x0a1ab9))
)))
.build()
);