blob: f88b2c97e9ba5c22c76f4ada6be81d87fec454aa [file] [log] [blame] [raw]
/**
* Copyright (c) 2010-2011, Vincent Vollers and Christopher J. Kucera
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Minecraft X-Ray team nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL VINCENT VOLLERS OR CJ KUCERA BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.apocalyptech.minecraft.xray;
import com.apocalyptech.minecraft.xray.WorldInfo;
import static com.apocalyptech.minecraft.xray.MinecraftConstants.*;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Graphics2D;
import java.awt.AlphaComposite;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.LineNumberReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileFilter;
import java.util.Map;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Comparator;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipFile;
import java.util.zip.ZipException;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import javax.imageio.ImageIO;
/***
* Utility class which has convenience methods to access the
* files of the current minecraft installation
* @author Vincent Vollers
*/
public class MinecraftEnvironment {
public static enum OS {Windows, MacOS, Linux};
public static OS os;
public static File baseDir;
public static File xrayBaseDir;
public static HashMap<Byte, Integer> silverfishDataPlain;
public static HashMap<Byte, Integer> silverfishDataHighlighted;
private static class MCDirectoryFilter implements FileFilter
{
public MCDirectoryFilter() {
// Nothing, really
}
public boolean accept(File pathname) {
if (pathname.exists() && pathname.canRead() && pathname.isDirectory())
{
File levelDat = new File(pathname, "level.dat");
return (levelDat.exists() && levelDat.canRead());
}
return false;
}
}
private static class BlockdefFilter implements FilenameFilter
{
public BlockdefFilter() {
// Nothing, really
}
public boolean accept(File pathname, String filename) {
File tempFile = new File(pathname, filename);
return (tempFile.isFile() &&
tempFile.canRead() &&
!filename.equalsIgnoreCase("minecraft.yaml") &&
filename.endsWith(".yaml"));
}
}
private static class CaseInsensitiveComparator implements Comparator<File>
{
public int compare(File filea, File fileb)
{
return filea.getName().compareToIgnoreCase(fileb.getName());
}
}
static {
String os = System.getProperty( "os.name" );
if (os.startsWith("Mac"))
{
MinecraftEnvironment.os = OS.MacOS;
}
else if (os.startsWith("Windows"))
{
MinecraftEnvironment.os = OS.Windows;
}
else
{
MinecraftEnvironment.os = OS.Linux;
}
switch(MinecraftEnvironment.os) {
case Windows:
String basedir = System.getenv("APPDATA");
if (basedir == null)
{
basedir = System.getProperty("user.home");
}
MinecraftEnvironment.baseDir = new File(basedir, ".minecraft");
MinecraftEnvironment.xrayBaseDir = new File(basedir, ".minecraft_xray");
break;
case Linux:
MinecraftEnvironment.baseDir = new File(System.getProperty("user.home"), ".minecraft");
MinecraftEnvironment.xrayBaseDir = new File(System.getProperty("user.home"), ".minecraft_xray");
break;
case MacOS:
// TODO: we should really be using "minecraft_xray" without a dot; perhaps have something to convert that.
// Apparently the minecraft directory itself doesn't use a dot here, so we're being weird by doing it
// ourselves.
File dotMinecraftEnv = new File(System.getProperty("user.home"), "Library/Application Support/.minecraft");
if(dotMinecraftEnv.exists()) {
MinecraftEnvironment.baseDir = dotMinecraftEnv;
MinecraftEnvironment.xrayBaseDir = new File(System.getProperty("user.home"), "Library/Application Support/.minecraft_xray");
} else {
MinecraftEnvironment.baseDir = new File(System.getProperty("user.home"), "Library/Application Support/minecraft"); // untested
MinecraftEnvironment.xrayBaseDir = new File(System.getProperty("user.home"), "Library/Application Support/minecraft_xray"); // untested
}
break;
default:
MinecraftEnvironment.baseDir = null;
MinecraftEnvironment.xrayBaseDir = null;
}
//XRay.logger.debug(MinecraftEnvironment.baseDir.getAbsolutePath());
}
/***
* Returns a list of WorldInfo objects, corresponding to available worlds
* @return
*/
public static ArrayList<WorldInfo> getAvailableWorlds() {
ArrayList<WorldInfo> worlds = new ArrayList<WorldInfo>();
File saveDir = new File(baseDir, "saves");
File[] worldDirs = saveDir.listFiles(new MCDirectoryFilter());
try
{
Arrays.sort(worldDirs, new CaseInsensitiveComparator());
for (File worldDir : worldDirs)
{
try
{
// First snatch up the overworld
WorldInfo info = new WorldInfo(worldDir.getCanonicalPath(), worldDir.getName(), 0, false);
worlds.add(info);
// Now see if there's any associated dimensions we can add.
ArrayList<WorldInfo> diminfo = info.getDimensionInfo();
for (WorldInfo dim : diminfo)
{
if (dim != null)
{
worlds.add(dim);
}
}
}
catch (Exception e)
{
XRay.logger.warn(e.toString());
// Nothing; guess we'll ignore it.
}
}
}
catch (NullPointerException e)
{
// This happens on the Arrays.sort() call if there's no "saves" dir at all.
}
return worlds;
}
/***
* Returns a file handle to a chunk file in a world. Will attempt to load
* from region data first, if it's present, and then from the old-style
* chunk-per-file format if the world's not from Beta 1.3 or later. It
* turns out that there isn't really any circumstance where there would be
* a mix, so we could be more strict about it, but whatever.
*
* @param world
* @param x
* @param z
* @return
*/
public static DataInputStream getChunkInputStream(WorldInfo world, int x, int z) {
if (world.has_region_data)
{
RegionFile rf = RegionFileCache.getRegionFile(new File(world.getBasePath()), x, z);
if (rf != null)
{
DataInputStream chunk = rf.getChunkDataInputStream(x & 31, z & 31);
if (chunk != null)
{
return chunk;
}
}
}
if (!world.is_beta_1_3_level)
{
int xx = x % 64;
if(xx<0) xx = 64+xx;
int zz = z % 64;
if(zz<0) zz = 64+zz;
String firstFolder = Integer.toString(xx, 36);
String secondFolder = Integer.toString(zz, 36);
String filename = "c." + Integer.toString(x, 36) + "." + Integer.toString(z, 36) + ".dat";
File chunk = new File(world.getBasePath(), firstFolder + "/" + secondFolder + "/" + filename);
if (chunk.exists())
{
// There's some code duplication here from DTFReader.readDTFFile()
try {
return new DataInputStream(new GZIPInputStream(new FileInputStream(chunk)));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return null;
}
/***
* Returns a file handle to the base minecraft directory
* @return
*/
public static File getMinecraftDirectory() {
return MinecraftEnvironment.baseDir;
}
/**
* Returns a file handle to our own data directory
* @return
*/
public static File getXrayDirectory() {
return MinecraftEnvironment.xrayBaseDir;
}
/**
* Returns a file handle to our config file; will create the
* directory if needed. This will also create some other
* subdirectories if needed.
* @return
*/
public static File getXrayConfigFile() {
if (MinecraftEnvironment.xrayBaseDir.exists())
{
if (!MinecraftEnvironment.xrayBaseDir.isDirectory())
{
return null;
}
}
else
{
if (!MinecraftEnvironment.xrayBaseDir.mkdir())
{
return null;
}
}
File texDir = new File(MinecraftEnvironment.xrayBaseDir, "textures");
if (!texDir.exists())
{
texDir.mkdir();
}
File blockdefDir = new File(MinecraftEnvironment.xrayBaseDir, "blockdefs");
if (!blockdefDir.exists())
{
blockdefDir.mkdir();
}
return new File(MinecraftEnvironment.xrayBaseDir, "xray.properties");
}
/**
* Get a list of BlockTypeCollections from the given directory
*/
public static ArrayList<BlockTypeCollection> getBlockTypeCollectionFilesFromDir(File dir, boolean global)
{
ArrayList<BlockTypeCollection> list = new ArrayList<BlockTypeCollection>();
BlockTypeCollection tempcollection;
String fullFilename;
if (dir.exists() && dir.isDirectory() && dir.canRead())
{
for (String filename : dir.list(new BlockdefFilter()))
{
fullFilename = dir.getPath() + "/" + filename;
try
{
tempcollection = MinecraftConstants.loadBlocks(fullFilename, global);
}
catch (BlockTypeLoadException e)
{
tempcollection = new BlockTypeCollection(new File(fullFilename), global, e);
}
list.add(tempcollection);
}
}
return list;
}
/**
* Returns a list of available BlockType files, both from our global directory,
* and the user xrayBaseDir
*/
public static ArrayList<BlockTypeCollection> getBlockTypeCollectionFiles()
{
ArrayList<BlockTypeCollection> list = new ArrayList<BlockTypeCollection>();
list.addAll(getBlockTypeCollectionFilesFromDir(new File("blockdefs"), true));
list.addAll(getBlockTypeCollectionFilesFromDir(new File(xrayBaseDir, "blockdefs"), false));
return list;
}
/***
* Returns a stream to an arbitrary file either from our override directory,
* the main jar, or from the user-specified texture pack.
*
* @return
*/
public static InputStream getMinecraftTexturepackData(String filename) {
// First check our override directory for the file
File overrideFile = new File(xrayBaseDir, "textures/" + filename);
if(overrideFile.exists()) {
try {
XRay.logger.info("Overriding " + filename + " at " + overrideFile.getPath());
return new FileInputStream(overrideFile);
} catch (FileNotFoundException e) {
// Don't do anything; just continue on our merry little way
}
}
// Next check the options.txt file to see if we should be using the defined
// texture pack.
File optionsFile = new File(baseDir, "options.txt");
String texturepack = null;
if (optionsFile.exists())
{
LineNumberReader reader = null;
try
{
reader = new LineNumberReader(new FileReader(optionsFile));
String line = null;
String[] parts;
while ((line = reader.readLine()) != null)
{
parts = line.split(":", 2);
if (parts.length == 2)
{
if (parts[0].equalsIgnoreCase("skin"))
{
if (parts[1].equalsIgnoreCase("Default"))
{
// Default skin, just break and
break;
}
else
{
// Use the specified texture pack
texturepack = parts[1];
break;
}
}
}
}
}
catch (FileNotFoundException e)
{
// Just ignore it and load the default terrain.png
}
catch (IOException e)
{
// Ditto, just ignore
}
if (reader != null)
{
try
{
reader.close();
}
catch (IOException e)
{
// do nothing
}
}
}
// Attempt to load in the texture pack
if (texturepack != null)
{
File packFile = new File(baseDir, "texturepacks/" + texturepack);
if (packFile.exists())
{
ZipFile zf = null;
try
{
zf = new ZipFile(packFile);
ZipEntry entry = zf.getEntry(filename);
if (entry != null)
{
XRay.logger.info("Using " + filename + " from texturepack " + texturepack);
return zf.getInputStream(entry);
}
}
catch (ZipException e)
{
// Do nothing.
}
catch (IOException e)
{
// Do nothing
}
if (zf != null)
{
try
{
zf.close();
}
catch (IOException e)
{
// do nothing
}
}
}
}
// If we got here, just do what we've always done.
return getMinecraftFile(filename);
}
/***
* Returns a stream to the texture data (overrides in the directory are handled)
* @return
*/
public static InputStream getMinecraftTextureData() {
return getMinecraftTexturepackData("terrain.png");
}
/***
* Returns a stream to the water texture data
* @return
*/
public static InputStream getMinecraftWaterData() {
return getMinecraftTexturepackData("misc/water.png");
}
/***
* Returns a stream to the water texture data
* @return
*/
public static InputStream getMinecraftParticleData() {
return getMinecraftTexturepackData("particles.png");
}
/**
* Returns a stream to the painting data
* @return
*/
public static InputStream getMinecraftPaintingData() {
return getMinecraftTexturepackData("art/kz.png");
}
/***
* Attempts to create a bufferedImage for a stream
* @param i
* @return
*/
public static BufferedImage buildImageFromInput(InputStream i) {
if (i == null)
{
return null;
}
try {
return ImageIO.read(i);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
/**
* Attempts to create a bufferedImage containing our painting sheet
* @return
*/
public static BufferedImage getMinecraftPaintings() {
return buildImageFromInput(getMinecraftPaintingData());
}
/**
* Tints a square at the given coords by the given color, into the specified BufferedImage
* @param coords The coordinates to read from and draw to
* @param square_width width of each square
* @param ac an AlphaComposite to blend with
* @param color The color to tint
* @param bi The image we'll be drawing to and reading from
* @param g2d That image's Graphics2D object
*/
private static void tintSquare(int[] coords, int square_width, AlphaComposite ac, Color color, BufferedImage bi, Graphics2D g2d)
{
Rectangle rect = new Rectangle(coords[0]*square_width, coords[1]*square_width, square_width, square_width);
g2d.setComposite(ac);
g2d.setColor(color);
g2d.fill(rect);
g2d.drawImage(bi, null, 0, 0);
}
/***
* Attempts to create a bufferedImage containing the texture sprite sheet. This does
* some munging to make things a little more useful for us. Namely:
*
* 1) It will attempt to colorize any biome-ready skin by first checking for green
* pixels in the texture, and then doing some blending if it looks greyscale.
* 2) It will colorize the redstone wire texture appropriately (due to the Beta 1.3
* change where redstone wire color depends on how far from the source it is)
* 3) We also copy in the water texture from misc/water.png, because many third-party
* skins don't actually have a water graphic in the same place as the default skin
* 4) Then we attempt to construct a passable "fire" texture from the particles file.
* 5) Then we create a "blank" texture, for use with unknown block types
* 6) Then we create a nether (portal) texture and an air portal texture
* 7) Lastly, we duplicate the texture with a green tint, immediately below the
* main texture group. We do this to support our "explored" highlighting - the
* tinting can be done easily via OpenGL itself, but there were pretty severe
* performance issues when I tried that on my laptop. If we just modify the texture
* and use offsets instead, there's no FPS drop on there. This DOES have the
* unfortunate downside that, when specifying texture coordinates with glTexCoord2f(),
* we can no longer think of the textures as perfectly "square." The Y offsets must
* be half of what we're used to. Perhaps it would make sense to double the X axis
* here as well, so that we could avoid some confusion; for now I'll leave it though.
*
* TODO: it would be good to use the data values loaded from YAML to figure out where
* to colorize, rather than hardcoding them here.
*
* @return
*/
public static ArrayList<BufferedImage> getMinecraftTexture()
throws BlockTypeLoadException
{
BufferedImage bi = buildImageFromInput(getMinecraftTextureData());
if (bi == null)
{
return null;
}
Graphics2D g2d = bi.createGraphics();
// Figure out our square size, and then check to see if the grass tile is
// grayscale or not; we do this by examining the middle row of pixels. If
// it *is* grayscale, then colorize it.
// It does seem a little stupid, at this point, to be bothering with this,
// but in the end I think I'd like this to continue working properly on older
// versions of Minecraft.
int square_width = bi.getWidth()/16;
int[] pixels = new int[square_width];
int i;
int r, g, b;
boolean grayscale = true;
int[] tex_coords = BLOCK_GRASS.getTexCoordsArr();
bi.getRGB(tex_coords[0]*square_width, (tex_coords[1]*square_width)+(square_width/2), square_width, 1, pixels, 0, square_width);
for (i=0; i<square_width; i++)
{
//a = (pixels[i] & 0xFF000000) >> 24;
r = (pixels[i] & 0x00FF0000) >> 16;
g = (pixels[i] & 0x0000FF00) >> 8;
b = (pixels[i] & 0x000000FF);
//XRay.logger.debug("Pixel " + i + ": " + r + ", " + g + ", " + b + ", " + a);
if (g > r+50 || g > b+50)
{
grayscale = false;
break;
}
}
// Now do our colorizing. We'll do it in two parts because technically we're doing
// a check for whether or not grass is grayscale (if grass isn't grayscale, leaves
// won't be either) (though technically if that's the case, our map won't contain
// any of the other blocks we're colorizing, either, so it's a moot point)
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.4f);
BlockType block;
Rectangle rect;
if (grayscale)
{
for (String blockname : new String[] { "GRASS", "LEAVES" })
{
block = blockCollection.getByName(blockname);
if (block != null)
{
tex_coords = block.getTexCoordsArr();
tintSquare(block.getTexCoordsArr(), square_width, ac, Color.green, bi, g2d);
}
}
// Our one hardcoded-for-now colorization: the side-grass overlay
int[] sideGrassCoordsOverlay = new int[] {6, 2};
tintSquare(sideGrassCoordsOverlay, square_width, ac, Color.green, bi, g2d);
// Overlay our custom-colorized side grass on top of the side-grass image
int[] sideGrassCoords = BlockType.getTexCoordsArr(BLOCK_GRASS.texture_dir_map.get(BlockType.DIRECTION_REL.SIDES));
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2d.drawImage(bi,
sideGrassCoords[0]*square_width, sideGrassCoords[1]*square_width,
(sideGrassCoords[0]+1)*square_width, (sideGrassCoords[1]+1)*square_width,
sideGrassCoordsOverlay[0]*square_width, sideGrassCoordsOverlay[1]*square_width,
(sideGrassCoordsOverlay[0]+1)*square_width, (sideGrassCoordsOverlay[1]+1)*square_width,
null);
}
// Some blocks to tint unconditionally
for (String blockname : new String[] { "VINE", "PUMPKIN_STEM", "LILY_PAD" })
{
block = blockCollection.getByName(blockname);
if (block != null)
{
tex_coords = block.getTexCoordsArr();
tintSquare(block.getTexCoordsArr(), square_width, ac, Color.green.darker(), bi, g2d);
}
}
// PUMPKIN_STEM has an additional texture as well
// TODO: Technically we should maybe do MELON_STEM (both here and above) separately,
// but we'd need to keep track of which textures we've tinted, so as not to tint
// something twice. Right now these both use the same texture (though technically
// we should copy the texture and tint it twice, differently for each) so I'm just
// doing the one.
block = blockCollection.getByName("PUMPKIN_STEM");
if (block != null)
{
if (block.texture_extra_map != null)
{
tex_coords = BlockType.getTexCoordsArr(block.texture_extra_map.get("curve"));
tintSquare(tex_coords, square_width, ac, Color.green.darker(), bi, g2d);
}
}
// Two data values of Tall Grass should get colorized
block = blockCollection.getByName("TALL_GRASS");
if (block != null)
{
if (block.texture_data_map != null)
{
for (byte data : new byte[] { 1, 2 })
{
if (block.texture_data_map.containsKey(data))
{
tintSquare(BlockType.getTexCoordsArr(block.texture_data_map.get(data)),
square_width, ac, Color.green.darker(), bi, g2d);
}
}
}
}
// Colorize redstone wire
block = blockCollection.getByName("REDSTONE_WIRE");
if (block != null)
{
AlphaComposite redstone_ac = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1f);
tintSquare(block.getTexCoordsArr(), square_width, redstone_ac, Color.red, bi, g2d);
}
// Generate separate textures for each of the Silverfish textures, and colorize them
// I feel a little guilty for occupying three whole new textures for this, especially given the
// recent addition of mod support. More and more likely that folks will break into the
// two-texture range (though at least we do support it now). I may want to look into just
// doing some GL trickery to tint them at render-time instead. If I ever get around to doing
// some simple shading on the blocks, that'd probably be a good opportunity to do so.
// TODO: that ^
block = BLOCK_SILVERFISH;
silverfishDataPlain = new HashMap<Byte, Integer>();
silverfishDataHighlighted = new HashMap<Byte, Integer>();
if (block.texture_data_map != null)
{
for (Map.Entry<Byte, Integer> entry : block.texture_data_map.entrySet())
{
int new_tex = blockCollection.reserveTexture();
int[] old_coords = BlockType.getTexCoordsArr(entry.getValue());
int[] new_coords = BlockType.getTexCoordsArr(new_tex);
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(bi,
new_coords[0]*square_width, new_coords[1]*square_width,
(new_coords[0]+1)*square_width, (new_coords[1]+1)*square_width,
old_coords[0]*square_width, old_coords[1]*square_width,
(old_coords[0]+1)*square_width, (old_coords[1]+1)*square_width,
null);
tintSquare(new_coords, square_width, ac, Color.red, bi, g2d);
silverfishDataPlain.put(entry.getKey(), entry.getValue());
silverfishDataHighlighted.put(entry.getKey(), new_tex);
}
}
// Load in the water texture separately and pretend it's a part of the main texture pack.
BLOCK_WATER.setTexIdx(blockCollection.reserveTexture());
int[] water_tex = BLOCK_WATER.getTexCoordsArr();
BLOCK_STATIONARY_WATER.setTexIdxCoords(water_tex[0], water_tex[1]);
BufferedImage bi2 = buildImageFromInput(getMinecraftWaterData());
int water_width = bi2.getWidth();
g2d.setComposite(AlphaComposite.Src);
if (square_width < water_width)
{
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
else
{
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
g2d.drawImage(bi2, water_tex[0]*square_width, water_tex[1]*square_width, square_width, square_width, null);
// Also create a fake sort of "fire" graphic to use
BLOCK_FIRE.setTexIdx(blockCollection.reserveTexture());
int[] fire_tex = BLOCK_FIRE.getTexCoordsArr();
bi2 = buildImageFromInput(getMinecraftParticleData());
int particle_width = bi2.getWidth()/16;
int fire_x = fire_tex[0];
int fire_y = fire_tex[1];
int flame_x = 0;
int flame_y = 3;
int start_fire_x = fire_x*square_width;
int start_fire_y = fire_y*square_width;
int start_flame_x = flame_x*particle_width;
int start_flame_y = flame_y*particle_width;
g2d.setComposite(AlphaComposite.Src);
g2d.setColor(new Color(0f, 0f, 0f, 0f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2d.fillRect(fire_x*square_width, fire_y*square_width, square_width, square_width);
if (square_width < (particle_width*2))
{
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
g2d.drawImage(bi2,
start_fire_x, start_fire_y, start_fire_x+(square_width/2), start_fire_y+(square_width/2),
start_flame_x, start_flame_y, start_flame_x+particle_width, start_flame_y+particle_width,
null);
g2d.drawImage(bi2,
start_fire_x+(square_width/2), start_fire_y, start_fire_x+square_width, start_fire_y+(square_width/2),
start_flame_x, start_flame_y, start_flame_x+particle_width, start_flame_y+particle_width,
null);
g2d.drawImage(bi2,
start_fire_x, start_fire_y+(square_width/2), start_fire_x+(square_width/2), start_fire_y+square_width,
start_flame_x, start_flame_y, start_flame_x+particle_width, start_flame_y+particle_width,
null);
g2d.drawImage(bi2,
start_fire_x+(square_width/2), start_fire_y+(square_width/2), start_fire_x+square_width, start_fire_y+square_width,
start_flame_x, start_flame_y, start_flame_x+particle_width, start_flame_y+particle_width,
null);
// Create an "empty block" graphic. We could just leave it, probably, but
// some texture packs get creative with the empty space.
// First the fill
BLOCK_UNKNOWN.setTexIdx(blockCollection.reserveTexture());
int[] unknown_tex = BLOCK_UNKNOWN.getTexCoordsArr();
int empty_start_x = square_width*unknown_tex[0];
int empty_start_y = square_width*unknown_tex[1];
g2d.setColor(new Color(214,127,255));
g2d.fillRect(empty_start_x, empty_start_y, square_width-1, square_width-1);
// Then the border
g2d.setColor(new Color(107,63,127));
g2d.drawRect(empty_start_x, empty_start_y, square_width-1, square_width-1);
// Create a nether portal texture
BLOCK_PORTAL.setTexIdx(blockCollection.reserveTexture());
int[] portal_tex = BLOCK_PORTAL.getTexCoordsArr();
int nether_start_x = square_width*portal_tex[0];
int nether_start_y = square_width*portal_tex[1];
g2d.setColor(new Color(.839f, .203f, .952f, .4f));
g2d.fillRect(nether_start_x, nether_start_y, square_width, square_width);
// Create an end portal texture
BLOCK_END_PORTAL.setTexIdx(blockCollection.reserveTexture());
int[] end_tex = BLOCK_END_PORTAL.getTexCoordsArr();
int end_start_x = square_width*end_tex[0];
int end_start_y = square_width*end_tex[1];
g2d.setColor(new Color(0f, 0f, 0f));
g2d.fillRect(end_start_x, end_start_y, square_width, square_width);
// Send our processed texture
blockCollection.setInitialTexture(bi);
// Load in extra texture sheets
blockCollection.importCustomTextureSheets();
// Load in filename textures, if needed
blockCollection.loadFilenameTextures();
// Report on the count of texture sheets
XRay.logger.debug("Texture Sheet count: " + blockCollection.textures.size());
// For each texture we now have, copy the texture underneath, tinted for our "explored" areas
i = 0;
ArrayList<BufferedImage> explored = new ArrayList<BufferedImage>();
for (BufferedImage image : blockCollection.textures)
{
bi2 = new BufferedImage(image.getWidth(), image.getHeight()*2, BufferedImage.TYPE_INT_ARGB);
g2d = bi2.createGraphics();
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
g2d.drawImage(image, 0, image.getHeight(), image.getWidth(), image.getHeight(), null);
g2d.setComposite(AlphaComposite.SrcAtop);
g2d.setColor(new Color(0f, 1f, 0f, .2f));
g2d.fillRect(0, image.getHeight(), image.getWidth(), image.getHeight());
explored.add(bi2);
/*
try {
ImageIO.write(image, "PNG", new File(System.getProperty("user.home"), "xray_terrain_" + i + ".png"));
XRay.logger.info("Wrote texture to ~/xray_terrain_" + i + ".png");
}
catch (Exception e)
{
// whatever
}
*/
i++;
}
// This bit here is extremely grody; we should just do this in-place or something
blockCollection.textures = explored;
// And while we're on that subject, let's populate our exploredBlocks HashMap
exploredBlocks = new HashMap<Short, Boolean>();
for (BlockType expblock : blockCollection.getBlocksFull())
{
if (expblock.getExplored())
{
exploredBlocks.put(expblock.id, true);
}
}
// a bit unnecessary since that's a constant...
return blockCollection.textures;
}
/***
* Creates an Inputstream to a file in side the main minecraft.jar file, or failing
* that, to our bundled fallbacks.
*
* @param fileName
* @return
*/
public static InputStream getMinecraftFile(String fileName){
File minecraftDataFile = new File(baseDir, "bin/minecraft.jar");
if(minecraftDataFile.exists()) {
if (minecraftDataFile.isDirectory())
{
// Some mods (TooManyItems in particular) on some OSes (OSX in particular)
// end up replacing minecraft.jar with an unpacked directory containing
// its contents. We may as well check for that.
File dataFile = new File(minecraftDataFile, fileName);
if (dataFile.exists())
{
try
{
return new FileInputStream(dataFile);
}
catch (FileNotFoundException e)
{
}
}
}
else
{
try {
JarFile jf = new JarFile(minecraftDataFile);
ZipEntry zipEntry = jf.getEntry(fileName);
if(zipEntry != null) {
return jf.getInputStream(zipEntry);
}
} catch (IOException e) {
XRay.logger.warn(e.toString());
//return null;
}
}
}
// If we get here, either we couldn't find minecraft.jar or there was
// something wrong with it (perhaps altered by a mod, or whatever). In
// the absence of anything else to do, we'll just load a bundled version
// that comes with X-Ray. If that doesn't work, we'll just sit down
// and have a cry. Note that this bit is sort of implicitly assuming that
// any file we might have bundled is a texture, hence the directory name.
if (fileName.equals("terrain.png") || fileName.equals("particles.png") ||
fileName.equals("art/kz.png") || fileName.equals("misc/water.png"))
{
XRay.logger.info("Resorting to bundled " + fileName);
File dataFile = new File("textures", fileName);
if (dataFile.exists())
{
try
{
return new FileInputStream(dataFile);
}
catch (FileNotFoundException e)
{
}
}
}
// *sob*
return null;
}
}