2020-05-06 09:48:49 +00:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2019-06-03 01:25:12 +00:00
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 1 Jun 2019 13:00:55 -0700
Subject: [PATCH] Chunk debug command
Prints all chunk information to a text file into the debug
folder in the root server folder. The format is in JSON, and
the data format is described in MCUtil#dumpChunks(File)
The command will output server version and all online players to the
file as well. We do not log anything but the location, world and
username of the player.
Also logs the value of these config values (note not all are paper's):
- keep spawn loaded value
- spawn radius
- view distance
Each chunk has the following logged:
- Coordinate
- Ticket level & its corresponding state
- Whether it is queued for unload
- Chunk status (may be unloaded)
- All tickets on the chunk
Example log:
https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt
For references on certain keywords (ticket, status, etc), please see:
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
2021-03-16 07:19:45 +00:00
index b67bd98cca4a06bc0ebaed577195dffc3b3251ec..a7a02072e5c7ce62cbecbb638fcc74abf2fb57ee 100644
2019-06-03 01:25:12 +00:00
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
2021-03-16 07:19:45 +00:00
@@ -9,10 +9,12 @@ import com.google.common.collect.Maps;
import net.minecraft.resources.MinecraftKey;
2021-01-05 21:25:45 +00:00
import net.minecraft.server.MinecraftServer;
2021-03-16 07:19:45 +00:00
import net.minecraft.server.level.ChunkProviderServer;
+import net.minecraft.server.level.PlayerChunk;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.level.ChunkCoordIntPair;
+import net.minecraft.server.MCUtil;
2021-01-05 21:25:45 +00:00
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
2021-03-16 07:19:45 +00:00
import org.bukkit.Bukkit;
2021-01-05 21:25:45 +00:00
@@ -41,7 +43,7 @@ import java.util.stream.Collectors;
2019-06-03 01:25:12 +00:00
2021-01-05 21:25:45 +00:00
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();
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
2019-06-03 01:25:12 +00:00
2021-01-05 21:25:45 +00:00
public PaperCommand(String name) {
super(name);
@@ -69,6 +71,21 @@ public class PaperCommand extends Command {
2019-06-03 01:25:12 +00:00
if (args.length == 3)
2021-01-05 21:25:45 +00:00
return getListMatchingLast(sender, args, EntityTypes.getEntityNameList().stream().map(MinecraftKey::toString).sorted().toArray(String[]::new));
2019-06-03 01:25:12 +00:00
break;
+ case "debug":
+ if (args.length == 2) {
2021-01-05 21:25:45 +00:00
+ return getListMatchingLast(sender, args, "help", "chunks");
2019-06-03 01:25:12 +00:00
+ }
2019-06-14 05:51:37 +00:00
+ break;
+ case "chunkinfo":
+ List<String> worldNames = new ArrayList<>();
+ worldNames.add("*");
+ for (org.bukkit.World world : Bukkit.getWorlds()) {
+ worldNames.add(world.getName());
+ }
+ if (args.length == 2) {
2021-01-05 21:25:45 +00:00
+ return getListMatchingLast(sender, args, worldNames);
2019-06-14 05:51:37 +00:00
+ }
+ break;
2019-06-03 01:25:12 +00:00
}
return Collections.emptyList();
}
2021-01-05 21:25:45 +00:00
@@ -135,6 +152,12 @@ public class PaperCommand extends Command {
2019-06-03 01:25:12 +00:00
case "reload":
doReload(sender);
break;
+ case "debug":
+ doDebug(sender, args);
2019-06-14 05:51:37 +00:00
+ break;
+ case "chunkinfo":
+ doChunkInfo(sender, args);
2019-06-03 01:25:12 +00:00
+ break;
case "ver":
2021-01-05 21:25:45 +00:00
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)
2019-06-03 01:25:12 +00:00
case "version":
2021-01-05 21:25:45 +00:00
@@ -152,6 +175,114 @@ public class PaperCommand extends Command {
2019-06-03 01:25:12 +00:00
return true;
}
2019-06-14 05:51:37 +00:00
+ private void doChunkInfo(CommandSender sender, String[] args) {
+ List<org.bukkit.World> worlds;
+ if (args.length < 2 || args[1].equals("*")) {
+ worlds = Bukkit.getWorlds();
+ } else {
+ worlds = new ArrayList<>(args.length - 1);
+ for (int i = 1; i < args.length; ++i) {
+ org.bukkit.World world = Bukkit.getWorld(args[i]);
+ if (world == null) {
+ sender.sendMessage(ChatColor.RED + "World '" + args[i] + "' is invalid");
+ return;
+ }
+ worlds.add(world);
+ }
+ }
+
2020-08-25 20:34:46 +00:00
+ int accumulatedTotal = 0;
+ int accumulatedInactive = 0;
+ int accumulatedBorder = 0;
+ int accumulatedTicking = 0;
+ int accumulatedEntityTicking = 0;
+
2019-06-14 05:51:37 +00:00
+ for (org.bukkit.World bukkitWorld : worlds) {
+ WorldServer world = ((CraftWorld)bukkitWorld).getHandle();
+
+ int total = 0;
+ int inactive = 0;
+ int border = 0;
+ int ticking = 0;
+ int entityTicking = 0;
+
+ for (PlayerChunk chunk : world.getChunkProvider().playerChunkMap.updatingChunks.values()) {
+ if (chunk.getFullChunkIfCached() == null) {
+ continue;
+ }
+
+ ++total;
+
+ PlayerChunk.State state = PlayerChunk.getChunkState(chunk.getTicketLevel());
+
+ switch (state) {
+ case INACCESSIBLE:
+ ++inactive;
+ continue;
+ case BORDER:
+ ++border;
+ continue;
+ case TICKING:
+ ++ticking;
+ continue;
+ case ENTITY_TICKING:
+ ++entityTicking;
+ continue;
+ }
+ }
+
2020-08-25 20:34:46 +00:00
+ accumulatedTotal += total;
+ accumulatedInactive += inactive;
+ accumulatedBorder += border;
+ accumulatedTicking += ticking;
+ accumulatedEntityTicking += entityTicking;
+
2019-06-15 04:21:39 +00:00
+ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + bukkitWorld.getName() + ChatColor.DARK_AQUA + ":");
2019-06-14 05:51:37 +00:00
+ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + total + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
+ + inactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + border + ChatColor.BLUE + " Ticking: "
+ + ChatColor.DARK_AQUA + ticking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + entityTicking);
+ }
2020-08-25 20:34:46 +00:00
+ if (worlds.size() > 1) {
+ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + "all listed worlds" + ChatColor.DARK_AQUA + ":");
+ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + accumulatedTotal + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
+ + accumulatedInactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + accumulatedBorder + ChatColor.BLUE + " Ticking: "
+ + ChatColor.DARK_AQUA + accumulatedTicking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + accumulatedEntityTicking);
+ }
2019-06-14 05:51:37 +00:00
+ }
+
2019-06-03 01:25:12 +00:00
+ private void doDebug(CommandSender sender, String[] args) {
+ if (args.length < 2) {
+ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
+ return;
+ }
+
+ String debugType = args[1].toLowerCase(Locale.ENGLISH);
+ switch (debugType) {
+ case "chunks":
+ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) {
+ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file");
+ break;
+ }
+ File file = new File(new File(new File("."), "debug"),
+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
+ sender.sendMessage(ChatColor.GREEN + "Writing chunk information dump to " + file.toString());
+ try {
+ MCUtil.dumpChunks(file);
+ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!");
+ } catch (Throwable thr) {
+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
+ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console");
+ }
+
+ break;
+ case "help":
+ // fall through to default
+ default:
+ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
+ 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
2021-03-16 07:19:45 +00:00
index 67806eaab081e938cd99a0d74225b1b7744ff2d7..5f8b8b5fac14983fbeb64ce3f09bf9a885179e8c 100644
2019-06-03 01:25:12 +00:00
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
2021-03-16 07:19:45 +00:00
@@ -13,7 +13,13 @@ import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.RayTrace;
import net.minecraft.world.level.World;
2020-05-22 23:03:48 +00:00
import org.apache.commons.lang.exception.ExceptionUtils;
2019-06-03 01:25:12 +00:00
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
import com.mojang.authlib.GameProfile;
+import com.mojang.datafixers.util.Either;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
2020-05-22 23:03:48 +00:00
import org.bukkit.craftbukkit.CraftWorld;
2021-03-16 07:19:45 +00:00
@@ -22,8 +28,11 @@ import org.spigotmc.AsyncCatcher;
2019-06-03 01:25:12 +00:00
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.*;
+import java.util.ArrayList;
2020-04-19 10:01:07 +00:00
import java.util.List;
2019-06-03 01:25:12 +00:00
import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
2020-05-17 01:38:19 +00:00
import java.util.concurrent.LinkedBlockingQueue;
2021-03-16 07:19:45 +00:00
@@ -529,4 +538,170 @@ public final class MCUtil {
2019-06-03 01:25:12 +00:00
return null;
}
+
+ public static ChunkStatus getChunkStatus(PlayerChunk chunk) {
+ List<ChunkStatus> statuses = ChunkProviderServer.getPossibleChunkStatuses();
+ for (int i = statuses.size() - 1; i >= 0; --i) {
+ ChunkStatus curr = statuses.get(i);
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = chunk.getStatusFutureUnchecked(curr);
+ if (future != PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE) {
+ return curr;
+ }
+ }
+ return null; // unloaded
+ }
+
+ public static void dumpChunks(File file) throws IOException {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ /*
+ * Json format:
+ *
+ * Main data format:
+ * -server-version:<string>
+ * -data-version:<int>
+ * -worlds:
+ * -name:<world name>
+ * -view-distance:<int>
+ * -keep-spawn-loaded:<boolean>
+ * -keep-spawn-loaded-range:<int>
+ * -visible-chunk-count:<int>
+ * -loaded-chunk-count:<int>
+ * -verified-fully-loaded-chunks:<int>
+ * -players:<array of player>
+ * -chunk-data:<array of chunks>
+ *
+ * Player format:
+ * -name:<string>
+ * -x:<double>
+ * -y:<double>
+ * -z:<double>
+ *
+ * Chunk Format:
+ * -x:<integer>
+ * -z:<integer>
+ * -ticket-level:<integer>
+ * -state:<string>
+ * -queued-for-unload:<boolean>
+ * -status:<string>
+ * -tickets:<array of tickets>
+ *
+ *
+ * Ticket format:
+ * -ticket-type:<string>
+ * -ticket-level:<int>
+ * -add-tick:<long>
+ * -object-reason:<string> // This depends on the type of ticket. ie POST_TELEPORT -> entity id
+ */
+ List<org.bukkit.World> worlds = org.bukkit.Bukkit.getWorlds();
+ JsonObject data = new JsonObject();
+
+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion());
+ data.addProperty("data-version", 0);
+
+ JsonArray worldsData = new JsonArray();
+
+ for (org.bukkit.World bukkitWorld : worlds) {
+ JsonObject worldData = new JsonObject();
+
+ WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
+ PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap;
+ Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = chunkMap.visibleChunks;
2020-08-02 05:39:36 +00:00
+ ChunkMapDistance chunkMapDistance = chunkMap.chunkDistanceManager;
2019-06-03 01:25:12 +00:00
+ List<PlayerChunk> allChunks = new ArrayList<>(visibleChunks.values());
+ List<EntityPlayer> players = world.players;
+
+ int fullLoadedChunks = 0;
+
+ for (PlayerChunk chunk : allChunks) {
2019-06-22 20:07:31 +00:00
+ if (chunk.getFullChunkIfCached() != null) {
2019-06-03 01:25:12 +00:00
+ ++fullLoadedChunks;
+ }
+ }
+
+ // sorting by coordinate makes the log easier to read
+ allChunks.sort((PlayerChunk v1, PlayerChunk v2) -> {
+ if (v1.location.x != v2.location.x) {
+ return Integer.compare(v1.location.x, v2.location.x);
+ }
+ return Integer.compare(v1.location.z, v2.location.z);
+ });
+
2020-06-26 06:29:44 +00:00
+ worldData.addProperty("name", world.getWorld().getName());
2019-06-03 01:25:12 +00:00
+ worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
+ worldData.addProperty("visible-chunk-count", visibleChunks.size());
+ worldData.addProperty("loaded-chunk-count", chunkMap.loadedChunks.size());
+ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks);
+
+ JsonArray playersData = new JsonArray();
+
+ for (EntityPlayer player : players) {
+ JsonObject playerData = new JsonObject();
+
+ playerData.addProperty("name", player.getName());
2019-12-12 18:45:00 +00:00
+ playerData.addProperty("x", player.locX());
+ playerData.addProperty("y", player.locY());
+ playerData.addProperty("z", player.locZ());
2019-06-03 01:25:12 +00:00
+
+ playersData.add(playerData);
+
+ }
+
+ worldData.add("players", playersData);
+
+ JsonArray chunksData = new JsonArray();
+
+ for (PlayerChunk playerChunk : allChunks) {
+ JsonObject chunkData = new JsonObject();
+
+ Set<Ticket<?>> tickets = chunkMapDistance.tickets.get(playerChunk.location.pair());
+ ChunkStatus status = getChunkStatus(playerChunk);
+
+ chunkData.addProperty("x", playerChunk.location.x);
+ chunkData.addProperty("z", playerChunk.location.z);
+ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
+ chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString());
+ chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair()));
+ chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
+
+ JsonArray ticketsData = new JsonArray();
+
+ if (tickets != null) {
+ for (Ticket<?> ticket : tickets) {
+ JsonObject ticketData = new JsonObject();
+
+ ticketData.addProperty("ticket-type", ticket.getTicketType().toString());
+ ticketData.addProperty("ticket-level", ticket.getTicketLevel());
+ ticketData.addProperty("object-reason", String.valueOf(ticket.getObjectReason()));
+ ticketData.addProperty("add-tick", ticket.getCreationTick());
+
+ ticketsData.add(ticketData);
+ }
+ }
+
+ chunkData.add("tickets", ticketsData);
+ chunksData.add(chunkData);
+ }
+
+
+ worldData.add("chunk-data", chunksData);
+ worldsData.add(worldData);
+ }
+
+ data.add("worlds", worldsData);
+
+ 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);
+ }
+ }
}
2021-03-16 07:19:45 +00:00
diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
index eb576566708d50c002e73aa746d2bc58b9b04c2b..0b2973a7272890ebee205bd74e7d75c174ceeb9a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
@@ -46,7 +46,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
public class ChunkProviderServer extends IChunkProvider {
- private static final List<ChunkStatus> b = ChunkStatus.a();
+ private static final List<ChunkStatus> b = ChunkStatus.a(); static final List<ChunkStatus> getPossibleChunkStatuses() { return ChunkProviderServer.b; } // Paper - OBFHELPER
private final ChunkMapDistance chunkMapDistance;
public final ChunkGenerator chunkGenerator;
private final WorldServer world;
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java
index 2c3d9a5d118cc4f3b5e78daf943911bb7386488a..9891cf98f8c740f84f9135ee8176e67abb648b3a 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java
@@ -54,7 +54,7 @@ public class PlayerChunk {
2019-06-03 01:25:12 +00:00
public int oldTicketLevel;
private int ticketLevel;
private int n;
- private final ChunkCoordIntPair location;
+ final ChunkCoordIntPair location; // Paper - private -> package
2020-08-25 02:22:08 +00:00
private boolean p;
private final ShortSet[] dirtyBlocks;
2019-06-03 01:25:12 +00:00
private int r;
2021-03-16 07:19:45 +00:00
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
index 90a3ca4e92fefc1f20b2878686030e9123238384..bdcc4542eb5da0a80f00a1d41d4773bb3be3996a 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
@@ -104,7 +104,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
2019-06-03 01:25:12 +00:00
public final Long2ObjectLinkedOpenHashMap<PlayerChunk> updatingChunks = new Long2ObjectLinkedOpenHashMap();
public volatile Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks;
private final Long2ObjectLinkedOpenHashMap<PlayerChunk> pendingUnload;
- private final LongSet loadedChunks;
+ final LongSet loadedChunks; // Paper - private -> package
public final WorldServer world;
private final LightEngineThreaded lightEngine;
private final IAsyncTaskHandler<Runnable> executor;
2021-03-16 07:19:45 +00:00
diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
index 51ef4adf66c1e21093e63ab46fa47e66c2425fdb..e06fe77f6ea05a93e95fce223bcfd0d16394f96f 100644
--- a/src/main/java/net/minecraft/server/level/Ticket.java
+++ b/src/main/java/net/minecraft/server/level/Ticket.java
2019-06-03 01:25:12 +00:00
@@ -6,8 +6,8 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
private final TicketType<T> a;
private final int b;
2019-07-20 04:01:24 +00:00
- public final T identifier;
2019-12-12 16:20:43 +00:00
- private long d;
2019-07-20 04:01:24 +00:00
+ public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER
2019-12-12 16:20:43 +00:00
+ private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER
2019-06-03 01:25:12 +00:00
2019-12-12 16:20:43 +00:00
protected Ticket(TicketType<T> tickettype, int i, T t0) {
2019-06-03 01:25:12 +00:00
this.a = tickettype;
2019-12-12 16:20:43 +00:00
@@ -51,6 +51,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
2019-06-03 01:25:12 +00:00
return this.a;
}
+ public final int getTicketLevel() { return this.b(); } // Paper - OBFHELPER
public int b() {
return this.b;
}