public class LiteModMiniHud implements LiteMod, Configurable
{
    public static final Logger logger = LogManager.getLogger(Reference.MOD_ID);

    public LiteModMiniHud()
    {
    }

    @Override
    public String getName()
    {
        return Reference.MOD_NAME;
    }

    @Override
    public String getVersion()
    {
        return Reference.MOD_VERSION;
    }

    @Override
    public Class<? extends ConfigPanel> getConfigPanelClass()
    {
        return MiniHudConfigPanel.class;
    }

    @Override
    public void init(File configPath)
    {
        Registry.INITIALIZATION_DISPATCHER.registerInitializationHandler(new InitHandler());
    }

    @Override
    public void upgradeSettings(String version, File configPath, File oldConfigPath)
    {
    }
} public class MiniHUD
{
    public static final Logger LOGGER = LogManager.getLogger(Reference.MOD_ID);

    public static void logInfo(String msg, Object... args)
    {
        if (Configs.Generic.DEBUG_MESSAGES.getBooleanValue())
        {
            LOGGER.info(msg, args);
        }
    }
} public class Reference
{
    public static final String MOD_ID = "minihud";
    public static final String MOD_NAME = "MiniHUD";
    public static final String MOD_VERSION = "@MOD_VERSION@";

    public static final ModInfo MOD_INFO = new ModInfo(MOD_ID, MOD_NAME);
} public enum RendererToggle implements ConfigInfo
{
    DEBUG_COLLISION_BOXES               ("debugCollisionBoxEnabled"),
    DEBUG_HEIGHT_MAP                    ("debugHeightMapEnabled"),
    DEBUG_NEIGHBOR_UPDATES              ("debugNeighborsUpdateEnabled"),
    DEBUG_PATH_FINDING                  ("debugPathfindingEnabled"),
    DEBUG_SOLID_FACES                   ("debugSolidFaceEnabled"),
    DEBUG_WATER                         ("debugWaterEnabled"),

    OVERLAY_BEACON_RANGE                ("overlayBeaconRange"),
    OVERLAY_BLOCK_GRID                  ("overlayBlockGrid"),
    OVERLAY_CHUNK_UNLOAD_BUCKET         ("overlayChunkUnloadBucket", KeyBindSettings.INGAME_BOTH),
    OVERLAY_LIGHT_LEVEL                 ("overlayLightLevel"),
    OVERLAY_RANDOM_TICKS_FIXED          ("overlayRandomTicksFixed"),
    OVERLAY_RANDOM_TICKS_PLAYER         ("overlayRandomTicksPlayer"),
    OVERLAY_REGION_FILE                 ("overlayRegionFile"),
    OVERLAY_SLIME_CHUNKS_OVERLAY        ("overlaySlimeChunks", KeyBindSettings.INGAME_BOTH),
    OVERLAY_SPAWNABLE_CHUNKS_FIXED      ("overlaySpawnableChunksFixed"),
    OVERLAY_SPAWNABLE_CHUNKS_PLAYER     ("overlaySpawnableChunksPlayer"),
    OVERLAY_SPAWNABLE_COLUMN_HEIGHTS    ("overlaySpawnableColumnHeights"),
    OVERLAY_SPAWNER_POSITIONS           ("overlaySpawnerPositions"),
    OVERLAY_SPAWN_CHUNK_OVERLAY_REAL    ("overlaySpawnChunkReal"),
    OVERLAY_SPAWN_CHUNK_OVERLAY_PLAYER  ("overlaySpawnChunkPlayer"),
    OVERLAY_STRUCTURE_MAIN_TOGGLE       ("overlayStructureMainToggle"),
    OVERLAY_WATER_FALLS                 ("overlayWaterFalls"),
    SHAPE_RENDERER                      ("shapeRenderer");

    public static final ImmutableList<RendererToggle> VALUES = ImmutableList.copyOf(values());
    public static final ImmutableList<BooleanConfig> TOGGLE_CONFIGS = ImmutableList.copyOf(VALUES.stream().map(RendererToggle::getBooleanConfig).collect(Collectors.toList()));
    public static final ImmutableList<HotkeyConfig> TOGGLE_HOTKEYS = ImmutableList.copyOf(VALUES.stream().map(RendererToggle::getHotkeyConfig).collect(Collectors.toList()));

    private final BooleanConfig toggleStatus;
    private final HotkeyConfig toggleHotkey;

    RendererToggle(String name)
    {
        this(name, KeyBindSettings.INGAME_DEFAULT);
    }

    RendererToggle(String name, KeyBindSettings settings)
    {
        this.toggleStatus = new BooleanConfig(name, false);
        this.toggleHotkey = new HotkeyConfig(name, "", settings);

        String nameLower = name.toLowerCase(Locale.ROOT);
        String nameKey = "minihud.renderer_toggle.name." + nameLower;
        String commentKey = "minihud.renderer_toggle.comment." + nameLower;

        if (name.startsWith("debug"))
        {
            this.toggleHotkey.getKeyBind().setCallback(new DebugRendererHotkeyCallback(this));
        }
        else
        {
            this.toggleHotkey.getKeyBind().setCallback(new RendererToggleHotkeyCallback(this.toggleStatus));
        }

        if (name.equals("overlayStructureMainToggle"))
        {
            this.toggleStatus.setValueChangeCallback((newValue, oldValue) -> DataStorage.getInstance().getStructureStorage().requestStructureDataUpdates());
        }

        this.toggleStatus.setNameTranslationKey(nameKey);
        this.toggleStatus.setPrettyNameTranslationKey(nameKey);
        this.toggleStatus.setCommentTranslationKey(commentKey);

        this.toggleHotkey.setNameTranslationKey(nameKey);
        this.toggleHotkey.setPrettyNameTranslationKey(nameKey);
        this.toggleHotkey.setCommentTranslationKey(commentKey);
    }

    public boolean isRendererEnabled()
    {
        return this.toggleStatus.getBooleanValue();
    }

    public void addValueChangeListener(EventListener listener)
    {
        this.toggleStatus.addValueChangeListener(listener);
    }

    public BooleanConfig getBooleanConfig()
    {
        return this.toggleStatus;
    }

    public HotkeyConfig getHotkeyConfig()
    {
        return this.toggleHotkey;
    }

    public KeyBind getKeyBind()
    {
        return this.toggleHotkey.getKeyBind();
    }

    @Override
    public ModInfo getModInfo()
    {
        return Reference.MOD_INFO;
    }

    @Override
    public String getName()
    {
        return this.toggleStatus.getName();
    }

    @Override
    public String getDisplayName()
    {
        return this.toggleStatus.getDisplayName();
    }

    @Override
    public Optional<String> getComment()
    {
        return this.toggleStatus.getComment();
    }

    @Override
    public boolean isModified()
    {
        return this.toggleStatus.isModified() || this.toggleHotkey.isModified();
    }

    @Override
    public void resetToDefault()
    {
        this.toggleStatus.resetToDefault();
        this.toggleHotkey.resetToDefault();
    }
} @Mixin(ChunkGeneratorEnd.class)
public interface ChunkGeneratorEndMixin
{
    @Accessor("endCityGen")
    MapGenEndCity minihud_getEndCityGenerator();
} @Mixin(ChunkGeneratorFlat.class)
public interface ChunkGeneratorFlatMixin
{
    @Accessor("structureGenerators")
    Map<String, MapGenStructure> minihud_getStructureGenerators();
} @Mixin(ChunkGeneratorHell.class)
public interface ChunkGeneratorHellMixin
{
    @Accessor("genNetherBridge")
    MapGenNetherBridge minihud_getFortressGenerator();
} @Mixin(ChunkGeneratorOverworld.class)
public interface ChunkGeneratorOverworldMixin
{
    @Accessor("oceanMonumentGenerator")
    StructureOceanMonument minihud_getOceanMonumentGenerator();

