// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.util;

import javax.annotation.Nullable;
import java.lang.management.MemoryUsage;
import com.hypixel.hytale.component.metric.ArchetypeChunkData;
import com.hypixel.hytale.component.system.ISystem;
import com.hypixel.hytale.component.ComponentRegistry;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap;
import com.hypixel.hytale.server.core.universe.world.worldgen.WorldGenTimingsCollector;
import com.hypixel.hytale.protocol.io.PacketStatsRecorder;
import java.util.List;
import com.hypixel.hytale.server.core.ShutdownReason;
import com.hypixel.hytale.metrics.metric.HistoricMetric;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import com.hypixel.hytale.metrics.InitStackThread;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.lang.management.MemoryPoolMXBean;
import java.util.Arrays;
import java.lang.management.GarbageCollectorMXBean;
import com.sun.management.OperatingSystemMXBean;
import java.util.Date;
import java.lang.management.ManagementFactory;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.storage.IndexedStorageFile;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import com.hypixel.hytale.server.core.universe.world.storage.provider.IndexedStorageChunkStorageProvider;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.HytaleServerConfig;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.PluginBase;
import com.hypixel.hytale.server.core.io.PacketHandler;
import com.hypixel.hytale.protocol.packets.connection.PongType;
import com.hypixel.hytale.common.util.FormatUtil;
import java.time.temporal.TemporalAccessor;
import java.time.LocalDateTime;
import com.hypixel.hytale.logger.backend.HytaleFileHandler;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.LinkOption;
import java.nio.file.Paths;
import java.io.PrintWriter;
import java.io.Writer;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.io.OutputStream;
import org.bouncycastle.util.io.TeeOutputStream;
import java.io.FileOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.ByteBuf;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.entity.entities.player.CameraManager;
import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager;
import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent;
import com.hypixel.hytale.server.core.universe.world.World;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executor;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Iterator;
import java.util.UUID;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import com.hypixel.hytale.metrics.MetricsRegistry;
import org.bson.BsonString;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import com.hypixel.hytale.plugin.early.EarlyPluginLoader;
import org.bson.BsonValue;
import java.util.Map;
import org.bson.BsonDocument;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import org.bson.BsonArray;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.HytaleServer;
import java.nio.file.Path;

public class DumpUtil
{
    @Nonnull
    public static Path dumpToJson() throws IOException {
        final Map<UUID, BsonDocument> playerComponents = collectPlayerComponentMetrics();
        final BsonDocument bson = HytaleServer.METRICS_REGISTRY.dumpToBson(HytaleServer.get()).asDocument();
        final BsonDocument universeBson = Universe.METRICS_REGISTRY.dumpToBson(Universe.get()).asDocument();
        final BsonArray playersArray = new BsonArray();
        for (final PlayerRef ref : Universe.get().getPlayers()) {
            final BsonDocument playerBson = PlayerRef.METRICS_REGISTRY.dumpToBson(ref).asDocument();
            final BsonDocument componentData = playerComponents.get(ref.getUuid());
            if (componentData != null) {
                playerBson.putAll(componentData);
            }
            playersArray.add(playerBson);
        }
        universeBson.put("Players", playersArray);
        bson.put("Universe", universeBson);
        final BsonArray earlyPluginsArray = new BsonArray();
        for (final ClassTransformer transformer : EarlyPluginLoader.getTransformers()) {
            earlyPluginsArray.add(new BsonString(transformer.getClass().getName()));
        }
        bson.put("EarlyPlugins", earlyPluginsArray);
        final Path path = MetricsRegistry.createDumpPath(".dump.json");
        Files.writeString(path, BsonUtil.toJson(bson), new OpenOption[0]);
        return path;
    }
    
    @Nonnull
    private static Map<UUID, BsonDocument> collectPlayerComponentMetrics() {
        final ConcurrentHashMap<UUID, BsonDocument> result = new ConcurrentHashMap<UUID, BsonDocument>();
        final Collection<World> worlds = Universe.get().getWorlds().values();
        final CompletableFuture[] futures = worlds.stream().map(world -> CompletableFuture.runAsync(() -> {
            for (final PlayerRef playerRef : world.getPlayerRefs()) {
                final BsonValue bson = PlayerRef.COMPONENT_METRICS_REGISTRY.dumpToBson(playerRef);
                result.put(playerRef.getUuid(), bson.asDocument());
            }
            return;
        }, world).orTimeout(30L, TimeUnit.SECONDS)).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf((CompletableFuture<?>[])futures).join();
        return result;
    }
    
    @Nonnull
    public static Map<UUID, PlayerTextData> collectPlayerTextData() {
        final ConcurrentHashMap<UUID, PlayerTextData> result = new ConcurrentHashMap<UUID, PlayerTextData>();
        final Collection<World> worlds = Universe.get().getWorlds().values();
        final CompletableFuture[] futures = worlds.stream().map(world -> CompletableFuture.runAsync(() -> {
            for (final PlayerRef playerRef : world.getPlayerRefs()) {
                final Ref<EntityStore> entityRef = playerRef.getReference();
                if (entityRef == null) {
                    continue;
                }
                else {
                    final Store<EntityStore> store = entityRef.getStore();
                    final MovementStatesComponent ms = store.getComponent(entityRef, MovementStatesComponent.getComponentType());
                    final MovementManager mm = store.getComponent(entityRef, MovementManager.getComponentType());
                    final CameraManager cm = store.getComponent(entityRef, CameraManager.getComponentType());
                    playerRef.getUuid();
                    new PlayerTextData(playerRef.getUuid(), (ms != null) ? ms.getMovementStates().toString() : null, (mm != null) ? mm.toString() : null, (cm != null) ? cm.toString() : null);
                    final PlayerTextData value;
                    final Object key;
                    result.put(key, value);
                }
            }
            return;
        }, world).orTimeout(30L, TimeUnit.SECONDS)).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf((CompletableFuture<?>[])futures).join();
        return result;
    }
    
