From 35eca198536de98acad15c4aaf99e091d1b54b98 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 8 Jul 2022 16:01:42 -0700 Subject: [PATCH] Refactor paper command (#8112) * Refactor paper command * Improve paper dumpitem output * Register paper command permissions Would be nice to add descriptions for these too, but that's an enhancement for another time * Update MobcapsCommandTest fail message * Notify on bad radius for fix light * fixup rebase --- .../Add-debug-for-sync-chunk-loads.patch | 221 +++--- ...aper-mobcaps-and-paper-playermobcaps.patch | 194 +++-- .../Add-tick-times-API-and-mspt-command.patch | 20 +- patches/server/Chunk-debug-command.patch | 196 +++-- .../server/Do-not-copy-visible-chunks.patch | 12 +- patches/server/Don-t-tick-markers.patch | 24 +- patches/server/Fix-Light-Command.patch | 164 +++-- ...ault-permission-message-configurable.patch | 18 +- patches/server/Paper-command.patch | 670 ++++++++++++------ patches/server/Paper-dumpitem-command.patch | 128 ++-- patches/server/Rewrite-the-light-engine.patch | 122 ++-- 11 files changed, 1016 insertions(+), 753 deletions(-) diff --git a/patches/server/Add-debug-for-sync-chunk-loads.patch b/patches/server/Add-debug-for-sync-chunk-loads.patch index 96f7d28392..4c89009e21 100644 --- a/patches/server/Add-debug-for-sync-chunk-loads.patch +++ b/patches/server/Add-debug-for-sync-chunk-loads.patch @@ -12,123 +12,6 @@ chunks, however it must be enabled by setting the startup flag - To clear clear the currently stored sync load info, use /paper syncloadinfo clear -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ - package com.destroystokyo.paper; - -+import com.destroystokyo.paper.io.SyncLoadFinder; - import com.google.common.base.Functions; - import com.google.common.base.Joiner; - import com.google.common.collect.ImmutableSet; -@@ -0,0 +0,0 @@ import com.google.common.collect.Lists; - import com.google.common.collect.Maps; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceLocation; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; -@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; - import org.bukkit.entity.Player; - - import java.io.File; -+import java.io.FileOutputStream; -+import java.io.PrintStream; -+import java.io.StringWriter; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; - import java.util.ArrayDeque; -@@ -0,0 +0,0 @@ import java.util.stream.Collectors; - import static net.kyori.adventure.text.Component.text; - import static net.kyori.adventure.text.format.NamedTextColor.BLUE; - import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; - import static net.kyori.adventure.text.format.NamedTextColor.GREEN; - import static net.kyori.adventure.text.format.NamedTextColor.RED; - import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build(); - - public PaperCommand(String name) { - super(name); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - return getListMatchingLast(sender, args, worldNames); - } - break; -+ case "syncloadinfo": -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, "clear"); -+ } -+ break; - } - return Collections.emptyList(); - } -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - case "fixlight": - this.doFixLight(sender, args); - break; -+ case "syncloadinfo": -+ this.doSyncLoadInfo(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - return true; - } - -+ private void doSyncLoadInfo(CommandSender sender, String[] args) { -+ if (!SyncLoadFinder.ENABLED) { -+ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); -+ return; -+ } -+ -+ if (args.length > 1 && args[1].equals("clear")) { -+ SyncLoadFinder.clear(); -+ sender.sendMessage(text("Sync load data cleared.", GRAY)); -+ return; -+ } -+ -+ File file = new File(new File(new File("."), "debug"), -+ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); -+ file.getParentFile().mkdirs(); -+ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); -+ -+ -+ try { -+ final JsonObject data = SyncLoadFinder.serialize(); -+ -+ StringWriter stringWriter = new StringWriter(); -+ JsonWriter jsonWriter = new JsonWriter(stringWriter); -+ jsonWriter.setIndent(" "); -+ jsonWriter.setLenient(false); -+ Streams.write(data, jsonWriter); -+ -+ String fileData = stringWriter.toString(); -+ -+ try ( -+ PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8") -+ ) { -+ out.print(fileData); -+ } -+ sender.sendMessage(text("Successfully written sync load information!", GREEN)); -+ } catch (Throwable thr) { -+ sender.sendMessage(text("Failed to write sync load information!", RED)); -+ thr.printStackTrace(); -+ } -+ } -+ - private void doChunkInfo(CommandSender sender, String[] args) { - List worlds; - if (args.length < 2 || args[1].equals("*")) { diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -310,6 +193,110 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; ++import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; + import it.unimi.dsi.fastutil.Pair; + import java.util.ArrayList; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); ++ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.destroystokyo.paper.io.SyncLoadFinder; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.PrintStream; ++import java.io.StringWriter; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.List; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class SyncLoadInfoCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doSyncLoadInfo(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return CommandUtil.getListMatchingLast(sender, args, "clear"); ++ } ++ ++ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); ++ return; ++ } ++ ++ if (args.length > 0 && args[0].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(text("Sync load data cleared.", GRAY)); ++ return; ++ } ++ ++ File file = new File(new File(new File("."), "debug"), ++ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ file.getParentFile().mkdirs(); ++ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); ++ ++ ++ try { ++ final JsonObject data = SyncLoadFinder.serialize(); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try ( ++ PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8") ++ ) { ++ out.print(fileData); ++ } ++ sender.sendMessage(text("Successfully written sync load information!", GREEN)); ++ } catch (Throwable thr) { ++ sender.sendMessage(text("Failed to write sync load information!", RED)); ++ thr.printStackTrace(); ++ } ++ } ++} diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java diff --git a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch index a46419aafb..6926c53646 100644 --- a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch +++ b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -9,88 +9,66 @@ each player when per-player mob spawning is enabled. Also has a hover text on each mob category listing what entity types are in said category -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ package com.destroystokyo.paper; - import com.destroystokyo.paper.io.SyncLoadFinder; - import com.google.common.base.Functions; - import com.google.common.base.Joiner; +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; ++import io.papermc.paper.command.subcommands.MobcapsCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); ++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ +import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableSet; - import com.google.common.collect.Iterables; - import com.google.common.collect.Lists; -@@ -0,0 +0,0 @@ import net.minecraft.resources.ResourceLocation; - import com.google.gson.JsonObject; - import com.google.gson.internal.Streams; - import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.function.ToIntFunction; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; -@@ -0,0 +0,0 @@ import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.level.ThreadedLevelLightEngine; - import net.minecraft.world.entity.EntityType; ++import net.minecraft.core.Registry; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.MobCategory; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.server.MCUtil; +import net.minecraft.world.level.NaturalSpawner; - import org.apache.commons.lang3.tuple.MutablePair; - import org.apache.commons.lang3.tuple.Pair; - import org.bukkit.Bukkit; -@@ -0,0 +0,0 @@ import java.util.List; - import java.util.Locale; - import java.util.Map; - import java.util.Set; -+import java.util.function.ToIntFunction; - import java.util.stream.Collectors; - - import static net.kyori.adventure.text.Component.text; -@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem", "mobcaps", "playermobcaps").build(); - - public PaperCommand(String name) { - super(name); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - return getListMatchingLast(sender, args, "help", "chunks"); - } - break; -+ case "mobcaps": -+ return getListMatchingLast(sender, args, this.suggestMobcaps(sender, args)); -+ case "playermobcaps": -+ return getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); - case "chunkinfo": - List worldNames = new ArrayList<>(); - worldNames.add("*"); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - case "syncloadinfo": - this.doSyncLoadInfo(sender, args); - break; -+ case "mobcaps": -+ this.printMobcaps(sender, args); -+ break; -+ case "playermobcaps": -+ this.printPlayerMobcaps(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - } - } - -+ public static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class MobcapsCommand implements PaperSubcommand { ++ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() + .put(MobCategory.MONSTER, NamedTextColor.RED) + .put(MobCategory.CREATURE, NamedTextColor.GREEN) + .put(MobCategory.AMBIENT, NamedTextColor.GRAY) @@ -101,8 +79,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + .put(MobCategory.MISC, TextColor.color(0x636363)) + .build(); + -+ private List suggestMobcaps(CommandSender sender, String[] args) { -+ if (args.length == 2) { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "mobcaps" -> this.printMobcaps(sender, args); ++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (subCommand) { ++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); ++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); ++ default -> throw new IllegalArgumentException(); ++ }; ++ } ++ ++ private List suggestMobcaps(final String[] args) { ++ if (args.length == 1) { + final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); + worlds.add("*"); + return worlds; @@ -111,8 +107,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return Collections.emptyList(); + } + -+ private List suggestPlayerMobcaps(CommandSender sender, String[] args) { -+ if (args.length == 2) { ++ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { ++ if (args.length == 1) { + final List list = new ArrayList<>(); + for (final Player player : Bukkit.getOnlinePlayers()) { + if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { @@ -125,21 +121,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return Collections.emptyList(); + } + -+ private void printMobcaps(CommandSender sender, String[] args) { ++ private void printMobcaps(final CommandSender sender, final String[] args) { + final List worlds; -+ if (args.length == 1) { ++ if (args.length == 0) { + if (sender instanceof Player player) { + worlds = List.of(player.getWorld()); + } else { + sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); + return; + } -+ } else if (args.length == 2) { -+ final String input = args[1]; ++ } else if (args.length == 1) { ++ final String input = args[0]; + if (input.equals("*")) { + worlds = Bukkit.getWorlds(); + } else { -+ final World world = Bukkit.getWorld(input); ++ final @Nullable World world = Bukkit.getWorld(input); + if (world == null) { + sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); + return; @@ -154,7 +150,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + for (final World world : worlds) { + final ServerLevel level = ((CraftWorld) world).getHandle(); -+ final NaturalSpawner.SpawnState state = level.getChunkSource().getLastSpawnState(); ++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); + + final int chunks; + if (state == null) { @@ -168,7 +164,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + Component.text(" (" + chunks + " spawnable chunks)") + )); + -+ sender.sendMessage(this.buildMobcapsComponent( ++ sender.sendMessage(createMobcapsComponent( + category -> { + if (state == null) { + return 0; @@ -181,17 +177,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ private void printPlayerMobcaps(CommandSender sender, String[] args) { -+ final Player player; -+ if (args.length == 1) { ++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { ++ final @Nullable Player player; ++ if (args.length == 0) { + if (sender instanceof Player pl) { + player = pl; + } else { + sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); + return; + } -+ } else if (args.length == 2) { -+ final String input = args[1]; ++ } else if (args.length == 1) { ++ final String input = args[0]; + player = Bukkit.getPlayerExact(input); + if (player == null) { + sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); @@ -211,13 +207,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); -+ sender.sendMessage(this.buildMobcapsComponent( ++ sender.sendMessage(createMobcapsComponent( + category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), + category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) + )); + } + -+ private Component buildMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { ++ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { + return MOB_CATEGORY_COLORS.entrySet().stream() + .map(entry -> { + final MobCategory category = entry.getKey(); @@ -267,10 +263,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + .map(ComponentLike::asComponent) + .collect(Component.toComponent(Component.newline())); + } -+ - private void doChunkInfo(CommandSender sender, String[] args) { - List worlds; - if (args.length < 2 || args[1].equals("*")) { ++} diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -328,30 +321,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } return limit; } -diff --git a/src/test/java/io/papermc/paper/PaperCommandTest.java b/src/test/java/io/papermc/paper/PaperCommandTest.java +diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/test/java/io/papermc/paper/PaperCommandTest.java ++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java @@ -0,0 +0,0 @@ -+package io.papermc.paper; ++package io.papermc.paper.command.subcommands; + -+import com.destroystokyo.paper.PaperCommand; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.world.entity.MobCategory; +import org.junit.Assert; +import org.junit.Test; + -+public class PaperCommandTest { ++public class MobcapsCommandTest { + @Test + public void testMobCategoryColors() { + final Set missing = new HashSet<>(); + for (final MobCategory value : MobCategory.values()) { -+ if (!PaperCommand.MOB_CATEGORY_COLORS.containsKey(value)) { ++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { + missing.add(value.getName()); + } + } -+ Assert.assertTrue("PaperCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); ++ Assert.assertTrue("MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); + } +} diff --git a/patches/server/Add-tick-times-API-and-mspt-command.patch b/patches/server/Add-tick-times-API-and-mspt-command.patch index e7327183e5..32ff23e3fd 100644 --- a/patches/server/Add-tick-times-API-and-mspt-command.patch +++ b/patches/server/Add-tick-times-API-and-mspt-command.patch @@ -4,13 +4,13 @@ Date: Sun, 5 Apr 2020 22:23:14 -0500 Subject: [PATCH] Add tick times API and /mspt command -diff --git a/src/main/java/com/destroystokyo/paper/MSPTCommand.java b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/MSPTCommand.java ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java @@ -0,0 +0,0 @@ -+package com.destroystokyo.paper; ++package io.papermc.paper.command; + +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; @@ -23,6 +23,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.Arrays; +import java.util.Collections; +import java.util.List; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GOLD; @@ -31,11 +33,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; + -+public class MSPTCommand extends Command { ++@DefaultQualifier(NonNull.class) ++public final class MSPTCommand extends Command { + private static final DecimalFormat DF = new DecimalFormat("########0.0"); + private static final Component SLASH = text("/"); + -+ public MSPTCommand(String name) { ++ public MSPTCommand(final String name) { + super(name); + this.description = "View server tick times"; + this.usageMessage = "/mspt"; @@ -113,13 +116,6 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/mai index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -0,0 +0,0 @@ - package io.papermc.paper.command; - -+import com.destroystokyo.paper.MSPTCommand; - import com.destroystokyo.paper.PaperCommand; - import net.minecraft.server.MinecraftServer; - import org.bukkit.command.Command; @@ -0,0 +0,0 @@ public final class PaperCommands { private static final Map COMMANDS = new HashMap<>(); static { diff --git a/patches/server/Chunk-debug-command.patch b/patches/server/Chunk-debug-command.patch index cba1513041..10da488739 100644 --- a/patches/server/Chunk-debug-command.patch +++ b/patches/server/Chunk-debug-command.patch @@ -31,90 +31,102 @@ For references on certain keywords (ticket, status, etc), please see: https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273 https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577 -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ import com.google.common.collect.Maps; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceLocation; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.level.ChunkPos; -+import net.minecraft.server.MCUtil; - import org.apache.commons.lang3.tuple.MutablePair; - import org.apache.commons.lang3.tuple.Pair; - import org.bukkit.Bukkit; -@@ -0,0 +0,0 @@ import java.util.Set; - import java.util.stream.Collectors; +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ + package io.papermc.paper.command; - import static net.kyori.adventure.text.Component.text; ++import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("entity"), new EntityCommand()); + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); ++ commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.BLUE; +import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; - import static net.kyori.adventure.text.format.NamedTextColor.GREEN; - import static net.kyori.adventure.text.format.NamedTextColor.RED; - import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); - - public PaperCommand(String name) { - super(name); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - if (args.length == 3) - return getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); - break; -+ case "debug": -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, "help", "chunks"); ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ChunkDebugCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> this.doDebug(sender, args); ++ case "chunkinfo" -> this.doChunkInfo(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); + } -+ break; -+ case "chunkinfo": ++ } ++ case "chunkinfo" -> { + List worldNames = new ArrayList<>(); + worldNames.add("*"); + for (org.bukkit.World world : Bukkit.getWorlds()) { + worldNames.add(world.getName()); + } -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, worldNames); ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, worldNames); + } -+ break; - } - return Collections.emptyList(); - } -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - case "reload": - doReload(sender); - break; -+ case "debug": -+ doDebug(sender, args); -+ break; -+ case "chunkinfo": -+ doChunkInfo(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - return true; - } - -+ private void doChunkInfo(CommandSender sender, String[] args) { ++ } ++ } ++ return Collections.emptyList(); ++ } ++ ++ private void doChunkInfo(final CommandSender sender, final String[] args) { + List worlds; -+ if (args.length < 2 || args[1].equals("*")) { ++ if (args.length < 1 || args[0].equals("*")) { + worlds = Bukkit.getWorlds(); + } else { -+ worlds = new ArrayList<>(args.length - 1); -+ for (int i = 1; i < args.length; ++i) { -+ org.bukkit.World world = Bukkit.getWorld(args[i]); ++ worlds = new ArrayList<>(args.length); ++ for (final String arg : args) { ++ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); + if (world == null) { -+ sender.sendMessage(text("World '" + args[i] + "' is invalid", RED)); ++ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); + return; + } + worlds.add(world); @@ -127,8 +139,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int accumulatedTicking = 0; + int accumulatedEntityTicking = 0; + -+ for (org.bukkit.World bukkitWorld : worlds) { -+ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle(); ++ for (final org.bukkit.World bukkitWorld : worlds) { ++ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); + + int total = 0; + int inactive = 0; @@ -136,7 +148,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int ticking = 0; + int entityTicking = 0; + -+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { ++ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { + if (chunk.getFullChunkNowUnchecked() == null) { + continue; + } @@ -146,18 +158,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel()); + + switch (state) { -+ case INACCESSIBLE: -+ ++inactive; -+ continue; -+ case BORDER: -+ ++border; -+ continue; -+ case TICKING: -+ ++ticking; -+ continue; -+ case ENTITY_TICKING: -+ ++entityTicking; -+ continue; ++ case INACCESSIBLE -> ++inactive; ++ case BORDER -> ++border; ++ case TICKING -> ++ticking; ++ case ENTITY_TICKING -> ++entityTicking; + } + } + @@ -188,16 +192,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ private void doDebug(CommandSender sender, String[] args) { -+ if (args.length < 2) { ++ private void doDebug(final CommandSender sender, final String[] args) { ++ if (args.length < 1) { + sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); + return; + } + -+ String debugType = args[1].toLowerCase(Locale.ENGLISH); ++ final String debugType = args[0].toLowerCase(Locale.ENGLISH); + switch (debugType) { -+ case "chunks": -+ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ case "chunks" -> { ++ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) { + sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); + break; + } @@ -211,19 +215,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); + sender.sendMessage(text("Failed to dump chunk information, see console", RED)); + } -+ -+ break; -+ case "help": -+ // fall through to default -+ default: -+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ return; ++ } ++ // "help" & default ++ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); + } + } + - /* - * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 - */ ++} diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java diff --git a/patches/server/Do-not-copy-visible-chunks.patch b/patches/server/Do-not-copy-visible-chunks.patch index e030df9418..2399a4adf1 100644 --- a/patches/server/Do-not-copy-visible-chunks.patch +++ b/patches/server/Do-not-copy-visible-chunks.patch @@ -8,16 +8,16 @@ tickDistanceManager call can take up quite a bit in the function. I saw approximately 1/3rd of the function on the copy. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { +--- a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -0,0 +0,0 @@ public final class ChunkDebugCommand implements PaperSubcommand { int ticking = 0; int entityTicking = 0; -- for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { -+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map +- for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { ++ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map if (chunk.getFullChunkNowUnchecked() == null) { continue; } diff --git a/patches/server/Don-t-tick-markers.patch b/patches/server/Don-t-tick-markers.patch index cb5c1a8f60..5c89e953ef 100644 --- a/patches/server/Don-t-tick-markers.patch +++ b/patches/server/Don-t-tick-markers.patch @@ -8,19 +8,19 @@ tick list at all and ignoring them in Spigot's activation range checks. The enti list is only used in the tick and tickPassenger methods, so we can safely not add the markers to it. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - ChunkPos chunk = e.chunkPosition(); - info.left++; - info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -- if (!chunkProviderServer.isPositionTicking(e)) { -+ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. - nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); - } - }); +--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -0,0 +0,0 @@ public final class EntityCommand implements PaperSubcommand { + ChunkPos chunk = e.chunkPosition(); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); +- if (!chunkProviderServer.isPositionTicking(e)) { ++ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. + nonEntityTicking.merge(key, 1, Integer::sum); + } + }); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java diff --git a/patches/server/Fix-Light-Command.patch b/patches/server/Fix-Light-Command.patch index 7d9aa60607..835378ed70 100644 --- a/patches/server/Fix-Light-Command.patch +++ b/patches/server/Fix-Light-Command.patch @@ -6,76 +6,87 @@ Subject: [PATCH] Fix Light Command This lets you run /paper fixlight (max 5) to automatically fix all light data in the chunks. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.server.level.ServerLevel; --import net.minecraft.world.entity.Entity; +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; ++import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); ++ commands.put(Set.of("fixlight"), new FixLightCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayDeque; ++import java.util.Deque; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ThreadedLevelLightEngine; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.server.MCUtil; -@@ -0,0 +0,0 @@ import org.bukkit.command.Command; - import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.CraftWorld; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; - import org.bukkit.entity.Player; - - import java.io.File; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; -+import java.util.ArrayDeque; - import java.util.ArrayList; - import java.util.Arrays; - import java.util.Collection; - import java.util.Collections; -+import java.util.Deque; - import java.util.Iterator; - import java.util.List; - import java.util.Locale; -@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build(); - - public PaperCommand(String name) { - super(name); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - case "chunkinfo": - doChunkInfo(sender, args); - break; -+ case "fixlight": -+ this.doFixLight(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - - Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); - } -+ private void doFixLight(CommandSender sender, String[] args) { ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class FixLightCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doFixLight(sender, args); ++ return true; ++ } ++ ++ private void doFixLight(final CommandSender sender, final String[] args) { + if (!(sender instanceof Player)) { -+ sender.sendMessage("Only players can use this command"); ++ sender.sendMessage(text("Only players can use this command", RED)); + return; + } ++ @Nullable Runnable post = null; + int radius = 2; -+ if (args.length > 1) { ++ if (args.length > 0) { + try { -+ radius = Math.min(5, Integer.parseInt(args[1])); -+ } catch (Exception e) { -+ sender.sendMessage("Not a number"); ++ final int parsed = Integer.parseInt(args[0]); ++ if (parsed < 0) { ++ sender.sendMessage(text("Radius cannot be negative!", RED)); ++ return; ++ } ++ final int maxRadius = 5; ++ radius = Math.min(maxRadius, parsed); ++ if (radius != parsed) { ++ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); ++ } ++ } catch (final Exception e) { ++ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); + return; + } -+ + } + + CraftPlayer player = (CraftPlayer) sender; @@ -85,28 +96,37 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, post); + } + -+ private void updateLight(CommandSender sender, ServerLevel world, ThreadedLevelLightEngine lightengine, Deque queue) { -+ ChunkPos coord = queue.poll(); ++ private void updateLight( ++ final CommandSender sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final Deque queue, ++ final @Nullable Runnable done ++ ) { ++ @Nullable ChunkPos coord = queue.poll(); + if (coord == null) { -+ sender.sendMessage("All Chunks Light updated"); ++ sender.sendMessage(text("All Chunks Light updated", GREEN)); ++ if (done != null) { ++ done.run(); ++ } + return; + } + world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { + if (ex != null) { -+ sender.sendMessage("Error loading chunk " + coord); -+ updateLight(sender, world, lightengine, queue); ++ sender.sendMessage(text("Error loading chunk " + coord, RED)); ++ updateLight(sender, world, lightengine, queue, done); + return; + } -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); ++ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); + if (chunk == null) { -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + return; + } + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue -+ sender.sendMessage("Updating Light " + coord); ++ sender.sendMessage(text("Updating Light " + coord)); + int cx = chunk.getPos().x << 4; + int cz = chunk.getPos().z << 4; + for (int y = 0; y < world.getHeight(); y++) { @@ -118,21 +138,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + lightengine.tryScheduleUpdate(); -+ ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); ++ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); + if (visibleChunk != null) { + world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { + MinecraftServer.getServer().processQueue.add(() -> { + visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false); -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + }); + }); + } else { -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + } + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize); + }, MinecraftServer.getServer()); + } - } ++} diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java diff --git a/patches/server/Make-the-default-permission-message-configurable.patch b/patches/server/Make-the-default-permission-message-configurable.patch index f51fe313a8..3e5d687745 100644 --- a/patches/server/Make-the-default-permission-message-configurable.patch +++ b/patches/server/Make-the-default-permission-message-configurable.patch @@ -4,16 +4,16 @@ Date: Sun, 18 Nov 2018 19:49:56 +0000 Subject: [PATCH] Make the default permission message configurable -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - - private static boolean testPermission(CommandSender commandSender, String permission) { - if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; -- commandSender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ commandSender.sendMessage(Bukkit.permissionMessage()); +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { + return true; + } +- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ sender.sendMessage(Bukkit.permissionMessage()); return false; } diff --git a/patches/server/Paper-command.patch b/patches/server/Paper-command.patch index d775737ff4..4f444a428f 100644 --- a/patches/server/Paper-command.patch +++ b/patches/server/Paper-command.patch @@ -4,102 +4,50 @@ Date: Mon, 29 Feb 2016 21:02:09 -0600 Subject: [PATCH] Paper command -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/CommandUtil.java b/src/main/java/io/papermc/paper/command/CommandUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/CommandUtil.java @@ -0,0 +0,0 @@ -+package com.destroystokyo.paper; ++package io.papermc.paper.command; + +import com.google.common.base.Functions; -+import com.google.common.base.Joiner; -+import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; -+import com.google.common.collect.Maps; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.level.ChunkPos; -+import org.apache.commons.lang3.tuple.MutablePair; -+import org.apache.commons.lang3.tuple.Pair; -+import org.bukkit.Bukkit; -+import org.bukkit.Location; -+import org.bukkit.World; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.entity.Player; -+ -+import java.io.File; -+import java.time.LocalDateTime; -+import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; -+import java.util.Collections; +import java.util.Iterator; +import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+import java.util.Set; -+import java.util.stream.Collectors; ++import net.minecraft.resources.ResourceLocation; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+ -+public class PaperCommand extends Command { -+ private static final String BASE_PERM = "bukkit.command.paper."; -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); -+ -+ public PaperCommand(String name) { -+ super(name); -+ this.description = "Paper related commands"; -+ this.usageMessage = "/paper [" + Joiner.on(" | ").join(SUBCOMMANDS) + "]"; -+ this.setPermission("bukkit.command.paper;" + Joiner.on(';').join(SUBCOMMANDS.stream().map(s -> BASE_PERM + s).collect(Collectors.toSet()))); -+ } -+ -+ private static boolean testPermission(CommandSender commandSender, String permission) { -+ if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; -+ commandSender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ return false; -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ if (args.length <= 1) -+ return getListMatchingLast(sender, args, SUBCOMMANDS); -+ -+ switch (args[0].toLowerCase(Locale.ENGLISH)) -+ { -+ case "entity": -+ if (args.length == 2) -+ return getListMatchingLast(sender, args, "help", "list"); -+ if (args.length == 3) -+ return getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); -+ break; -+ } -+ return Collections.emptyList(); ++@DefaultQualifier(NonNull.class) ++public final class CommandUtil { ++ private CommandUtil() { + } + + // Code from Mojang - copyright them -+ public static List getListMatchingLast(CommandSender sender, String[] args, String... matches) { -+ return getListMatchingLast(sender, args, (Collection) Arrays.asList(matches)); ++ public static List getListMatchingLast( ++ final CommandSender sender, ++ final String[] args, ++ final String... matches ++ ) { ++ return getListMatchingLast(sender, args, Arrays.asList(matches)); + } + -+ public static boolean matches(String s, String s1) { ++ public static boolean matches(final String s, final String s1) { + return s1.regionMatches(true, 0, s, 0, s.length()); + } + -+ public static List getListMatchingLast(CommandSender sender, String[] strings, Collection collection) { ++ public static List getListMatchingLast( ++ final CommandSender sender, ++ final String[] strings, ++ final Collection collection ++ ) { + String last = strings[strings.length - 1]; + ArrayList results = Lists.newArrayList(); + @@ -109,7 +57,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + while (iterator.hasNext()) { + String s1 = (String) iterator.next(); + -+ if (matches(last, s1) && (sender.hasPermission(BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { ++ if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { + results.add(s1); + } + } @@ -130,171 +78,156 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return results; + } + // end copy stuff ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command; ++ ++import io.papermc.paper.command.subcommands.EntityCommand; ++import io.papermc.paper.command.subcommands.HeapDumpCommand; ++import io.papermc.paper.command.subcommands.ReloadCommand; ++import io.papermc.paper.command.subcommands.VersionCommand; ++import it.unimi.dsi.fastutil.Pair; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.Util; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.plugin.PluginManager; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperCommand extends Command { ++ static final String BASE_PERM = "bukkit.command.paper."; ++ // subcommand label -> subcommand ++ private static final Map SUBCOMMANDS = Util.make(() -> { ++ final Map, PaperSubcommand> commands = new HashMap<>(); ++ ++ commands.put(Set.of("heap"), new HeapDumpCommand()); ++ commands.put(Set.of("entity"), new EntityCommand()); ++ commands.put(Set.of("reload"), new ReloadCommand()); ++ commands.put(Set.of("version"), new VersionCommand()); ++ ++ return commands.entrySet().stream() ++ .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) ++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); ++ }); ++ // alias -> subcommand label ++ private static final Map ALIASES = Util.make(() -> { ++ final Map> aliases = new HashMap<>(); ++ ++ aliases.put("version", Set.of("ver")); ++ ++ return aliases.entrySet().stream() ++ .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) ++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); ++ }); ++ ++ public PaperCommand(final String name) { ++ super(name); ++ this.description = "Paper related commands"; ++ this.usageMessage = "/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"; ++ final List permissions = new ArrayList<>(); ++ permissions.add("bukkit.command.paper"); ++ permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); ++ this.setPermission(String.join(";", permissions)); ++ final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); ++ for (final String perm : permissions) { ++ pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); ++ } ++ } ++ ++ private static boolean testPermission(final CommandSender sender, final String permission) { ++ if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { ++ return true; ++ } ++ sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ return false; ++ } + + @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; ++ public List tabComplete( ++ final CommandSender sender, ++ final String alias, ++ final String[] args, ++ final @Nullable Location location ++ ) throws IllegalArgumentException { ++ if (args.length <= 1) { ++ return CommandUtil.getListMatchingLast(sender, args, SUBCOMMANDS.keySet()); ++ } ++ ++ final @Nullable Pair subCommand = resolveCommand(args[0]); ++ if (subCommand != null) { ++ return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute( ++ final CommandSender sender, ++ final String commandLabel, ++ final String[] args ++ ) { ++ if (!testPermission(sender)) { ++ return true; ++ } + + if (args.length == 0) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } -+ if (SUBCOMMANDS.contains(args[0].toLowerCase(Locale.ENGLISH))) { -+ if (!testPermission(sender, args[0].toLowerCase(Locale.ENGLISH))) return true; -+ } -+ switch (args[0].toLowerCase(Locale.ENGLISH)) { -+ case "heap": -+ dumpHeap(sender); -+ break; -+ case "entity": -+ listEntities(sender, args); -+ break; -+ case "reload": -+ doReload(sender); -+ break; -+ case "ver": -+ if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) -+ case "version": -+ Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); -+ if (ver != null) { -+ ver.execute(sender, commandLabel, new String[0]); -+ break; -+ } -+ // else - fall through to default -+ default: -+ sender.sendMessage(text("Usage: " + this.usageMessage, RED)); -+ return false; ++ final @Nullable Pair subCommand = resolveCommand(args[0]); ++ ++ if (subCommand == null) { ++ sender.sendMessage(text("Usage: " + this.usageMessage, RED)); ++ return false; + } + -+ return true; ++ if (!testPermission(sender, subCommand.first())) { ++ return true; ++ } ++ final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); ++ return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + } + -+ /* -+ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 -+ */ -+ private void listEntities(CommandSender sender, String[] args) { -+ if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) { -+ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); -+ return; ++ private static @Nullable Pair resolveCommand(String label) { ++ label = label.toLowerCase(Locale.ENGLISH); ++ @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label); ++ if (subCommand == null) { ++ final @Nullable String command = ALIASES.get(label); ++ if (command != null) { ++ label = command; ++ subCommand = SUBCOMMANDS.get(command); ++ } + } + -+ switch (args[1].toLowerCase(Locale.ENGLISH)) { -+ case "list": -+ String filter = "*"; -+ if (args.length > 2) { -+ if (args[2].toLowerCase(Locale.ENGLISH).equals("help")) { -+ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED)); -+ return; -+ } -+ filter = args[2]; -+ } -+ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); -+ Set names = Registry.ENTITY_TYPE.keySet().stream() -+ .filter(n -> n.toString().matches(cleanfilter)) -+ .collect(Collectors.toSet()); -+ -+ if (names.isEmpty()) { -+ sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ -+ String worldName; -+ if (args.length > 3) { -+ worldName = args[3]; -+ } else if (sender instanceof Player) { -+ worldName = ((Player) sender).getWorld().getName(); -+ } else { -+ sender.sendMessage(text("Please specify the name of a world", RED)); -+ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ -+ Map>> list = Maps.newHashMap(); -+ World bukkitWorld = Bukkit.getWorld(worldName); -+ if (bukkitWorld == null) { -+ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ ServerLevel world = ((CraftWorld) Bukkit.getWorld(worldName)).getHandle(); -+ -+ Map nonEntityTicking = Maps.newHashMap(); -+ ServerChunkCache chunkProviderServer = world.getChunkSource(); -+ -+ world.getAllEntities().forEach(e -> { -+ ResourceLocation key = EntityType.getKey(e.getType()); -+ -+ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); -+ ChunkPos chunk = e.chunkPosition(); -+ info.left++; -+ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -+ if (!chunkProviderServer.isPositionTicking(e)) { -+ nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); -+ } -+ }); -+ -+ if (names.size() == 1) { -+ ResourceLocation name = names.iterator().next(); -+ Pair> info = list.get(name); -+ int nonTicking = nonEntityTicking.getOrDefault(name, Integer.valueOf(0)).intValue(); -+ if (info == null) { -+ sender.sendMessage(text("No entities found.", RED)); -+ return; -+ } -+ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); -+ info.getRight().entrySet().stream() -+ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) -+ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))); -+ } else { -+ List> info = list.entrySet().stream() -+ .filter(e -> names.contains(e.getKey())) -+ .map(e -> Pair.of(e.getKey(), e.getValue().left)) -+ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) -+ .collect(Collectors.toList()); -+ -+ if (info == null || info.size() == 0) { -+ sender.sendMessage(text("No entities found.", RED)); -+ return; -+ } -+ -+ int count = info.stream().mapToInt(Pair::getRight).sum(); -+ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); -+ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); -+ info.forEach(e -> { -+ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), Integer.valueOf(0)).intValue(); -+ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); -+ }); -+ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); -+ } -+ break; ++ if (subCommand != null) { ++ return Pair.of(label, subCommand); + } -+ } + -+ private void dumpHeap(CommandSender sender) { -+ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); -+ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); -+ -+ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); -+ -+ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); -+ if (file != null) { -+ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN)); -+ } else { -+ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); -+ } -+ } -+ -+ private void doReload(CommandSender sender) { -+ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); -+ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); -+ -+ MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); -+ server.paperConfigurations.reloadConfigs(server); -+ server.server.reloadCount++; -+ -+ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); ++ return null; + } +} diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -305,13 +238,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package io.papermc.paper.command; + -+import com.destroystokyo.paper.PaperCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; + +import java.util.HashMap; +import java.util.Map; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + ++@DefaultQualifier(NonNull.class) +public final class PaperCommands { + + private PaperCommands() { @@ -328,6 +263,289 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }); + } +} +diff --git a/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/src/main/java/io/papermc/paper/command/PaperSubcommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/PaperSubcommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command; ++ ++import java.util.Collections; ++import java.util.List; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface PaperSubcommand { ++ boolean execute(CommandSender sender, String subCommand, String[] args); ++ ++ default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return Collections.emptyList(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.common.collect.Maps; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.ChunkPos; ++import org.apache.commons.lang3.tuple.MutablePair; ++import org.apache.commons.lang3.tuple.Pair; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class EntityCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.listEntities(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "list"); ++ } else if (args.length == 2) { ++ return CommandUtil.getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); ++ } ++ return Collections.emptyList(); ++ } ++ ++ /* ++ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 ++ */ ++ private void listEntities(final CommandSender sender, final String[] args) { ++ // help ++ if (args.length < 1 || !args[0].toLowerCase(Locale.ENGLISH).equals("list")) { ++ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); ++ return; ++ } ++ ++ if ("list".equals(args[0].toLowerCase(Locale.ENGLISH))) { ++ String filter = "*"; ++ if (args.length > 1) { ++ if (args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED)); ++ return; ++ } ++ filter = args[1]; ++ } ++ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); ++ Set names = Registry.ENTITY_TYPE.keySet().stream() ++ .filter(n -> n.toString().matches(cleanfilter)) ++ .collect(Collectors.toSet()); ++ if (names.isEmpty()) { ++ sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ String worldName; ++ if (args.length > 2) { ++ worldName = args[2]; ++ } else if (sender instanceof Player) { ++ worldName = ((Player) sender).getWorld().getName(); ++ } else { ++ sender.sendMessage(text("Please specify the name of a world", RED)); ++ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ Map>> list = Maps.newHashMap(); ++ @Nullable World bukkitWorld = Bukkit.getWorld(worldName); ++ if (bukkitWorld == null) { ++ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); ++ Map nonEntityTicking = Maps.newHashMap(); ++ ServerChunkCache chunkProviderServer = world.getChunkSource(); ++ world.getAllEntities().forEach(e -> { ++ ResourceLocation key = EntityType.getKey(e.getType()); ++ ++ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); ++ ChunkPos chunk = e.chunkPosition(); ++ info.left++; ++ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); ++ if (!chunkProviderServer.isPositionTicking(e)) { ++ nonEntityTicking.merge(key, 1, Integer::sum); ++ } ++ }); ++ if (names.size() == 1) { ++ ResourceLocation name = names.iterator().next(); ++ Pair> info = list.get(name); ++ int nonTicking = nonEntityTicking.getOrDefault(name, 0); ++ if (info == null) { ++ sender.sendMessage(text("No entities found.", RED)); ++ return; ++ } ++ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); ++ info.getRight().entrySet().stream() ++ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))); ++ } else { ++ List> info = list.entrySet().stream() ++ .filter(e -> names.contains(e.getKey())) ++ .map(e -> Pair.of(e.getKey(), e.getValue().left)) ++ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .toList(); ++ ++ if (info.isEmpty()) { ++ sender.sendMessage(text("No entities found.", RED)); ++ return; ++ } ++ ++ int count = info.stream().mapToInt(Pair::getRight).sum(); ++ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); ++ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); ++ info.forEach(e -> { ++ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), 0); ++ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); ++ }); ++ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++ ++@DefaultQualifier(NonNull.class) ++public final class HeapDumpCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.dumpHeap(sender); ++ return true; ++ } ++ ++ private void dumpHeap(final CommandSender sender) { ++ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); ++ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); ++ ++ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); ++ ++ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); ++ if (file != null) { ++ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN)); ++ } else { ++ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ReloadCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doReload(sender); ++ return true; ++ } ++ ++ private void doReload(final CommandSender sender) { ++ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); ++ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); ++ ++ MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); ++ server.paperConfigurations.reloadConfigs(server); ++ server.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class VersionCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); ++ if (ver != null) { ++ ver.execute(sender, "paper", new String[0]); ++ } ++ return true; ++ } ++} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/patches/server/Paper-dumpitem-command.patch b/patches/server/Paper-dumpitem-command.patch index b89750e255..1fcac6a93b 100644 --- a/patches/server/Paper-dumpitem-command.patch +++ b/patches/server/Paper-dumpitem-command.patch @@ -5,60 +5,88 @@ Subject: [PATCH] Paper dumpitem command Let's you quickly view the item in your hands NBT data -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.CraftWorld; - import org.bukkit.craftbukkit.entity.CraftPlayer; +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ + package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; ++import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); ++ commands.put(Set.of("dumpitem"), new DumpItemCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.Objects; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.minecraft.core.Registry; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; - import org.bukkit.entity.Player; -+import org.bukkit.inventory.ItemStack; - - import java.io.File; - import java.io.FileOutputStream; -@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build(); - - public PaperCommand(String name) { - super(name); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - case "reload": - doReload(sender); - break; -+ case "dumpitem": -+ doDumpItem(sender); -+ break; - case "debug": - doDebug(sender, args); - break; -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - - Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); - } -+ private void doDumpItem(CommandSender sender) { ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++import static net.kyori.adventure.text.format.TextDecoration.ITALIC; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpItemCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doDumpItem(sender); ++ return true; ++ } ++ ++ private void doDumpItem(final CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only players can use this command"); + return; + } -+ ItemStack itemInHand = ((CraftPlayer) sender).getItemInHand(); -+ net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(itemInHand); -+ net.minecraft.nbt.CompoundTag tag = itemStack.getTag(); -+ if (tag != null) { -+ net.kyori.adventure.text.Component nbtComponent = io.papermc.paper.adventure.PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); -+ Bukkit.getConsoleSender().sendMessage(nbtComponent); -+ sender.sendMessage(nbtComponent); -+ } else { -+ sender.sendMessage("Item does not have NBT"); -+ } ++ final ItemStack itemStack = CraftItemStack.asNMSCopy(((CraftPlayer) sender).getItemInHand()); ++ final @Nullable CompoundTag tag = itemStack.getTag(); ++ final @Nullable Component nbtComponent = tag == null ? null : PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); ++ final String itemId = Objects.requireNonNull(((CraftWorld) ((CraftPlayer) sender).getWorld()).getHandle().registryAccess() ++ .registryOrThrow(Registry.ITEM_REGISTRY).getKey(itemStack.getItem())).toString(); ++ final Component message = text() ++ .append(text(itemId, YELLOW)) ++ .apply(b -> { ++ if (nbtComponent != null) { ++ b.append(nbtComponent); ++ } ++ }) ++ .build(); ++ Bukkit.getConsoleSender().sendMessage(message); ++ sender.sendMessage(message); ++ sender.sendMessage(text().content(" Click to copy item to clipboard") ++ .color(GRAY) ++ .decorate(ITALIC) ++ .clickEvent(ClickEvent.copyToClipboard(tag == null ? itemId : (itemId + tag)))); + } -+ - private void doFixLight(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("Only players can use this command"); ++} diff --git a/patches/server/Rewrite-the-light-engine.patch b/patches/server/Rewrite-the-light-engine.patch index e385429c0f..f01896f8e0 100644 --- a/patches/server/Rewrite-the-light-engine.patch +++ b/patches/server/Rewrite-the-light-engine.patch @@ -4357,24 +4357,68 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + +} -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - } +--- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +0,0 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.ThreadedLevelLightEngine; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.LevelChunk; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.entity.CraftPlayer; +@@ -0,0 +0,0 @@ import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; + import static net.kyori.adventure.text.format.NamedTextColor.GREEN; + import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@@ -0,0 +0,0 @@ public final class FixLightCommand implements PaperSubcommand { + sender.sendMessage(text("Radius cannot be negative!", RED)); + return; + } +- final int maxRadius = 5; ++ final int maxRadius = 32; // Paper - MOOOOOORE + radius = Math.min(maxRadius, parsed); + if (radius != parsed) { + post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); +@@ -0,0 +0,0 @@ public final class FixLightCommand implements PaperSubcommand { + ServerPlayer handle = player.getHandle(); + ServerLevel world = (ServerLevel) handle.level; + ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ // Paper start - rewrite light engine ++ if (true) { ++ this.starlightFixLight(handle, world, lightengine, radius, post); ++ return; ++ } ++ // Paper end - rewrite light engine + + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + updateLight(sender, world, lightengine, queue, post); } + // Paper start - rewrite light engine -+ private void starlightFixLight(ServerPlayer sender, ServerLevel world, ThreadedLevelLightEngine lightengine, int radius) { ++ private void starlightFixLight( ++ final ServerPlayer sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final int radius, ++ final @Nullable Runnable done ++ ) { + long start = System.nanoTime(); + java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos + + int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { + final ChunkPos chunkPos = iterator.next(); + -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = (net.minecraft.world.level.chunk.ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); + if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); @@ -4386,51 +4430,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + int[] relitChunks = new int[1]; + lightengine.relight(chunks, -+ (ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit chunk ", BLUE), text(chunkPos.toString()), -+ text(", progress: ", BLUE), text((int)(Math.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%") -+ )); -+ }, -+ (int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6*(end - start)); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit ", BLUE), text(totalRelit), -+ text(" chunks. Took ", BLUE), text(diff + "ms") -+ )); -+ }); ++ (ChunkPos chunkPos) -> { ++ ++relitChunks[0]; ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit chunk ", BLUE), text(chunkPos.toString()), ++ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") ++ )); ++ }, ++ (int totalRelit) -> { ++ final long end = System.nanoTime(); ++ final long diff = Math.round(1.0e-6 * (end - start)); ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit ", BLUE), text(totalRelit), ++ text(" chunks. Took ", BLUE), text(diff + "ms") ++ )); ++ if (done != null) { ++ done.run(); ++ } ++ }); + sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); + } + // Paper end - rewrite light engine + - private void doFixLight(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("Only players can use this command"); -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - int radius = 2; - if (args.length > 1) { - try { -- radius = Math.min(5, Integer.parseInt(args[1])); -+ radius = Math.min(32, Integer.parseInt(args[1])); // Paper - MOOOOOORE - } catch (Exception e) { - sender.sendMessage("Not a number"); - return; -@@ -0,0 +0,0 @@ public class PaperCommand extends Command { - ServerLevel world = (ServerLevel) handle.level; - ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); - -+ // Paper start - rewrite light engine -+ if (true) { -+ this.starlightFixLight(handle, world, lightengine, radius); -+ return; -+ } -+ // Paper end - rewrite light engine -+ - net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); - Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); - updateLight(sender, world, lightengine, queue); + private void updateLight( + final CommandSender sender, + final ServerLevel world, diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java