    @Accessor("scatteredFeatureGenerator")
    MapGenScatteredFeature minihud_getScatteredFeatureGenerator();

    @Accessor("strongholdGenerator")
    MapGenStronghold minihud_getStrongholdGenerator();

    @Accessor("villageGenerator")
    MapGenVillage minihud_getVillageGenerator();

    @Accessor("woodlandMansionGenerator")
    WoodlandMansion minihud_getWoodlandMansionGenerator();
} @Mixin(ChunkProviderServer.class)
public interface ChunkProviderServerMixin
{
    @Accessor("chunkGenerator")
    IChunkGenerator minihud_getChunkGenerator();
} @Mixin(MapGenStructure.class)
public interface MapGenStructureMixin
{
    @Accessor("structureMap")
    Long2ObjectMap<StructureStart> minihud_getStructureMap();
} public class CarpetStructurePacketHandler implements PluginChannelHandler
{
    public static final List<ResourceLocation> CHANNELS = ImmutableList.of(new ResourceLocation("carpet:client"));
    public static final CarpetStructurePacketHandler INSTANCE = new CarpetStructurePacketHandler();

    @Override
    public List<ResourceLocation> getChannels()
    {
        return CHANNELS;
    }

    @Override
    public void onPacketReceived(PacketBuffer buf)
    {
        DataStorage.getInstance().getStructureStorage().updateStructureDataFromCarpetServer(buf);
    }
} public class ServuxStructurePacketHandler implements PluginChannelHandler
{
    public static final List<ResourceLocation> CHANNELS = ImmutableList.of(new ResourceLocation("servux:structure"));
    public static final ServuxStructurePacketHandler INSTANCE = new ServuxStructurePacketHandler();

    @Override
    public List<ResourceLocation> getChannels()
    {
        return CHANNELS;
    }

    @Override
    public void onPacketReceived(PacketBuffer buf)
    {
        DataStorage.getInstance().getStructureStorage().updateStructureDataFromServuxServer(buf);
    }
} public class MiscUtils
{
    private static final Random RAND = new Random();

    public static long bytesToMb(long bytes)
    {
        return bytes / 1024L / 1024L;
    }

    public static double intAverage(int[] values)
    {
        long sum = 0L;

        for (int value : values)
        {
            sum += value;
        }

        return (double) sum / (double) values.length;
    }

    public static boolean canSlimeSpawnAt(int posX, int posZ, long worldSeed)
    {
        return canSlimeSpawnInChunk(posX >> 4, posZ >> 4, worldSeed);
    }

    public static boolean canSlimeSpawnInChunk(int chunkX, int chunkZ, long worldSeed)
    {
        long slimeSeed = 987234911L;
        long rngSeed = worldSeed +
                       (long) (chunkX * chunkX *  4987142) +
                       (long) (chunkX * 5947611) +
                       (long) (chunkZ * chunkZ) * 4392871L +
                       (long) (chunkZ * 389711) ^ slimeSeed;

        RAND.setSeed(rngSeed);

        return RAND.nextInt(10) == 0;
    }

    public static int getChunkUnloadBucket(int chunkX, int chunkZ)
    {
        if (Configs.Generic.CHUNK_UNLOAD_BUCKET_HASH_SIZE.getBooleanValue())
        {
            return getChunkOrder(chunkX, chunkZ, DataStorage.getInstance().getDroppedChunksHashSize());
        }
        // The old simple calculation, without knowledge of the HashSet size
        else
        {
            int longHash = Long.valueOf(ChunkPos.asLong(chunkX, chunkZ)).hashCode();
            return (longHash ^ (longHash >>> 16)) & 0xFFFF;
        }
    }

    /**
     * This method has been taken from the Carpet mod, by gnembon
     */
    public static int getCurrentHashSize(WorldServer server)
    {
        ChunkProviderServerMixin provider = (ChunkProviderServerMixin) server.getChunkProvider();

        try
        {
            Set<Long> droppedChunks = provider.minihud_getDroppedChunks();
            Field field = droppedChunks.getClass().getDeclaredField("map");
            field.setAccessible(true);

            @SuppressWarnings("unchecked")
            HashMap<Object, Object> map = (HashMap<Object, Object>) field.get(droppedChunks);
            field = map.getClass().getDeclaredField("table");
            field.setAccessible(true);

            Object[] table = (Object []) field.get(map);

            if (table == null)
            {
                return 2;
            }

            return table.length;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return -1;
    }


    /**
     * This method has been taken from the Carpet mod, by gnembon
     */
    public static int getChunkOrder(int chunkX, int chunkZ, int hashSize)
    {
        try
        {
            Method method = HashMap.class.getDeclaredMethod("hash", Object.class);
            method.setAccessible(true);

            return (Integer) method.invoke(null, Long.hashCode(ChunkPos.asLong(chunkX, chunkZ))) & (hashSize - 1);
        }
        catch (Exception e)
        {
            LiteModMiniHud.logger.error("Error while trying to get the chunk unload order");
            return -1;
        }
    }

    public static int getSpawnableChunksCount(WorldServer world)
    {
        Set<ChunkPos> eligibleChunksForSpawning = new HashSet<>();
        int chunkCount = 0;

        for (EntityPlayer player : world.playerEntities)
        {
            if (player.isSpectator() == false)
            {
                int cx = MathHelper.floor(player.posX / 16.0D);
                int cz = MathHelper.floor(player.posZ / 16.0D);
                int chunkRadius = 8;

                for (int cxOff = -chunkRadius; cxOff <= chunkRadius; ++cxOff)
                {
                    for (int czOff = -chunkRadius; czOff <= chunkRadius; ++czOff)
                    {
                        boolean edgeChunk = cxOff == -chunkRadius || cxOff == chunkRadius || czOff == -chunkRadius || czOff == chunkRadius;
                        ChunkPos chunkPos = new ChunkPos(cxOff + cx, czOff + cz);

                        if (eligibleChunksForSpawning.contains(chunkPos) == false)
                        {
                            ++chunkCount;

                            if (edgeChunk == false && world.getWorldBorder().contains(chunkPos))
                            {
                                PlayerChunkMapEntry playerchunkmapentry = world.getPlayerChunkMap().getEntry(chunkPos.x, chunkPos.z);

                                if (playerchunkmapentry != null && playerchunkmapentry.isSentToPlayers())
                                {
                                    eligibleChunksForSpawning.add(chunkPos);
                                }
                            }
                        }
                    }
                }
            }
        }

        return chunkCount;
    }

    public static void getItemTooltip(ItemStack stack, List<String> lines)
    {
        boolean showPretty = Configs.Generic.ITEM_NBT_KEY_PRETTY.isHeld();
        boolean showString = Configs.Generic.ITEM_NBT_KEY_STRING.isHeld();

        // If the vanilla advanced tooltips are disabled, add them here, when showing a tooltip
        if (GameUtils.getClient().gameSettings.advancedItemTooltips == false && (showPretty || showString))
        {
            if (stack.isItemDamaged())
            {
                lines.add(StringUtils.translate("item.durability", stack.getMaxDamage() - stack.getItemDamage(), stack.getMaxDamage()));
            }

            String regName = Item.REGISTRY.getNameForObject(stack.getItem()).toString();
            lines.add(StringUtils.translate("minihud.tooltip.item.registry_name", regName));

            if (stack.hasTagCompound())
            {
                lines.add(StringUtils.translate("minihud.tooltip.item.nbt", stack.getTagCompound().getKeySet().size()));
            }
        }

        NBTTagCompound tag = stack.getTagCompound();

        if (tag != null)
        {
            String color = StringUtils.translate("minihud.tooltip.item.stringified_nbt_base_color");

            if (showPretty)
            {
                lines.addAll((new PrettyNbtStringifier(color).getNbtLines(tag)));
            }

            if (showString)
            {
                String str = (new SimpleNbtStringifier(color)).getNbtString(tag);
                StringUtils.splitTextToLines(lines, str, 240);
            }
        }
    }

    public static boolean isStructureWithinRange(IntBoundingBox bb, BlockPos playerPos, int maxRange)
    {
        return playerPos.getX() >= (bb.minX - maxRange) &&
               playerPos.getX() <= (bb.maxX + maxRange) &&
               playerPos.getZ() >= (bb.minZ - maxRange) &&
               playerPos.getZ() <= (bb.maxZ + maxRange);
    }

    public static boolean isStructureWithinRange(StructureBoundingBox bb, BlockPos playerPos, int maxRange)
    {
        return playerPos.getX() >= (bb.minX - maxRange) &&
               playerPos.getX() <= (bb.maxX + maxRange) &&
               playerPos.getZ() >= (bb.minZ - maxRange) &&
               playerPos.getZ() <= (bb.maxZ + maxRange);
    }
}
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.ArrayListMultimap;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldServer;
import net.minecraft.world.gen.ChunkGeneratorEnd;
import net.minecraft.world.gen.ChunkGeneratorFlat;
import net.minecraft.world.gen.ChunkGeneratorHell;
import net.minecraft.world.gen.ChunkGeneratorOverworld;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.structure.MapGenScatteredFeature;
import net.minecraft.world.gen.structure.MapGenStronghold;
import net.minecraft.world.gen.structure.MapGenStructure;
import net.minecraft.world.gen.structure.MapGenStructureIO;
import net.minecraft.world.gen.structure.MapGenVillage;
import net.minecraft.world.gen.structure.StructureComponent;
import net.minecraft.world.gen.structure.StructureOceanMonument;
import net.minecraft.world.gen.structure.StructureStart;
import fi.dy.masa.malilib.registry.Registry;
import fi.dy.masa.malilib.util.FileUtils;
import fi.dy.masa.malilib.util.GameUtils;
import fi.dy.masa.malilib.util.StringUtils;
import fi.dy.masa.malilib.util.data.Constants;
import fi.dy.masa.malilib.util.nbt.NbtUtils;
import fi.dy.masa.minihud.LiteModMiniHud;
import fi.dy.masa.minihud.MiniHUD;
import fi.dy.masa.minihud.Reference;
import fi.dy.masa.minihud.config.RendererToggle;
import fi.dy.masa.minihud.mixin.structure.ChunkGeneratorEndMixin;
import fi.dy.masa.minihud.mixin.structure.ChunkGeneratorFlatMixin;
import fi.dy.masa.minihud.mixin.structure.ChunkGeneratorHellMixin;
import fi.dy.masa.minihud.mixin.structure.ChunkGeneratorOverworldMixin;
import fi.dy.masa.minihud.mixin.structure.ChunkProviderServerMixin;
import fi.dy.masa.minihud.mixin.structure.MapGenStructureMixin;
import fi.dy.masa.minihud.network.CarpetStructurePacketHandler;
import fi.dy.masa.minihud.network.ServuxStructurePacketHandler;
import fi.dy.masa.minihud.util.MiscUtils;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
            LiteModMiniHud.logger.warn("Failed to read structure data from Servux mod packet", e);
        }
    }

    private void readStructureDataServuxV1(NBTTagCompound nbt)
    {
        NBTTagList tagList = nbt.getTagList("Structures", Constants.NBT.TAG_COMPOUND);

        synchronized (this.structureMap)
        {
            this.structureMap.clear();

            StructureData.readStructureDataServux(this.structureMap, tagList);

            this.hasStructureDataFromServer = true;
            this.structuresDirty = true;
            this.structuresNeedUpdating = false;

            EntityPlayer player = GameUtils.getClientPlayer();

            if (player != null)
            {
                this.lastStructureUpdatePos = new BlockPos(player);
            }

            MiniHUD.logInfo("Structure data updated from Servux server, structures: {}", this.structureMap.size());
        }
    }

    private void readStructureDataCarpetAll(NBTTagCompound nbt)
    {
        NBTTagList tagList = nbt.getTagList("Boxes", Constants.NBT.TAG_LIST);
        DataStorage.getInstance().setWorldSeed(nbt.getLong("Seed"));

        synchronized (this.structureMap)
        {
            this.structureMap.clear();
            StructureData.readStructureDataCarpetAllBoxes(this.structureMap, tagList);
            this.hasStructureDataFromServer = true;
            this.structuresDirty = true;
            this.structuresNeedUpdating = false;

            EntityPlayer player = GameUtils.getClientPlayer();

            if (player != null)
            {
                this.lastStructureUpdatePos = new BlockPos(player);
            }

            MiniHUD.logInfo("Structure data updated from Carpet server (all), structures: {}",
                            this.structureMap.size());
        }
    }

    private void readStructureDataCarpetSplitHeader(NBTTagCompound nbt, int boxCount)
    {
        DataStorage.getInstance().setWorldSeed(nbt.getLong("Seed"));

        synchronized (this.structureMap)
        {
            this.structureMap.clear();
            StructureData.readStructureDataCarpetIndividualBoxesHeader(boxCount);
        }

        MiniHUD.logInfo("Structure data header received from Carpet server, expecting {} boxes", boxCount);
    }

    private void readStructureDataCarpetSplitBoxes(PacketBuffer data, int boxCount) throws IOException
    {
        synchronized (this.structureMap)
        {
            for (int i = 0; i < boxCount; ++i)
            {
                NBTTagCompound nbt = data.readCompoundTag();
                StructureData.readStructureDataCarpetIndividualBoxes(this.structureMap, nbt);
            }

            this.hasStructureDataFromServer = true;
            this.structuresDirty = true;
            this.structuresNeedUpdating = false;

            EntityPlayer player = GameUtils.getClientPlayer();

            if (player != null)
            {
                this.lastStructureUpdatePos = new BlockPos(player);
            }

            MiniHUD.logInfo("Structure data received from Carpet server (split boxes), received {} boxes", boxCount);
        }
    }

    private void addStructureDataFromGenerator(IChunkGenerator chunkGenerator, BlockPos playerPos, int maxRange)
    {
        if (chunkGenerator instanceof ChunkGeneratorOverworld)
        {
            MapGenStructure mapGen;

            mapGen = ((ChunkGeneratorOverworldMixin) chunkGenerator).minihud_getOceanMonumentGenerator();
            this.addStructuresWithinRange(StructureType.OCEAN_MONUMENT, mapGen, playerPos, maxRange);

            mapGen = ((ChunkGeneratorOverworldMixin) chunkGenerator).minihud_getScatteredFeatureGenerator();
            this.addTempleStructuresWithinRange(mapGen, playerPos, maxRange);

            mapGen = ((ChunkGeneratorOverworldMixin) chunkGenerator).minihud_getStrongholdGenerator();
            this.addStructuresWithinRange(StructureType.STRONGHOLD, mapGen, playerPos, maxRange);

            mapGen = ((ChunkGeneratorOverworldMixin) chunkGenerator).minihud_getVillageGenerator();
            this.addStructuresWithinRange(StructureType.VILLAGE, mapGen, playerPos, maxRange);

            mapGen = ((ChunkGeneratorOverworldMixin) chunkGenerator).minihud_getWoodlandMansionGenerator();
            this.addStructuresWithinRange(StructureType.MANSION, mapGen, playerPos, maxRange);
        }
        else if (chunkGenerator instanceof ChunkGeneratorHell)
        {
            MapGenStructure mapGen = ((ChunkGeneratorHellMixin) chunkGenerator).minihud_getFortressGenerator();
            this.addStructuresWithinRange(StructureType.NETHER_FORTRESS, mapGen, playerPos, maxRange);
        }
        else if (chunkGenerator instanceof ChunkGeneratorEnd)
        {
            MapGenStructure mapGen = ((ChunkGeneratorEndMixin) chunkGenerator).minihud_getEndCityGenerator();