diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 658cffb..9a6e251 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,8 +3,8 @@ cloud-build-logic = "0.0.3" ktlint = "0.50.0" checkstyle = "10.12.5" kotlin = "1.9.20" -shadow = "8.1.1" -paperweight = "1.7.1" +shadow = "9.0.0-beta4" +paperweight = "1.7.7" run-paper = "2.3.0" pluginyml = "0.6.0" @@ -54,6 +54,6 @@ paperweight-userdev = { id = "io.papermc.paperweight.userdev", version.ref = "pa cloud-buildLogic-spotless = { id = "org.incendo.cloud-build-logic.spotless", version.ref = "cloud-build-logic" } cloud-buildLogic-rootProject-publishing = { id = "org.incendo.cloud-build-logic.publishing.root-project", version.ref = "cloud-build-logic" } cloud-buildLogic-rootProject-spotless = { id = "org.incendo.cloud-build-logic.spotless.root-project", version.ref = "cloud-build-logic" } -shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-paper" } pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" } diff --git a/hyperverse-core/build.gradle.kts b/hyperverse-core/build.gradle.kts index 10cbea7..271db6d 100644 --- a/hyperverse-core/build.gradle.kts +++ b/hyperverse-core/build.gradle.kts @@ -44,22 +44,25 @@ dependencies { implementation("net.kyori:adventure-text-minimessage:4.14.0") implementation(projects.hyperverseNmsUnsupported) - runtimeOnly(project(":hyperverse-nms-1-17")) { + runtimeOnly(projects.hyperverseNms117) { targetConfiguration = "reobf" } - runtimeOnly(project(":hyperverse-nms-1-18")) { + runtimeOnly(projects.hyperverseNms118) { targetConfiguration = "reobf" } - runtimeOnly(project(":hyperverse-nms-1-19")) { + runtimeOnly(projects.hyperverseNms119) { targetConfiguration = "reobf" } - runtimeOnly(project(":hyperverse-nms-1-20")) { + runtimeOnly(projects.hyperverseNms120) { targetConfiguration = "reobf" } - runtimeOnly(project(":hyperverse-nms-1-20-6")) { + runtimeOnly(projects.hyperverseNms1206) { targetConfiguration = "reobf" } - runtimeOnly(project(":hyperverse-nms-1-21")) { + runtimeOnly(projects.hyperverseNms121) { + targetConfiguration = "reobf" + } + runtimeOnly(projects.hyperverseNms1213) { targetConfiguration = "reobf" } } diff --git a/hyperverse-core/src/main/java/org/incendo/hyperverse/Hyperverse.java b/hyperverse-core/src/main/java/org/incendo/hyperverse/Hyperverse.java index 944714b..dea7604 100644 --- a/hyperverse-core/src/main/java/org/incendo/hyperverse/Hyperverse.java +++ b/hyperverse-core/src/main/java/org/incendo/hyperverse/Hyperverse.java @@ -98,7 +98,8 @@ public final class Hyperverse extends JavaPlugin implements HyperverseAPI, Liste Version.parseMinecraft("1.19.4"), Version.parseMinecraft("1.20.4"), Version.parseMinecraft("1.20.6"), - Version.parseMinecraft("1.21") + Version.parseMinecraft("1.21.1"), + Version.parseMinecraft("1.21.3") ); private WorldManager worldManager; diff --git a/hyperverse-nms-1-21-3/build.gradle.kts b/hyperverse-nms-1-21-3/build.gradle.kts new file mode 100644 index 0000000..19a0053 --- /dev/null +++ b/hyperverse-nms-1-21-3/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("hyperverse.base-conventions") + alias(libs.plugins.paperweight.userdev) +} + +indra { + javaVersions { + minimumToolchain(21) + target(21) + } +} + +dependencies { + paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") + compileOnly(projects.hyperverseNmsCommon) +} + +tasks { + assemble { + dependsOn(reobfJar) + } +} diff --git a/hyperverse-nms-1-21-3/src/main/java/org/incendo/hyperverse/platform/v1_21_3/NMSImpl.java b/hyperverse-nms-1-21-3/src/main/java/org/incendo/hyperverse/platform/v1_21_3/NMSImpl.java new file mode 100644 index 0000000..792be08 --- /dev/null +++ b/hyperverse-nms-1-21-3/src/main/java/org/incendo/hyperverse/platform/v1_21_3/NMSImpl.java @@ -0,0 +1,255 @@ +// +// Hyperverse - A minecraft world management plugin +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +package org.incendo.hyperverse.platform.v1_21_3; + +import co.aikar.taskchain.TaskChainFactory; +import com.google.inject.Inject; +import io.papermc.lib.PaperLib; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; + +import net.minecraft.BlockUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.entity.EntityLookup; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import net.minecraft.world.level.portal.PortalForcer; +import net.minecraft.world.phys.Vec3; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.filter.RegexFilter; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.incendo.hyperverse.util.HyperConfigShouldGroupProfiles; +import org.incendo.hyperverse.util.NMS; + +@SuppressWarnings("unused") +public class NMSImpl implements NMS { + + private final TaskChainFactory taskFactory; + private Field entitySectionManager; + private Field entityLookup; + private org.apache.logging.log4j.core.Logger worldServerLogger; + + @Inject public NMSImpl(final TaskChainFactory taskFactory, final @HyperConfigShouldGroupProfiles boolean hyperConfiguration) { + this.taskFactory = taskFactory; + if (hyperConfiguration) { + try { + final Field field = ServerLevel.class.getDeclaredField("LOGGER"); + field.setAccessible(true); + this.worldServerLogger = (Logger) field.get(null); + } catch (final Exception e) { + e.printStackTrace(); + } + try { + final RegexFilter regexFilter = RegexFilter + .createFilter("[\\S\\s]*Force-added player with duplicate UUID[\\S\\s]*", null, false, + Filter.Result.DENY, Filter.Result.ACCEPT); + this.worldServerLogger.addFilter(regexFilter); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + @Override @Nullable public Location getOrCreateNetherPortal(@NotNull final org.bukkit.entity.Entity entity, + @NotNull final Location origin) { + final ServerLevel worldServer = Objects.requireNonNull(((CraftWorld) origin.getWorld()).getHandle()); + final PortalForcer portalTravelAgent = Objects.requireNonNull(worldServer.getPortalForcer()); + final Entity nmsEntity = Objects.requireNonNull(((CraftEntity) entity).getHandle()); + final BlockPos blockPosition = new BlockPos(origin.getBlockX(), origin.getBlockY(), origin.getBlockZ()); + final WorldBorder worldBorder = worldServer.getWorldBorder(); + Optional existingPortalPosition = Objects.requireNonNull(portalTravelAgent, "travel agent") + .findClosestPortalPosition(Objects.requireNonNull(blockPosition, "position"), worldBorder,128); + if (existingPortalPosition.isPresent()) { + BlockPos bottomLeft = existingPortalPosition.get(); + return new Location(origin.getWorld(), bottomLeft.getX(), bottomLeft.getY(), bottomLeft.getZ()); + } + Optional createdPortal = portalTravelAgent.createPortal(blockPosition, + nmsEntity.getDirection().getAxis(), nmsEntity, + 16); + if (createdPortal.isEmpty()) { + return null; + } + final BlockUtil.FoundRectangle rectangle = createdPortal.get(); + return new Location(origin.getWorld(), rectangle.minCorner.getX() + 1D, rectangle.minCorner.getY() - 1D, + rectangle.minCorner.getZ() + 1D); + } + + @Override @Nullable public Location getDimensionSpawn(@NotNull final Location origin) { + if (Objects.requireNonNull(origin.getWorld()).getEnvironment() + == World.Environment.THE_END) { + return new Location(origin.getWorld(), 100, 50, 0); + } + return origin.getWorld().getSpawnLocation(); + } + + @Override public void writePlayerData(@NotNull final Player player, @NotNull final Path file) { + final CompoundTag playerTag = new CompoundTag(); + final net.minecraft.world.entity.player.Player entityPlayer = ((CraftPlayer) player).getHandle(); + entityPlayer.save(playerTag); + + if (!playerTag.contains("hyperverse")) { + playerTag.put("hyperverse", new CompoundTag()); + } + final CompoundTag hyperverse = playerTag.getCompound("hyperverse"); + hyperverse.putLong("writeTime", System.currentTimeMillis()); + hyperverse.putString("version", Bukkit.getPluginManager().getPlugin("Hyperverse").getDescription().getVersion()); + + taskFactory.newChain().async(() -> { + try (final OutputStream outputStream = Files.newOutputStream(file)) { + NbtIo.writeCompressed(playerTag, outputStream); + } catch (final Exception e) { + e.printStackTrace(); + } + }).execute(); + } + + @Override public void readPlayerData(@NotNull final Player player, @NotNull final Path file, @NotNull final Runnable whenDone) { + final Location originLocation = player.getLocation().clone(); + taskFactory.newChain().asyncFirst(() -> { + try (final InputStream inputStream = Files.newInputStream(file)) { + return Optional.of(NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap())); + } catch (final Exception e) { + e.printStackTrace(); + } + return Optional.empty(); + }).syncLast((optionalCompound) -> { + if (!optionalCompound.isPresent()) { + return; + } + final CompoundTag compound = (CompoundTag) optionalCompound.get(); + PaperLib.getChunkAtAsync(originLocation).thenAccept(chunk -> { + // Health and hunger don't update properly, so we + // give them a little help + final float health = compound.getFloat("Health"); + final int foodLevel = compound.getInt("foodLevel"); + + // Restore bed spawn + final String spawnWorld = compound.getString("SpawnWorld"); + final int spawnX = compound.getInt("SpawnX"); + final int spawnY = compound.getInt("SpawnY"); + final int spawnZ = compound.getInt("SpawnZ"); + final Location spawnLocation = new Location(Bukkit.getWorld(spawnWorld), spawnX, + spawnY, spawnZ); + + final ServerPlayer entityPlayer = ((CraftPlayer) player).getHandle(); + + // We re-write the extra Bukkit data as to not + // mess up the profile + ((CraftPlayer) player).setExtraData(compound); + // Set the position to the player's current position + Vec3 pos = entityPlayer.position(); + compound.put("Pos", doubleList(pos.x, pos.y, pos.z)); + // Set the world to the player's current world + compound.putString("world", player.getWorld().getName()); + // Store persistent values + ((CraftPlayer) player).storeBukkitValues(compound); + + // We start by doing a total reset + entityPlayer.reset(); + entityPlayer.load(compound); + + entityPlayer.effectsDirty = true; + entityPlayer.onUpdateAbilities(); + player.teleport(originLocation); + + final ServerLevel worldServer = ((CraftWorld) originLocation.getWorld()).getHandle(); + final DimensionType dimensionManager = worldServer.dimensionType(); + + // Prevent annoying message + // Spigot-obf = decouple() + entityPlayer.unRide(); + worldServer.removePlayerImmediately(entityPlayer, Entity.RemovalReason.CHANGED_DIMENSION); + // worldServer.removePlayer above should remove the player from the + // map, but that doesn't always happen. This is a last effort + // attempt to prevent the annoying "Force re-added" message + // from appearing + try { + if (this.entitySectionManager == null) { + this.entitySectionManager = worldServer.getClass().getDeclaredField("entityManager"); + this.entitySectionManager.setAccessible(true); + } + final PersistentEntitySectionManager esm = (PersistentEntitySectionManager) this.entitySectionManager.get(worldServer); + if (this.entityLookup == null) { + this.entityLookup = esm.getClass().getDeclaredField("visibleEntityStorage"); + } + final EntityLookup lookup = (EntityLookup) this.entityLookup.get(esm); + lookup.remove(entityPlayer); + } catch (final NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + // pre 1.18 code = PlayerList#moveToWorld + entityPlayer.server.getPlayerList().remove(entityPlayer); + worldServer.getServer().getPlayerList().respawn(entityPlayer, true, + Entity.RemovalReason.CHANGED_DIMENSION, PlayerRespawnEvent.RespawnReason.PLUGIN, originLocation); + + // Apply health and foodLevel + player.setHealth(health); + player.setFoodLevel(foodLevel); + player.setPortalCooldown(40); + player.setBedSpawnLocation(spawnLocation, true); + }); + }).execute(whenDone); + } + + @Override @Nullable public Location findBedRespawn(@NotNull final Location spawnLocation) { + final CraftWorld craftWorld = (CraftWorld) spawnLocation.getWorld(); + if (craftWorld == null) { + return null; + } + + return ServerPlayer.findRespawnAndUseSpawnBlock(craftWorld.getHandle(), new BlockPos(spawnLocation.getBlockX(), + spawnLocation.getBlockY(), spawnLocation.getBlockZ()), 0, true, false) + .map(ServerPlayer.RespawnPosAngle::position) + .map(pos -> new Location(spawnLocation.getWorld(), pos.x(), pos.y(), pos.z())) + .orElse(null); + } + + private static ListTag doubleList(final double... values) { + final ListTag tagList = new ListTag(); + for (final double d : values) { + tagList.add(DoubleTag.valueOf(d)); + } + return tagList; + } + +} diff --git a/hyperverse-nms-1-21/build.gradle.kts b/hyperverse-nms-1-21/build.gradle.kts index 07cc6e2..80cb781 100644 --- a/hyperverse-nms-1-21/build.gradle.kts +++ b/hyperverse-nms-1-21/build.gradle.kts @@ -11,7 +11,7 @@ indra { } dependencies { - paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT") compileOnly(projects.hyperverseNmsCommon) } diff --git a/settings.gradle.kts b/settings.gradle.kts index a90ecc1..e08e5ae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,3 +19,4 @@ include(":hyperverse-nms-1-19") include(":hyperverse-nms-1-20") include(":hyperverse-nms-1-20-6") include(":hyperverse-nms-1-21") +include(":hyperverse-nms-1-21-3")