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
This commit is contained in:
Jason Penilla 2022-07-08 16:01:42 -07:00
parent 21e92425e9
commit 35eca19853
11 changed files with 1016 additions and 753 deletions

View file

@ -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 - To clear clear the currently stored sync load info, use
/paper syncloadinfo clear /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<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>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<org.bukkit.World> 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 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 new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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<String> 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 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java

View file

@ -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 Also has a hover text on each mob category listing what entity types are
in said category 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@ package com.destroystokyo.paper; @@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.DumpItemCommand;
import com.destroystokyo.paper.io.SyncLoadFinder; import io.papermc.paper.command.subcommands.EntityCommand;
import com.google.common.base.Functions; import io.papermc.paper.command.subcommands.FixLightCommand;
import com.google.common.base.Joiner; 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.ImmutableMap;
import com.google.common.collect.ImmutableSet; +import io.papermc.paper.command.CommandUtil;
import com.google.common.collect.Iterables; +import io.papermc.paper.command.PaperSubcommand;
import com.google.common.collect.Lists; +import java.util.ArrayList;
@@ -0,0 +0,0 @@ import net.minecraft.resources.ResourceLocation; +import java.util.Collections;
import com.google.gson.JsonObject; +import java.util.List;
import com.google.gson.internal.Streams; +import java.util.Map;
import com.google.gson.stream.JsonWriter; +import java.util.function.ToIntFunction;
+import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.ComponentLike;
+import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.JoinConfiguration;
+import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextColor;
import net.minecraft.server.MinecraftServer; +import net.minecraft.core.Registry;
import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerPlayer;
@@ -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.world.entity.MobCategory; +import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.server.MCUtil;
+import net.minecraft.world.level.NaturalSpawner; +import net.minecraft.world.level.NaturalSpawner;
import org.apache.commons.lang3.tuple.MutablePair; +import org.bukkit.Bukkit;
import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.World;
import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender;
@@ -0,0 +0,0 @@ import java.util.List; +import org.bukkit.craftbukkit.CraftWorld;
import java.util.Locale; +import org.bukkit.craftbukkit.entity.CraftPlayer;
import java.util.Map; +import org.bukkit.entity.Player;
import java.util.Set; +import org.checkerframework.checker.nullness.qual.NonNull;
+import java.util.function.ToIntFunction; +import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.stream.Collectors; +import org.checkerframework.framework.qual.DefaultQualifier;
+
import static net.kyori.adventure.text.Component.text; +@DefaultQualifier(NonNull.class)
@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; +public final class MobcapsCommand implements PaperSubcommand {
+ static final Map<MobCategory, TextColor> MOB_CATEGORY_COLORS = ImmutableMap.<MobCategory, TextColor>builder()
public class PaperCommand extends Command {
private static final String BASE_PERM = "bukkit.command.paper.";
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>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<String> 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<MobCategory, TextColor> MOB_CATEGORY_COLORS = ImmutableMap.<MobCategory, TextColor>builder()
+ .put(MobCategory.MONSTER, NamedTextColor.RED) + .put(MobCategory.MONSTER, NamedTextColor.RED)
+ .put(MobCategory.CREATURE, NamedTextColor.GREEN) + .put(MobCategory.CREATURE, NamedTextColor.GREEN)
+ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) + .put(MobCategory.AMBIENT, NamedTextColor.GRAY)
@ -101,8 +79,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ .put(MobCategory.MISC, TextColor.color(0x636363)) + .put(MobCategory.MISC, TextColor.color(0x636363))
+ .build(); + .build();
+ +
+ private List<String> suggestMobcaps(CommandSender sender, String[] args) { + @Override
+ if (args.length == 2) { + 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<String> 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<String> suggestMobcaps(final String[] args) {
+ if (args.length == 1) {
+ final List<String> worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); + final List<String> worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList());
+ worlds.add("*"); + worlds.add("*");
+ return worlds; + return worlds;
@ -111,8 +107,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return Collections.emptyList(); + return Collections.emptyList();
+ } + }
+ +
+ private List<String> suggestPlayerMobcaps(CommandSender sender, String[] args) { + private List<String> suggestPlayerMobcaps(final CommandSender sender, final String[] args) {
+ if (args.length == 2) { + if (args.length == 1) {
+ final List<String> list = new ArrayList<>(); + final List<String> list = new ArrayList<>();
+ for (final Player player : Bukkit.getOnlinePlayers()) { + for (final Player player : Bukkit.getOnlinePlayers()) {
+ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { + if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) {
@ -125,21 +121,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return Collections.emptyList(); + return Collections.emptyList();
+ } + }
+ +
+ private void printMobcaps(CommandSender sender, String[] args) { + private void printMobcaps(final CommandSender sender, final String[] args) {
+ final List<World> worlds; + final List<World> worlds;
+ if (args.length == 1) { + if (args.length == 0) {
+ if (sender instanceof Player player) { + if (sender instanceof Player player) {
+ worlds = List.of(player.getWorld()); + worlds = List.of(player.getWorld());
+ } else { + } else {
+ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); + sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED));
+ return; + return;
+ } + }
+ } else if (args.length == 2) { + } else if (args.length == 1) {
+ final String input = args[1]; + final String input = args[0];
+ if (input.equals("*")) { + if (input.equals("*")) {
+ worlds = Bukkit.getWorlds(); + worlds = Bukkit.getWorlds();
+ } else { + } else {
+ final World world = Bukkit.getWorld(input); + final @Nullable World world = Bukkit.getWorld(input);
+ if (world == null) { + if (world == null) {
+ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); + sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED));
+ return; + return;
@ -154,7 +150,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ for (final World world : worlds) { + for (final World world : worlds) {
+ final ServerLevel level = ((CraftWorld) world).getHandle(); + final ServerLevel level = ((CraftWorld) world).getHandle();
+ final NaturalSpawner.SpawnState state = level.getChunkSource().getLastSpawnState(); + final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState();
+ +
+ final int chunks; + final int chunks;
+ if (state == null) { + if (state == null) {
@ -168,7 +164,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ Component.text(" (" + chunks + " spawnable chunks)") + Component.text(" (" + chunks + " spawnable chunks)")
+ )); + ));
+ +
+ sender.sendMessage(this.buildMobcapsComponent( + sender.sendMessage(createMobcapsComponent(
+ category -> { + category -> {
+ if (state == null) { + if (state == null) {
+ return 0; + return 0;
@ -181,17 +177,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ } + }
+ +
+ private void printPlayerMobcaps(CommandSender sender, String[] args) { + private void printPlayerMobcaps(final CommandSender sender, final String[] args) {
+ final Player player; + final @Nullable Player player;
+ if (args.length == 1) { + if (args.length == 0) {
+ if (sender instanceof Player pl) { + if (sender instanceof Player pl) {
+ player = pl; + player = pl;
+ } else { + } else {
+ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); + sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED));
+ return; + return;
+ } + }
+ } else if (args.length == 2) { + } else if (args.length == 1) {
+ final String input = args[1]; + final String input = args[0];
+ player = Bukkit.getPlayerExact(input); + player = Bukkit.getPlayerExact(input);
+ if (player == null) { + if (player == null) {
+ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); + 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(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.chunkSource.chunkMap.getMobCountNear(serverPlayer, category),
+ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) + category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category))
+ )); + ));
+ } + }
+ +
+ private Component buildMobcapsComponent(final ToIntFunction<MobCategory> countGetter, final ToIntFunction<MobCategory> limitGetter) { + private static Component createMobcapsComponent(final ToIntFunction<MobCategory> countGetter, final ToIntFunction<MobCategory> limitGetter) {
+ return MOB_CATEGORY_COLORS.entrySet().stream() + return MOB_CATEGORY_COLORS.entrySet().stream()
+ .map(entry -> { + .map(entry -> {
+ final MobCategory category = entry.getKey(); + final MobCategory category = entry.getKey();
@ -267,10 +263,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ .map(ComponentLike::asComponent) + .map(ComponentLike::asComponent)
+ .collect(Component.toComponent(Component.newline())); + .collect(Component.toComponent(Component.newline()));
+ } + }
+ +}
private void doChunkInfo(CommandSender sender, String[] args) {
List<org.bukkit.World> 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 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@ -328,30 +321,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
} }
return limit; 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 new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null --- /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 @@ @@ -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.HashSet;
+import java.util.Set; +import java.util.Set;
+import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.entity.MobCategory;
+import org.junit.Assert; +import org.junit.Assert;
+import org.junit.Test; +import org.junit.Test;
+ +
+public class PaperCommandTest { +public class MobcapsCommandTest {
+ @Test + @Test
+ public void testMobCategoryColors() { + public void testMobCategoryColors() {
+ final Set<String> missing = new HashSet<>(); + final Set<String> missing = new HashSet<>();
+ for (final MobCategory value : MobCategory.values()) { + for (final MobCategory value : MobCategory.values()) {
+ if (!PaperCommand.MOB_CATEGORY_COLORS.containsKey(value)) { + if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) {
+ missing.add(value.getName()); + 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());
+ } + }
+} +}

View file

@ -4,13 +4,13 @@ Date: Sun, 5 Apr 2020 22:23:14 -0500
Subject: [PATCH] Add tick times API and /mspt command 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 new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null --- /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 @@ @@ -0,0 +0,0 @@
+package com.destroystokyo.paper; +package io.papermc.paper.command;
+ +
+import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.Component;
+import net.minecraft.server.MinecraftServer; +import net.minecraft.server.MinecraftServer;
@ -23,6 +23,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import java.util.Arrays; +import java.util.Arrays;
+import java.util.Collections; +import java.util.Collections;
+import java.util.List; +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.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GOLD; +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.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; +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 DecimalFormat DF = new DecimalFormat("########0.0");
+ private static final Component SLASH = text("/"); + private static final Component SLASH = text("/");
+ +
+ public MSPTCommand(String name) { + public MSPTCommand(final String name) {
+ super(name); + super(name);
+ this.description = "View server tick times"; + this.description = "View server tick times";
+ this.usageMessage = "/mspt"; + 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/io/papermc/paper/command/PaperCommands.java --- a/src/main/java/io/papermc/paper/command/PaperCommands.java
+++ b/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 { @@ -0,0 +0,0 @@ public final class PaperCommands {
private static final Map<String, Command> COMMANDS = new HashMap<>(); private static final Map<String, Command> COMMANDS = new HashMap<>();
static { static {

View file

@ -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=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 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@ import com.google.common.collect.Maps; @@ -0,0 +0,0 @@
import net.minecraft.core.Registry; package io.papermc.paper.command;
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;
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.BLUE;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; +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.GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; +
+@DefaultQualifier(NonNull.class)
public class PaperCommand extends Command { +public final class ChunkDebugCommand implements PaperSubcommand {
private static final String BASE_PERM = "bukkit.command.paper."; + @Override
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version").build(); + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); + switch (subCommand) {
+ case "debug" -> this.doDebug(sender, args);
public PaperCommand(String name) { + case "chunkinfo" -> this.doChunkInfo(sender, args);
super(name); + }
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { + return true;
if (args.length == 3) + }
return getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); +
break; + @Override
+ case "debug": + public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
+ if (args.length == 2) { + switch (subCommand) {
+ return getListMatchingLast(sender, args, "help", "chunks"); + case "debug" -> {
+ if (args.length == 1) {
+ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks");
+ } + }
+ break; + }
+ case "chunkinfo": + case "chunkinfo" -> {
+ List<String> worldNames = new ArrayList<>(); + List<String> worldNames = new ArrayList<>();
+ worldNames.add("*"); + worldNames.add("*");
+ for (org.bukkit.World world : Bukkit.getWorlds()) { + for (org.bukkit.World world : Bukkit.getWorlds()) {
+ worldNames.add(world.getName()); + worldNames.add(world.getName());
+ } + }
+ if (args.length == 2) { + if (args.length == 1) {
+ return getListMatchingLast(sender, args, worldNames); + return CommandUtil.getListMatchingLast(sender, args, worldNames);
+ } + }
+ break; + }
} + }
return Collections.emptyList(); + return Collections.emptyList();
} + }
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { +
case "reload": + private void doChunkInfo(final CommandSender sender, final String[] args) {
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) {
+ List<org.bukkit.World> worlds; + List<org.bukkit.World> worlds;
+ if (args.length < 2 || args[1].equals("*")) { + if (args.length < 1 || args[0].equals("*")) {
+ worlds = Bukkit.getWorlds(); + worlds = Bukkit.getWorlds();
+ } else { + } else {
+ worlds = new ArrayList<>(args.length - 1); + worlds = new ArrayList<>(args.length);
+ for (int i = 1; i < args.length; ++i) { + for (final String arg : args) {
+ org.bukkit.World world = Bukkit.getWorld(args[i]); + org.bukkit.@Nullable World world = Bukkit.getWorld(arg);
+ if (world == null) { + if (world == null) {
+ sender.sendMessage(text("World '" + args[i] + "' is invalid", RED)); + sender.sendMessage(text("World '" + arg + "' is invalid", RED));
+ return; + return;
+ } + }
+ worlds.add(world); + worlds.add(world);
@ -127,8 +139,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ int accumulatedTicking = 0; + int accumulatedTicking = 0;
+ int accumulatedEntityTicking = 0; + int accumulatedEntityTicking = 0;
+ +
+ for (org.bukkit.World bukkitWorld : worlds) { + for (final org.bukkit.World bukkitWorld : worlds) {
+ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle(); + final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
+ +
+ int total = 0; + int total = 0;
+ int inactive = 0; + int inactive = 0;
@ -136,7 +148,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ int ticking = 0; + int ticking = 0;
+ int entityTicking = 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) { + if (chunk.getFullChunkNowUnchecked() == null) {
+ continue; + continue;
+ } + }
@ -146,18 +158,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel()); + ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel());
+ +
+ switch (state) { + switch (state) {
+ case INACCESSIBLE: + case INACCESSIBLE -> ++inactive;
+ ++inactive; + case BORDER -> ++border;
+ continue; + case TICKING -> ++ticking;
+ case BORDER: + case ENTITY_TICKING -> ++entityTicking;
+ ++border;
+ continue;
+ case TICKING:
+ ++ticking;
+ continue;
+ case ENTITY_TICKING:
+ ++entityTicking;
+ continue;
+ } + }
+ } + }
+ +
@ -188,16 +192,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ } + }
+ +
+ private void doDebug(CommandSender sender, String[] args) { + private void doDebug(final CommandSender sender, final String[] args) {
+ if (args.length < 2) { + if (args.length < 1) {
+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); + sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
+ return; + return;
+ } + }
+ +
+ String debugType = args[1].toLowerCase(Locale.ENGLISH); + final String debugType = args[0].toLowerCase(Locale.ENGLISH);
+ switch (debugType) { + switch (debugType) {
+ case "chunks": + case "chunks" -> {
+ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { + 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)); + sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED));
+ break; + break;
+ } + }
@ -211,19 +215,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); + MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
+ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); + sender.sendMessage(text("Failed to dump chunk information, see console", RED));
+ } + }
+ + }
+ break; + // "help" & default
+ case "help": + default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
+ // fall through to default
+ default:
+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
+ return;
+ } + }
+ } + }
+ +
/* +}
* Ported from MinecraftForge - author: LexManos <LexManos@gmail.com> - License: LGPLv2.1
*/
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java --- a/src/main/java/net/minecraft/server/MCUtil.java

View file

@ -8,16 +8,16 @@ tickDistanceManager call can take up quite a bit in
the function. I saw approximately 1/3rd of the function the function. I saw approximately 1/3rd of the function
on the copy. 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { @@ -0,0 +0,0 @@ public final class ChunkDebugCommand implements PaperSubcommand {
int ticking = 0; int ticking = 0;
int entityTicking = 0; int entityTicking = 0;
- for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { - for (final 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.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map
if (chunk.getFullChunkNowUnchecked() == null) { if (chunk.getFullChunkNowUnchecked() == null) {
continue; continue;
} }

View file

@ -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 list is only used in the tick and tickPassenger methods, so we can safely not add the
markers to it. 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { @@ -0,0 +0,0 @@ public final class EntityCommand implements PaperSubcommand {
ChunkPos chunk = e.chunkPosition(); ChunkPos chunk = e.chunkPosition();
info.left++; info.left++;
info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1);
- if (!chunkProviderServer.isPositionTicking(e)) { - if (!chunkProviderServer.isPositionTicking(e)) {
+ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. + if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked.
nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); 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 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java --- a/src/main/java/net/minecraft/server/level/ServerLevel.java

View file

@ -6,76 +6,87 @@ Subject: [PATCH] Fix Light Command
This lets you run /paper fixlight <chunkRadius> (max 5) to automatically This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
fix all light data in the chunks. 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@ import net.minecraft.server.MinecraftServer; @@ -0,0 +0,0 @@ package io.papermc.paper.command;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache; import io.papermc.paper.command.subcommands.ChunkDebugCommand;
import net.minecraft.server.level.ServerLevel; import io.papermc.paper.command.subcommands.EntityCommand;
-import net.minecraft.world.entity.Entity; +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.ServerPlayer;
+import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.server.level.ThreadedLevelLightEngine;
import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.server.MCUtil; +import org.bukkit.command.CommandSender;
@@ -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 org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player; +import org.bukkit.entity.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.File; +import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.LocalDateTime; +import org.checkerframework.framework.qual.DefaultQualifier;
import java.time.format.DateTimeFormatter; +
+import java.util.ArrayDeque; +import static net.kyori.adventure.text.Component.text;
import java.util.ArrayList; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
import java.util.Arrays; +import static net.kyori.adventure.text.format.NamedTextColor.RED;
import java.util.Collection; +
import java.util.Collections; +@DefaultQualifier(NonNull.class)
+import java.util.Deque; +public final class FixLightCommand implements PaperSubcommand {
import java.util.Iterator; + @Override
import java.util.List; + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
import java.util.Locale; + this.doFixLight(sender, args);
@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; + return true;
+ }
public class PaperCommand extends Command { +
private static final String BASE_PERM = "bukkit.command.paper."; + private void doFixLight(final CommandSender sender, final String[] args) {
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>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) {
+ if (!(sender instanceof Player)) { + if (!(sender instanceof Player)) {
+ sender.sendMessage("Only players can use this command"); + sender.sendMessage(text("Only players can use this command", RED));
+ return; + return;
+ } + }
+ @Nullable Runnable post = null;
+ int radius = 2; + int radius = 2;
+ if (args.length > 1) { + if (args.length > 0) {
+ try { + try {
+ radius = Math.min(5, Integer.parseInt(args[1])); + final int parsed = Integer.parseInt(args[0]);
+ } catch (Exception e) { + if (parsed < 0) {
+ sender.sendMessage("Not a number"); + 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; + return;
+ } + }
+
+ } + }
+ +
+ CraftPlayer player = (CraftPlayer) sender; + CraftPlayer player = (CraftPlayer) sender;
@ -85,28 +96,37 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation());
+ Deque<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + Deque<ChunkPos> 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<ChunkPos> queue) { + private void updateLight(
+ ChunkPos coord = queue.poll(); + final CommandSender sender,
+ final ServerLevel world,
+ final ThreadedLevelLightEngine lightengine,
+ final Deque<ChunkPos> queue,
+ final @Nullable Runnable done
+ ) {
+ @Nullable ChunkPos coord = queue.poll();
+ if (coord == null) { + if (coord == null) {
+ sender.sendMessage("All Chunks Light updated"); + sender.sendMessage(text("All Chunks Light updated", GREEN));
+ if (done != null) {
+ done.run();
+ }
+ return; + return;
+ } + }
+ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { + world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
+ if (ex != null) { + if (ex != null) {
+ sender.sendMessage("Error loading chunk " + coord); + sender.sendMessage(text("Error loading chunk " + coord, RED));
+ updateLight(sender, world, lightengine, queue); + updateLight(sender, world, lightengine, queue, done);
+ return; + 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) { + if (chunk == null) {
+ updateLight(sender, world, lightengine, queue); + updateLight(sender, world, lightengine, queue, done);
+ return; + return;
+ } + }
+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue + 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 cx = chunk.getPos().x << 4;
+ int cz = chunk.getPos().z << 4; + int cz = chunk.getPos().z << 4;
+ for (int y = 0; y < world.getHeight(); y++) { + for (int y = 0; y < world.getHeight(); y++) {
@ -118,21 +138,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ } + }
+ lightengine.tryScheduleUpdate(); + lightengine.tryScheduleUpdate();
+ ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); + @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey);
+ if (visibleChunk != null) { + if (visibleChunk != null) {
+ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { + world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> {
+ MinecraftServer.getServer().processQueue.add(() -> { + MinecraftServer.getServer().processQueue.add(() -> {
+ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false); + 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 { + } else {
+ updateLight(sender, world, lightengine, queue); + updateLight(sender, world, lightengine, queue, done);
+ } + }
+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize); + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize);
+ }, MinecraftServer.getServer()); + }, MinecraftServer.getServer());
+ } + }
} +}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java --- a/src/main/java/net/minecraft/server/level/ChunkMap.java

View file

@ -4,16 +4,16 @@ Date: Sun, 18 Nov 2018 19:49:56 +0000
Subject: [PATCH] Make the default permission message configurable 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) {
private static boolean testPermission(CommandSender commandSender, String permission) { return true;
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)); - 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));
+ commandSender.sendMessage(Bukkit.permissionMessage()); + sender.sendMessage(Bukkit.permissionMessage());
return false; return false;
} }

View file

@ -4,102 +4,50 @@ Date: Mon, 29 Feb 2016 21:02:09 -0600
Subject: [PATCH] Paper command 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 new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null --- /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 @@ @@ -0,0 +0,0 @@
+package com.destroystokyo.paper; +package io.papermc.paper.command;
+ +
+import com.google.common.base.Functions; +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.Iterables;
+import com.google.common.collect.Lists; +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.ArrayList;
+import java.util.Arrays; +import java.util.Arrays;
+import java.util.Collection; +import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator; +import java.util.Iterator;
+import java.util.List; +import java.util.List;
+import java.util.Locale; +import net.minecraft.resources.ResourceLocation;
+import java.util.Map; +import org.bukkit.command.CommandSender;
+import java.util.Set; +import org.checkerframework.checker.nullness.qual.NonNull;
+import java.util.stream.Collectors; +import org.checkerframework.framework.qual.DefaultQualifier;
+ +
+import static net.kyori.adventure.text.Component.text; +@DefaultQualifier(NonNull.class)
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +public final class CommandUtil {
+import static net.kyori.adventure.text.format.NamedTextColor.RED; + private CommandUtil() {
+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<String> SUBCOMMANDS = ImmutableSet.<String>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<String> 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();
+ } + }
+ +
+ // Code from Mojang - copyright them + // Code from Mojang - copyright them
+ public static List<String> getListMatchingLast(CommandSender sender, String[] args, String... matches) { + public static List<String> getListMatchingLast(
+ return getListMatchingLast(sender, args, (Collection) Arrays.asList(matches)); + 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()); + return s1.regionMatches(true, 0, s, 0, s.length());
+ } + }
+ +
+ public static List<String> getListMatchingLast(CommandSender sender, String[] strings, Collection<?> collection) { + public static List<String> getListMatchingLast(
+ final CommandSender sender,
+ final String[] strings,
+ final Collection<?> collection
+ ) {
+ String last = strings[strings.length - 1]; + String last = strings[strings.length - 1];
+ ArrayList<String> results = Lists.newArrayList(); + ArrayList<String> results = Lists.newArrayList();
+ +
@ -109,7 +57,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ while (iterator.hasNext()) { + while (iterator.hasNext()) {
+ String s1 = (String) iterator.next(); + 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); + results.add(s1);
+ } + }
+ } + }
@ -130,171 +78,156 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return results; + return results;
+ } + }
+ // end copy stuff + // 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<String, PaperSubcommand> SUBCOMMANDS = Util.make(() -> {
+ final Map<Set<String>, 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<String, String> ALIASES = Util.make(() -> {
+ final Map<String, Set<String>> 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<String> 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 + @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { + public List<String> tabComplete(
+ if (!testPermission(sender)) return true; + 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<String, PaperSubcommand> 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) { + if (args.length == 0) {
+ sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ return false; + return false;
+ } + }
+ if (SUBCOMMANDS.contains(args[0].toLowerCase(Locale.ENGLISH))) { + final @Nullable Pair<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
+ if (!testPermission(sender, args[0].toLowerCase(Locale.ENGLISH))) return true; +
+ } + if (subCommand == null) {
+ switch (args[0].toLowerCase(Locale.ENGLISH)) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ case "heap": + return false;
+ 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;
+ } + }
+ +
+ 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);
+ } + }
+ +
+ /* + private static @Nullable Pair<String, PaperSubcommand> resolveCommand(String label) {
+ * Ported from MinecraftForge - author: LexManos <LexManos@gmail.com> - License: LGPLv2.1 + label = label.toLowerCase(Locale.ENGLISH);
+ */ + @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label);
+ private void listEntities(CommandSender sender, String[] args) { + if (subCommand == null) {
+ if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) { + final @Nullable String command = ALIASES.get(label);
+ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); + if (command != null) {
+ return; + label = command;
+ subCommand = SUBCOMMANDS.get(command);
+ }
+ } + }
+ +
+ switch (args[1].toLowerCase(Locale.ENGLISH)) { + if (subCommand != null) {
+ case "list": + return Pair.of(label, subCommand);
+ 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<ResourceLocation> 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<ResourceLocation, MutablePair<Integer, Map<ChunkPos, Integer>>> 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<ResourceLocation, Integer> nonEntityTicking = Maps.newHashMap();
+ ServerChunkCache chunkProviderServer = world.getChunkSource();
+
+ world.getAllEntities().forEach(e -> {
+ ResourceLocation key = EntityType.getKey(e.getType());
+
+ MutablePair<Integer, Map<ChunkPos, Integer>> 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<Integer, Map<ChunkPos, Integer>> 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<Pair<ResourceLocation, Integer>> 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;
+ } + }
+ }
+ +
+ private void dumpHeap(CommandSender sender) { + return null;
+ 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));
+ } + }
+} +}
diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java 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 @@ @@ -0,0 +0,0 @@
+package io.papermc.paper.command; +package io.papermc.paper.command;
+ +
+import com.destroystokyo.paper.PaperCommand;
+import net.minecraft.server.MinecraftServer; +import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command; +import org.bukkit.command.Command;
+ +
+import java.util.HashMap; +import java.util.HashMap;
+import java.util.Map; +import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+ +
+@DefaultQualifier(NonNull.class)
+public final class PaperCommands { +public final class PaperCommands {
+ +
+ private 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<String> 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<String> 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 <LexManos@gmail.com> - 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<ResourceLocation> 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<ResourceLocation, MutablePair<Integer, Map<ChunkPos, Integer>>> 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<ResourceLocation, Integer> nonEntityTicking = Maps.newHashMap();
+ ServerChunkCache chunkProviderServer = world.getChunkSource();
+ world.getAllEntities().forEach(e -> {
+ ResourceLocation key = EntityType.getKey(e.getType());
+
+ MutablePair<Integer, Map<ChunkPos, Integer>> 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<Integer, Map<ChunkPos, Integer>> 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<Pair<ResourceLocation, Integer>> 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 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java

View file

@ -5,60 +5,88 @@ Subject: [PATCH] Paper dumpitem command
Let's you quickly view the item in your hands NBT data 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@ import org.bukkit.command.CommandSender; @@ -0,0 +0,0 @@
import org.bukkit.craftbukkit.CraftServer; package io.papermc.paper.command;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer; 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.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player; +import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.File; +import org.checkerframework.framework.qual.DefaultQualifier;
import java.io.FileOutputStream; +
@@ -0,0 +0,0 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; +import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class PaperCommand extends Command { +import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
private static final String BASE_PERM = "bukkit.command.paper."; +import static net.kyori.adventure.text.format.TextDecoration.ITALIC;
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build(); +
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build(); +@DefaultQualifier(NonNull.class)
+public final class DumpItemCommand implements PaperSubcommand {
public PaperCommand(String name) { + @Override
super(name); + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { + this.doDumpItem(sender);
case "reload": + return true;
doReload(sender); + }
break; +
+ case "dumpitem": + private void doDumpItem(final CommandSender sender) {
+ 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) {
+ if (!(sender instanceof Player)) { + if (!(sender instanceof Player)) {
+ sender.sendMessage("Only players can use this command"); + sender.sendMessage("Only players can use this command");
+ return; + return;
+ } + }
+ ItemStack itemInHand = ((CraftPlayer) sender).getItemInHand(); + final ItemStack itemStack = CraftItemStack.asNMSCopy(((CraftPlayer) sender).getItemInHand());
+ net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(itemInHand); + final @Nullable CompoundTag tag = itemStack.getTag();
+ net.minecraft.nbt.CompoundTag tag = itemStack.getTag(); + final @Nullable Component nbtComponent = tag == null ? null : PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag));
+ if (tag != null) { + final String itemId = Objects.requireNonNull(((CraftWorld) ((CraftPlayer) sender).getWorld()).getHandle().registryAccess()
+ net.kyori.adventure.text.Component nbtComponent = io.papermc.paper.adventure.PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); + .registryOrThrow(Registry.ITEM_REGISTRY).getKey(itemStack.getItem())).toString();
+ Bukkit.getConsoleSender().sendMessage(nbtComponent); + final Component message = text()
+ sender.sendMessage(nbtComponent); + .append(text(itemId, YELLOW))
+ } else { + .apply(b -> {
+ sender.sendMessage("Item does not have NBT"); + 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");

View file

@ -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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java --- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
@@ -0,0 +0,0 @@ public class PaperCommand extends Command { @@ -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<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
updateLight(sender, world, lightengine, queue, post);
} }
+ // Paper start - rewrite light engine + // 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(); + long start = System.nanoTime();
+ java.util.LinkedHashSet<ChunkPos> chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos + java.util.LinkedHashSet<ChunkPos> 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]; + int[] pending = new int[1];
+ for (java.util.Iterator<ChunkPos> iterator = chunks.iterator(); iterator.hasNext();) { + for (java.util.Iterator<ChunkPos> iterator = chunks.iterator(); iterator.hasNext(); ) {
+ final ChunkPos chunkPos = iterator.next(); + 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)) { + if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) {
+ // cannot relight this chunk + // cannot relight this chunk
+ iterator.remove(); + iterator.remove();
@ -4386,51 +4430,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ int[] relitChunks = new int[1]; + int[] relitChunks = new int[1];
+ lightengine.relight(chunks, + lightengine.relight(chunks,
+ (ChunkPos chunkPos) -> { + (ChunkPos chunkPos) -> {
+ ++relitChunks[0]; + ++relitChunks[0];
+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( + sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append(
+ text("Relit chunk ", BLUE), text(chunkPos.toString()), + text("Relit chunk ", BLUE), text(chunkPos.toString()),
+ text(", progress: ", BLUE), text((int)(Math.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%") + text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%")
+ )); + ));
+ }, + },
+ (int totalRelit) -> { + (int totalRelit) -> {
+ final long end = System.nanoTime(); + final long end = System.nanoTime();
+ final long diff = Math.round(1.0e-6*(end - start)); + final long diff = Math.round(1.0e-6 * (end - start));
+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( + sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append(
+ text("Relit ", BLUE), text(totalRelit), + text("Relit ", BLUE), text(totalRelit),
+ text(" chunks. Took ", BLUE), text(diff + "ms") + 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"))); + sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks")));
+ } + }
+ // Paper end - rewrite light engine + // Paper end - rewrite light engine
+ +
private void doFixLight(CommandSender sender, String[] args) { private void updateLight(
if (!(sender instanceof Player)) { final CommandSender sender,
sender.sendMessage("Only players can use this command"); final ServerLevel world,
@@ -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<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
updateLight(sender, world, lightengine, queue);
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java