    @Nonnull
    public static String hexDump(@Nonnull final ByteBuf buf) {
        final int readerIndex = buf.readerIndex();
        final byte[] data = new byte[buf.readableBytes()];
        buf.readBytes(data);
        buf.readerIndex(readerIndex);
        return hexDump(data);
    }
    
    @Nonnull
    public static String hexDump(@Nonnull final byte[] data) {
        if (data.length == 0) {
            return "[EMPTY ARRAY]";
        }
        return ByteBufUtil.hexDump(data);
    }
    
    @Nonnull
    public static Path dump(final boolean crash, final boolean printToConsole) {
        final Path filePath = createDumpPath(crash, "dump.txt");
        FileOutputStream fileOutputStream = null;
        OutputStream outputStream;
        try {
            fileOutputStream = new FileOutputStream(filePath.toFile());
            if (printToConsole) {
                outputStream = new TeeOutputStream(fileOutputStream, System.err);
            }
            else {
                outputStream = fileOutputStream;
            }
        }
        catch (final IOException e) {
            e.printStackTrace();
            System.err.println();
            System.err.println("FAILED TO GET OUTPUT STREAM FOR " + String.valueOf(filePath));
            System.err.println("FAILED TO GET OUTPUT STREAM FOR " + String.valueOf(filePath));
            System.err.println();
            outputStream = System.err;
        }
        try {
            write(new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)), true));
        }
        finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                }
                catch (final IOException ex) {}
            }
        }
        return filePath;
    }
    
    @Nonnull
    public static Path createDumpPath(final boolean crash, final String ext) {
        final Path path = Paths.get("dumps", new String[0]);
        try {
            if (!Files.exists(path, new LinkOption[0])) {
                Files.createDirectories(path, (FileAttribute<?>[])new FileAttribute[0]);
            }
        }
        catch (final IOException e) {
            e.printStackTrace();
        }
        final String name = (crash ? "crash-" : "") + HytaleFileHandler.LOG_FILE_DATE_FORMAT.format(LocalDateTime.now());
        Path filePath = path.resolve(name + "." + ext);
        for (int i = 0; Files.exists(filePath, new LinkOption[0]); filePath = path.resolve(name + "_" + i++ + "." + ext)) {}
        return filePath;
    }
    
    private static void write(@Nonnull final PrintWriter writer) {
        final int width = 200;
        final int height = 20;
        final long startNanos = System.nanoTime();
        section("Summary", () -> {
            final Universe universe = Universe.get();
            writer.println("World Count: " + universe.getWorlds().size());
            for (World world : universe.getWorlds().values()) {
                writer.println("- " + world.getName());
                final HistoricMetric metrics = world.getBufferedTickLengthMetricSet();
                final long[] periodsNanos = metrics.getPeriodsNanos();
                final int periodIndex = periodsNanos.length - 1;
                final long lastTime = periodsNanos[periodIndex];
                final double average = metrics.getAverage(periodIndex);
                final long max = metrics.calculateMax(periodIndex);
                final long min = metrics.calculateMin(periodIndex);
                final String length = FormatUtil.timeUnitToString(lastTime, TimeUnit.NANOSECONDS, true);
                final String value = FormatUtil.simpleTimeUnitFormat(min, average, max, TimeUnit.NANOSECONDS, TimeUnit.MILLISECONDS, 3);
                final String limit = FormatUtil.simpleTimeUnitFormat(world.getTickStepNanos(), TimeUnit.NANOSECONDS, 3);
                writer.printf("\tTick (%s): %s (Limit: %s)\n", length, value, limit);
                writer.printf("\tPlayer count: %d\n", world.getPlayerCount());
            }
            writer.println("Player count: " + universe.getPlayerCount());
            for (PlayerRef ref : universe.getPlayers()) {
                writer.printf("- %s (%s)\n", ref.getUsername(), ref.getUuid());
                final PacketHandler.PingInfo pingInfo = ref.getPacketHandler().getPingInfo(PongType.Raw);
                final HistoricMetric pingMetricSet = pingInfo.getPingMetricSet();
                final long min2 = pingMetricSet.calculateMin(1);
                final long average2 = (long)pingMetricSet.getAverage(1);
                final long max2 = pingMetricSet.calculateMax(1);
                writer.println("\tPing(raw) Min: " + FormatUtil.timeUnitToString(min2, PacketHandler.PingInfo.TIME_UNIT) + ", Avg: " + FormatUtil.timeUnitToString(average2, PacketHandler.PingInfo.TIME_UNIT) + ", Max: " + FormatUtil.timeUnitToString(max2, PacketHandler.PingInfo.TIME_UNIT));
            }
            return;
        }, writer);
        section("Server Lifecycle", () -> {
            final HytaleServer server = HytaleServer.get();
            writer.println("Boot Timestamp: " + String.valueOf(server.getBoot()));
            writer.println("Boot Start (nanos): " + server.getBootStart());
            writer.println("Booting: " + server.isBooting());
            writer.println("Booted: " + server.isBooted());
            writer.println("Shutting Down: " + server.isShuttingDown());
            final ShutdownReason shutdownReason = server.getShutdownReason();
            if (shutdownReason != null) {
                writer.println("Shutdown Reason: " + String.valueOf(shutdownReason));
            }
            return;
        }, writer);
        section("Early Plugins", () -> {
            final List<ClassTransformer> transformers = EarlyPluginLoader.getTransformers();
            writer.println("Class Transformer Count: " + transformers.size());
            for (ClassTransformer transformer : transformers) {
                writer.println("- " + transformer.getClass().getName() + " (priority=" + transformer.priority());
            }
            return;
        }, writer);
        section("Plugins", () -> {
            final List<PluginBase> plugins = HytaleServer.get().getPluginManager().getPlugins();
            writer.println("Plugin Count: " + plugins.size());
            for (PluginBase plugin : plugins) {
                Label_0080_12: {
                    if (plugin instanceof final JavaPlugin javaPlugin) {
                        if (javaPlugin.getClassLoader().isInServerClassPath()) {
                            break Label_0080_12;
                        }
                    }
                }
                final boolean b;
                final boolean isBuiltin = b;
                writer.println("- " + String.valueOf(plugin.getIdentifier()) + (isBuiltin ? " [Builtin]" : " [External]"));
                writer.println("\tType: " + plugin.getType().getDisplayName());
                writer.println("\tState: " + String.valueOf(plugin.getState()));
                writer.println("\tManifest:");
                final BsonDocument manifestBson = PluginManifest.CODEC.encode(plugin.getManifest()).asDocument();
                printIndented(writer, BsonUtil.toJson(manifestBson), "\t\t");
            }
            return;
        }, writer);
        section("Server Config", () -> {
            final HytaleServerConfig config = HytaleServer.get().getConfig();
            final BsonDocument bson = HytaleServerConfig.CODEC.encode(config).asDocument();
            printIndented(writer, BsonUtil.toJson(bson), "\t");
            return;
        }, writer);
        final Map<UUID, PlayerTextData> playerTextData = collectPlayerTextData();
        section("Server Info", () -> {
            writer.println("HytaleServer:");
            writer.println("\t- " + String.valueOf(HytaleServer.get()));
            writer.println("\tBooted: " + HytaleServer.get().isBooting());
            writer.println("\tShutting Down: " + HytaleServer.get().isShuttingDown());
            writer.println();
            writer.println("Worlds: ");
            final Map<String, World> worlds = Universe.get().getWorlds();
            worlds.forEach((worldName, world) -> {
                writer.println("- " + worldName);
                writer.println("\t" + String.valueOf(world));
                final HistoricMetric bufferedDeltaMetricSet = world.getBufferedTickLengthMetricSet();
                final long[] periods = bufferedDeltaMetricSet.getPeriodsNanos();
                long[] historyTimestamps = null;
                long[] historyValues = null;
                for (int i = 0; i < periods.length; ++i) {
                    final long period = periods[i];
                    final String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true);
                    final double average3 = bufferedDeltaMetricSet.getAverage(i);
                    final long min4 = bufferedDeltaMetricSet.calculateMin(i);
                    final long max4 = bufferedDeltaMetricSet.calculateMax(i);
                    writer.println("\tTick (" + historyLengthFormatted + "): Min: " + FormatUtil.simpleTimeUnitFormat(min4, TimeUnit.NANOSECONDS, 3) + ", Avg: " + FormatUtil.simpleTimeUnitFormat(Math.round(average3), TimeUnit.NANOSECONDS, 3) + "ns, Max: " + FormatUtil.simpleTimeUnitFormat(max4, TimeUnit.NANOSECONDS, 3));
                    historyTimestamps = bufferedDeltaMetricSet.getTimestamps(i);
                    historyValues = bufferedDeltaMetricSet.getValues(i);
                    final StringBuilder sb = new StringBuilder();
                    sb.append("\tTick Graph ").append(historyLengthFormatted).append(":\n");
                    StringUtil.generateGraph(sb, width, height, startNanos - period, startNanos, 0.0, (double)Math.max(max4, world.getTickStepNanos()), value -> FormatUtil.simpleTimeUnitFormat(MathUtil.fastCeil(value), TimeUnit.NANOSECONDS, 3), historyTimestamps.length, ii -> historyTimestamps[ii], ii -> (double)historyValues[ii]);
                    writer.println(sb);
                }
                writer.println("\tPlayers: ");
                for (Player player : world.getPlayers()) {
                    writer.println("\t- " + String.valueOf(player));
                    final PlayerTextData playerData = playerTextData.get(player.getUuid());
                    writer.println("\t\tMovement States: " + ((playerData != null) ? playerData.movementStates() : "N/A"));
                    writer.println("\t\tMovement Manager: " + ((playerData != null) ? playerData.movementManager() : "N/A"));
                    writer.println("\t\tPage Manager: " + String.valueOf(player.getPageManager()));
                    writer.println("\t\tHud Manager: " + String.valueOf(player.getHudManager()));
                    writer.println("\t\tCamera Manager: " + ((playerData != null) ? playerData.cameraManager() : "N/A"));
                    writer.println("\t\tChunk Tracker:");
                    final String[] arr$ = player.getPlayerRef().getChunkTracker().getLoadedChunksDebug().split("\n");
                    for (int len$ = arr$.length, i$7 = 0; i$7 < len$; ++i$7) {
                        final String line = arr$[i$7];
                        writer.println("\t\t\t" + line);
                    }
                    writer.println("\t\tQueued Packets Count: " + player.getPlayerConnection().getQueuedPacketsCount());
                    writer.println("\t\tPing:");
                    final PongType[] arr$2 = PongType.values();
                    for (int len$2 = arr$2.length, i$8 = 0; i$8 < len$2; ++i$8) {
                        final PongType pongType = arr$2[i$8];
                        final PacketHandler.PingInfo pingInfo3 = player.getPlayerConnection().getPingInfo(pongType);
                        writer.println("\t\t- " + pongType.name());
                        final HistoricMetric pingMetricSet3 = pingInfo3.getPingMetricSet();
                        final long average4 = (long)pingMetricSet3.getAverage(1);
                        final long min5 = pingMetricSet3.calculateMin(1);
                        final long max5 = pingMetricSet3.calculateMax(1);
                        writer.println("\t\t\tPing: Min: " + min5 + ", Avg: " + average4 + ", Max: " + max5);
                        writer.println("\t\t\t      Min: " + FormatUtil.timeUnitToString(min5, PacketHandler.PingInfo.TIME_UNIT) + ", Avg: " + FormatUtil.timeUnitToString(average4, PacketHandler.PingInfo.TIME_UNIT) + ", Max: " + FormatUtil.timeUnitToString(max5, PacketHandler.PingInfo.TIME_UNIT));
                        final long[] pingPeriods = pingMetricSet3.getPeriodsNanos();
                        for (int j = 0; j < pingPeriods.length; ++j) {
                            final long period2 = pingPeriods[j];
                            final long min6 = pingMetricSet3.calculateMin(1);
                            final long max6 = pingMetricSet3.calculateMax(1);
                            final long[] historyTimestamps2 = pingMetricSet3.getTimestamps(j);
                            final long[] historyValues2 = pingMetricSet3.getValues(j);
                            final String historyLengthFormatted2 = FormatUtil.timeUnitToString(period2, TimeUnit.NANOSECONDS, true);
                            final StringBuilder sb2 = new StringBuilder();
                            sb2.append("\t\t\tPing Graph ").append(historyLengthFormatted2).append(":\n");
                            StringUtil.generateGraph(sb2, width, height, startNanos - period2, startNanos, (double)min6, (double)max6, value -> FormatUtil.timeUnitToString(MathUtil.fastCeil(value), PacketHandler.PingInfo.TIME_UNIT), historyTimestamps2.length, ii -> historyTimestamps[ii], ii -> (double)historyValues[ii]);
                            writer.println(sb2);
                        }
                        writer.println("\t\t\tPacket Queue: Min: " + pingInfo3.getPacketQueueMetric().getMin() + ", Avg: " + (long)pingInfo3.getPacketQueueMetric().getAverage() + ", Max: " + pingInfo3.getPacketQueueMetric().getMax());
                    }
                    writer.println();
                    final PacketStatsRecorder recorder = player.getPlayerConnection().getPacketStatsRecorder();
                    if (recorder != null) {
                        final int recentSeconds = 30;
                        long totalSentCount = 0L;
                        long totalSentUncompressed = 0L;
                        long totalSentWire = 0L;
                        int recentSentCount = 0;
                        long recentSentUncompressed = 0L;
                        long recentSentWire = 0L;
                        writer.println("\t\tPackets Sent:");
                        for (int k = 0; k < 512; ++k) {
                            final PacketStatsRecorder.PacketStatsEntry entry = recorder.getEntry(k);
                            if (entry.getSentCount() > 0) {
                                totalSentCount += entry.getSentCount();
                                totalSentUncompressed += entry.getSentUncompressedTotal();
                                totalSentWire += ((entry.getSentCompressedTotal() > 0L) ? entry.getSentCompressedTotal() : entry.getSentUncompressedTotal());
                                writer.println("\t\t\t" + entry.getName() + " (" + k + "):");
                                printPacketStats(writer, "\t\t\t\t", "Total", entry.getSentCount(), entry.getSentUncompressedTotal(), entry.getSentCompressedTotal(), entry.getSentUncompressedMin(), entry.getSentUncompressedMax(), entry.getSentCompressedMin(), entry.getSentCompressedMax(), entry.getSentUncompressedAvg(), entry.getSentCompressedAvg(), 0);
                                final PacketStatsRecorder.RecentStats recent = entry.getSentRecently();
                                if (recent.count() > 0) {
                                    recentSentCount += recent.count();
                                    recentSentUncompressed += recent.uncompressedTotal();
                                    recentSentWire += ((recent.compressedTotal() > 0L) ? recent.compressedTotal() : recent.uncompressedTotal());
                                    printPacketStats(writer, "\t\t\t\t", "Recent", recent.count(), recent.uncompressedTotal(), recent.compressedTotal(), recent.uncompressedMin(), recent.uncompressedMax(), recent.compressedMin(), recent.compressedMax(), recent.uncompressedTotal() / (double)recent.count(), (recent.compressedTotal() > 0L) ? (recent.compressedTotal() / (double)recent.count()) : 0.0, recentSeconds);
                                }
                            }
                        }
                        writer.println("\t\t\t--- Summary ---");
                        writer.println("\t\t\t\tTotal: " + totalSentCount + " packets, " + FormatUtil.bytesToString(totalSentUncompressed) + " serialized, " + FormatUtil.bytesToString(totalSentWire) + " wire");
                        if (recentSentCount > 0) {
                            writer.println(String.format("\t\t\t\tRecent: %d packets (%.1f/sec), %s serialized, %s wire", recentSentCount, recentSentCount / (double)recentSeconds, FormatUtil.bytesToString(recentSentUncompressed), FormatUtil.bytesToString(recentSentWire)));
                        }
                        writer.println();
                        long totalRecvCount = 0L;
                        long totalRecvUncompressed = 0L;
                        long totalRecvWire = 0L;
                        int recentRecvCount = 0;
                        long recentRecvUncompressed = 0L;
                        long recentRecvWire = 0L;
                        writer.println("\t\tPackets Received:");
                        for (int l = 0; l < 512; ++l) {
                            final PacketStatsRecorder.PacketStatsEntry entry2 = recorder.getEntry(l);
                            if (entry2.getReceivedCount() > 0) {
                                totalRecvCount += entry2.getReceivedCount();
                                totalRecvUncompressed += entry2.getReceivedUncompressedTotal();
                                totalRecvWire += ((entry2.getReceivedCompressedTotal() > 0L) ? entry2.getReceivedCompressedTotal() : entry2.getReceivedUncompressedTotal());
                                writer.println("\t\t\t" + entry2.getName() + " (" + l + "):");
                                printPacketStats(writer, "\t\t\t\t", "Total", entry2.getReceivedCount(), entry2.getReceivedUncompressedTotal(), entry2.getReceivedCompressedTotal(), entry2.getReceivedUncompressedMin(), entry2.getReceivedUncompressedMax(), entry2.getReceivedCompressedMin(), entry2.getReceivedCompressedMax(), entry2.getReceivedUncompressedAvg(), entry2.getReceivedCompressedAvg(), 0);
                                final PacketStatsRecorder.RecentStats recent2 = entry2.getReceivedRecently();
                                if (recent2.count() > 0) {
                                    recentRecvCount += recent2.count();
                                    recentRecvUncompressed += recent2.uncompressedTotal();
                                    recentRecvWire += ((recent2.compressedTotal() > 0L) ? recent2.compressedTotal() : recent2.uncompressedTotal());
                                    printPacketStats(writer, "\t\t\t\t", "Recent", recent2.count(), recent2.uncompressedTotal(), recent2.compressedTotal(), recent2.uncompressedMin(), recent2.uncompressedMax(), recent2.compressedMin(), recent2.compressedMax(), recent2.uncompressedTotal() / (double)recent2.count(), (recent2.compressedTotal() > 0L) ? (recent2.compressedTotal() / (double)recent2.count()) : 0.0, recentSeconds);
                                }
                            }
                        }
                        writer.println("\t\t\t--- Summary ---");
                        writer.println("\t\t\t\tTotal: " + totalRecvCount + " packets, " + FormatUtil.bytesToString(totalRecvUncompressed) + " serialized, " + FormatUtil.bytesToString(totalRecvWire) + " wire");
                        if (recentRecvCount > 0) {
                            writer.println(String.format("\t\t\t\tRecent: %d packets (%.1f/sec), %s serialized, %s wire", recentRecvCount, recentRecvCount / (double)recentSeconds, FormatUtil.bytesToString(recentRecvUncompressed), FormatUtil.bytesToString(recentRecvWire)));
                        }
                        writer.println();
                    }
                }
                writer.println("\tComponent Stores:");
                try {
                    CompletableFuture.runAsync(() -> {
                        printComponentStore(writer, width, height, "Chunks", startNanos, world.getChunkStore().getStore());
                        printComponentStore(writer, width, height, "Entities", startNanos, world.getEntityStore().getStore());
                        return;
                    }, world).orTimeout(30L, TimeUnit.SECONDS).join();
                }
                catch (final CompletionException e) {
                    if (!(e.getCause() instanceof TimeoutException)) {
                        e.printStackTrace();
                        writer.println("\t\tFAILED TO DUMP COMPONENT STORES! EXCEPTION!");
                    }
                    else {
                        writer.println("\t\tFAILED TO DUMP COMPONENT STORES! TIMEOUT!");
                    }
                }
                writer.println();
                writer.println();
                final WorldGenTimingsCollector timings = world.getChunkStore().getGenerator().getTimings();
                writer.println("\tWorld Gen Timings: ");
                if (timings != null) {
                    writer.println("\t\tChunk Count: " + timings.getChunkCounter());
                    writer.println("\t\tChunk Time: " + timings.getChunkTime());
                    writer.println("\t\tZone Biome Result Time: " + timings.zoneBiomeResult());
                    writer.println("\t\tPrepare Time: " + timings.prepare());
                    writer.println("\t\tBlock Generation Time: " + timings.blocksGeneration());
                    writer.println("\t\tCave Generation Time: " + timings.caveGeneration());
                    writer.println("\t\tPrefab Generation: " + timings.prefabGeneration());
                    writer.println("\t\tQueue Length: " + timings.getQueueLength());
                    writer.println("\t\tGenerating Count: " + timings.getGeneratingCount());
                }
                else {
                    writer.println("\t\tNo Timings Data Collected!");
                }
                final IndexedStorageChunkStorageProvider.IndexedStorageCache storageCache = world.getChunkStore().getStore().getResource(IndexedStorageChunkStorageProvider.IndexedStorageCache.getResourceType());
                if (storageCache != null) {
                    final Long2ObjectConcurrentHashMap<IndexedStorageFile> cache = storageCache.getCache();
                    writer.println();
                    writer.println("\tIndexed Storage Cache:");
                    for (Long2ObjectMap.Entry<IndexedStorageFile> entry3 : cache.long2ObjectEntrySet()) {
                        final long key = entry3.getLongKey();
                        writer.println("\t\t" + ChunkUtil.xOfChunkIndex(key) + ", " + ChunkUtil.zOfChunkIndex(key));
                        final IndexedStorageFile storageFile = entry3.getValue();
                        try {
                            writer.println("\t\t- Size: " + FormatUtil.bytesToString(storageFile.size()));
                        }
                        catch (final IOException e2) {
                            writer.println("\t\t- Size: ERROR: " + e2.getMessage());
                        }
                        writer.println("\t\t- Blob Count: " + storageFile.keys().size());
                        final int segmentSize = storageFile.segmentSize();
                        final int segmentCount = storageFile.segmentCount();
                        writer.println("\t\t- Segment Size: " + segmentSize);
                        writer.println("\t\t- Segment Count: " + segmentCount);
                        writer.println("\t\t- Segment Used %: " + segmentCount * 100 / (double)segmentSize);
                        writer.println("\t\t- " + String.valueOf(storageFile));
                    }
                }
                return;
            });
            final List<PlayerRef> playersNotInWorld = Universe.get().getPlayers().stream().filter(ref -> ref.getReference() == null).toList();
            if (!playersNotInWorld.isEmpty()) {
                writer.println();
                writer.println("Players not in world (" + playersNotInWorld.size() + "):");
                for (PlayerRef ref2 : playersNotInWorld) {
                    writer.println("- " + ref2.getUsername() + " (" + String.valueOf(ref2.getUuid()));
                    writer.println("\tQueued Packets: " + ref2.getPacketHandler().getQueuedPacketsCount());
                    final PacketHandler.PingInfo pingInfo2 = ref2.getPacketHandler().getPingInfo(PongType.Raw);
                    final HistoricMetric pingMetricSet2 = pingInfo2.getPingMetricSet();
                    final long min3 = pingMetricSet2.calculateMin(1);
                    final long avg = (long)pingMetricSet2.getAverage(1);
                    final long max3 = pingMetricSet2.calculateMax(1);
                    writer.println("\tPing(raw): Min: " + FormatUtil.timeUnitToString(min3, PacketHandler.PingInfo.TIME_UNIT) + ", Avg: " + FormatUtil.timeUnitToString(avg, PacketHandler.PingInfo.TIME_UNIT) + ", Max: " + FormatUtil.timeUnitToString(max3, PacketHandler.PingInfo.TIME_UNIT));
                }
            }
            return;
        }, writer);
        section("System info", () -> {
            final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
            final java.lang.management.OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
            final long currentTimeMillis = System.currentTimeMillis();
            writer.println("Start Time: " + String.valueOf(new Date(runtimeMXBean.getStartTime())) + " (" + runtimeMXBean.getStartTime() + "ms)");
            writer.println("Current Time: " + String.valueOf(new Date(currentTimeMillis)) + " (" + currentTimeMillis + "ms)");
            writer.println("Process Uptime: " + FormatUtil.timeUnitToString(runtimeMXBean.getUptime(), TimeUnit.MILLISECONDS) + " (" + runtimeMXBean.getUptime() + "ms)");
            writer.println("Available processors (cores): " + Runtime.getRuntime().availableProcessors() + " - " + operatingSystemMXBean.getAvailableProcessors());
            writer.println("System Load Average: " + operatingSystemMXBean.getSystemLoadAverage());
            writer.println();
            if (operatingSystemMXBean instanceof final OperatingSystemMXBean sunOSBean) {
                writer.println("Total Physical Memory: " + FormatUtil.bytesToString(sunOSBean.getTotalPhysicalMemorySize()) + " (" + sunOSBean.getTotalPhysicalMemorySize() + " Bytes)");
                writer.println("Free Physical Memory: " + FormatUtil.bytesToString(sunOSBean.getFreePhysicalMemorySize()) + " (" + sunOSBean.getFreePhysicalMemorySize() + " Bytes)");
                writer.println("Total Swap Memory: " + FormatUtil.bytesToString(sunOSBean.getTotalSwapSpaceSize()) + " (" + sunOSBean.getTotalSwapSpaceSize() + " Bytes)");
                writer.println("Free Swap Memory: " + FormatUtil.bytesToString(sunOSBean.getFreeSwapSpaceSize()) + " (" + sunOSBean.getFreeSwapSpaceSize() + " Bytes)");
                writer.println("System CPU Load: " + sunOSBean.getSystemCpuLoad());
                writer.println("Process CPU Load: " + sunOSBean.getProcessCpuLoad());
                writer.println();
            }
            writer.println("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
            writer.println("Processor Architecture: " + System.getenv("PROCESSOR_ARCHITECTURE"));
            writer.println("Processor Architecture W64/32: " + System.getenv("PROCESSOR_ARCHITEW6432"));
            writer.println("Number of Processors: " + System.getenv("NUMBER_OF_PROCESSORS"));
            writer.println();
            writer.println("Runtime Name: " + runtimeMXBean.getName());
            writer.println();
            writer.println("OS Name: " + operatingSystemMXBean.getName());
            writer.println("OS Arch: " + operatingSystemMXBean.getArch());
            writer.println("OS Version: " + operatingSystemMXBean.getVersion());
            writer.println();
            writer.println("Spec Name: " + runtimeMXBean.getSpecName());
            writer.println("Spec Vendor: " + runtimeMXBean.getSpecVendor());
            writer.println("Spec Version: " + runtimeMXBean.getSpecVersion());
            writer.println();
            writer.println("VM Name: " + runtimeMXBean.getVmName());
            writer.println("VM Vendor: " + runtimeMXBean.getVmVendor());
            writer.println("VM Version: " + runtimeMXBean.getVmVersion());
            writer.println();
            writer.println("Management Spec Version: " + runtimeMXBean.getManagementSpecVersion());
            writer.println();
            writer.println("Library Path: " + runtimeMXBean.getLibraryPath());
            try {
                writer.println("Boot ClassPath: " + runtimeMXBean.getBootClassPath());
            }
            catch (final UnsupportedOperationException ex) {}
            writer.println("ClassPath: " + runtimeMXBean.getClassPath());
            writer.println();
            writer.println("Input Arguments: " + String.valueOf(runtimeMXBean.getInputArguments()));
            writer.println("System Properties: " + String.valueOf(runtimeMXBean.getSystemProperties()));
            return;
        }, writer);
        section("Current process info", () -> {
            final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
            writeMemoryUsage(writer, "Heap Memory Usage: ", memoryMXBean.getHeapMemoryUsage());
            writeMemoryUsage(writer, "Non-Heap Memory Usage: ", memoryMXBean.getNonHeapMemoryUsage());
            writer.println("Objects Pending Finalization Count: " + memoryMXBean.getObjectPendingFinalizationCount());
            return;
        }, writer);
        section("Garbage collector", () -> {
            for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
                writer.println("Name: " + garbageCollectorMXBean.getName());
                writer.println("\tMemory Pool Names: " + Arrays.toString(garbageCollectorMXBean.getMemoryPoolNames()));
                writer.println("\tCollection Count: " + garbageCollectorMXBean.getCollectionCount());
                writer.println("\tCollection Time: " + garbageCollectorMXBean.getCollectionTime());
                writer.println();
            }
            return;
        }, writer);
        section("Memory pools", () -> {
            for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
                writer.println("Name: " + memoryPoolMXBean.getName());
                writer.println("\tType: " + String.valueOf(memoryPoolMXBean.getType()));
                writer.println("\tPeak Usage: " + String.valueOf(memoryPoolMXBean.getPeakUsage()));
                writer.println("\tUsage: " + String.valueOf(memoryPoolMXBean.getUsage()));
                writer.println("\tUsage Threshold Supported: " + memoryPoolMXBean.isUsageThresholdSupported());
                if (memoryPoolMXBean.isUsageThresholdSupported()) {
                    writer.println("\tUsage Threshold: " + memoryPoolMXBean.getUsageThreshold());
                    writer.println("\tUsage Threshold Count: " + memoryPoolMXBean.getUsageThresholdCount());
                    writer.println("\tUsage Threshold Exceeded: " + memoryPoolMXBean.isUsageThresholdExceeded());
                }
                writer.println("\tCollection Usage: " + String.valueOf(memoryPoolMXBean.getCollectionUsage()));
                writer.println("\tCollection Usage Threshold Supported: " + memoryPoolMXBean.isCollectionUsageThresholdSupported());
                if (memoryPoolMXBean.isCollectionUsageThresholdSupported()) {
                    writer.println("\tCollection Usage Threshold: " + memoryPoolMXBean.getCollectionUsageThreshold());
                    writer.println("\tCollection Usage Threshold Count: " + memoryPoolMXBean.getCollectionUsageThresholdCount());
                    writer.println("\tCollection Usage Threshold Exceeded: " + memoryPoolMXBean.isCollectionUsageThresholdExceeded());
                }
                writer.println();
            }
            return;
        }, writer);
        final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        final ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
        section("Threads (Count: " + threadInfos.length, () -> {
            final Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
            final Long2ObjectMap<Thread> threadIdMap = new Long2ObjectOpenHashMap<Thread>();
            for (final Thread thread : allStackTraces.keySet()) {
                threadIdMap.put(thread.getId(), thread);
            }
            final ThreadInfo[] arr$3 = threadInfos;
            for (int len$3 = arr$3.length, i$13 = 0; i$13 < len$3; ++i$13) {
                final ThreadInfo threadInfo = arr$3[i$13];
                final Thread thread2 = threadIdMap.get(threadInfo.getThreadId());
                if (thread2 != null) {
                    writer.println("Name: " + thread2.getName());
                    writer.println("State: " + String.valueOf(threadInfo.getThreadState()));
                    writer.println("Thread Class: " + String.valueOf(thread2.getClass()));
                    writer.println("Thread Group: " + String.valueOf(thread2.getThreadGroup()));
                    writer.println("Priority: " + thread2.getPriority());
                    writer.println("CPU Time: " + threadMXBean.getThreadCpuTime(threadInfo.getThreadId()));
                    writer.println("Waited Time: " + threadInfo.getWaitedTime());
                    writer.println("Waited Count: " + threadInfo.getWaitedCount());
                    writer.println("Blocked Time: " + threadInfo.getBlockedTime());
                    writer.println("Blocked Count: " + threadInfo.getBlockedCount());
                    writer.println("Lock Name: " + threadInfo.getLockName());
                    writer.println("Lock Owner Id: " + threadInfo.getLockOwnerId());
                    writer.println("Lock Owner Name: " + threadInfo.getLockOwnerName());
                    writer.println("Daemon: " + thread2.isDaemon());
                    writer.println("Interrupted: " + thread2.isInterrupted());
                    writer.println("Uncaught Exception Handler: " + String.valueOf(thread2.getUncaughtExceptionHandler().getClass()));
                    if (thread2 instanceof InitStackThread) {
                        writer.println("Init Stack: ");
                        final StackTraceElement[] arr$4;
                        final StackTraceElement[] trace = arr$4 = ((InitStackThread)thread2).getInitStack();
                        for (int len$4 = arr$4.length, i$14 = 0; i$14 < len$4; ++i$14) {
                            final StackTraceElement traceElement = arr$4[i$14];
                            writer.println("\tat " + String.valueOf(traceElement));
                        }
                    }
                    writer.println("Current Stack: ");
                    final StackTraceElement[] arr$5;
                    final StackTraceElement[] trace2 = arr$5 = allStackTraces.get(thread2);
                    for (int len$5 = arr$5.length, i$15 = 0; i$15 < len$5; ++i$15) {
                        final StackTraceElement traceElement2 = arr$5[i$15];
                        writer.println("\tat " + String.valueOf(traceElement2));
                    }
                }
                else {
                    writer.println("Failed to find thread!!!");
                }
                writer.println(threadInfo);
            }
            return;
        }, writer);
        section("Security Manager", () -> {
            final SecurityManager securityManager = System.getSecurityManager();
            if (securityManager != null) {
                writer.println("Class: " + securityManager.getClass().getName());
            }
            else {
                writer.println("No Security Manager found!");
            }
            return;
        }, writer);
        section("Classes", () -> {
            final ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
            writer.println("Loaded Class Count: " + classLoadingMXBean.getLoadedClassCount());
            writer.println("Unloaded Class Count: " + classLoadingMXBean.getUnloadedClassCount());
            writer.println("Total Loaded Class Count: " + classLoadingMXBean.getTotalLoadedClassCount());
            return;
        }, writer);
        section("System Classloader", () -> {
            final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            writeClassLoader(writer, systemClassLoader);
            return;
        }, writer);
        section("DumpUtil Classloader", () -> {
            final ClassLoader systemClassLoader2 = DumpUtil.class.getClassLoader();
            writeClassLoader(writer, systemClassLoader2);
        }, writer);
    }
    
    private static void printPacketStats(@Nonnull final PrintWriter writer, @Nonnull final String indent, @Nonnull final String label, final int count, final long uncompressedTotal, final long compressedTotal, final long uncompressedMin, final long uncompressedMax, final long compressedMin, final long compressedMax, final double uncompressedAvg, final double compressedAvg, final int recentSeconds) {
        final StringBuilder sb = new StringBuilder();
        sb.append(label).append(": ").append(count).append(" packet").append((count != 1) ? "s" : "");
        if (recentSeconds > 0) {
            sb.append(String.format(" (%.1f/sec)", count / (double)recentSeconds));
        }
        sb.append("\n").append(indent).append("  Size: ").append(FormatUtil.bytesToString(uncompressedTotal));
        if (compressedTotal > 0L) {
            sb.append(" -> ").append(FormatUtil.bytesToString(compressedTotal)).append(" wire");
            final double ratio = 100.0 * compressedTotal / uncompressedTotal;
            sb.append(String.format(" (%.1f%%)", ratio));
        }
        sb.append("\n").append(indent).append("  Avg: ").append(FormatUtil.bytesToString((long)uncompressedAvg));
        if (compressedAvg > 0.0) {
            sb.append(" -> ").append(FormatUtil.bytesToString((long)compressedAvg)).append(" wire");
        }
        sb.append("\n").append(indent).append("  Range: ").append(FormatUtil.bytesToString(uncompressedMin)).append(" - ").append(FormatUtil.bytesToString(uncompressedMax));
        if (compressedMax > 0L) {
            sb.append(" (wire: ").append(FormatUtil.bytesToString(compressedMin)).append(" - ").append(FormatUtil.bytesToString(compressedMax)).append(")");
        }
        writer.println(indent + String.valueOf(sb));
    }
    
    private static void printComponentStore(@Nonnull final PrintWriter writer, final int width, final int height, final String name, final long startNanos, @Nonnull final Store<?> componentStore) {
        writer.println("\t- " + name);
        writer.println("\t Archetype Chunk Count: " + componentStore.getArchetypeChunkCount());
        writer.println("\t Entity Count: " + componentStore.getEntityCount());
        final ComponentRegistry.Data<?> data = componentStore.getRegistry().getData();
        final HistoricMetric[] systemMetrics = componentStore.getSystemMetrics();
        for (int systemIndex = 0; systemIndex < data.getSystemSize(); ++systemIndex) {
            final ISystem<?> system = data.getSystem(systemIndex);
            final HistoricMetric systemMetric = systemMetrics[systemIndex];
            writer.println("\t\t " + system.getClass().getName());
            writer.println("\t\t " + String.valueOf(system));
            writer.println("\t\t Archetype Chunk Count: " + componentStore.getArchetypeChunkCountFor(systemIndex));
            writer.println("\t\t Entity Count: " + componentStore.getEntityCountFor(systemIndex));
            if (systemMetric != null) {
                final long[] periods = systemMetric.getPeriodsNanos();
                for (int i = 0; i < periods.length; ++i) {
                    final long period = periods[i];
                    final String historyLengthFormatted = FormatUtil.timeUnitToString(period, TimeUnit.NANOSECONDS, true);
                    final double average = systemMetric.getAverage(i);
                    final long min = systemMetric.calculateMin(i);
                    final long max = systemMetric.calculateMax(i);
                    writer.println("\t\t\t(" + historyLengthFormatted + "): Min: " + FormatUtil.timeUnitToString(min, TimeUnit.NANOSECONDS) + ", Avg: " + FormatUtil.timeUnitToString((long)average, TimeUnit.NANOSECONDS) + ", Max: " + FormatUtil.timeUnitToString(max, TimeUnit.NANOSECONDS));
                    final long[] historyTimestamps = systemMetric.getTimestamps(i);
                    final long[] historyValues = systemMetric.getValues(i);
                    final StringBuilder sb = new StringBuilder();
                    StringUtil.generateGraph(sb, width, height, startNanos - period, startNanos, (double)min, (double)max, value -> FormatUtil.timeUnitToString(MathUtil.fastCeil(value), TimeUnit.NANOSECONDS), historyTimestamps.length, ii -> historyTimestamps[ii], ii -> (double)historyValues[ii]);
                    writer.println(sb);
                }
            }
        }
        writer.println("\t\t Archetype Chunks:");
        final ArchetypeChunkData[] collectArchetypeChunkData = componentStore.collectArchetypeChunkData();
        for (int length = collectArchetypeChunkData.length, j = 0; j < length; ++j) {
            final ArchetypeChunkData chunkData = collectArchetypeChunkData[j];
            writer.println("\t\t\t- Entities: " + chunkData.getEntityCount() + ", Components: " + Arrays.toString(chunkData.getComponentTypes()));
        }
    }
    
    private static void section(final String name, @Nonnull final Runnable runnable, @Nonnull final PrintWriter writer) {
        writer.println("**** " + name + " ****");
        try {
            runnable.run();
        }
        catch (final Throwable t) {
            new RuntimeException("Failed to get data for section: " + name, t).printStackTrace(writer);
        }
        writer.println();
        writer.println();
    }
    
    private static void printIndented(@Nonnull final PrintWriter writer, @Nonnull final String text, @Nonnull final String indent) {
        final String[] split = text.split("\n");
        for (int length = split.length, i = 0; i < length; ++i) {
            final String line = split[i];
            writer.println(indent + line);
        }
    }
    
    private static void writeMemoryUsage(@Nonnull final PrintWriter writer, final String title, @Nonnull final MemoryUsage memoryUsage) {
        writer.println(title);
        writer.println("\tInit: " + FormatUtil.bytesToString(memoryUsage.getInit()) + " (" + memoryUsage.getInit() + " Bytes)");
        writer.println("\tUsed: " + FormatUtil.bytesToString(memoryUsage.getUsed()) + " (" + memoryUsage.getUsed() + " Bytes)");
        writer.println("\tCommitted: " + FormatUtil.bytesToString(memoryUsage.getCommitted()) + " (" + memoryUsage.getCommitted() + " Bytes)");
        final long max = memoryUsage.getMax();
        if (max > 0L) {
            writer.println("\tMax: " + FormatUtil.bytesToString(max) + " (" + max + " Bytes)");
            final long free = max - memoryUsage.getCommitted();
            writer.println("\tFree: " + FormatUtil.bytesToString(free) + " (" + free + " Bytes)");
        }
    }
    
    private static void writeClassLoader(@Nonnull final PrintWriter writer, @Nullable ClassLoader systemClassLoader) {
        if (systemClassLoader != null) {
            writer.println("Class: " + systemClassLoader.getClass().getName());
            while (systemClassLoader.getParent() != null) {
                systemClassLoader = systemClassLoader.getParent();
                writer.println(" - Parent: " + systemClassLoader.getClass().getName());
            }
        }
        else {
            writer.println("No class loader found!");
        }
    }
    
    record PlayerTextData(@Nonnull UUID uuid, @Nullable String movementStates, @Nullable String movementManager, @Nullable String cameraManager) {
        @Nonnull
        public UUID uuid() {
            return this.uuid;
        }
        
        @Nullable
        public String movementStates() {
            return this.movementStates;
        }
        
        @Nullable
        public String movementManager() {
            return this.movementManager;
        }
        
        @Nullable
        public String cameraManager() {
            return this.cameraManager;
        }
    }
}
