All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.minecraft.MinecraftMapTileProvider Maven / Gradle / Ivy

There is a newer version: 2.23.2
Show newest version
package org.pepsoft.minecraft;

import org.jnbt.CompoundTag;
import org.jnbt.NBTInputStream;
import org.pepsoft.util.MathUtils;
import org.pepsoft.util.swing.TileListener;
import org.pepsoft.util.swing.TileProvider;
import org.pepsoft.worldpainter.ColourScheme;
import org.pepsoft.worldpainter.colourschemes.DynMapColourScheme;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.pepsoft.minecraft.Constants.SUPPORTED_VERSION_1;
import static org.pepsoft.util.swing.TiledImageViewer.TILE_SIZE;

/**
 * Created by Pepijn Schmitz on 27-10-16.
 */
public class MinecraftMapTileProvider implements TileProvider {
    public MinecraftMapTileProvider(File mapDir) throws IOException {
        this.mapDir = mapDir;

        // Read the metadata
        Level level = Level.load(new File(mapDir, "level.dat"));
        maxHeight = level.getMaxHeight();
        version = level.getVersion();

        // Scan the region files to determine a rough extent
        File regionDir = new File(mapDir, "region");
        Pattern regionFilePattern = (version == SUPPORTED_VERSION_1)
            ? Pattern.compile("r\\.(-?\\d+)\\.(-?\\d+)\\.mcr")
            : Pattern.compile("r\\.(-?\\d+)\\.(-?\\d+)\\.mca");
        File[] regionFiles = regionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
        if ((regionFiles != null) && (regionFiles.length > 0)) {
            int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE;
            for (File file: regionFiles) {
                Matcher matcher = regionFilePattern.matcher(file.getName());
                matcher.matches();
                int x = Integer.parseInt(matcher.group(1));
                int z = Integer.parseInt(matcher.group(2));
                MinecraftMapTileProvider.this.fileCache.put(new Point(x, z), file);
                if (x < minX) {
                    minX = x;
                }
                if (x > maxX) {
                    maxX = x;
                }
                if (z < minZ) {
                    minZ = z;
                }
                if (z > maxZ) {
                    maxZ = z;
                }
            }
            extent = new Rectangle(minX << 2, minZ << 2, (maxX - minX + 1) << 2, (maxZ - minZ + 1) << 2);
        } else {
            extent = null;
        }

        colourScheme = new DynMapColourScheme("default", true);
    }

    @Override
    public int getTileSize() {
        return TILE_SIZE;
    }

    @Override
    public boolean isTilePresent(int x, int y) {
        if (zoom == 0) {
            Point regionCoords = new Point(x >> 2, y >> 2);
            return fileCache.containsKey(regionCoords);
        } else {
            return true;
        }
    }

    @Override
    public boolean paintTile(Image tileImage, int x, int y, int dx, int dy) {
        final BufferedImage image = renderBufferRef.get();
        int scale = MathUtils.pow(2, -zoom);
        final int chunkX1 = x * 8 * scale, chunkY1 = y * 8 * scale;
        final int chunkX2 = chunkX1 + 8 * scale - 1, chunkY2 = chunkY1 + 8 * scale - 1;
        int previousRegionX = Integer.MIN_VALUE, previousRegionY = Integer.MIN_VALUE;
        RegionFile previousRegion = null;
        final int step = Math.max(scale / 16, 1);
        for (int chunkX = chunkX1; chunkX <= chunkX2; chunkX += step) {
            for (int chunkY = chunkY1; chunkY <= chunkY2; chunkY += step) {
                try {
                    int regionX = chunkX >> 5, regionY = chunkY >> 5;
                    RegionFile region;
                    if ((regionX != previousRegionX) || (regionY != previousRegionY)) {
                        region = getRegionFile(regionX, regionY);
                        previousRegion = region;
                        previousRegionX = regionX;
                        previousRegionY = regionY;
                    } else {
                        region = previousRegion;
                    }
                    if (region == null) {
                        continue;
                    }
                    DataInputStream dataIn = region.getChunkDataInputStream(chunkX & 0x1f, chunkY & 0x1f);
                    if (dataIn != null) {
                        Chunk chunk;
                        try (NBTInputStream in = new NBTInputStream(dataIn)) {
                            chunk = (version == Constants.SUPPORTED_VERSION_2)
                                    ? new ChunkImpl2((CompoundTag) in.readTag(), maxHeight)
                                    : new ChunkImpl((CompoundTag) in.readTag(), maxHeight);
                        }
                        for (int blockX = 0; blockX < 16; blockX += scale) {
                            for (int blockY = 0; blockY < 16; blockY += scale) {
                                image.setRGB((((chunkX - chunkX1) << 4) | blockX) / scale, (((chunkY - chunkY1) << 4) | blockY) / scale, 0xff000000 | getColour(chunk, blockX, blockY));
                            }
                        }
                    } else {
                        for (int blockX = 0; blockX < 16; blockX += scale) {
                            for (int blockY = 0; blockY < 16; blockY += scale) {
                                image.setRGB((((chunkX - chunkX1) << 4) | blockX) / scale, (((chunkY - chunkY1) << 4) | blockY) / scale, 0);
                            }
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException("I/O error while reading chunk data", e);
                }
            }
        }
        Graphics2D g2 = (Graphics2D) tileImage.getGraphics();
        try {
            g2.setComposite(AlphaComposite.Src);
            g2.drawImage(image, dx, dy, null);
        } finally {
            g2.dispose();
        }
        return true;
    }

    @Override
    public int getTilePriority(int x, int y) {
        return 0; // All tiles have equal priority
    }

    @Override
    public Rectangle getExtent() {
        return extent;
    }

    @Override
    public void addTileListener(TileListener tileListener) {
        listeners.add(tileListener);
    }

    @Override
    public void removeTileListener(TileListener tileListener) {
        listeners.remove(tileListener);
    }

    @Override
    public boolean isZoomSupported() {
        return true;
    }

    @Override
    public int getZoom() {
        return zoom;
    }

    @Override
    public void setZoom(int zoom) {
        if (zoom != this.zoom) {
            this.zoom = zoom;
        }
    }

    private synchronized RegionFile getRegionFile(int x, int y) throws IOException {
        Point coords = new Point(x, y);
        RegionFile regionFile = regionFileCache.get(coords);
        if (regionFile == null) {
            if (fileCache.containsKey(coords)) {
                regionFile = new RegionFile(fileCache.get(coords), true);
            } else {
                regionFile = NULL;
            }
            regionFileCache.put(coords, regionFile);
        }
        if (regionFile == NULL) {
            return null;
        } else {
            return regionFile;
        }
    }

    private int getColour(Chunk chunk, int x, int y) {
        for (int z = maxHeight - 1; z >= 0; z--) {
            int blockType = chunk.getBlockType(x, z, y);
            if (blockType != Constants.BLK_AIR) {
                return colourScheme.getColour(blockType, chunk.getDataValue(x, z, y));
            }
        }
        return DEFAULT_VOID_COLOUR;
    }

    private final File mapDir;
    private final int maxHeight, version;
    private final ColourScheme colourScheme;
    private final List listeners = new ArrayList<>();
    private final Map fileCache = new HashMap<>();
    private final Map regionFileCache = new HashMap<>();
    private final Rectangle extent;
    private int zoom = 0;

    private static final int DEFAULT_VOID_COLOUR = 0x00FFFF;
    private static final RegionFile NULL = new RegionFile();
    private static final ThreadLocal renderBufferRef = ThreadLocal.withInitial(() -> new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB));
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy