Compare commits

...
Author SHA1 Message Date
Jason Penilla 73116ac9dd Update publishing endpoint 2025-09-24 23:56:30 -07:00
Jake Potrebic eddc7b48f3 Update to configurate 4.2.0 (#12869) 2025-09-24 23:48:02 -07:00
Jason Penilla 6abf5f0b74 Avoid and discourage use of Maven Central as a CDN (#12692)
* Default LibraryLoader to Google's Maven Central mirror, add MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR, and warn on use of Maven Central with MavenLibraryResolver

* Account for both Maven Central URLs

* Update Javadoc

(cherry picked from commit 62b7f86dae)
2025-06-23 22:34:01 -07:00
Spottedleaf 12d8fe0beb Fix infinite loop in RegionFile IO
If an exception is thrown during decompress then the read process
would be started again, which of course would eventually throw in
the decompress process.
2025-06-09 02:57:21 -07:00
Shane Freeder 09e9afd295 Avoid off-main mutations of state 2025-05-19 18:44:47 +01:00
Bjarne Koll af71568bb3 Update mache 2025-05-11 22:29:09 +02:00
Nassim Jahnke e2ca477396 Remove simplify remote item matching option for now 2025-05-09 14:57:15 +02:00
OwenandWarrior c2bb144fd5 Properly save level data async (#12530)
Previously we added a parameter allowing for level data to be saved asynchronously which was then overriden by a vanilla parameter which does the opposite.

This reverts back to the previous behavior that we were doing before.

Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com>
2025-05-08 00:06:40 +01:00
David 76753219c4 Allow Server#getDefaultGameMode before worlds are initialized (#12491)
Backport-Of: d1810f241c
2025-04-28 20:56:18 +02:00
David a838a886dc Add PlayerRespawnEvent#isMissingRespawnBlock (#12422) 2025-04-17 19:52:07 +01:00
53 changed files with 745 additions and 398 deletions

No files matched your search

+1 -1
View File
@@ -61,7 +61,7 @@ subprojects {
extensions.configure<PublishingExtension> {
repositories {
maven("https://repo.papermc.io/repository/maven-snapshots/") {
maven("https://artifactory.papermc.io/artifactory/snapshots/") {
name = "paperSnapshots"
credentials(PasswordCredentials::class)
}
@@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory;
* MavenLibraryResolver resolver = new MavenLibraryResolver();
* resolver.addDependency(new Dependency(new DefaultArtifact("org.jooq:jooq:3.17.7"), null));
* resolver.addRepository(new RemoteRepository.Builder(
* "central", "default", "https://repo1.maven.org/maven2/"
* "central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR
* ).build());
* }</pre>
* <p>
@@ -50,6 +50,21 @@ import org.slf4j.LoggerFactory;
@NullMarked
public class MavenLibraryResolver implements ClassPathLibrary {
/**
* The default Maven Central mirror, configurable through the {@code PAPER_DEFAULT_CENTRAL_REPOSITORY} environment
* variable. Use this instead of Maven Central directly when you do not have your own mirror, as using
* Maven Central as a CDN is against the Maven Central Terms of Service, and you will cause users to hit
* rate limits.
*
* <p>This repository is also used by the legacy {@link org.bukkit.plugin.java.LibraryLoader}.</p>
*/
public static final String MAVEN_CENTRAL_DEFAULT_MIRROR = getDefaultMavenCentralMirror();
private static final List<String> MAVEN_CENTRAL_URLS = List.of(
"https://repo1.maven.org/maven2",
"http://repo1.maven.org/maven2",
"https://repo.maven.apache.org/maven2",
"http://repo.maven.apache.org/maven2"
);
private static final Logger LOGGER = LoggerFactory.getLogger("MavenLibraryResolver");
private final RepositorySystem repository;
@@ -105,6 +120,12 @@ public class MavenLibraryResolver implements ClassPathLibrary {
* dependencies from
*/
public void addRepository(final RemoteRepository remoteRepository) {
if (MAVEN_CENTRAL_URLS.stream().anyMatch(remoteRepository.getUrl()::startsWith)) {
LOGGER.warn(
"Use of Maven Central as a CDN is against the Maven Central Terms of Service. Use MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR instead.",
new RuntimeException("Plugin used Maven Central for library resolution")
);
}
this.repositories.add(remoteRepository);
}
@@ -130,4 +151,15 @@ public class MavenLibraryResolver implements ClassPathLibrary {
store.addLibrary(file.toPath());
}
}
private static String getDefaultMavenCentralMirror() {
String central = System.getenv("PAPER_DEFAULT_CENTRAL_REPOSITORY");
if (central == null) {
central = System.getProperty("org.bukkit.plugin.java.LibraryLoader.centralURL");
}
if (central == null) {
central = "https://maven-central.storage-download.googleapis.com/maven2";
}
return central;
}
}
@@ -18,8 +18,9 @@ public class PlayerRespawnEvent extends PlayerEvent {
private Location respawnLocation;
private final boolean isBedSpawn;
private final boolean isAnchorSpawn;
private final boolean missingRespawnBlock;
private final RespawnReason respawnReason;
private final java.util.Set<RespawnFlag> respawnFlags; // Paper
private final java.util.Set<RespawnFlag> respawnFlags;
@Deprecated(since = "1.16.1", forRemoval = true)
public PlayerRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnLocation, final boolean isBedSpawn) {
@@ -33,23 +34,20 @@ public class PlayerRespawnEvent extends PlayerEvent {
@Deprecated(forRemoval = true)
public PlayerRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnLocation, final boolean isBedSpawn, final boolean isAnchorSpawn, @NotNull final RespawnReason respawnReason) {
// Paper start
this(respawnPlayer, respawnLocation, isBedSpawn, isAnchorSpawn, respawnReason, com.google.common.collect.ImmutableSet.builder());
this(respawnPlayer, respawnLocation, isBedSpawn, isAnchorSpawn, false, respawnReason, com.google.common.collect.ImmutableSet.builder());
}
@ApiStatus.Internal
public PlayerRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnLocation, final boolean isBedSpawn, final boolean isAnchorSpawn, @NotNull final RespawnReason respawnReason, @NotNull final com.google.common.collect.ImmutableSet.Builder<org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag> respawnFlags) {
// Paper end
public PlayerRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnLocation, final boolean isBedSpawn, final boolean isAnchorSpawn, final boolean missingRespawnBlock, @NotNull final RespawnReason respawnReason, @NotNull final com.google.common.collect.ImmutableSet.Builder<org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag> respawnFlags) {
super(respawnPlayer);
this.respawnLocation = respawnLocation;
this.isBedSpawn = isBedSpawn;
this.isAnchorSpawn = isAnchorSpawn;
this.respawnReason = respawnReason;
// Paper start
this.missingRespawnBlock = missingRespawnBlock;
if (this.isBedSpawn) { respawnFlags.add(RespawnFlag.BED_SPAWN); }
if (this.isAnchorSpawn) { respawnFlags.add(RespawnFlag.ANCHOR_SPAWN); }
this.respawnFlags = respawnFlags.build();
// Paper end
}
/**
@@ -92,6 +90,19 @@ public class PlayerRespawnEvent extends PlayerEvent {
return isAnchorSpawn;
}
/**
* Gets whether the player is missing a valid respawn block.
* <p>
* This will occur if the players respawn block is obstructed,
* or it is the first death after it was either destroyed or
* in case of a respawn anchor, ran out of charges.
*
* @return whether the player is missing a valid respawn block
*/
public boolean isMissingRespawnBlock() {
return this.missingRespawnBlock;
}
/**
* Gets the reason this respawn event was called.
*
@@ -132,7 +143,6 @@ public class PlayerRespawnEvent extends PlayerEvent {
PLUGIN;
}
// Paper start
/**
* Get the set of flags that apply to this respawn.
*
@@ -157,5 +167,4 @@ public class PlayerRespawnEvent extends PlayerEvent {
*/
END_PORTAL,
}
// Paper end
}
@@ -1,12 +1,12 @@
// CHECKSTYLE:OFF
package org.bukkit.plugin.java;
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -49,6 +49,10 @@ public class LibraryLoader
public static java.util.function.BiFunction<URL[], ClassLoader, URLClassLoader> LIBRARY_LOADER_FACTORY; // Paper - rewrite reflection in libraries
public static java.util.function.Function<List<java.nio.file.Path>, List<java.nio.file.Path>> REMAPPER; // Paper - remap libraries
private static List<RemoteRepository> getRepositories() {
return List.of(new RemoteRepository.Builder("central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR).build());
}
public LibraryLoader(@NotNull Logger logger)
{
this.logger = logger;
@@ -78,7 +82,7 @@ public class LibraryLoader
session.setSystemProperties( System.getProperties() );
session.setReadOnly();
this.repositories = repository.newResolutionRepositories( session, Arrays.asList( new RemoteRepository.Builder( "central", "default", "https://repo.maven.apache.org/maven2" ).build() ) );
this.repositories = repository.newResolutionRepositories( session, getRepositories() );
}
@Nullable
+2 -3
View File
@@ -11,7 +11,7 @@ plugins {
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
dependencies {
mache("io.papermc:mache:1.21.4+build.7")
mache("io.papermc:mache:1.21.4+build.8")
paperclip("io.papermc:paperclip:3.0.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
@@ -143,8 +143,7 @@ dependencies {
implementation("io.netty:netty-codec-haproxy:4.1.115.Final") // Add support for proxy protocol
implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1")
implementation("org.ow2.asm:asm-commons:9.7.1")
implementation("org.spongepowered:configurate-yaml:4.2.0-20250225.064233-199")
implementation("org.spongepowered:configurate-core:4.2.0-20250225.064233-204") // Pinned dependency of above pinned yaml snapshot.
implementation("org.spongepowered:configurate-yaml:4.2.0")
implementation("commons-lang:commons-lang:2.6")
runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0")
runtimeOnly("com.mysql:mysql-connector-j:9.1.0")
@@ -1112,7 +1112,7 @@ index 0000000000000000000000000000000000000000..a814512fcfb85312474ae2c2c2144384
+}
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
new file mode 100644
index 0000000000000000000000000000000000000000..1acea58838f057ab87efd103cbecb6f5aeaef393
index 0000000000000000000000000000000000000000..25bceb88f7d040034a740c18ed5a3aab50b0c72b
--- /dev/null
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
@@ -0,0 +1,1700 @@
@@ -2261,7 +2261,7 @@ index 0000000000000000000000000000000000000000..1acea58838f057ab87efd103cbecb6f5
+ LOGGER.error("Failed to decompress chunk data for task: " + this.toString(), thr);
+ }
+
+ if (compoundTag == null) {
+ if (throwable == null && compoundTag == null) {
+ // need to re-try from the start
+ this.scheduleReadIO();
+ return;
@@ -27496,7 +27496,7 @@ index d1f235ebd835f58cf0c703c3a64d29825d98e183..080091efc19bc768bb9a660f366c42e8
}
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 0bb610f12e3ddda649ecb5ad62ffdc7bfd243223..19428343b37c9b739b3d28984d52e257f85f253f 100644
index 6e2dcbf45fa424e910a01dd44c85af72d3a35a36..5f08126b011d945af0c330f5477351e1e31866bb 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -178,7 +178,7 @@ import net.minecraft.world.scores.Team;
@@ -27508,7 +27508,7 @@ index 0bb610f12e3ddda649ecb5ad62ffdc7bfd243223..19428343b37c9b739b3d28984d52e257
private static final Logger LOGGER = LogUtils.getLogger();
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
@@ -395,6 +395,36 @@ public class ServerPlayer extends Player {
@@ -388,6 +388,36 @@ public class ServerPlayer extends Player {
public @Nullable String clientBrandName = null; // Paper - Brand support
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
@@ -50,7 +50,7 @@ index 409c1134327bfcc338c3ac5e658a83cc396645d1..cc2d442682496197d29ace79b22e6cf6
ProfilerFiller profilerFiller = Profiler.get();
this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 42995dac38248032b6abecc27124adfe12ec4cab..28a67294c3e678e01d5dfd68b950234213d8e55c 100644
index e77e111cad399da8b6885418337c8e495328dc71..0651a536ad38c3c6d1ac2423b6d351738719339f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1318,6 +1318,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -64,7 +64,7 @@ index 42995dac38248032b6abecc27124adfe12ec4cab..28a67294c3e678e01d5dfd68b9502342
+ }
+
+ if (doFull) {
+ this.saveLevelData(true);
+ this.saveLevelData(false);
+ }
+ // chunk autosave is already called by the ChunkSystem during unload processing (ChunkMap#processUnloads)
+ // Copied from save()
@@ -83,7 +83,7 @@ index 42995dac38248032b6abecc27124adfe12ec4cab..28a67294c3e678e01d5dfd68b9502342
// Paper start - add close param
this.save(progress, flush, skipSave, false);
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index f44600604a7bf68c990cd74a1ac2d7900ff6e88e..69b8074e18775c846d5991f40bc2e0a5186500ac 100644
index 2e54c38272ed93286678baaaf18a234be65f28f0..a5e9e0e7d60ed79db01bd526f039ca07440826c1 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -180,6 +180,7 @@ import org.slf4j.Logger;
@@ -95,7 +95,7 @@ index f44600604a7bf68c990cd74a1ac2d7900ff6e88e..69b8074e18775c846d5991f40bc2e0a5
private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
private static final int FLY_STAT_RECORDING_SPEED = 25;
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 7d1d4abfb04829d8c4722e326c6c6b8fb2ab91f4..5a4960fdbd97d830ac79845697eea9372c48a13b 100644
index 065f4c810439dde464529b54ae300ecfcb1c2c31..c9dbe659374e3ce140316116e05110567e44b810 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -482,6 +482,7 @@ public abstract class PlayerList {
@@ -78,10 +78,10 @@ index 87d4291a3944f706a694536da6de0f28c548ab8d..5576bf1d1d70ab7a010653d3207909b5
profiler.popPush("spawnAndTick");
boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 02fb30a3adf92de0795aee213caf94a228b01ca0..67f6e40216e0be063a3cfb61427f095f7c74d785 100644
index 37134adbe1e669be74543b3d5407fe1fa610ece9..ca542eb32cf14f15104235fa924cc3579657dc96 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -375,6 +375,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -368,6 +368,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
public boolean queueHealthUpdatePacket;
public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
// Paper end - cancellable death event
@@ -60,10 +60,10 @@ index 5576bf1d1d70ab7a010653d3207909b5de867e70..6540b2d6a1062d883811ce240c49d30d
spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
} else {
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 67f6e40216e0be063a3cfb61427f095f7c74d785..3de65c4025be91d938a350c884975cb6edc234d3 100644
index ca542eb32cf14f15104235fa924cc3579657dc96..60966b42131c4802e49e838451ff688665e7301e 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -379,6 +379,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -372,6 +372,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS];
// Paper end - Optional per player mob spawns
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e9876667fd1807 100644
index 73ca5aff2365df4f31c960989778b245a7f7bcd3..a0fea6f87000e17d1affc377d2b7e617ce345db6 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -563,7 +563,7 @@ public class ServerGamePacketListenerImpl
@@ -88,7 +88,7 @@ index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e98766
}
@Override
@@ -1371,7 +1403,7 @@ public class ServerGamePacketListenerImpl
@@ -1372,7 +1404,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -97,7 +97,7 @@ index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e98766
d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
@@ -1410,6 +1442,7 @@ public class ServerGamePacketListenerImpl
@@ -1411,6 +1443,7 @@ public class ServerGamePacketListenerImpl
boolean flag1 = this.player.verticalCollisionBelow;
this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
@@ -105,7 +105,7 @@ index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e98766
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
@@ -1442,7 +1475,17 @@ public class ServerGamePacketListenerImpl
@@ -1443,7 +1476,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -124,7 +124,7 @@ index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e98766
if (teleportBack) {
io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
toX, toY, toZ, toYaw, toPitch, false);
@@ -1578,7 +1621,7 @@ public class ServerGamePacketListenerImpl
@@ -1579,7 +1622,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -133,7 +133,7 @@ index 12c83cb084563a4e3f7f357d8b600941544ef2b0..90e582ca30851857add5e2d830e98766
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
@@ -1597,6 +1640,33 @@ public class ServerGamePacketListenerImpl
@@ -1598,6 +1641,33 @@ public class ServerGamePacketListenerImpl
}
}
@@ -11,7 +11,7 @@ a portal in spectator mode and then later switching to creative mode
would portal the player.
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 90e582ca30851857add5e2d830e9876667fd1807..c569cdfa4cba4f65892ffd4045c611837049f440 100644
index a0fea6f87000e17d1affc377d2b7e617ce345db6..a2c70d36f79b7f8560923e098e52fa7f82ca7416 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -659,7 +659,7 @@ public class ServerGamePacketListenerImpl
@@ -23,7 +23,7 @@ index 90e582ca30851857add5e2d830e9876667fd1807..c569cdfa4cba4f65892ffd4045c61183
Vec3 vec3 = new Vec3(rootVehicle.getX() - x, rootVehicle.getY() - y, rootVehicle.getZ() - z);
this.handlePlayerKnownMovement(vec3);
rootVehicle.setOnGroundWithMovement(packet.onGround(), vec3);
@@ -1577,7 +1577,7 @@ public class ServerGamePacketListenerImpl
@@ -1578,7 +1578,7 @@ public class ServerGamePacketListenerImpl
Vec3 vec3 = new Vec3(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z);
this.player.setOnGroundWithMovement(packet.isOnGround(), packet.horizontalCollision(), vec3);
this.player.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.isOnGround());
@@ -10,7 +10,7 @@
@Nullable
private Vec3 startingToFallPosition;
@Nullable
@@ -258,6 +_,20 @@
@@ -258,6 +_,13 @@
}
}
@@ -20,13 +20,6 @@
+ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy()));
+ }
+ // Paper end - Sync offhand slot in menus
+
+ // Paper start - add flag to simplify remote matching logic
+ @Override
+ public ServerPlayer player() {
+ return ServerPlayer.this;
+ }
+ // Paper end - add flag to simplify remote matching logic
+
@Override
public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) {
@@ -518,7 +511,7 @@
BlockPos respawnPosition = this.getRespawnPosition();
float respawnAngle = this.getRespawnAngle();
boolean isRespawnForced = this.isRespawnForced();
@@ -973,13 +_,66 @@
@@ -973,13 +_,67 @@
Optional<ServerPlayer.RespawnPosAngle> optional = findRespawnAndUseSpawnBlock(level, respawnPosition, respawnAngle, isRespawnForced, useCharge);
if (optional.isPresent()) {
ServerPlayer.RespawnPosAngle respawnPosAngle = optional.get();
@@ -560,6 +553,7 @@
+ location,
+ isBedSpawn,
+ isAnchorSpawn,
+ teleportTransition.missingRespawnBlock(),
+ respawnReason,
+ builder
+ );
@@ -396,7 +396,14 @@
this.player.setClientLoaded(true);
}
@@ -521,6 +_,7 @@
@@ -515,12 +_,14 @@
@Override
public void handleBundleItemSelectedPacket(ServerboundSelectBundleItemPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper
this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
}
@Override
public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -72,41 +72,6 @@
}
}
}
@@ -243,7 +_,7 @@
private void synchronizeSlotToRemote(int slotIndex, ItemStack stack, Supplier<ItemStack> supplier) {
if (!this.suppressRemoteUpdates) {
ItemStack itemStack = this.remoteSlots.get(slotIndex);
- if (!ItemStack.matches(itemStack, stack)) {
+ if (!this.matchesRemote(itemStack, stack)) { // Paper - add flag to simplify remote matching logic
ItemStack itemStack1 = supplier.get();
this.remoteSlots.set(slotIndex, itemStack1);
if (this.synchronizer != null) {
@@ -267,7 +_,7 @@
private void synchronizeCarriedToRemote() {
if (!this.suppressRemoteUpdates) {
- if (!ItemStack.matches(this.getCarried(), this.remoteCarried)) {
+ if (!this.matchesRemote(this.getCarried(), this.remoteCarried)) { // Paper - add flag to simplify remote matching logic
this.remoteCarried = this.getCarried().copy();
if (this.synchronizer != null) {
this.synchronizer.sendCarriedChange(this, this.remoteCarried);
@@ -276,6 +_,16 @@
}
}
+ // Paper start - add flag to simplify remote matching logic
+ private boolean matchesRemote(final ItemStack stack, final ItemStack other) {
+ if (this.synchronizer != null && this.synchronizer.player() != null && this.synchronizer.player().getBukkitEntity().simplifyContainerDesyncCheck()) {
+ // Only check the item type and count
+ return stack == other || (stack.getCount() == other.getCount() && ItemStack.isSameItem(stack, other));
+ }
+ return ItemStack.matches(stack, other);
+ }
+ // Paper end - add flag to simplify remote matching logic
+
public void setRemoteSlot(int slot, ItemStack stack) {
this.remoteSlots.set(slot, stack.copy());
}
@@ -343,6 +_,7 @@
this.resetQuickCraft();
}
@@ -1,15 +1,9 @@
--- a/net/minecraft/world/inventory/ContainerSynchronizer.java
+++ b/net/minecraft/world/inventory/ContainerSynchronizer.java
@@ -11,4 +_,12 @@
@@ -11,4 +_,6 @@
void sendCarriedChange(AbstractContainerMenu containerMenu, ItemStack stack);
void sendDataChange(AbstractContainerMenu container, int id, int value);
+
+ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus
+
+ // Paper start - add flag to simplify remote matching logic
+ default net.minecraft.server.level.@org.jspecify.annotations.Nullable ServerPlayer player() {
+ return null;
+ }
+ // Paper end - add flag to simplify remote matching logic
}
@@ -36,3 +36,28 @@
return Math.max(fraction.divideBy(BundleContents.getWeight(stack)).intValue(), 0);
}
@@ -206,15 +_,21 @@
}
public void toggleSelectedItem(int selectedItem) {
- this.selectedItem = this.selectedItem != selectedItem && selectedItem < this.items.size() ? selectedItem : -1;
- }
+ this.selectedItem = !indexIsOutsideAllowedBounds(selectedItem) ? selectedItem : -1; // Paper
+ }
+
+ // Paper start
+ private boolean indexIsOutsideAllowedBounds(int param0) {
+ return param0 < 0 || param0 >= this.items.size();
+ }
+ // Paper end
@Nullable
public ItemStack removeOne() {
if (this.items.isEmpty()) {
return null;
} else {
- int i = this.selectedItem != -1 && this.selectedItem < this.items.size() ? this.selectedItem : 0;
+ int i = !indexIsOutsideAllowedBounds(this.selectedItem) ? this.selectedItem : 0; // Paper
ItemStack itemStack = this.items.remove(i).copy();
this.weight = this.weight.subtract(BundleContents.getWeight(itemStack).multiplyBy(Fraction.getFraction(itemStack.getCount(), 1)));
this.toggleSelectedItem(-1);
@@ -3,6 +3,7 @@ package io.papermc.paper.configuration;
import com.mojang.logging.LogUtils;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.configuration.constraint.Constraints;
import io.papermc.paper.configuration.serializer.collection.map.WriteKeyBack;
import io.papermc.paper.configuration.type.number.DoubleOr;
import io.papermc.paper.configuration.type.number.IntOr;
import io.papermc.paper.util.ItemObfuscationBinding;
@@ -28,7 +29,7 @@ import java.util.Set;
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
public class GlobalConfiguration extends ConfigurationPart {
private static final Logger LOGGER = LogUtils.getLogger();
static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): <insert changes here>
static final int CURRENT_VERSION = 30; // (when you change the version, change the comment, so it conflicts on rebases): upgrade packet to use ids
private static GlobalConfiguration instance;
public static boolean isFirstStart = false;
public static GlobalConfiguration get() {
@@ -185,8 +186,6 @@ public class GlobalConfiguration extends ConfigurationPart {
public CompressionFormat compressionFormat = CompressionFormat.ZLIB;
@Comment("This setting controls if equipment should be updated when handling certain player actions.")
public boolean updateEquipmentOnPlayerActions = true;
@Comment("Only checks an item's amount and type instead of its full data during inventory desync checks.")
public boolean simplifyRemoteItemMatching = false;
public enum CompressionFormat {
GZIP,
@@ -276,7 +275,7 @@ public class GlobalConfiguration extends ConfigurationPart {
public class PacketLimiter extends ConfigurationPart {
public Component kickMessage = Component.translatable("disconnect.exceeded_packet_rate", NamedTextColor.RED);
public PacketLimit allPackets = new PacketLimit(7.0, 500.0, PacketLimit.ViolateAction.KICK);
public Map<Class<? extends Packet<?>>, PacketLimit> overrides = Map.of(ServerboundPlaceRecipePacket.class, new PacketLimit(4.0, 5.0, PacketLimit.ViolateAction.DROP));
public Map<@WriteKeyBack Class<? extends Packet<?>>, PacketLimit> overrides = Map.of(ServerboundPlaceRecipePacket.class, new PacketLimit(4.0, 5.0, PacketLimit.ViolateAction.DROP));
@ConfigSerializable
public record PacketLimit(@Required double interval, @Required double maxPacketRate, ViolateAction action) {
@@ -5,21 +5,25 @@ import com.google.common.collect.Table;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
import io.papermc.paper.configuration.mapping.Definition;
import io.papermc.paper.configuration.mapping.FieldProcessor;
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
import io.papermc.paper.configuration.mapping.MergeMap;
import io.papermc.paper.configuration.serializer.ComponentSerializer;
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
import io.papermc.paper.configuration.serializer.ServerboundPacketClassSerializer;
import io.papermc.paper.configuration.serializer.ResourceLocationSerializer;
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
import io.papermc.paper.configuration.serializer.collections.TableSerializer;
import io.papermc.paper.configuration.serializer.collection.TableSerializer;
import io.papermc.paper.configuration.serializer.collection.map.FastutilMapSerializer;
import io.papermc.paper.configuration.serializer.collection.map.MapSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
import io.papermc.paper.configuration.transformation.Transformations;
import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
import io.papermc.paper.configuration.transformation.global.versioned.V29_LogIPs;
import io.papermc.paper.configuration.transformation.global.versioned.V30_PacketIds;
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWorldHeight;
@@ -41,10 +45,12 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -192,7 +198,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
}
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig());
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig(defaultFieldProcessors()));
}
@Override
@@ -205,7 +211,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
return options
.header(GLOBAL_HEADER)
.serializers(builder -> builder
.register(new PacketClassSerializer())
.register(new ServerboundPacketClassSerializer())
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
);
}
@@ -228,7 +234,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
return super.createWorldObjectMapperFactoryBuilder(contextMap)
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
.addNodeResolver(new NestedSetting.Factory())
.addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap)));
.addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap), defaultFieldProcessors()));
}
private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) {
@@ -287,6 +293,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
V29_LogIPs.apply(versionedBuilder);
V30_PacketIds.apply(versionedBuilder);
// ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
versionedBuilder.build().apply(node);
}
@@ -330,6 +337,12 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
}
}
private static List<Definition<? extends Annotation, ?, ? extends FieldProcessor.Factory<?, ?>>> defaultFieldProcessors() {
return List.of(
MergeMap.DEFINITION
);
}
private static ContextMap createWorldContextMap(ServerLevel level) {
return createWorldContextMap(level.levelStorageAccess.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules());
}
@@ -7,7 +7,7 @@ import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer;
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
import io.papermc.paper.configuration.mapping.MergeMap;
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
import io.papermc.paper.configuration.serializer.collection.map.ThrowExceptions;
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
import io.papermc.paper.configuration.type.BooleanOrDefault;
import io.papermc.paper.configuration.type.DespawnRange;
@@ -191,15 +191,14 @@ public class WorldConfiguration extends ConfigurationPart {
}
}
@MapSerializer.ThrowExceptions
public Reference2ObjectMap<EntityType<?>, IntOr.Disabled> despawnTime = Util.make(new Reference2ObjectOpenHashMap<>(), map -> {
public @ThrowExceptions Reference2ObjectMap<EntityType<?>, IntOr.Disabled> despawnTime = Util.make(new Reference2ObjectOpenHashMap<>(), map -> {
map.put(EntityType.SNOWBALL, IntOr.Disabled.DISABLED);
map.put(EntityType.LLAMA_SPIT, IntOr.Disabled.DISABLED);
});
@PostProcess
public void precomputeDespawnDistances() throws SerializationException {
for (Map.Entry<MobCategory, DespawnRangePair> entry : this.despawnRanges.entrySet()) {
for (final Map.Entry<MobCategory, DespawnRangePair> entry : this.despawnRanges.entrySet()) {
final MobCategory category = entry.getKey();
final DespawnRangePair range = entry.getValue();
range.hard().preComputed(category.getDespawnDistance(), category.getSerializedName());
@@ -0,0 +1,11 @@
package io.papermc.paper.configuration.mapping;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Annotation;
public record Definition<A extends Annotation, T, F>(Class<A> annotation, TypeToken<T> type, F factory) {
public Definition(final Class<A> annotation, final Class<T> type, final F factory) {
this(annotation, TypeToken.get(type), factory);
}
}
@@ -0,0 +1,16 @@
package io.papermc.paper.configuration.mapping;
import java.lang.reflect.AnnotatedType;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.serialize.SerializationException;
import static com.google.common.base.Preconditions.checkState;
public record DeserializedFieldInfo<V>(AnnotatedType fieldType, Object deserializedValue, @Nullable FieldProcessor<V> processor) {
@SuppressWarnings("unchecked")
public @Nullable V runProcessor(final @Nullable Object valueInField, final @Nullable Object deserializedValue) throws SerializationException {
checkState(this.processor != null, "processor is null");
return this.processor.process(this.fieldType, (V) deserializedValue, (V) valueInField);
}
}
@@ -0,0 +1,16 @@
package io.papermc.paper.configuration.mapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.serialize.SerializationException;
public interface FieldProcessor<V> {
@Nullable V process(final AnnotatedType target, @Nullable V deserializedValue, @Nullable V valueInField) throws SerializationException;
interface Factory<A extends Annotation, T> {
FieldProcessor<T> make(A data, AnnotatedType annotatedType);
}
}
@@ -1,52 +1,103 @@
package io.papermc.paper.configuration.mapping;
import io.leangen.geantyref.GenericTypeReflector;
import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.WorldConfiguration;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SequencedMap;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.serialize.SerializationException;
import static io.leangen.geantyref.GenericTypeReflector.erase;
import static java.util.Objects.requireNonNullElseGet;
public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, Object>> {
public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, DeserializedFieldInfo<?>>> {
private final InnerClassInstanceSupplier instanceSupplier;
private final FieldDiscoverer<Map<Field, Object>> delegate;
private final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors;
@SuppressWarnings("unchecked")
public InnerClassFieldDiscoverer(final Map<Class<?>, Object> initialOverrides) {
this.instanceSupplier = new InnerClassInstanceSupplier(initialOverrides);
this.delegate = (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(this.instanceSupplier);
private InnerClassFieldDiscoverer(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer<Map<Field, Object>> delegate, final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors) {
this.instanceSupplier = instanceSupplier;
this.delegate = delegate;
this.fieldProcessors = fieldProcessors;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public @Nullable <V> InstanceFactory<Map<Field, Object>> discover(final AnnotatedType target, final FieldCollector<Map<Field, Object>, V> collector) throws SerializationException {
public @Nullable <V> InstanceFactory<Map<Field, DeserializedFieldInfo<?>>> discover(final AnnotatedType target, final FieldCollector<Map<Field, DeserializedFieldInfo<?>>, V> collector) throws SerializationException {
final Class<?> clazz = erase(target.getType());
if (ConfigurationPart.class.isAssignableFrom(clazz)) {
final FieldDiscoverer.@Nullable InstanceFactory<Map<Field, Object>> instanceFactoryDelegate = this.delegate.<V>discover(target, (name, type, annotations, deserializer, serializer) -> {
if (!erase(type.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes
collector.accept(name, type, annotations, deserializer, serializer);
// we don't care about the returned instance factory, we just need to call it to collect fields
final Object dummyInstance = this.delegate.<V>discover(target, (name, fieldType, container, deserializer, serializer) -> {
if (!erase(fieldType.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes
FieldProcessor<?> processor = null;
processor: for (final Annotation annotation : container.getAnnotations()) {
final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> definitions = this.fieldProcessors.get(annotation.annotationType());
if (definitions != null) {
for (final Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>> def : definitions) {
if (GenericTypeReflector.isSuperType(def.type().getType(), GenericTypeReflector.box(fieldType.getType()))) {
processor = ((FieldProcessor.Factory)def.factory()).make(annotation, fieldType);
break processor;
}
}
}
}
final FieldProcessor<?> finalProcessor = processor;
collector.accept(
name,
fieldType,
container,
(intermediate, newValue, implicitInitializer) -> {
// we only create this map to collect the field, it should only ever have 1 entry
final SequencedMap<Field, Object> map = new LinkedHashMap<>();
deserializer.accept(map, newValue, implicitInitializer);
final Object deserializedValue;
deserializedValue = requireNonNullElseGet(newValue, () -> new ImplicitProvider(implicitInitializer));
intermediate.put(map.firstEntry().getKey(), new DeserializedFieldInfo<>(fieldType, deserializedValue, finalProcessor));
},
serializer
);
}
});
if (instanceFactoryDelegate instanceof MutableInstanceFactory<Map<Field, Object>> mutableInstanceFactoryDelegate) {
return new InnerClassInstanceFactory(this.instanceSupplier, mutableInstanceFactoryDelegate, target);
if (dummyInstance == null) {
return null;
}
return new InnerClassInstanceFactory(this.instanceSupplier, target);
}
return null;
}
public static FieldDiscoverer<?> worldConfig(WorldConfiguration worldConfiguration) {
record ImplicitProvider(Supplier<Object> provider) {
}
@SuppressWarnings("unchecked")
private static InnerClassFieldDiscoverer create(final Map<Class<?>, Object> overrides, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> processors = new LinkedHashMap<>();
for (final Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>> definition : fieldProcessors) {
processors.computeIfAbsent(definition.annotation(), k -> new ArrayList<>()).add(definition);
}
final InnerClassInstanceSupplier instanceSupplier = new InnerClassInstanceSupplier(overrides);
return new InnerClassFieldDiscoverer(instanceSupplier, (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(instanceSupplier), processors);
}
public static FieldDiscoverer<?> worldConfig(final WorldConfiguration worldConfiguration, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
final Map<Class<?>, Object> overrides = Map.of(
WorldConfiguration.class, worldConfiguration
);
return new InnerClassFieldDiscoverer(overrides);
return create(overrides, fieldProcessors);
}
public static FieldDiscoverer<?> globalConfig() {
return new InnerClassFieldDiscoverer(Collections.emptyMap());
public static FieldDiscoverer<?> globalConfig(final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
return create(Collections.emptyMap(), fieldProcessors);
}
}
@@ -2,56 +2,68 @@ package io.papermc.paper.configuration.mapping;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.serialize.SerializationException;
import static io.leangen.geantyref.GenericTypeReflector.erase;
final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> {
final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory<Map<Field, DeserializedFieldInfo<?>>> {
private final InnerClassInstanceSupplier instanceSupplier;
private final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback;
private final AnnotatedType targetType;
InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback, final AnnotatedType targetType) {
InnerClassInstanceFactory(
final InnerClassInstanceSupplier instanceSupplier,
final AnnotatedType targetType
) {
this.instanceSupplier = instanceSupplier;
this.fallback = fallback;
this.targetType = targetType;
}
@Override
public Map<Field, Object> begin() {
return this.fallback.begin();
public Map<Field, DeserializedFieldInfo<?>> begin() {
return new LinkedHashMap<>();
}
@SuppressWarnings("unchecked")
@Override
public void complete(final Object instance, final Map<Field, Object> intermediate) throws SerializationException {
final Iterator<Map.Entry<Field, Object>> iter = intermediate.entrySet().iterator();
try {
while (iter.hasNext()) { // manually merge any mergeable maps
Map.Entry<Field, Object> entry = iter.next();
if (entry.getKey().isAnnotationPresent(MergeMap.class) && Map.class.isAssignableFrom(entry.getKey().getType()) && intermediate.get(entry.getKey()) instanceof Map<?, ?> map) {
iter.remove();
Map<Object, Object> existingMap = (Map<Object, Object>) entry.getKey().get(instance);
if (existingMap != null) {
existingMap.putAll(map);
} else {
entry.getKey().set(instance, entry.getValue());
public void complete(final Object instance, final Map<Field, DeserializedFieldInfo<?>> intermediate) throws SerializationException {
// modeled off of native ObjectFieldDiscoverer
for (final Map.Entry<Field, DeserializedFieldInfo<?>> entry : intermediate.entrySet()) {
final boolean hasProcessor = entry.getValue().processor() != null;
try {
final Object valueInField = entry.getKey().get(instance);
if (entry.getValue().deserializedValue() instanceof InnerClassFieldDiscoverer.ImplicitProvider(final Supplier<Object> provider)) {
Object newFieldValue = provider.get();
if (hasProcessor) {
newFieldValue = entry.getValue().runProcessor(valueInField, newFieldValue);
}
if (newFieldValue != null) {
if (valueInField == null) {
entry.getKey().set(instance, newFieldValue);
}
}
} else {
Object newFieldValue = entry.getValue().deserializedValue();
if (hasProcessor) {
newFieldValue = entry.getValue().runProcessor(valueInField, newFieldValue);
}
entry.getKey().set(instance, newFieldValue);
}
} catch (final IllegalAccessException e) {
throw new SerializationException(this.targetType.getType(), e);
} catch (final SerializationException ex) {
ex.initType(entry.getValue().fieldType());
}
} catch (final IllegalAccessException e) {
throw new SerializationException(this.targetType.getType(), e);
}
this.fallback.complete(instance, intermediate);
}
@Override
public Object complete(final Map<Field, Object> intermediate) throws SerializationException {
public Object complete(final Map<Field, DeserializedFieldInfo<?>> intermediate) throws SerializationException {
final Object targetInstance = Objects.requireNonNull(this.instanceSupplier.instanceMap().get(erase(this.targetType.getType())), () -> this.targetType.getType() + " must already have an instance created");
this.complete(targetInstance, intermediate);
return targetInstance;
@@ -59,6 +71,6 @@ final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstance
@Override
public boolean canCreateInstances() {
return this.fallback.canCreateInstances();
return true;
}
}
@@ -57,7 +57,7 @@ final class InnerClassInstanceSupplier implements CheckedFunction<AnnotatedType,
final Object instance = instanceSupplier.get();
this.instanceMap.put(type, instance);
return () -> instance;
} catch (ReflectiveOperationException e) {
} catch (final ReflectiveOperationException e) {
throw new SerializationException(ConfigurationPart.class, target + " must be a valid ConfigurationPart", e);
}
} else {
@@ -1,11 +1,20 @@
package io.papermc.paper.configuration.mapping;
import com.google.common.collect.Sets;
import com.mojang.logging.LogUtils;
import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.serializer.collection.map.MapSerializer;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import static com.google.common.base.Preconditions.checkState;
/**
* For use in maps inside {@link ConfigurationPart}s that have default keys that shouldn't be removed by users
@@ -17,4 +26,39 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MergeMap {
Logger LOGGER = LogUtils.getClassLogger();
Definition<MergeMap, Map<?, ?>, Factory> DEFINITION = new Definition<>(MergeMap.class, MapSerializer.TYPE, new Factory());
/**
* If marked as restricted, the field won't allow new keys beyond what
* is already in the field when deserializing.
*
* @return True if restricted
*/
boolean restricted() default true;
final class Factory implements FieldProcessor.Factory<MergeMap, Map<?, ?>> {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public FieldProcessor<Map<?, ?>> make(final MergeMap data, final AnnotatedType annotatedType) {
return (target, deserializedValue, valueInField) -> {
if (valueInField != null && deserializedValue != null) {
if (data.restricted()) {
final Set<?> invalidKeys = Sets.difference(deserializedValue.keySet(), valueInField.keySet()).immutableCopy();
for (final Object invalidKey : invalidKeys) {
LOGGER.error("The key {} is not allowed to be added to the field {}", invalidKey, target);
}
((Map) deserializedValue).keySet().removeAll(invalidKeys);
}
((Map) valueInField).putAll(deserializedValue);
return valueInField;
} else {
checkState(!data.restricted() || valueInField != null, "If marked as restricted, field must have a value");
return deserializedValue;
}
};
}
}
}
@@ -1,26 +1,25 @@
package io.papermc.paper.configuration.serializer;
import java.lang.reflect.AnnotatedType;
import java.util.function.Predicate;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import java.lang.reflect.Type;
import java.util.function.Predicate;
public class ComponentSerializer extends ScalarSerializer<Component> {
public class ComponentSerializer extends ScalarSerializer.Annotated<Component> {
public ComponentSerializer() {
super(Component.class);
}
@Override
public Component deserialize(Type type, Object obj) throws SerializationException {
public Component deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return MiniMessage.miniMessage().deserialize(obj.toString());
}
@Override
protected Object serialize(Component component, Predicate<Class<?>> typeSupported) {
return MiniMessage.miniMessage().serialize(component);
protected Object serialize(final AnnotatedType type, final Component item, final Predicate<Class<?>> typeSupported) {
return MiniMessage.miniMessage().serialize(item);
}
}
@@ -1,11 +1,10 @@
package io.papermc.paper.configuration.serializer;
import io.papermc.paper.configuration.type.EngineMode;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import java.lang.reflect.Type;
import java.util.function.Predicate;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
@@ -14,11 +13,11 @@ public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
}
@Override
public EngineMode deserialize(Type type, Object obj) throws SerializationException {
if (obj instanceof Integer id) {
public EngineMode deserialize(final Type type, final Object obj) throws SerializationException {
if (obj instanceof final Integer id) {
try {
return EngineMode.valueOf(id);
} catch (IllegalArgumentException e) {
} catch (final IllegalArgumentException e) {
throw new SerializationException(id + " is not a valid id for type " + type + " for this node");
}
}
@@ -27,7 +26,7 @@ public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
}
@Override
protected Object serialize(EngineMode item, Predicate<Class<?>> typeSupported) {
protected Object serialize(final EngineMode item, final Predicate<Class<?>> typeSupported) {
return item.getId();
}
}
@@ -2,7 +2,7 @@ package io.papermc.paper.configuration.serializer;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Type;
import java.lang.reflect.AnnotatedType;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@@ -17,7 +17,7 @@ import static io.leangen.geantyref.GenericTypeReflector.erase;
/**
* Enum serializer that lists options if fails and accepts `-` as `_`.
*/
public class EnumValueSerializer extends ScalarSerializer<Enum<?>> {
public class EnumValueSerializer extends ScalarSerializer.Annotated<Enum<?>> {
private static final Logger LOGGER = LogUtils.getClassLogger();
@@ -27,23 +27,23 @@ public class EnumValueSerializer extends ScalarSerializer<Enum<?>> {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public @Nullable Enum<?> deserialize(final Type type, final Object obj) throws SerializationException {
public @Nullable Enum<?> deserialize(final AnnotatedType annotatedType, final Object obj) throws SerializationException {
final String enumConstant = obj.toString();
final Class<? extends Enum> typeClass = erase(type).asSubclass(Enum.class);
final Class<? extends Enum> typeClass = erase(annotatedType.getType()).asSubclass(Enum.class);
Enum<?> ret = EnumLookup.lookupEnum(typeClass, enumConstant);
if (ret == null) {
ret = EnumLookup.lookupEnum(typeClass, enumConstant.replace("-", "_"));
}
if (ret == null) {
boolean longer = typeClass.getEnumConstants().length > 10;
List<String> options = Arrays.stream(typeClass.getEnumConstants()).limit(10L).map(Enum::name).toList();
final boolean longer = typeClass.getEnumConstants().length > 10;
final List<String> options = Arrays.stream(typeClass.getEnumConstants()).limit(10L).map(Enum::name).toList();
LOGGER.error("Invalid enum constant provided, expected one of [{}{}], but got {}", String.join(", ", options), longer ? ", ..." : "", enumConstant);
}
return ret;
}
@Override
public Object serialize(final Enum<?> item, final Predicate<Class<?>> typeSupported) {
public Object serialize(final AnnotatedType type, final Enum<?> item, final Predicate<Class<?>> typeSupported) {
return item.name();
}
}
@@ -3,7 +3,7 @@ package io.papermc.paper.configuration.serializer;
import com.destroystokyo.paper.util.SneakyThrow;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.lang.reflect.Type;
import java.lang.reflect.AnnotatedType;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
@@ -11,7 +11,7 @@ import net.minecraft.commands.arguments.NbtPathArgument;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath> {
public class NbtPathSerializer extends ScalarSerializer.Annotated<NbtPathArgument.NbtPath> {
public static final NbtPathSerializer SERIALIZER = new NbtPathSerializer();
private static final NbtPathArgument DUMMY_ARGUMENT = new NbtPathArgument();
@@ -20,23 +20,13 @@ public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath>
super(NbtPathArgument.NbtPath.class);
}
@Override
public NbtPathArgument.NbtPath deserialize(final Type type, final Object obj) throws SerializationException {
return fromString(obj.toString());
}
@Override
protected Object serialize(final NbtPathArgument.NbtPath item, final Predicate<Class<?>> typeSupported) {
return item.toString();
}
public static List<NbtPathArgument.NbtPath> fromString(final List<String> tags) {
List<NbtPathArgument.NbtPath> paths = new ArrayList<>();
final List<NbtPathArgument.NbtPath> paths = new ArrayList<>();
try {
for (final String tag : tags) {
paths.add(fromString(tag));
}
} catch (SerializationException ex) {
} catch (final SerializationException ex) {
SneakyThrow.sneaky(ex);
}
return List.copyOf(paths);
@@ -45,8 +35,18 @@ public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath>
private static NbtPathArgument.NbtPath fromString(final String tag) throws SerializationException {
try {
return DUMMY_ARGUMENT.parse(new StringReader(tag));
} catch (CommandSyntaxException e) {
} catch (final CommandSyntaxException e) {
throw new SerializationException(NbtPathArgument.NbtPath.class, e);
}
}
@Override
public NbtPathArgument.NbtPath deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return fromString(obj.toString());
}
@Override
protected Object serialize(final AnnotatedType type, final NbtPathArgument.NbtPath item, final Predicate<Class<?>> typeSupported) {
return item.toString();
}
}
@@ -1,86 +0,0 @@
package io.papermc.paper.configuration.serializer;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
import io.papermc.paper.util.MappingEnvironment;
import io.papermc.paper.util.ObfHelper;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import net.minecraft.network.protocol.Packet;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
@SuppressWarnings("Convert2Diamond")
public final class PacketClassSerializer extends ScalarSerializer<Class<? extends Packet<?>>> implements MapSerializer.WriteBack {
private static final Logger LOGGER = LogUtils.getClassLogger();
private static final TypeToken<Class<? extends Packet<?>>> TYPE = new TypeToken<Class<? extends Packet<?>>>() {};
private static final List<String> SUBPACKAGES = List.of("game", "handshake", "login", "status");
private static final BiMap<String, String> MOJANG_TO_OBF;
static {
final ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
final @Nullable Map<String, ObfHelper.ClassMapping> classMappingMap = ObfHelper.INSTANCE.mappingsByMojangName();
if (classMappingMap != null) {
classMappingMap.forEach((mojMap, classMapping) -> {
if (mojMap.startsWith("net.minecraft.network.protocol.")) {
builder.put(classMapping.mojangName(), classMapping.obfName());
}
});
}
MOJANG_TO_OBF = builder.build();
}
public PacketClassSerializer() {
super(TYPE);
}
@SuppressWarnings("unchecked")
@Override
public Class<? extends Packet<?>> deserialize(final Type type, final Object obj) throws SerializationException {
Class<?> packetClass = null;
for (final String subpackage : SUBPACKAGES) {
final String fullClassName = "net.minecraft.network.protocol." + subpackage + "." + obj;
try {
packetClass = Class.forName(fullClassName);
break;
} catch (final ClassNotFoundException ex) {
final String spigotClassName = MOJANG_TO_OBF.get(fullClassName);
if (spigotClassName != null) {
try {
packetClass = Class.forName(spigotClassName);
} catch (final ClassNotFoundException ignore) {}
}
}
}
if (packetClass == null || !Packet.class.isAssignableFrom(packetClass)) {
throw new SerializationException("Could not deserialize a packet from " + obj);
}
return (Class<? extends Packet<?>>) packetClass;
}
@Override
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
final String name = packetClass.getName();
@Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null || !MappingEnvironment.reobf() ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
mojName = name;
}
if (mojName != null) {
int pos = mojName.lastIndexOf('.');
if (pos != -1 && pos != mojName.length() - 1) {
return mojName.substring(pos + 1);
}
}
LOGGER.error("Could not serialize {} into a mojang-mapped packet class name", packetClass);
return null;
}
}
@@ -1,12 +1,13 @@
package io.papermc.paper.configuration.serializer;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
import java.util.function.Predicate;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocation> {
public class ResourceLocationSerializer extends ScalarSerializer.Annotated<ResourceLocation> {
public static final ScalarSerializer<ResourceLocation> INSTANCE = new ResourceLocationSerializer();
@@ -15,12 +16,12 @@ public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocatio
}
@Override
public ResourceLocation deserialize(final Type type, final Object obj) throws SerializationException {
public ResourceLocation deserialize(final AnnotatedType annotatedType, final Object obj) throws SerializationException {
return ResourceLocation.read(obj.toString()).getOrThrow(s -> new SerializationException(ResourceLocation.class, s));
}
@Override
protected Object serialize(final ResourceLocation item, final Predicate<Class<?>> typeSupported) {
protected Object serialize(final AnnotatedType annotatedType, final ResourceLocation item, final Predicate<Class<?>> typeSupported) {
return item.toString();
}
}
@@ -0,0 +1,118 @@
package io.papermc.paper.configuration.serializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.PacketType;
import net.minecraft.network.protocol.common.CommonPacketTypes;
import net.minecraft.network.protocol.configuration.ConfigurationPacketTypes;
import net.minecraft.network.protocol.cookie.CookiePacketTypes;
import net.minecraft.network.protocol.game.GamePacketTypes;
import net.minecraft.network.protocol.handshake.HandshakePacketTypes;
import net.minecraft.network.protocol.login.LoginPacketTypes;
import net.minecraft.network.protocol.ping.PingPacketTypes;
import net.minecraft.network.protocol.status.StatusPacketTypes;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
@SuppressWarnings("Convert2Diamond")
public final class ServerboundPacketClassSerializer extends ScalarSerializer<Class<? extends Packet<?>>> {
private static final Logger LOGGER = LogUtils.getClassLogger();
private static final TypeToken<Class<? extends Packet<?>>> TYPE = new TypeToken<Class<? extends Packet<?>>>() {};
static final Set<Class<?>> PACKET_CLASS_HOLDERS = Set.of(
PingPacketTypes.class,
HandshakePacketTypes.class,
StatusPacketTypes.class,
LoginPacketTypes.class,
ConfigurationPacketTypes.class,
CommonPacketTypes.class,
CookiePacketTypes.class,
GamePacketTypes.class
);
private static final Map<ResourceLocation, PacketInfo> ID_TO_INFO;
private static final Map<Class<?>, PacketInfo> CLASS_TO_INFO;
static {
try {
final ImmutableMap.Builder<ResourceLocation, PacketInfo> idBuilder = ImmutableMap.builder();
final ImmutableMap.Builder<Class<?>, PacketInfo> classBuilder = ImmutableMap.builder();
for (final Class<?> holder : PACKET_CLASS_HOLDERS) {
for (final Field field : holder.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) || !Modifier.isFinal(field.getModifiers()) || !field.getType().equals(PacketType.class)) {
continue;
}
final Type genericType = field.getGenericType();
if (!(genericType instanceof final ParameterizedType parameterizedType)) {
throw new RuntimeException("Unexpected generic type: " + genericType);
}
final PacketType<?> type = (PacketType<?>) field.get(null);
if (type.flow() != PacketFlow.SERVERBOUND) {
// we only care about serverbound for rate limiting
continue;
}
final Class<?> packetClass = GenericTypeReflector.erase(parameterizedType.getActualTypeArguments()[0]);
final PacketInfo info = new PacketInfo(packetClass.asSubclass(Packet.class), type);
idBuilder.put(type.id(), info);
classBuilder.put(packetClass, info);
}
}
ID_TO_INFO = idBuilder.buildOrThrow();
CLASS_TO_INFO = classBuilder.buildOrThrow();
Preconditions.checkState(ID_TO_INFO.size() == 72, "Packet class map must have 74 entries");
} catch (final ReflectiveOperationException e) {
throw new RuntimeException("Could not create packet class map", e);
}
}
public ServerboundPacketClassSerializer() {
super(TYPE);
}
@Override
public Class<? extends Packet<?>> deserialize(final Type type, final Object obj) throws SerializationException {
final ResourceLocation location = ResourceLocation.tryParse(obj.toString());
if (location == null) {
throw new SerializationException(ResourceLocation.class, "Could not deserialize a packet from " + obj);
}
final PacketInfo info = ID_TO_INFO.get(location);
if (info == null) {
throw new SerializationException("Could not deserialize a packet from " + obj);
}
return info.packetClass();
}
@Override
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
final PacketInfo info = CLASS_TO_INFO.get(packetClass);
if (info == null) {
LOGGER.error("Could not serialize {} into a packet identifier", packetClass);
return null;
} else {
return info.type().id().toString();
}
}
@SuppressWarnings("rawtypes")
record PacketInfo(Class<? extends Packet> clazz, PacketType<?> type) {
@SuppressWarnings("unchecked")
public Class<? extends Packet<?>> packetClass() {
return (Class<? extends Packet<?>>) this.clazz;
}
}
}
@@ -12,6 +12,7 @@ import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public final class StringRepresentableSerializer extends ScalarSerializer<StringRepresentable> {
private static final Map<Type, Function<String, StringRepresentable>> TYPES = Collections.synchronizedMap(Map.ofEntries(
createEntry(MobCategory.class)
));
@@ -24,20 +25,22 @@ public final class StringRepresentableSerializer extends ScalarSerializer<String
return TYPES.containsKey(type);
}
private static <E extends Enum<E> & StringRepresentable> Map.Entry<Type, Function<String, @Nullable StringRepresentable>> createEntry(Class<E> type) {
return Map.entry(type, s -> {
for (E value : type.getEnumConstants()) {
if (value.getSerializedName().equals(s)) {
return value;
private static <E extends Enum<E> & StringRepresentable> Map.Entry<Type, Function<String, @Nullable StringRepresentable>> createEntry(final Class<E> type) {
return Map.entry(
type, s -> {
for (final E value : type.getEnumConstants()) {
if (value.getSerializedName().equals(s)) {
return value;
}
}
return null;
}
return null;
});
);
}
@Override
public StringRepresentable deserialize(Type type, Object obj) throws SerializationException {
Function<String, StringRepresentable> function = TYPES.get(type);
public StringRepresentable deserialize(final Type type, final Object obj) throws SerializationException {
final Function<String, StringRepresentable> function = TYPES.get(type);
if (function == null) {
throw new SerializationException(type + " isn't registered");
}
@@ -45,7 +48,7 @@ public final class StringRepresentableSerializer extends ScalarSerializer<String
}
@Override
protected Object serialize(StringRepresentable item, Predicate<Class<?>> typeSupported) {
protected Object serialize(final StringRepresentable item, final Predicate<Class<?>> typeSupported) {
return item.getSerializedName();
}
}
@@ -1,9 +1,11 @@
package io.papermc.paper.configuration.serializer.collections;
package io.papermc.paper.configuration.serializer.collection;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
import io.leangen.geantyref.TypeFactory;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
@@ -15,33 +17,34 @@ import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
public class TableSerializer implements TypeSerializer.Annotated<Table<?, ?, ?>> {
private static final int ROW_TYPE_ARGUMENT_INDEX = 0;
private static final int COLUMN_TYPE_ARGUMENT_INDEX = 1;
private static final int VALUE_TYPE_ARGUMENT_INDEX = 2;
@Override
public Table<?, ?, ?> deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
public Table<?, ?, ?> deserialize(final AnnotatedType type, final ConfigurationNode node) throws SerializationException {
final Table<?, ?, ?> table = HashBasedTable.create();
if (!node.empty() && node.isMap()) {
this.deserialize0(table, (ParameterizedType) type, node);
this.deserialize0(table, (AnnotatedParameterizedType) type, node);
}
return table;
}
@SuppressWarnings("unchecked")
private <R, C, V> void deserialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException {
final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
private <R, C, V> void deserialize0(final Table<R, C, V> table, final AnnotatedParameterizedType type, final ConfigurationNode node) throws SerializationException {
final AnnotatedType rowType = type.getAnnotatedActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final AnnotatedType columnType = type.getAnnotatedActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final AnnotatedType valueType = type.getAnnotatedActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
final TypeSerializer<R> rowKeySerializer = (TypeSerializer<R>) node.options().serializers().get(rowType);
if (rowKeySerializer == null) {
throw new SerializationException("Could not find serializer for table row type " + rowType);
}
final Type mapType = TypeFactory.parameterizedClass(Map.class, columnType, valueType);
final TypeSerializer<Map<C, V>> columnValueSerializer = (TypeSerializer<Map<C, V>>) node.options().serializers().get(mapType);
final ParameterizedType mapType = (ParameterizedType) TypeFactory.parameterizedClass(Map.class, columnType.getType(), valueType.getType());
final AnnotatedParameterizedType annotatedMapType = TypeFactory.parameterizedAnnotatedType(mapType, type.getAnnotations(), columnType.getAnnotations(), valueType.getAnnotations());
final TypeSerializer<Map<C, V>> columnValueSerializer = (TypeSerializer<Map<C, V>>) node.options().serializers().get(annotatedMapType);
if (columnValueSerializer == null) {
throw new SerializationException("Could not find serializer for table column-value map " + type);
}
@@ -56,17 +59,21 @@ public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
}
@Override
public void serialize(final Type type, final @Nullable Table<?, ?, ?> table, final ConfigurationNode node) throws SerializationException {
if (table != null) {
this.serialize0(table, (ParameterizedType) type, node);
public void serialize(
final AnnotatedType type,
final @Nullable Table<?, ?, ?> obj,
final ConfigurationNode node
) throws SerializationException {
if (obj != null) {
this.serialize0(obj, (AnnotatedParameterizedType) type, node);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private <R, C, V> void serialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException {
final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
private <R, C, V> void serialize0(final Table<R, C, V> table, final AnnotatedParameterizedType type, final ConfigurationNode node) throws SerializationException {
final AnnotatedType rowType = type.getAnnotatedActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final AnnotatedType columnType = type.getAnnotatedActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final AnnotatedType valueType = type.getAnnotatedActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
final TypeSerializer rowKeySerializer = node.options().serializers().get(rowType);
if (rowKeySerializer == null) {
@@ -77,12 +84,14 @@ public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
for (final R key : table.rowKeySet()) {
rowKeySerializer.serialize(rowType, key, rowKeyNode.set(key));
final Object keyObj = Objects.requireNonNull(rowKeyNode.raw());
node.node(keyObj).set(TypeFactory.parameterizedClass(Map.class, columnType, valueType), table.row(key));
final ParameterizedType mapType = (ParameterizedType) TypeFactory.parameterizedClass(Map.class, columnType.getType(), valueType.getType());
final AnnotatedParameterizedType annotatedMapType = TypeFactory.parameterizedAnnotatedType(mapType, type.getAnnotations(), columnType.getAnnotations(), valueType.getAnnotations());
node.node(keyObj).set(annotatedMapType, table.row(key));
}
}
@Override
public @Nullable Table<?, ?, ?> emptyValue(Type specificType, ConfigurationOptions options) {
public @Nullable Table<?, ?, ?> emptyValue(final Type specificType, final ConfigurationOptions options) {
return ImmutableTable.of();
}
}
@@ -1,8 +1,7 @@
package io.papermc.paper.configuration.serializer.collections;
package io.papermc.paper.configuration.serializer.collection.map;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
@@ -1,11 +1,9 @@
package io.papermc.paper.configuration.serializer.collections;
package io.papermc.paper.configuration.serializer.collection.map;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashSet;
@@ -36,56 +34,54 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
private final boolean clearInvalids;
private final TypeSerializer<Map<?, ?>> fallback;
public MapSerializer(boolean clearInvalids) {
public MapSerializer(final boolean clearInvalids) {
this.clearInvalids = clearInvalids;
this.fallback = requireNonNull(TypeSerializerCollection.defaults().get(TYPE), "Could not find default Map<?, ?> serializer");
}
@Retention(RetentionPolicy.RUNTIME)
public @interface ThrowExceptions {}
@Override
public Map<?, ?> deserialize(AnnotatedType annotatedType, ConfigurationNode node) throws SerializationException {
public Map<?, ?> deserialize(final AnnotatedType annotatedType, final ConfigurationNode node) throws SerializationException {
if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) {
return this.fallback.deserialize(annotatedType, node);
}
final Map<Object, Object> map = new LinkedHashMap<>();
final Type type = annotatedType.getType();
if (node.isMap()) {
if (!(type instanceof ParameterizedType parameterizedType)) {
if (!(annotatedType instanceof final AnnotatedParameterizedType annotatedParameterizedType)) {
throw new SerializationException(type, "Raw types are not supported for collections");
}
if (parameterizedType.getActualTypeArguments().length != 2) {
if (annotatedParameterizedType.getAnnotatedActualTypeArguments().length != 2) {
throw new SerializationException(type, "Map expected two type arguments!");
}
final Type key = parameterizedType.getActualTypeArguments()[0];
final Type value = parameterizedType.getActualTypeArguments()[1];
final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
final AnnotatedType key = annotatedParameterizedType.getAnnotatedActualTypeArguments()[0];
final AnnotatedType value = annotatedParameterizedType.getAnnotatedActualTypeArguments()[1];
final TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
if (keySerializer == null) {
throw new SerializationException(type, "No type serializer available for key type " + key);
}
if (valueSerializer == null) {
throw new SerializationException(type, "No type serializer available for value type " + value);
}
final boolean writeKeyBack = key.isAnnotationPresent(WriteKeyBack.class);
final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
final Set<Object> keysToClear = new HashSet<>();
for (Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) {
final @Nullable Object deserializedKey = deserialize(key, keySerializer, "key", keyNode.set(ent.getKey()), node.path());
final @Nullable Object deserializedValue = deserialize(value, valueSerializer, "value", ent.getValue(), ent.getValue().path());
for (final Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) {
final Object deserializedKey = this.deserialize(key.getType(), keySerializer, "key", keyNode.set(ent.getKey()), node.path());
final Object deserializedValue = this.deserialize(value.getType(), valueSerializer, "value", ent.getValue(), ent.getValue().path());
if (deserializedKey == null || deserializedValue == null) {
continue;
}
if (keySerializer instanceof WriteBack) {
if (serialize(key, keySerializer, deserializedKey, "key", keyNode, node.path()) && !ent.getKey().equals(requireNonNull(keyNode.raw(), "Key must not be null!"))) {
if (writeKeyBack) {
if (this.serialize(key.getType(), keySerializer, deserializedKey, "key", keyNode, node.path()) && !ent.getKey().equals(requireNonNull(keyNode.raw(), "Key must not be null!"))) {
keysToClear.add(ent.getKey());
}
}
map.put(deserializedKey, deserializedValue);
}
if (keySerializer instanceof WriteBack) { // supports cleaning keys which deserialize to the same value
for (Object keyToClear : keysToClear) {
if (writeKeyBack) { // supports cleaning keys which deserialize to the same value
for (final Object keyToClear : keysToClear) {
node.node(keyToClear).raw(null);
}
}
@@ -93,10 +89,10 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
return map;
}
private @Nullable Object deserialize(Type type, TypeSerializer<?> serializer, String mapPart, ConfigurationNode node, NodePath path) {
private @Nullable Object deserialize(final Type type, final TypeSerializer<?> serializer, final String mapPart, final ConfigurationNode node, final NodePath path) {
try {
return serializer.deserialize(type, node);
} catch (SerializationException ex) {
} catch (final SerializationException ex) {
ex.initPath(node::path);
LOGGER.error("Could not deserialize {} {} into {} at {}: {}", mapPart, node.raw(), type, path, ex.rawMessage());
}
@@ -104,22 +100,22 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
}
@Override
public void serialize(AnnotatedType annotatedType, @Nullable Map<?, ?> obj, ConfigurationNode node) throws SerializationException {
public void serialize(final AnnotatedType annotatedType, final @Nullable Map<?, ?> obj, final ConfigurationNode node) throws SerializationException {
if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) {
this.fallback.serialize(annotatedType, obj, node);
return;
}
final Type type = annotatedType.getType();
if (!(type instanceof ParameterizedType parameterizedType)) {
if (!(annotatedType instanceof final AnnotatedParameterizedType annotatedParameterizedType)) {
throw new SerializationException(type, "Raw types are not supported for collections");
}
if (parameterizedType.getActualTypeArguments().length != 2) {
if (annotatedParameterizedType.getAnnotatedActualTypeArguments().length != 2) {
throw new SerializationException(type, "Map expected two type arguments!");
}
final Type key = parameterizedType.getActualTypeArguments()[0];
final Type value = parameterizedType.getActualTypeArguments()[1];
final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
final AnnotatedType key = annotatedParameterizedType.getAnnotatedActualTypeArguments()[0];
final AnnotatedType value = annotatedParameterizedType.getAnnotatedActualTypeArguments()[1];
final TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
if (keySerializer == null) {
throw new SerializationException(type, "No type serializer available for key type " + key);
@@ -135,22 +131,22 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
final Set<Object> unvisitedKeys;
if (node.empty()) {
node.raw(Collections.emptyMap());
unvisitedKeys = Collections.emptySet();
unvisitedKeys = new HashSet<>();
} else {
unvisitedKeys = new HashSet<>(node.childrenMap().keySet());
}
final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
for (Map.Entry<?, ?> ent : obj.entrySet()) {
if (!serialize(key, keySerializer, ent.getKey(), "key", keyNode, node.path())) {
for (final Map.Entry<?, ?> ent : obj.entrySet()) {
if (!this.serialize(key.getType(), keySerializer, ent.getKey(), "key", keyNode, node.path())) {
continue;
}
final Object keyObj = requireNonNull(keyNode.raw(), "Key must not be null!");
final ConfigurationNode child = node.node(keyObj);
serialize(value, valueSerializer, ent.getValue(), "value", child, child.path());
this.serialize(value.getType(), valueSerializer, ent.getValue(), "value", child, child.path());
unvisitedKeys.remove(keyObj);
}
if (this.clearInvalids) {
for (Object unusedChild : unvisitedKeys) {
for (final Object unusedChild : unvisitedKeys) {
node.removeChild(unusedChild);
}
}
@@ -158,11 +154,11 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
}
@SuppressWarnings({"rawtypes", "unchecked"})
private boolean serialize(Type type, TypeSerializer serializer, Object object, String mapPart, ConfigurationNode node, NodePath path) {
private boolean serialize(final Type type, final TypeSerializer serializer, final Object object, final String mapPart, final ConfigurationNode node, final NodePath path) {
try {
serializer.serialize(type, object, node);
return true;
} catch (SerializationException ex) {
} catch (final SerializationException ex) {
ex.initPath(node::path);
LOGGER.error("Could not serialize {} {} from {} at {}: {}", mapPart, object, type, path, ex.rawMessage());
}
@@ -170,13 +166,11 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
}
@Override
public @Nullable Map<?, ?> emptyValue(AnnotatedType specificType, ConfigurationOptions options) {
public @Nullable Map<?, ?> emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
if (specificType.isAnnotationPresent(ThrowExceptions.class)) {
return this.fallback.emptyValue(specificType, options);
}
return new LinkedHashMap<>();
}
public interface WriteBack { // marker interface
}
}
@@ -0,0 +1,10 @@
package io.papermc.paper.configuration.serializer.collection.map;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThrowExceptions {}
@@ -0,0 +1,10 @@
package io.papermc.paper.configuration.serializer.collection.map;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteKeyBack {}
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collection.map;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collection;
import org.jspecify.annotations.NullMarked;
@@ -1,4 +0,0 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collections;
import org.jspecify.annotations.NullMarked;
@@ -1,6 +1,7 @@
package io.papermc.paper.configuration.serializer.registry;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
import java.util.function.Predicate;
import net.minecraft.core.Registry;
@@ -10,20 +11,30 @@ import net.minecraft.resources.ResourceLocation;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> {
abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer.Annotated<T> {
private final RegistryAccess registryAccess;
private final ResourceKey<? extends Registry<R>> registryKey;
private final boolean omitMinecraftNamespace;
protected RegistryEntrySerializer(TypeToken<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
protected RegistryEntrySerializer(
final TypeToken<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<R>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type);
this.registryAccess = registryAccess;
this.registryKey = registryKey;
this.omitMinecraftNamespace = omitMinecraftNamespace;
}
protected RegistryEntrySerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
protected RegistryEntrySerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<R>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type);
this.registryAccess = registryAccess;
this.registryKey = registryKey;
@@ -37,14 +48,14 @@ abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> {
protected abstract T convertFromResourceKey(ResourceKey<R> key) throws SerializationException;
@Override
public final T deserialize(Type type, Object obj) throws SerializationException {
public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return this.convertFromResourceKey(this.deserializeKey(obj));
}
protected abstract ResourceKey<R> convertToResourceKey(T value);
@Override
protected final Object serialize(T item, Predicate<Class<?>> typeSupported) {
protected final Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final ResourceKey<R> key = this.convertToResourceKey(item);
if (this.omitMinecraftNamespace && key.location().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
return key.location().getPath();
@@ -13,22 +13,32 @@ import org.spongepowered.configurate.serialize.SerializationException;
public final class RegistryHolderSerializer<T> extends RegistryEntrySerializer<Holder<T>, T> {
@SuppressWarnings("unchecked")
public RegistryHolderSerializer(TypeToken<T> typeToken, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
public RegistryHolderSerializer(
final TypeToken<T> typeToken,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super((TypeToken<Holder<T>>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryAccess, registryKey, omitMinecraftNamespace);
}
public RegistryHolderSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
public RegistryHolderSerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
this(TypeToken.get(type), registryAccess, registryKey, omitMinecraftNamespace);
Preconditions.checkArgument(type.getTypeParameters().length == 0, "%s must have 0 type parameters", type);
}
@Override
protected Holder<T> convertFromResourceKey(ResourceKey<T> key) throws SerializationException {
protected Holder<T> convertFromResourceKey(final ResourceKey<T> key) throws SerializationException {
return this.registry().get(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key));
}
@Override
protected ResourceKey<T> convertToResourceKey(Holder<T> value) {
protected ResourceKey<T> convertToResourceKey(final Holder<T> value) {
return value.unwrap().map(Function.identity(), r -> this.registry().getResourceKey(r).orElseThrow());
}
}
@@ -11,16 +11,26 @@ import org.spongepowered.configurate.serialize.SerializationException;
*/
public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T, T> {
public RegistryValueSerializer(TypeToken<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
public RegistryValueSerializer(
final TypeToken<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type, registryAccess, registryKey, omitMinecraftNamespace);
}
public RegistryValueSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
public RegistryValueSerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type, registryAccess, registryKey, omitMinecraftNamespace);
}
@Override
protected T convertFromResourceKey(ResourceKey<T> key) throws SerializationException {
protected T convertFromResourceKey(final ResourceKey<T> key) throws SerializationException {
final T value = this.registry().getValue(key);
if (value == null) {
throw new SerializationException("Missing value in " + this.registry() + " with key " + key.location());
@@ -29,7 +39,7 @@ public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T,
}
@Override
protected ResourceKey<T> convertToResourceKey(T value) {
protected ResourceKey<T> convertToResourceKey(final T value) {
return this.registry().getResourceKey(value).orElseThrow();
}
}
@@ -0,0 +1,67 @@
package io.papermc.paper.configuration.transformation.global.versioned;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Map;
import java.util.Objects;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.NodePath;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.transformation.TransformAction;
import static java.util.Objects.requireNonNull;
import static org.spongepowered.configurate.NodePath.path;
public class V30_PacketIds implements TransformAction {
public static final V30_PacketIds INSTANCE = new V30_PacketIds();
private static final int VERSION = 30;
private static final Gson GSON = new Gson();
private static final Map<String, ResourceLocation> MOJANG_TO_ID;
static {
final ImmutableMap.Builder<String, ResourceLocation> builder2 = ImmutableMap.builder();
final InputStream input = V30_PacketIds.class.getResourceAsStream("/config-data/packet-limiter-upgrade-data.json");
if (input == null) {
throw new RuntimeException("Failed to load packet limiter upgrade data");
}
try (final Reader reader = new InputStreamReader(new BufferedInputStream(input))) {
final JsonArray array = GSON.fromJson(reader, JsonArray.class);
for (final JsonElement element : array) {
final JsonObject obj = element.getAsJsonObject();
builder2.put(obj.get("simple_class_name").getAsString(), ResourceLocation.parse(obj.get("id").getAsString()));
}
} catch (final IOException e) {
throw new RuntimeException("Failed to load packet limiter upgrade data", e);
}
MOJANG_TO_ID = builder2.build();
}
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(path("packet-limiter", "overrides", ConfigurationTransformation.WILDCARD_OBJECT), INSTANCE).build());
}
private V30_PacketIds() {
}
@Override
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) {
final String oldClassName = path.get(path.size() - 1).toString();
if (MOJANG_TO_ID.containsKey(oldClassName)) {
return path.with(path.size() - 1, MOJANG_TO_ID.get(oldClassName).toString()).array();
}
return null;
}
}
@@ -1,6 +1,6 @@
package io.papermc.paper.configuration.type;
import java.lang.reflect.Type;
import java.lang.reflect.AnnotatedType;
import java.util.Locale;
import java.util.function.Predicate;
import org.apache.commons.lang3.BooleanUtils;
@@ -9,38 +9,38 @@ import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public record BooleanOrDefault(@Nullable Boolean value) {
private static final String DEFAULT_VALUE = "default";
public static final BooleanOrDefault USE_DEFAULT = new BooleanOrDefault(null);
public static final ScalarSerializer<BooleanOrDefault> SERIALIZER = new Serializer();
private static final String DEFAULT_VALUE = "default";
public boolean or(boolean fallback) {
public boolean or(final boolean fallback) {
return this.value == null ? fallback : this.value;
}
private static final class Serializer extends ScalarSerializer<BooleanOrDefault> {
private static final class Serializer extends ScalarSerializer.Annotated<BooleanOrDefault> {
Serializer() {
super(BooleanOrDefault.class);
}
@Override
public BooleanOrDefault deserialize(Type type, Object obj) throws SerializationException {
if (obj instanceof String string) {
public BooleanOrDefault deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
if (obj instanceof final String string) {
if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
return USE_DEFAULT;
}
try {
return new BooleanOrDefault(BooleanUtils.toBoolean(string.toLowerCase(Locale.ROOT), "true", "false"));
} catch (IllegalArgumentException ex) {
} catch (final IllegalArgumentException ex) {
throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'", ex);
}
} else if (obj instanceof Boolean bool) {
} else if (obj instanceof final Boolean bool) {
return new BooleanOrDefault(bool);
}
throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'");
}
@Override
protected Object serialize(BooleanOrDefault item, Predicate<Class<?>> typeSupported) {
protected Object serialize(final AnnotatedType type, final BooleanOrDefault item, final Predicate<Class<?>> typeSupported) {
final Boolean value = item.value;
if (value != null) {
return value.toString();
@@ -1,6 +1,7 @@
package io.papermc.paper.configuration.type.number;
import com.google.common.base.Preconditions;
import java.lang.reflect.AnnotatedType;
import java.util.OptionalDouble;
import java.util.function.DoublePredicate;
import java.util.function.Function;
@@ -20,17 +21,17 @@ public interface DoubleOr {
}
record Default(OptionalDouble value) implements DoubleOr {
private static final String DEFAULT_VALUE = "default";
public static final Default USE_DEFAULT = new Default(OptionalDouble.empty());
private static final String DEFAULT_VALUE = "default";
public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
}
record Disabled(OptionalDouble value) implements DoubleOr {
private static final String DISABLED_VALUE = "disabled";
public static final Disabled DISABLED = new Disabled(OptionalDouble.empty());
private static final String DISABLED_VALUE = "disabled";
public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
public boolean test(DoublePredicate predicate) {
public boolean test(final DoublePredicate predicate) {
return this.value.isPresent() && predicate.test(this.value.getAsDouble());
}
@@ -40,12 +41,12 @@ public interface DoubleOr {
}
final class Serializer<T extends DoubleOr> extends OptionalNumSerializer<T, OptionalDouble> {
Serializer(final Class<T> classOfT, final Function<OptionalDouble, T> factory, String emptySerializedValue, T emptyValue) {
Serializer(final Class<T> classOfT, final Function<OptionalDouble, T> factory, final String emptySerializedValue, final T emptyValue) {
super(classOfT, emptySerializedValue, emptyValue, OptionalDouble::empty, OptionalDouble::isEmpty, factory, double.class);
}
@Override
protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) {
protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final OptionalDouble value = item.value();
if (value.isPresent()) {
return value.getAsDouble();
@@ -2,6 +2,7 @@ package io.papermc.paper.configuration.type.number;
import com.google.common.base.Preconditions;
import com.mojang.logging.LogUtils;
import java.lang.reflect.AnnotatedType;
import java.util.OptionalInt;
import java.util.function.Function;
import java.util.function.IntPredicate;
@@ -28,17 +29,17 @@ public interface IntOr {
}
record Default(OptionalInt value) implements IntOr {
private static final String DEFAULT_VALUE = "default";
public static final Default USE_DEFAULT = new Default(OptionalInt.empty());
private static final String DEFAULT_VALUE = "default";
public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
}
record Disabled(OptionalInt value) implements IntOr {
private static final String DISABLED_VALUE = "disabled";
public static final Disabled DISABLED = new Disabled(OptionalInt.empty());
private static final String DISABLED_VALUE = "disabled";
public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
public boolean test(IntPredicate predicate) {
public boolean test(final IntPredicate predicate) {
return this.value.isPresent() && predicate.test(this.value.getAsInt());
}
@@ -49,7 +50,7 @@ public interface IntOr {
final class Serializer<T extends IntOr> extends OptionalNumSerializer<T, OptionalInt> {
private Serializer(Class<T> classOfT, Function<OptionalInt, T> factory, String emptySerializedValue, T emptyValue) {
private Serializer(final Class<T> classOfT, final Function<OptionalInt, T> factory, final String emptySerializedValue, final T emptyValue) {
super(classOfT, emptySerializedValue, emptyValue, OptionalInt::empty, OptionalInt::isEmpty, factory, int.class);
}
@@ -73,7 +74,7 @@ public interface IntOr {
}
@Override
protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) {
protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final OptionalInt value = item.value();
if (value.isPresent()) {
return value.getAsInt();
@@ -17,7 +17,15 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
private final Function<O, T> factory;
private final Class<?> number;
protected OptionalNumSerializer(final Class<T> classOfT, final String emptySerializedValue, final T emptyValue, final Supplier<O> empty, final Predicate<O> isEmpty, final Function<O, T> factory, final Class<?> number) {
protected OptionalNumSerializer(
final Class<T> classOfT,
final String emptySerializedValue,
final T emptyValue,
final Supplier<O> empty,
final Predicate<O> isEmpty,
final Function<O, T> factory,
final Class<?> number
) {
super(classOfT);
this.emptySerializedValue = emptySerializedValue;
this.emptyValue = emptyValue;
@@ -30,7 +38,7 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
@Override
public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
final O value;
if (obj instanceof String string) {
if (obj instanceof final String string) {
if (this.emptySerializedValue.equalsIgnoreCase(string)) {
value = this.empty.get();
} else if (NumberUtils.isParsable(string)) {
@@ -38,7 +46,7 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
} else {
throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));
}
} else if (obj instanceof Number num) {
} else if (obj instanceof final Number num) {
value = this.full(num);
} else {
throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));
@@ -41,6 +41,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
// import jline.console.ConsoleReader;
import net.minecraft.Optionull;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
@@ -2333,7 +2334,11 @@ public final class CraftServer implements Server {
@Override
public GameMode getDefaultGameMode() {
return GameMode.getByValue(this.console.getLevel(net.minecraft.world.level.Level.OVERWORLD).serverLevelData.getGameType().getId());
return GameMode.getByValue(Optionull.mapOrDefault(
this.console.getLevel(net.minecraft.world.level.Level.OVERWORLD),
l -> l.serverLevelData.getGameType(),
this.console.getProperties().gamemode
).getId());
}
@Override
@@ -221,7 +221,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener();
public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API
private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit
private boolean simplifyContainerDesyncCheck = GlobalConfiguration.get().unsupportedSettings.simplifyRemoteItemMatching;
private long lastSaveTime; // Paper - getLastPlayed replacement API
public CraftPlayer(CraftServer server, ServerPlayer entity) {
@@ -3603,20 +3602,4 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void setDeathScreenScore(final int score) {
getHandle().setScore(score);
}
/**
* Returns whether container desync checks should skip the full item comparison of remote carried and changed slots
* and should instead only check their type and amount.
* <p>
* This is useful if the client is not able to produce the same item stack (or as of 1.21.5, its data hashes) as the server.
*
* @return whether to simplify container desync checks
*/
public boolean simplifyContainerDesyncCheck() {
return simplifyContainerDesyncCheck;
}
public void setSimplifyContainerDesyncCheck(final boolean simplifyContainerDesyncCheck) {
this.simplifyContainerDesyncCheck = simplifyContainerDesyncCheck;
}
}
@@ -0,0 +1 @@
[{"id":"minecraft:finish_configuration","fq_class_name":"net.minecraft.network.protocol.configuration.ServerboundFinishConfigurationPacket","simple_class_name":"ServerboundFinishConfigurationPacket"},{"id":"minecraft:select_known_packs","fq_class_name":"net.minecraft.network.protocol.configuration.ServerboundSelectKnownPacks","simple_class_name":"ServerboundSelectKnownPacks"},{"id":"minecraft:ping_request","fq_class_name":"net.minecraft.network.protocol.ping.ServerboundPingRequestPacket","simple_class_name":"ServerboundPingRequestPacket"},{"id":"minecraft:intention","fq_class_name":"net.minecraft.network.protocol.handshake.ClientIntentionPacket","simple_class_name":"ClientIntentionPacket"},{"id":"minecraft:cookie_response","fq_class_name":"net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket","simple_class_name":"ServerboundCookieResponsePacket"},{"id":"minecraft:client_information","fq_class_name":"net.minecraft.network.protocol.common.ServerboundClientInformationPacket","simple_class_name":"ServerboundClientInformationPacket"},{"id":"minecraft:custom_payload","fq_class_name":"net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket","simple_class_name":"ServerboundCustomPayloadPacket"},{"id":"minecraft:keep_alive","fq_class_name":"net.minecraft.network.protocol.common.ServerboundKeepAlivePacket","simple_class_name":"ServerboundKeepAlivePacket"},{"id":"minecraft:pong","fq_class_name":"net.minecraft.network.protocol.common.ServerboundPongPacket","simple_class_name":"ServerboundPongPacket"},{"id":"minecraft:resource_pack","fq_class_name":"net.minecraft.network.protocol.common.ServerboundResourcePackPacket","simple_class_name":"ServerboundResourcePackPacket"},{"id":"minecraft:custom_click_action","fq_class_name":"net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket","simple_class_name":"ServerboundCustomClickActionPacket"},{"id":"minecraft:accept_teleportation","fq_class_name":"net.minecraft.network.protocol.game.ServerboundAcceptTeleportationPacket","simple_class_name":"ServerboundAcceptTeleportationPacket"},{"id":"minecraft:block_entity_tag_query","fq_class_name":"net.minecraft.network.protocol.game.ServerboundBlockEntityTagQueryPacket","simple_class_name":"ServerboundBlockEntityTagQueryPacket"},{"id":"minecraft:bundle_item_selected","fq_class_name":"net.minecraft.network.protocol.game.ServerboundSelectBundleItemPacket","simple_class_name":"ServerboundSelectBundleItemPacket"},{"id":"minecraft:change_difficulty","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChangeDifficultyPacket","simple_class_name":"ServerboundChangeDifficultyPacket"},{"id":"minecraft:change_game_mode","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChangeGameModePacket","simple_class_name":"ServerboundChangeGameModePacket"},{"id":"minecraft:chat_ack","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChatAckPacket","simple_class_name":"ServerboundChatAckPacket"},{"id":"minecraft:chat_command","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChatCommandPacket","simple_class_name":"ServerboundChatCommandPacket"},{"id":"minecraft:chat_command_signed","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChatCommandSignedPacket","simple_class_name":"ServerboundChatCommandSignedPacket"},{"id":"minecraft:chat","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChatPacket","simple_class_name":"ServerboundChatPacket"},{"id":"minecraft:chat_session_update","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChatSessionUpdatePacket","simple_class_name":"ServerboundChatSessionUpdatePacket"},{"id":"minecraft:chunk_batch_received","fq_class_name":"net.minecraft.network.protocol.game.ServerboundChunkBatchReceivedPacket","simple_class_name":"ServerboundChunkBatchReceivedPacket"},{"id":"minecraft:client_command","fq_class_name":"net.minecraft.network.protocol.game.ServerboundClientCommandPacket","simple_class_name":"ServerboundClientCommandPacket"},{"id":"minecraft:client_tick_end","fq_class_name":"net.minecraft.network.protocol.game.ServerboundClientTickEndPacket","simple_class_name":"ServerboundClientTickEndPacket"},{"id":"minecraft:command_suggestion","fq_class_name":"net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket","simple_class_name":"ServerboundCommandSuggestionPacket"},{"id":"minecraft:configuration_acknowledged","fq_class_name":"net.minecraft.network.protocol.game.ServerboundConfigurationAcknowledgedPacket","simple_class_name":"ServerboundConfigurationAcknowledgedPacket"},{"id":"minecraft:container_button_click","fq_class_name":"net.minecraft.network.protocol.game.ServerboundContainerButtonClickPacket","simple_class_name":"ServerboundContainerButtonClickPacket"},{"id":"minecraft:container_click","fq_class_name":"net.minecraft.network.protocol.game.ServerboundContainerClickPacket","simple_class_name":"ServerboundContainerClickPacket"},{"id":"minecraft:container_close","fq_class_naLine truncated