diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b45c50c05c..be4ee71e6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: name: Setup Java uses: actions/setup-java@v4 with: - java-version: 21 + java-version: 25 distribution: temurin # Configures gradle with caching - name: Setup Gradle diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1df7f1e719..539c9f34b2 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,7 +9,7 @@ repositories { } dependencies { - implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.3.1") - implementation("com.diffplug.gradle.spotless:com.diffplug.gradle.spotless.gradle.plugin:8.2.1") + implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.5.1") + implementation("com.diffplug.gradle.spotless:com.diffplug.gradle.spotless.gradle.plugin:8.8.0") implementation("de.skuzzle.restrictimports:de.skuzzle.restrictimports.gradle.plugin:3.0.0") } diff --git a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts index 7f9f78b51c..d8f0f4a472 100644 --- a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts @@ -6,7 +6,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } @@ -17,32 +17,43 @@ repositories { maven("https://oss.sonatype.org/content/repositories/snapshots/") // Snapshots maven("https://repo.viaversion.com/") // ViaVersion maven("https://repo.pgm.fyi/snapshots") // SportPaper & other PGM-specific stuff - maven("https://jitpack.io") // MinusKube SmartsInv fix for shift click maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents + exclusiveContent { + forRepository { + maven("https://jitpack.io") + } + filter { + includeGroup("com.github.OvercastCommunity.adventure-platform") + includeGroup("com.github.MinusKube") + } + } } dependencies { api("org.jdom:jdom2:2.0.6.1") - api("net.kyori:adventure-api:4.26.1") - api("net.kyori:adventure-text-serializer-plain:4.26.1") - api("net.kyori:adventure-platform-bukkit:4.4.1") + api("net.kyori:adventure-api:5.2.0") + api("net.kyori:adventure-text-serializer-plain:5.2.0") + // adventure-platform fork supporting Adventure 5.x + // https://github.com/OvercastCommunity/adventure-platform + api("com.github.OvercastCommunity.adventure-platform:adventure-platform-bukkit:b4bfb8a6b4") api("org.incendo:cloud-core:2.0.0") api("org.incendo:cloud-annotations:2.0.0") - api("org.incendo:cloud-paper:2.0.0-beta.14") - api("org.incendo:cloud-minecraft-extras:2.0.0-beta.14") + api("org.incendo:cloud-paper:2.0.0-beta.17") + api("org.incendo:cloud-minecraft-extras:2.0.0-beta.17") api("me.lucko:commodore:2.2") - api("fr.mrmicky:fastboard:2.1.5") + api("fr.mrmicky:fastboard:2.2.0") + // Latest SmartInvs commit api("com.github.MinusKube:SmartInvs:9c9dbbe") { isTransitive = false } - api("org.eclipse.jgit:org.eclipse.jgit:7.5.0.202512021534-r") { isTransitive = false } + api("org.eclipse.jgit:org.eclipse.jgit:7.7.0.202606012155-r") { isTransitive = false } api("net.objecthunter:exp4j:0.4.9-pgm") api("org.reflections:reflections:0.10.2") // Annotations api("org.jspecify:jspecify:1.0.0") - compileOnly("org.jetbrains:annotations:26.0.2-1") + compileOnly("org.jetbrains:annotations:26.1.0") // Optional runtime dependencies - compileOnly("com.github.retrooper:packetevents-spigot:2.12.0") + compileOnly("com.github.retrooper:packetevents-spigot:2.13.0") compileOnly("com.viaversion:viaversion-api:5.0.0") // Paper and SportPaper include these (or equivalents) @@ -71,7 +82,7 @@ spotless { removeUnusedImports() trimTrailingWhitespace() formatAnnotations() - palantirJavaFormat("2.87.0").style("GOOGLE").formatJavadoc(true) + palantirJavaFormat("2.96.0").style("GOOGLE").formatJavadoc(true) } } diff --git a/buildSrc/src/main/kotlin/extensions.kt b/buildSrc/src/main/kotlin/extensions.kt index beb6f54c2c..45c09a90c4 100644 --- a/buildSrc/src/main/kotlin/extensions.kt +++ b/buildSrc/src/main/kotlin/extensions.kt @@ -1,13 +1,14 @@ import org.gradle.api.Project +import org.gradle.api.provider.Provider -fun Project.latestCommitHash(): String { +fun Project.latestCommitHash(): Provider { return runGitCommand(listOf("rev-parse", "--short", "HEAD")) } -fun Project.runGitCommand(args: List): String { +fun Project.runGitCommand(args: List): Provider { return providers.exec { commandLine("git") args(args) - }.standardOutput.asText.get().trim() + }.standardOutput.asText.map { it.trim() } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e9b9a8fb35..3cb56089d9 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } dependencies { - compileOnly("dev.pgm.paper:paper-api:1.8_1.21.11-SNAPSHOT") + compileOnly("dev.pgm.paper:paper-api:1.8_26.2-SNAPSHOT") implementation(project(":util")) runtimeOnly(project(":platform-sportpaper")) { exclude("*") } @@ -86,15 +86,17 @@ tasks { val version = project.version.toString() val commitHash = project.latestCommitHash() + inputs.property("commitHash", commitHash) + filesMatching(listOf("plugin.yml", "paper-plugin.yml")) { expand( mapOf( "name" to name, "description" to description, - "apiVersion" to "1.21.11", + "apiVersion" to "26.2", "mainClass" to "tc.oc.pgm.PGMPlugin", "version" to version, - "commitHash" to commitHash, + "commitHash" to commitHash.get(), "url" to "https://pgm.dev/" ) ) diff --git a/core/src/main/java/tc/oc/pgm/PGMPlugin.java b/core/src/main/java/tc/oc/pgm/PGMPlugin.java index bc6dc3cb6f..a82d25ae49 100644 --- a/core/src/main/java/tc/oc/pgm/PGMPlugin.java +++ b/core/src/main/java/tc/oc/pgm/PGMPlugin.java @@ -1,5 +1,7 @@ package tc.oc.pgm; +import static tc.oc.pgm.util.bukkit.MiscUtils.MISC_UTILS; + import com.google.common.collect.Lists; import fr.minuskube.inv.InventoryManager; import java.io.File; @@ -67,8 +69,6 @@ import tc.oc.pgm.rotation.MapPoolManager; import tc.oc.pgm.rotation.RandomMapOrder; import tc.oc.pgm.tablist.MatchTabManager; -import tc.oc.pgm.util.FileUtils; -import tc.oc.pgm.util.bukkit.ViaUtils; import tc.oc.pgm.util.chunk.NullChunkGenerator; import tc.oc.pgm.util.concurrent.BukkitExecutorService; import tc.oc.pgm.util.listener.AfkTracker; @@ -133,8 +133,6 @@ public void onEnable() { getLogger().log(Level.SEVERE, "Failed to initialize PGM platform", t); getServer().getPluginManager().disablePlugin(this); } - // Fix before any audiences have the chance of creating - if (Platform.isLegacy()) ViaUtils.removeViaChatFacet(); Permissions.registerAll(); @@ -212,14 +210,7 @@ public void onEnable() { config.getGroups().isEmpty() ? null : new ConfigDecorationProvider()); // Sometimes match folders need to be cleaned up if the server previously crashed - final File[] worldDirs = getServer().getWorldContainer().listFiles(); - if (worldDirs != null) { - for (File dir : worldDirs) { - if (dir.isDirectory() && Match.isMatchWorld(dir.getName())) { - FileUtils.delete(dir); - } - } - } + MISC_UTILS.cleanupWorldFiles(); matchManager = new MatchManagerImpl(logger); diff --git a/core/src/main/java/tc/oc/pgm/action/actions/MessageAction.java b/core/src/main/java/tc/oc/pgm/action/actions/MessageAction.java index 89c4395e33..4b12a85619 100644 --- a/core/src/main/java/tc/oc/pgm/action/actions/MessageAction.java +++ b/core/src/main/java/tc/oc/pgm/action/actions/MessageAction.java @@ -14,6 +14,7 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.title.Title; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import tc.oc.pgm.action.replacements.Replacement; import tc.oc.pgm.filters.Filterable; @@ -71,18 +72,25 @@ private Component replaceClickEvents( Component component, Function replacer) { var click = component.clickEvent(); if (click != null && click.payload() instanceof ClickEvent.Payload.Text payload) { - var matcher = PATTERN.matcher(payload.value()); - var result = new StringBuilder(); - while (matcher.find()) matcher.appendReplacement(result, replacer.apply(matcher)); - matcher.appendTail(result); - var resultPayload = ClickEvent.Payload.string(result.toString()); - component = component.clickEvent(ClickEvent.clickEvent(click.action(), resultPayload)); + component = component.clickEvent(replaceEvent(click, payload, replacer)); } var children = new ArrayList<>(component.children()); children.replaceAll(child -> replaceClickEvents(child, replacer)); return component.children(children); } + private @NonNull ClickEvent replaceEvent( + ClickEvent click, + ClickEvent.Payload.@NonNull Text payload, + Function replacer) { + var matcher = PATTERN.matcher(payload.value()); + var result = new StringBuilder(); + while (matcher.find()) matcher.appendReplacement(result, replacer.apply(matcher)); + matcher.appendTail(result); + var resultPayload = ClickEvent.Payload.string(result.toString()); + return ClickEvent.clickEvent(click.action(), resultPayload); + } + private Title replace(Title title, T scope) { if (replacements == null) return title; return Title.title( diff --git a/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java b/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java index e11ab2cf72..71688fd20f 100644 --- a/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java +++ b/core/src/main/java/tc/oc/pgm/flag/LegacyFlagBeamMatchModule.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -144,13 +145,14 @@ class Beam { ItemStack wool = new ItemBuilder().material(Materials.WOOL).color(flag.getDyeColor()).build(); - this.base = ENTITIES.fakeArmorStand(wool); - this.legacyBase = ENTITIES.fakeWitherSkull(); + World world = match.getWorld(); + this.base = ENTITIES.fakeArmorStand(wool, world); + this.legacyBase = ENTITIES.fakeWitherSkull(world); this.segments = range( 0, 64) // ~100 blocks is the height which the particles appear to be reasonably // visible (similar amount to amount closest to the flag), we limit this to 64 blocks // to reduce load on the client - .mapToObj(i -> ENTITIES.fakeArmorStand(wool)) + .mapToObj(_ -> ENTITIES.fakeArmorStand(wool, match.getWorld())) .collect(Collectors.toList()); } diff --git a/core/src/main/java/tc/oc/pgm/match/MatchImpl.java b/core/src/main/java/tc/oc/pgm/match/MatchImpl.java index c25201faee..54d0054efb 100644 --- a/core/src/main/java/tc/oc/pgm/match/MatchImpl.java +++ b/core/src/main/java/tc/oc/pgm/match/MatchImpl.java @@ -2,6 +2,7 @@ import static tc.oc.pgm.util.Assert.assertNotNull; import static tc.oc.pgm.util.Assert.assertTrue; +import static tc.oc.pgm.util.bukkit.MiscUtils.MISC_UTILS; import static tc.oc.pgm.util.nms.NMSHacks.NMS_HACKS; import com.google.common.collect.ImmutableList; @@ -946,8 +947,8 @@ public void destroy() { Level.SEVERE, "Unable to unload world " + worldName + " (this can cause memory leaks!)"); } - final File oldMatchFolder = new File(PGM.get().getServer().getWorldContainer(), worldName); - if (oldMatchFolder.exists()) { + final File oldMatchFolder = MISC_UTILS.getWorldFolder(worldName); + if (oldMatchFolder != null && oldMatchFolder.exists()) { FileUtils.delete(oldMatchFolder); } } diff --git a/core/src/main/java/tc/oc/pgm/match/MatchPlayerImpl.java b/core/src/main/java/tc/oc/pgm/match/MatchPlayerImpl.java index 2df90eb529..d67476282b 100644 --- a/core/src/main/java/tc/oc/pgm/match/MatchPlayerImpl.java +++ b/core/src/main/java/tc/oc/pgm/match/MatchPlayerImpl.java @@ -60,12 +60,10 @@ import tc.oc.pgm.util.bukkit.ViaUtils; import tc.oc.pgm.util.listener.AfkTracker; import tc.oc.pgm.util.named.NameStyle; +import tc.oc.pgm.util.nms.packets.FakeEntity; public class MatchPlayerImpl implements MatchPlayer, Comparable { - // TODO: Probably should be moved to a better location - private static final int FROZEN_VEHICLE_ENTITY_ID = NMS_HACKS.allocateEntityId(); - private static final String DEATH_KEY = "isDead"; private static final MetadataValue DEATH_VALUE = new FixedMetadataValue(PGM.get(), true); @@ -80,6 +78,7 @@ public class MatchPlayerImpl implements MatchPlayer, Comparable { private final AtomicBoolean visible; private final AtomicBoolean protocolReady; private final AtomicInteger protocolVersion; + private final FakeEntity frozenVehicle; private final AfkTracker.Activity activity; private long lastKitTick = 0; @@ -96,6 +95,7 @@ public MatchPlayerImpl(Match match, Player player) { this.visible = new AtomicBoolean(false); this.protocolReady = new AtomicBoolean(ViaUtils.isReady(player)); this.protocolVersion = new AtomicInteger(ViaUtils.getProtocolVersion(player)); + this.frozenVehicle = ENTITIES.fakeFreezeEntity(player, this::isLegacy); this.activity = PGM.get().getAfkTracker().getActivity(player); } @@ -335,10 +335,10 @@ public void setFrozen(boolean yes) { if (bukkit == null) return; if (yes) { - ENTITIES.spawnFreezeEntity(bukkit, FROZEN_VEHICLE_ENTITY_ID, isLegacy()).send(bukkit); - ENTITIES.entityMount(bukkit.getEntityId(), FROZEN_VEHICLE_ENTITY_ID).send(bukkit); + frozenVehicle.spawn(bukkit.getLocation()).send(bukkit); + frozenVehicle.ride(bukkit.getEntityId()).send(bukkit); } else { - ENTITIES.destroyEntitiesPacket(FROZEN_VEHICLE_ENTITY_ID).send(bukkit); + frozenVehicle.destroy().send(bukkit); } resetInteraction(); } @@ -453,7 +453,7 @@ public void tick(Match match, Tick tick) { // If the player right-clicks on another vehicle while frozen, the client will // eject them from the freeze entity unconditionally, so we have to spam them // with these packets to keep them on it. - ENTITIES.entityMount(bukkit.getEntityId(), FROZEN_VEHICLE_ENTITY_ID).send(bukkit); + frozenVehicle.ride(bukkit.getEntityId()).send(bukkit); } } diff --git a/core/src/main/java/tc/oc/pgm/tracker/trackers/OwnedMobTracker.java b/core/src/main/java/tc/oc/pgm/tracker/trackers/OwnedMobTracker.java index 6709dcacaa..bccaeb77d5 100644 --- a/core/src/main/java/tc/oc/pgm/tracker/trackers/OwnedMobTracker.java +++ b/core/src/main/java/tc/oc/pgm/tracker/trackers/OwnedMobTracker.java @@ -71,7 +71,7 @@ public void onSlimeSplit(CreatureSpawnEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onSlimeSplit(SlimeSplitEvent event) { if (event.getCount() > 0 && resolveEntity(event.getEntity()) != null) { - splitter = new WeakReference<>(event.getEntity()); + splitter = new WeakReference<>((Slime) event.getEntity()); } } } diff --git a/gradle.properties b/gradle.properties index 5ad69748ca..7d63a8df14 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ -org.gradle.configuration-cache=true +# https://github.com/diffplug/spotless/issues/2850 +org.gradle.configuration-cache=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d..b1b8ef56b4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b54..a9db11550c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index adff685a03..249efbb032 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/platform/platform-modern/build.gradle.kts b/platform/platform-modern/build.gradle.kts index e8077e601e..bf3ccff767 100644 --- a/platform/platform-modern/build.gradle.kts +++ b/platform/platform-modern/build.gradle.kts @@ -1,11 +1,11 @@ plugins { id("buildlogic.java-conventions") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.21" } dependencies { implementation(project(":core")) implementation(project(":util")) - paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") + paperweight.paperDevBundle("26.2.build.+") } paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernEffects.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernEffects.java index 265ce5d0f8..968cc32592 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernEffects.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernEffects.java @@ -16,7 +16,7 @@ import tc.oc.pgm.util.material.BlockMaterialData; import tc.oc.pgm.util.platform.Supports; -@Supports(value = PAPER, minVersion = "1.20.6") +@Supports(value = PAPER, minVersion = "26.2") public class ModernEffects implements Effects { @Override public void coloredDust(Player player, Location location, Color color) { @@ -77,7 +77,8 @@ public void explosion(Player player, Location location) { public void blockBreak(Location location, BlockMaterialData material) { location .getWorld() - .playEffect(location, Effect.STEP_SOUND, ((ModernBlockMaterialData) material).getBlock()); + .playEffect( + location, Effect.DESTROY_BLOCK, ((ModernBlockMaterialData) material).getBlock()); } @Override diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernMiscUtil.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernMiscUtil.java index 946a0ff35b..c2a3196fe4 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernMiscUtil.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernMiscUtil.java @@ -2,18 +2,24 @@ import static tc.oc.pgm.util.platform.Supports.Variant.PAPER; +import java.io.File; import java.nio.file.Path; import java.util.List; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; +import org.bukkit.Bukkit; import org.bukkit.ExplosionResult; import org.bukkit.Location; +import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Sound; import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.enchantments.Enchantment; @@ -34,8 +40,10 @@ import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.scoreboard.Team; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.platform.modern.material.ModernBlockMaterialData; import tc.oc.pgm.util.DataVersions; +import tc.oc.pgm.util.FileUtils; import tc.oc.pgm.util.bukkit.MiscUtils; import tc.oc.pgm.util.material.BlockMaterialData; import tc.oc.pgm.util.platform.Supports; @@ -133,4 +141,26 @@ public Entity getFakePickupEntity(PlayerPickupItemEvent ev) { if (ev instanceof PlayerPickupArrowEvent arrowEvent) return arrowEvent.getArrow(); return ev.getItem(); } + + @Override + public File getWorldFolder(String worldName) { + var console = ((CraftServer) Bukkit.getServer()).getServer(); + var dimensionKey = + CraftNamespacedKey.toResourceKey(Registries.DIMENSION, new NamespacedKey("pgm", worldName)); + return console.storageSource.getDimensionPath(dimensionKey).toFile(); + } + + @Override + public void cleanupWorldFiles() { + // Probe a non-existent dimension to get the dimension folder + final File dimsFolder = new File(getWorldFolder("probe").getParent()); + final File[] dimDirs = dimsFolder.listFiles(); + if (dimDirs != null) { + for (File dir : dimDirs) { + if (dir.isDirectory() && Match.isMatchWorld(dir.getName())) { + FileUtils.delete(dir); + } + } + } + } } diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java index 25093e747d..695d855fd9 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java @@ -1,15 +1,14 @@ package tc.oc.pgm.platform.modern.impl; +import static tc.oc.pgm.util.bukkit.MiscUtils.MISC_UTILS; import static tc.oc.pgm.util.nms.Packets.ENTITIES; import static tc.oc.pgm.util.platform.Supports.Variant.PAPER; import com.destroystokyo.paper.profile.ProfileProperty; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Dynamic; -import com.mojang.serialization.Lifecycle; import io.papermc.paper.world.PaperWorldLoader; -import java.io.File; +import io.papermc.paper.world.migration.WorldFolderMigration; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -17,38 +16,32 @@ import java.util.UUID; import java.util.logging.Level; import net.kyori.adventure.text.Component; -import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtException; -import net.minecraft.nbt.ReportedNbtException; -import net.minecraft.resources.Identifier; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.TickTask; import net.minecraft.server.WorldLoader; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Util; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.worldupdate.UpgradeProgress; import net.minecraft.world.entity.Mob; -import net.minecraft.world.level.LevelSettings; -import net.minecraft.world.level.WorldDataConfiguration; import net.minecraft.world.level.biome.BiomeManager; -import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.LevelStem; -import net.minecraft.world.level.levelgen.WorldDimensions; import net.minecraft.world.level.levelgen.WorldGenSettings; -import net.minecraft.world.level.storage.LevelDataAndDimensions; +import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.LevelSummary; import net.minecraft.world.level.storage.PrimaryLevelData; -import net.minecraft.world.level.validation.ContentValidationException; +import net.minecraft.world.level.storage.SavedDataStorage; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.Nameable; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.block.Block; @@ -58,6 +51,7 @@ import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.generator.CraftWorldInfo; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.entity.Fireball; import org.bukkit.entity.Firework; @@ -75,6 +69,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import tc.oc.pgm.api.PGM; import tc.oc.pgm.platform.modern.PgmBootstrap; import tc.oc.pgm.platform.modern.material.ModernBlockMaterialData; @@ -86,7 +81,7 @@ import tc.oc.pgm.util.platform.Supports; import tc.oc.pgm.util.skin.Skin; -@Supports(value = PAPER, minVersion = "1.21.11") +@Supports(value = PAPER, minVersion = "26.1.2") public class ModernNMSHacks implements NMSHacks { @Override public void skipFireworksLaunch(Firework firework) { @@ -147,7 +142,6 @@ public List getBlocks(Chunk bukkitChunk, Material material) { var nmsBlock = CraftMagicNumbers.getBlock(material); var chunk = craftChunk.getHandle(ChunkStatus.FULL); - int baseY = chunk.getMinY(); for (int i = 0; i < chunk.getSections().length; i++) { var section = chunk.getSections()[i]; if (section == null || section.hasOnlyAir()) continue; @@ -155,7 +149,7 @@ public List getBlocks(Chunk bukkitChunk, Material material) { var states = section.getStates(); if (!states.maybeHas(bs -> bs.getBukkitMaterial() == material)) continue; - final int chunkY = baseY + (i * LevelChunkSection.SECTION_HEIGHT); + final int chunkY = chunk.getSectionYFromSectionIndex(i) << 4; // Iteration order is relevant, as indexes are packed as x | z << 4 | y << 8 for (int y = 0; y < 16; y++) { @@ -192,30 +186,22 @@ public World createWorld(String worldName, World.Environment env, boolean terrai } /** {@link org.bukkit.craftbukkit.CraftServer#createWorld} adapted to support custom dimensions */ - public World createWorld(WorldCreator creator) { + public World createWorld(@NonNull WorldCreator creator) { var server = (CraftServer) Bukkit.getServer(); var console = server.getServer(); // NMS server - Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); - String name = creator.name(); ChunkGenerator chunkGenerator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); - File folder = new File(server.getWorldContainer(), name); World world = server.getWorld(name); - - World worldByKey = server.getWorld(creator.key()); + NamespacedKey worldKey = new NamespacedKey("pgm", name); + World worldByKey = server.getWorld(worldKey); if (world != null || worldByKey != null) { if (world == worldByKey) { return world; } - throw new IllegalArgumentException("Cannot create a world with key " + creator.key() - + " and name " + name + " one (or both) already match a world that exists"); - } - - if (folder.exists()) { - Preconditions.checkArgument( - folder.isDirectory(), "File (%s) exists and isn't a folder", name); + throw new IllegalArgumentException("Cannot create a world with key " + worldKey + " and name " + + name + " one (or both) already match a world that exists"); } if (chunkGenerator == null) { @@ -226,7 +212,7 @@ public World createWorld(WorldCreator creator) { biomeProvider = server.getBiomeProvider(name); } - ResourceKey<@NonNull LevelStem> actualDimension = + ResourceKey actualDimension = switch (creator.environment()) { case NORMAL -> LevelStem.OVERWORLD; case NETHER -> LevelStem.NETHER; @@ -235,87 +221,106 @@ public World createWorld(WorldCreator creator) { throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")"); }; - LevelStorageSource.LevelStorageAccess levelStorageAccess; + LevelStem configuredStem = + console.registryAccess().lookupOrThrow(Registries.LEVEL_STEM).getValue(actualDimension); + if (configuredStem == null) { + throw new IllegalStateException("Missing configured level stem " + actualDimension); + } + + ResourceKey dimensionKey = + CraftNamespacedKey.toResourceKey(Registries.DIMENSION, worldKey); + + // PGM: We need to read data version from level.dat pre-migration + int dataVersion = MISC_UTILS.getWorldDataVersion( + server.getWorldContainer().toPath().resolve(name).resolve("level.dat")); + WorldGenSettings legacyWorldGenSettings = readLegacyWorldGenSettings(console, name); + try { - levelStorageAccess = LevelStorageSource.createDefault( - server.getWorldContainer().toPath()) - .validateAndCreateAccess(name, actualDimension); - } catch (IOException | ContentValidationException ex) { - throw new RuntimeException(ex); + WorldFolderMigration.migrateApiWorld( + console.storageSource, console.registryAccess(), name, actualDimension, dimensionKey); + } catch (IOException ex) { + throw new RuntimeException("Failed to migrate legacy world " + name, ex); } - PrimaryLevelData primaryLevelData; - WorldLoader.DataLoadContext context = console.worldLoaderContext; - RegistryAccess.Frozen registryAccess = context.datapackDimensions(); - Registry<@NonNull LevelStem> contextLevelStemRegistry = - registryAccess.lookupOrThrow(Registries.LEVEL_STEM); - Dynamic dataTag = getLevelData(levelStorageAccess).dataTag(); + PaperWorldLoader.LoadedWorldData loadedWorldData = + PaperWorldLoader.loadWorldData(console, dimensionKey, name); - if (dataTag == null) return null; + PrimaryLevelData primaryLevelData = (PrimaryLevelData) console.getWorldData(); - var summary = levelStorageAccess.getSummary(dataTag); - LevelDataAndDimensions levelDataAndDimensions = getLevelDataAndDimensions( - dataTag, - context.dataConfiguration(), - contextLevelStemRegistry, - context.datapackWorldgen(), - creator.seed()); - primaryLevelData = (PrimaryLevelData) levelDataAndDimensions.worldData(); + // PGM: only load pre-existing worlds; return null if no WorldGenSettings found + WorldGenSettings worldGenSettings = LevelStorageSource.readExistingSavedData( + console.storageSource, dimensionKey, console.registryAccess(), WorldGenSettings.TYPE) + .result() + .orElse(legacyWorldGenSettings); + if (worldGenSettings == null) return null; - registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess(); - contextLevelStemRegistry = registryAccess.lookupOrThrow(Registries.LEVEL_STEM); + // PGM: override seed from WorldCreator + worldGenSettings = new WorldGenSettings( + worldGenSettings.options().withSeed(OptionalLong.of(creator.seed())), + worldGenSettings.dimensions()); - primaryLevelData.customDimensions = contextLevelStemRegistry; - primaryLevelData.checkName(name); - primaryLevelData.setModdedInfo( - console.getServerModName(), console.getModdedStatus().shouldReportAsModified()); + WorldLoader.DataLoadContext context = console.worldLoaderContext; + RegistryAccess.Frozen registryAccess = context.datapackDimensions(); + Registry contextLevelStemRegistry = + registryAccess.lookupOrThrow(Registries.LEVEL_STEM); - long i = BiomeManager.obfuscateSeed(primaryLevelData.worldGenOptions().seed()); - LevelStem customStem = contextLevelStemRegistry.getValue(actualDimension); + long biomeZoomSeed = BiomeManager.obfuscateSeed(worldGenSettings.options().seed()); - if (customStem == null) return null; + LevelStem customStem = worldGenSettings.dimensions().get(actualDimension).orElse(null); + if (customStem == null) customStem = contextLevelStemRegistry.getValue(actualDimension); + if (customStem == null) { + throw new IllegalStateException( + "Missing level stem for world " + name + " using key " + actualDimension); + } + + // PGM: replace dimension type for legacy worlds + if (dataVersion < DataVersions.V1_18_EXP_1 && actualDimension == LevelStem.OVERWORLD) { + var dimHolder = console + .registryAccess() + .lookupOrThrow(Registries.DIMENSION_TYPE) + .getOrThrow(PgmBootstrap.LEGACY_OVERWORLD); + customStem = new LevelStem(dimHolder, customStem.generator()); + } WorldInfo worldInfo = new CraftWorldInfo( - primaryLevelData, - levelStorageAccess, + loadedWorldData.bukkitName(), + CraftNamespacedKey.fromMinecraft(dimensionKey.identifier()), + worldGenSettings.options().seed(), + primaryLevelData.enabledFeatures(), creator.environment(), customStem.type().value(), customStem.generator(), - console.registryAccess()); + console.registryAccess(), + loadedWorldData.uuid()); if (biomeProvider == null && chunkGenerator != null) { biomeProvider = chunkGenerator.getDefaultBiomeProvider(worldInfo); } - // If the world is < 1.18-exp.1, replace dimension type - var dataVersion = summary.levelVersion().minecraftVersion().version(); - boolean isOld = dataVersion < DataVersions.V1_18_EXP_1; - if (isOld && actualDimension == LevelStem.OVERWORLD) { - var dimReg = console.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE); - var dimHolder = dimReg.getOrThrow(PgmBootstrap.LEGACY_OVERWORLD); - - customStem = new LevelStem(dimHolder, customStem.generator()); - } - - ResourceKey dimensionKey; - dimensionKey = ResourceKey.create( - Registries.DIMENSION, - Identifier.fromNamespaceAndPath(creator.key().namespace(), creator.key().value())); + SavedDataStorage savedDataStorage = new SavedDataStorage( + console.storageSource.getDimensionPath(dimensionKey).resolve(LevelResource.DATA.id()), + console.getFixerUpper(), + console.registryAccess()); + savedDataStorage.set( + WorldGenSettings.TYPE, + new WorldGenSettings(worldGenSettings.options(), worldGenSettings.dimensions())); ServerLevel serverLevel = new PGMServerLevel( console, - console.executor, - levelStorageAccess, - primaryLevelData, + Util.backgroundExecutor(), + console.storageSource, + worldGenSettings, dimensionKey, customStem, false, // isDebug - i, - ImmutableList.of(), + biomeZoomSeed, + ImmutableList.of(), // no spawners true, // tickTime - console.overworld().getRandomSequences(), + actualDimension, creator.environment(), chunkGenerator, - biomeProvider); + biomeProvider, + savedDataStorage, + loadedWorldData); if (server.getWorld(name) == null) { return null; @@ -327,75 +332,33 @@ public World createWorld(WorldCreator creator) { .setMetadata("is-post-flattening", new FixedMetadataValue(PGM.get(), true)); console.addLevel(serverLevel); - console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions()); + console.initWorld(serverLevel, creator); serverLevel.setSpawnSettings(true); console.prepareLevel(serverLevel); server.getPluginManager().callEvent(new WorldLoadEvent(serverLevel.getWorld())); return serverLevel.getWorld(); } - /** - * {@link io.papermc.paper.world.PaperWorldLoader#getLevelData} adapted for - * {@link ModernNMSHacks#createWorld} - */ - private static PaperWorldLoader.LevelDataResult getLevelData( - final LevelStorageSource.LevelStorageAccess levelStorageAccess) { - // Abort if level.dat is empty - if (!levelStorageAccess.hasWorldData()) { - throw new UnsupportedOperationException("Cannot use PGM createWorld with an empty level.dat"); - } - - Dynamic dataTag; - LevelSummary summary; - try { - dataTag = levelStorageAccess.getDataTag(); - summary = levelStorageAccess.getSummary(dataTag); - } catch (NbtException | ReportedNbtException | IOException e) { - LevelStorageSource.LevelDirectory levelDirectory = levelStorageAccess.getLevelDirectory(); - MinecraftServer.LOGGER.warn( - "Failed to load world data from {}", levelDirectory.dataFile(), e); - return new PaperWorldLoader.LevelDataResult(null, true); - } - - if (summary.requiresManualConversion()) { - MinecraftServer.LOGGER.info( - "This world must be opened in an older version (like 1.6.4) to be safely converted"); - return new PaperWorldLoader.LevelDataResult(null, true); - } - - if (!summary.isCompatible()) { - MinecraftServer.LOGGER.info("This world was created by an incompatible version."); - return new PaperWorldLoader.LevelDataResult(null, true); + private static @Nullable WorldGenSettings readLegacyWorldGenSettings( + DedicatedServer console, String name) { + try (LevelStorageSource.LevelStorageAccess sourceAccess = + console.storageSource.parent().createAccess(name)) { + if (!sourceAccess.hasWorldData()) return null; + + Dynamic levelData = sourceAccess.getUnfixedDataTagWithFallback(); + Dynamic fixedLevelData = + DataFixers.getFileFixer().fix(sourceAccess, levelData, new UpgradeProgress()); + Dynamic registryLevelData = + RegistryOps.injectRegistryContext(fixedLevelData, console.registryAccess()); + + return registryLevelData + .get("WorldGenSettings") + .read(WorldGenSettings.CODEC) + .result() + .orElse(null); + } catch (IOException | RuntimeException ignored) { + return null; } - - return new PaperWorldLoader.LevelDataResult(dataTag, false); - } - - /** - * Modified version of - * {@link net.minecraft.world.level.storage.LevelStorageSource#getLevelDataAndDimensions} for - * passing a custom or random seed. - */ - private static LevelDataAndDimensions getLevelDataAndDimensions( - Dynamic levelData, - WorldDataConfiguration dataConfiguration, - Registry<@NonNull LevelStem> levelStemRegistry, - HolderLookup.Provider registries, - long seed) { - Dynamic worldDataTag = RegistryOps.injectRegistryContext(levelData, registries); - Dynamic worldGenSettingsTag = worldDataTag.get("WorldGenSettings").orElseEmptyMap(); - WorldGenSettings worldGenSettings = - WorldGenSettings.CODEC.parse(worldGenSettingsTag).getOrThrow(); - LevelSettings levelSettings = LevelSettings.parse(worldDataTag, dataConfiguration); - WorldDimensions.Complete complete = worldGenSettings.dimensions().bake(levelStemRegistry); - Lifecycle lifecycle = complete.lifecycle().add(registries.allRegistriesLifecycle()); - PrimaryLevelData primaryLevelData = PrimaryLevelData.parse( - worldDataTag, - levelSettings, - complete.specialWorldProperty(), - worldGenSettings.options().withSeed(OptionalLong.of(seed)), - lifecycle); - return new LevelDataAndDimensions(primaryLevelData, complete); } @Override @@ -445,7 +408,7 @@ public int getMaxWorldSize(World world) { } @Override - public int allocateEntityId() { - return Bukkit.getUnsafe().nextEntityId(); + public int allocateEntityId(World world) { + return Bukkit.getUnsafe().nextEntityId(world); } } diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernEncodeUtil.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernEncodeUtil.java index ba8b3d4011..0a19381078 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernEncodeUtil.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernEncodeUtil.java @@ -15,6 +15,6 @@ static ModernBlockData decode(int encoded) { if (encoded == ENCODED_NULL_MATERIAL) return null; var vanillaBlockstate = Block.BLOCK_STATE_REGISTRY.byId(encoded); if (vanillaBlockstate == null) return null; - return new ModernBlockData(vanillaBlockstate.createCraftBlockData()); + return new ModernBlockData(vanillaBlockstate.asBlockData()); } } diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernMaterialUtils.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernMaterialUtils.java index e7d2709a42..c616b8160b 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernMaterialUtils.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/material/ModernMaterialUtils.java @@ -35,7 +35,7 @@ import tc.oc.pgm.util.xml.InvalidXMLException; import tc.oc.pgm.util.xml.Node; -@Supports(value = PAPER, minVersion = "1.20.6") +@Supports(value = PAPER, minVersion = "26.1.2") @SuppressWarnings("deprecation") public class ModernMaterialUtils implements MaterialUtils { @@ -136,7 +136,7 @@ public Set getPossibleBlocks(Material material) { var states = block.getStateDefinition().getPossibleStates(); Set materials = new HashSet<>(states.size()); for (var state : states) { - materials.add(new ModernBlockData(state.createCraftBlockData())); + materials.add(new ModernBlockData(state.asBlockData())); } return materials; } @@ -192,7 +192,7 @@ public MaterialMatcher.Builder visit(Material material, short data) { // log:0 -> oak_log[axis=y], // log:4 -> oak_log[axis=x], // log:8 -> oak_log[axis=z] - case BLOCK_STATE -> add(new BlockStateMaterialMatcher(block.createCraftBlockData())); + case BLOCK_STATE -> add(new BlockStateMaterialMatcher(block.asBlockData())); // Legacy translates into LESS states than are available in modern, eg: // step:0 -> smooth_stone_slab[type=bottom,waterlogged=false], // step:8 -> smooth_stone_slab[type=top,waterlogged=false] diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernEntityPackets.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernEntityPackets.java index 93a36139c6..0a1c02c1e5 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernEntityPackets.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernEntityPackets.java @@ -18,7 +18,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.PositionMoveRotation; import net.minecraft.world.entity.decoration.ArmorStand; @@ -53,7 +53,7 @@ public Packet spawnArmorStand(Location loc, int entityId, Vector velocity) { loc.getZ(), loc.getPitch(), loc.getYaw(), - EntityType.ARMOR_STAND, + EntityTypes.ARMOR_STAND, 0, CraftVector.toVec3(velocity), 0), @@ -75,7 +75,7 @@ public Packet spawnWitherSkull(Location loc, int entityId, Vector velocity) { loc.getZ(), loc.getPitch(), loc.getYaw(), - EntityType.WITHER_SKULL, + EntityTypes.WITHER_SKULL, 0, CraftVector.toVec3(velocity), 0)); diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernTabPackets.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernTabPackets.java index f6df337013..80fa0e1e62 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernTabPackets.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/packets/ModernTabPackets.java @@ -23,7 +23,7 @@ import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry; import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; -import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.level.GameType; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.PlayerTeam; @@ -65,7 +65,7 @@ public Packet spawnPlayerPacket(int entityId, UUID uuid, Location loc, Player pl loc.getZ(), loc.getPitch(), loc.getYaw(), - EntityType.PLAYER, + EntityTypes.PLAYER, 0, new Vec3(0, 0, 0), 0), diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/util/PGMServerLevel.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/util/PGMServerLevel.java index ea544d683a..a832847d2e 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/util/PGMServerLevel.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/util/PGMServerLevel.java @@ -1,22 +1,22 @@ package tc.oc.pgm.platform.modern.util; +import io.papermc.paper.world.PaperWorldLoader; import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.RandomSequences; import net.minecraft.world.level.CustomSpawner; import net.minecraft.world.level.Level; import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapIndex; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; -import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.storage.SavedDataStorage; import org.bukkit.World; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; @@ -29,32 +29,36 @@ public PGMServerLevel( MinecraftServer server, Executor dispatcher, LevelStorageSource.LevelStorageAccess levelStorageAccess, - PrimaryLevelData serverLevelData, + WorldGenSettings worldGenSettings, ResourceKey dimension, LevelStem levelStem, boolean isDebug, long biomeZoomSeed, List customSpawners, boolean tickTime, - @Nullable RandomSequences randomSequences, + ResourceKey typeKey, World.Environment env, ChunkGenerator gen, - BiomeProvider biomeProvider) { + BiomeProvider biomeProvider, + SavedDataStorage savedDataStorage, + PaperWorldLoader.LoadedWorldData loadedWorldData) { super( server, dispatcher, levelStorageAccess, - serverLevelData, + worldGenSettings, dimension, levelStem, isDebug, biomeZoomSeed, customSpawners, tickTime, - randomSequences, + typeKey, env, gen, - biomeProvider); + biomeProvider, + savedDataStorage, + loadedWorldData); } // Redirect all map operations to world-level storage @@ -62,7 +66,7 @@ public PGMServerLevel( @Override public MapItemSavedData getMapData(@NonNull MapId mapId) { // Paper start - Call missing map initialize event and set id - final DimensionDataStorage storage = getDataStorage(); + final SavedDataStorage storage = getDataStorage(); final Optional cacheEntry = storage.cache.get(MapItemSavedData.type(mapId)); if (cacheEntry == null) { // Cache did not contain, try to load and may init diff --git a/platform/platform-modern/src/main/resources/pgm_compat_datapack/data/pgm/dimension_type/legacy_overworld.json b/platform/platform-modern/src/main/resources/pgm_compat_datapack/data/pgm/dimension_type/legacy_overworld.json index 7c1e82058c..1010bc7b1a 100644 --- a/platform/platform-modern/src/main/resources/pgm_compat_datapack/data/pgm/dimension_type/legacy_overworld.json +++ b/platform/platform-modern/src/main/resources/pgm_compat_datapack/data/pgm/dimension_type/legacy_overworld.json @@ -33,6 +33,7 @@ }, "minecraft:gameplay/nether_portal_spawns_piglin": true, "minecraft:gameplay/respawn_anchor_works": false, + "minecraft:visual/ambient_light_color": "#0a0a0a", "minecraft:visual/cloud_color": "#ccffffff", "minecraft:visual/cloud_height": 128.33, "minecraft:visual/fog_color": "#c0d8ff", @@ -48,5 +49,7 @@ "max_inclusive": 7 }, "monster_spawn_block_light_limit": 0, - "timelines": "#minecraft:in_overworld" + "timelines": "#minecraft:in_overworld", + "default_clock": "minecraft:overworld", + "has_ender_dragon_fight": false } diff --git a/platform/platform-modern/src/main/resources/pgm_compat_datapack/pack.mcmeta b/platform/platform-modern/src/main/resources/pgm_compat_datapack/pack.mcmeta index ffff058047..b2a5431302 100644 --- a/platform/platform-modern/src/main/resources/pgm_compat_datapack/pack.mcmeta +++ b/platform/platform-modern/src/main/resources/pgm_compat_datapack/pack.mcmeta @@ -1,7 +1,7 @@ { "pack": { "description": "PGM legacy world compatibility datapack", - "min_format": 94.1, - "max_format": 94.1 + "min_format": 107.1, + "max_format": 107.1 } } \ No newline at end of file diff --git a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpMiscUtil.java b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpMiscUtil.java index 01ca3b38b6..8588d87d05 100644 --- a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpMiscUtil.java +++ b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpMiscUtil.java @@ -4,12 +4,14 @@ import static tc.oc.pgm.util.platform.Supports.Priority.HIGH; import static tc.oc.pgm.util.platform.Supports.Variant.SPORTPAPER; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import net.kyori.adventure.key.Key; import net.minecraft.server.v1_8_R3.EntityPotion; import net.minecraft.server.v1_8_R3.NBTCompressedStreamTools; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.block.Block; @@ -30,8 +32,10 @@ import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.ItemStack; +import tc.oc.pgm.api.match.Match; import tc.oc.pgm.platform.sportpaper.material.LegacyMaterialData; import tc.oc.pgm.util.DataVersions; +import tc.oc.pgm.util.FileUtils; import tc.oc.pgm.util.bukkit.MiscUtils; import tc.oc.pgm.util.material.BlockMaterialData; import tc.oc.pgm.util.platform.Supports; @@ -123,4 +127,21 @@ public boolean isDestructiveExplosion(EntityExplodeEvent ev) { public Entity getFakePickupEntity(PlayerPickupItemEvent ev) { return ev.getItem(); } + + @Override + public File getWorldFolder(String worldName) { + return new File(Bukkit.getServer().getWorldContainer(), worldName); + } + + @Override + public void cleanupWorldFiles() { + final File[] worldDirs = Bukkit.getServer().getWorldContainer().listFiles(); + if (worldDirs != null) { + for (File dir : worldDirs) { + if (dir.isDirectory() && Match.isMatchWorld(dir.getName())) { + FileUtils.delete(dir); + } + } + } + } } diff --git a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java index a4fee8c33d..471c3f9364 100644 --- a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java +++ b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java @@ -226,7 +226,7 @@ public int getMaxWorldSize(World world) { } @Override - public int allocateEntityId() { + public int allocateEntityId(World world) { return Bukkit.allocateEntityId(); } } diff --git a/util/build.gradle.kts b/util/build.gradle.kts index 3260b1fdaa..1e8604e033 100644 --- a/util/build.gradle.kts +++ b/util/build.gradle.kts @@ -4,9 +4,9 @@ plugins { } dependencies { - compileOnly("dev.pgm.paper:paper-api:1.8_1.21.11-SNAPSHOT") - testImplementation("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") - testImplementation("org.junit.jupiter:junit-jupiter:6.0.2") + compileOnly("dev.pgm.paper:paper-api:1.8_26.2-SNAPSHOT") + testImplementation("io.papermc.paper:paper-api:26.2.build.+") + testImplementation("org.junit.jupiter:junit-jupiter:6.1.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/util/src/main/java/tc/oc/pgm/util/bukkit/MiscUtils.java b/util/src/main/java/tc/oc/pgm/util/bukkit/MiscUtils.java index 92522eef04..136f374309 100644 --- a/util/src/main/java/tc/oc/pgm/util/bukkit/MiscUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/bukkit/MiscUtils.java @@ -1,5 +1,6 @@ package tc.oc.pgm.util.bukkit; +import java.io.File; import java.nio.file.Path; import java.util.List; import net.kyori.adventure.key.Key; @@ -60,4 +61,8 @@ default void initScoreboardTeam(Team team, NamedTextColor color) {} boolean isDestructiveExplosion(EntityExplodeEvent ev); Entity getFakePickupEntity(PlayerPickupItemEvent ev); + + File getWorldFolder(String worldName); + + void cleanupWorldFiles(); } diff --git a/util/src/main/java/tc/oc/pgm/util/bukkit/ViaUtils.java b/util/src/main/java/tc/oc/pgm/util/bukkit/ViaUtils.java index ffa85435b9..5dc10b65f7 100644 --- a/util/src/main/java/tc/oc/pgm/util/bukkit/ViaUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/bukkit/ViaUtils.java @@ -3,8 +3,6 @@ import static tc.oc.pgm.util.platform.Supports.Variant.SPORTPAPER; import com.viaversion.viaversion.api.Via; -import java.lang.reflect.Field; -import java.util.List; import org.bukkit.entity.Player; import tc.oc.pgm.util.platform.Platform; @@ -12,7 +10,7 @@ public class ViaUtils { public static final int VERSION_1_7 = 5; public static final int VERSION_1_8 = 47; public static final int VERSION_1_13 = 393; - public static final int VERSION_1_21_10 = 773; + public static final int VERSION_26_2 = 776; private static final boolean ENABLED = isViaLoaded(); @@ -37,29 +35,11 @@ public static int getProtocolVersion(Player player) { if (enabled()) { return Via.getAPI().getPlayerVersion(player.getUniqueId()); } else { - return Platform.VARIANT == SPORTPAPER ? VERSION_1_8 : VERSION_1_21_10; + return Platform.VARIANT == SPORTPAPER ? VERSION_1_8 : VERSION_26_2; } } public static boolean isReady(Player player) { return !enabled() || Via.getAPI().isInjected(player.getUniqueId()); } - - /** - * Adventure has a ViaFacet$Chat class, which sends text using 1.16 format for support for hex - * codes. The issue is by doing that, it skips all translation layers from 1.8 to 1.16, including - * a needed rename for translated items to work. Removing this means hex colors would be - * restricted to the 16 colors even for 1.16 clients (pgm doesn't use them) but translations will - * be correct. - */ - public static void removeViaChatFacet() { - try { - Class bukkitAudience = Class.forName("net.kyori.adventure.platform.bukkit.BukkitAudience"); - Field f = bukkitAudience.getDeclaredField("CHAT"); - f.setAccessible(true); - List list = (List) f.get(null); - list.removeIf(el -> el.getClass().getName().endsWith("ViaFacet$Chat")); - } catch (ReflectiveOperationException ignored) { - } - } } diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java index 8b45ef008e..76998c184e 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java @@ -56,5 +56,5 @@ public interface NMSHacks { int getMaxWorldSize(World world); - int allocateEntityId(); + int allocateEntityId(World world); } diff --git a/util/src/main/java/tc/oc/pgm/util/nms/packets/EntityPackets.java b/util/src/main/java/tc/oc/pgm/util/nms/packets/EntityPackets.java index b6e7774065..2b93f8c26b 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/packets/EntityPackets.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/packets/EntityPackets.java @@ -6,8 +6,10 @@ import java.util.List; import java.util.UUID; +import java.util.function.BooleanSupplier; import org.bukkit.ChatColor; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -18,12 +20,12 @@ public interface EntityPackets { - default int allocateEntityId() { - return NMS_HACKS.allocateEntityId(); + default int allocateEntityId(World world) { + return NMS_HACKS.allocateEntityId(world); } - default FakeEntity fakeWitherSkull() { - return new FakeEntity.Impl(allocateEntityId()) { + default FakeEntity fakeWitherSkull(World world) { + return new FakeEntity.Impl(allocateEntityId(world)) { @Override public Packet spawn(Location location, Vector velocity) { return spawnWitherSkull(location, entityId(), velocity); @@ -31,8 +33,8 @@ public Packet spawn(Location location, Vector velocity) { }; } - default FakeEntity fakeArmorStand(@Nullable ItemStack helmet) { - return new FakeEntity.Impl(allocateEntityId()) { + default FakeEntity fakeArmorStand(@Nullable ItemStack helmet, World world) { + return new FakeEntity.Impl(allocateEntityId(world)) { @Override public Packet spawn(Location location, Vector velocity) { Packet spawn = spawnArmorStand(location, entityId(), velocity); @@ -41,6 +43,15 @@ public Packet spawn(Location location, Vector velocity) { }; } + default FakeEntity fakeFreezeEntity(Player player, BooleanSupplier legacy) { + return new FakeEntity.Impl(allocateEntityId(player.getWorld())) { + @Override + public Packet spawn(Location location, Vector velocity) { + return spawnFreezeEntity(player, entityId(), legacy.getAsBoolean()); + } + }; + } + default Packet spawnFreezeEntity(Player player, int entityId, boolean legacy) { if (legacy) { Location loc = player.getLocation().add(0, 0.286, 0); @@ -61,7 +72,7 @@ default FakeEntity fakePlayer(Player original, ChatColor color) { // Add color to void matching real name. Cut to avoid exceeding 16 chars String playerName = color + StringUtils.substring(original.getName(), 0, 14); String suffix = StringUtils.substring(original.getName(), 14, 16); - return new FakeEntity.Impl(allocateEntityId()) { + return new FakeEntity.Impl(allocateEntityId(original.getWorld())) { @Override public Packet spawn(Location location, Vector velocity) { var tabInfo = TAB_PACKETS.createPlayerInfoPacket(EnumPlayerInfoAction.ADD_PLAYER); diff --git a/util/src/main/java/tc/oc/pgm/util/platform/Platform.java b/util/src/main/java/tc/oc/pgm/util/platform/Platform.java index 84b2617d97..ceced9f44e 100644 --- a/util/src/main/java/tc/oc/pgm/util/platform/Platform.java +++ b/util/src/main/java/tc/oc/pgm/util/platform/Platform.java @@ -4,9 +4,10 @@ import static tc.oc.pgm.util.Assert.assertTrue; import java.util.Arrays; +import java.util.regex.Pattern; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import org.reflections.Reflections; import org.reflections.util.ConfigurationBuilder; import tc.oc.pgm.util.Version; @@ -21,20 +22,30 @@ public abstract class Platform { private static final Reflections REFLECTIONS = new Reflections( new ConfigurationBuilder().forPackage("tc.oc.pgm.platform").setScanners(TypesAnnotated)); + private static final Pattern VERSION_MATCHER = Pattern.compile("\\d+(?:\\.\\d+){1,2}"); public static final Version MINECRAFT_VERSION; public static final Variant VARIANT; + private static Version parseServerVersion(final @NonNull String versionName) { + var matcher = VERSION_MATCHER.matcher(versionName); + + if (!matcher.find()) + throw new IllegalArgumentException("Could not parse server version from: " + versionName); + + return TextParser.parseVersion(matcher.group()); + } + static { var sv = Bukkit.getServer(); - MINECRAFT_VERSION = TextParser.parseVersion(sv.getBukkitVersion().split("-")[0]); + MINECRAFT_VERSION = parseServerVersion(sv.getBukkitVersion()); VARIANT = Arrays.stream(Variant.values()) .filter(v -> v.matcher.test(sv)) .findFirst() .orElse(null); } - public static final @NotNull Manifest MANIFEST = get(Manifest.class); + public static final @NonNull Manifest MANIFEST = get(Manifest.class); /** * Do a minimum sanity-check of the platform's viability and early-load some codepaths @@ -49,7 +60,7 @@ public static void init() throws Throwable { assertTrue(ColorUtils.COLOR_UTILS.isColorAffected(item.getItemType())); } - public static @NotNull T get(Class clazz) { + public static @NonNull T get(Class clazz) { return (T) Platform.getBestSupported(clazz); } diff --git a/util/src/main/java/tc/oc/pgm/util/tablist/PlayerTabEntry.java b/util/src/main/java/tc/oc/pgm/util/tablist/PlayerTabEntry.java index 892c095d8b..9170fcb37f 100644 --- a/util/src/main/java/tc/oc/pgm/util/tablist/PlayerTabEntry.java +++ b/util/src/main/java/tc/oc/pgm/util/tablist/PlayerTabEntry.java @@ -51,7 +51,7 @@ private static UUID randomUUIDVersion2SameDefaultSkin(UUID original) { public PlayerTabEntry(Player player) { super(randomUUIDVersion2SameDefaultSkin(player.getUniqueId())); this.player = player; - this.spareEntityId = NMS_HACKS.allocateEntityId(); + this.spareEntityId = NMS_HACKS.allocateEntityId(player.getWorld()); } @Override