blob: f1ab0d324b2ab28aefc0837f117695fd51c80e38 [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 java.io.File;
import java.io.IOException;
import java.io.FilenameFilter;
import java.lang.Comparable;
import java.lang.NumberFormatException;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.ArrayList;
import java.util.Collections;
/**
* Class to aid in maintaining a list of possible worlds for us to use.
*/
public class WorldInfo implements Comparable<WorldInfo>
{
private String basepath;
private String dirName;
private String levelName;
private boolean userChosen;
private int dimension;
public TreeMap<String, File> mp_players;
public static HashMap<Integer, String> known_dimensions;
// Some static initializations
static
{
known_dimensions = new HashMap<Integer, String>();
known_dimensions.put(-1, "Nether");
known_dimensions.put(1, "The End");
}
// Couple of variables to determine whether our chunks are stored in the new
// Region format introduced in Beta 1.3. Minecraft converts chunks on the fly,
// so for a time a world directory will have chunks in both formats. Once all
// the chunks have been converted, Minecraft will write out a few new tags in
// the main level data file, so we can stop looking for the old format.
public boolean has_region_data = false;
public boolean is_beta_1_3_level = false;
private class PlayerDatFilter implements FilenameFilter
{
public PlayerDatFilter() {
// Nothing, really
}
public boolean accept(File directory, String filename) {
return (filename.endsWith(".dat"));
}
}
/**
* Instansiate a new object. "userChosen" refers to when the user has specified
* a userChosen path, instead of one of the standard Minecraft singleplayer world
* locations.
*
* @param basepath
* @param dimension
* @param userChosen
*/
public WorldInfo(String basepath, String dirName, int dimension, boolean userChosen)
{
this.basepath = basepath;
this.dimension = dimension;
this.userChosen = userChosen;
this.dirName = dirName;
this.populateMPPlayerList();
// Load in the minecraft level, to read its name
if (basepath != null)
{
this.levelName = MinecraftLevel.getLevelName(this);
}
else
{
this.levelName = null;
}
}
/**
* Instansiate a userChosen WorldInfo - path will be added later when the user
* selects it.
*/
public WorldInfo()
{
this(null, null, 0, true);
}
public void populateMPPlayerList()
{
this.mp_players = new TreeMap<String, File>();
if (this.basepath != null)
{
File playerdir = this.getPlayerListDir();
if (playerdir != null && playerdir.exists() && playerdir.isDirectory())
{
String basename;
File[] players = playerdir.listFiles(new PlayerDatFilter());
for (File player : players)
{
basename = player.getName();
this.mp_players.put(basename.substring(0, basename.lastIndexOf('.')), player);
}
}
}
}
/**
* Finalizes the world location for a userChosen world. This will
* unset the "userChosen" attribute.
*
* @param newpath
*/
public void finalizeWorldLocation(File newpath) throws Exception, NumberFormatException
{
if (this.userChosen)
{
this.basepath = newpath.getCanonicalPath();
this.dirName = newpath.getName();
File test = new File(this.getBasePath(), "level.dat");
if (test.exists())
{
this.dimension = 0;
}
else
{
test = new File(this.getBasePath(), "../level.dat");
if (test.exists())
{
this.dimension = Integer.parseInt(this.getDirName().substring(3));
}
else
{
throw new Exception("We couldn't find a level.dat for world at " + this.getBasePath());
}
}
this.userChosen = false;
}
this.populateMPPlayerList();
}
/**
* Gets the base path
*
* @return
*/
public String getBasePath()
{
return this.basepath;
}
/**
* Gets the base path as a File object
*
*/
public File getBaseFile()
{
return new File(this.getBasePath());
}
public File getLevelDatFile()
{
if (this.isOverworld())
{
return new File(this.getBasePath(), "level.dat");
}
else
{
return new File(this.getBasePath(), "../level.dat");
}
}
public File getPlayerListDir()
{
if (this.isOverworld())
{
return new File(this.getBasePath(), "players");
}
else
{
return new File(this.getBasePath(), "../players");
}
}
/**
* Returns our directory name (this value is meaningless for userChosen worlds)
*
* @return
*/
public String getDirName()
{
return this.dirName;
}
/**
* Returns our level name
*
* @return
*/
public String getLevelName()
{
return this.levelName;
}
/**
* A userChosen world is one that lives outside the usual Minecraft directory
* structure.
*
* @return
*/
public boolean isCustom()
{
return this.userChosen;
}
/**
* Are we a dimension world? Note that userChosen worlds will always return false
* until their path is set with finalizeWorldLocation()
*
* @return
*/
public boolean isDimension(int dimension)
{
return (this.dimension == dimension);
}
/**
* Return which dimension we are
*/
public int getDimension()
{
return this.dimension;
}
/**
* Returns whether this is a dimension that we "know" about (mostly just for
* the text label)
*/
public boolean isKnownDimension()
{
return (known_dimensions.containsKey(this.dimension));
}
/**
* Returns a text description of this dimension
*/
public String getDimensionDesc()
{
if (this.isKnownDimension())
{
return (String)known_dimensions.get(this.dimension);
}
else if (this.isDimension(0))
{
return "Overworld";
}
else
{
return "Dimension " + Integer.toString(this.dimension);
}
}
/**
* Are we an overworld? Note that userChosen worlds will always return true
* until their path is set with finalizeWorldLocation()
*/
public boolean isOverworld()
{
return (this.dimension == 0);
}
/**
* Do we have a dimension subdirectory to read?
*
* @return
*/
public boolean hasDimension(int dimension)
{
if (!this.userChosen && this.dimension == dimension)
{
return false;
}
else
{
File test = new File(this.getBasePath(), "DIM" + Integer.toString(dimension));
return (test.exists() && test.canRead() && test.isDirectory());
}
}
/**
* Do we have an overworld?
*
* @return
*/
public boolean hasOverworld()
{
if (!this.userChosen && this.dimension != 0)
{
File test = new File(this.getBasePath(), "../level.dat");
return (test.exists() && test.canRead());
}
else
{
return false;
}
}
/**
* Returns an array of new WorldInfo objects pointing to any associated
* extra dimensions, if we have one.
*
* @return A new WorldInfo array
*/
public ArrayList<WorldInfo> getDimensionInfo()
{
ArrayList<WorldInfo> ret_array = new ArrayList<WorldInfo>();
File dir = this.getBaseFile();
if (dir != null && dir.exists() && dir.isDirectory())
{
File[] dimensions = dir.listFiles(new DimensionFilter());
for (File dim_dir : dimensions)
{
try
{
int dimension = DimensionFilter.get_dimension(dim_dir.getName());
ret_array.add(new WorldInfo(dim_dir.getCanonicalPath(), this.dirName, dimension, this.userChosen));
}
catch (DimensionFilterException e)
{
// whatever, just skip
}
catch (IOException e)
{
XRay.logger.warn("Exception attempting to read world at " + this.dirName + ": " + e.toString());
// whatever, just skip
}
}
}
Collections.sort(ret_array);
return ret_array;
}
/**
* Returns a new WorldInfo object pointing to the overworld, if we currently
* live in a subdirectory of another world.
*
* @return A new WorldInfo, or null
*/
public WorldInfo getOverworldInfo()
{
if (this.hasOverworld())
{
File info = new File(this.getBasePath(), "..");
try
{
return new WorldInfo(info.getCanonicalPath(), this.dirName, 0, this.userChosen);
}
catch (IOException e)
{
return null;
}
}
else
{
return null;
}
}
/**
* Method for Comparable interface, so we can sort based on dimension.
*/
public int compareTo(WorldInfo anotherInstance)
{
return this.getDimension() - anotherInstance.getDimension();
}
/**
* Gets an ordered list of all dimensions for this world, including the one
* we're on, and the overworld, etc.
*/
public ArrayList<WorldInfo> getAllDimensions()
{
ArrayList<WorldInfo> dims;
if (this.isOverworld())
{
dims = this.getDimensionInfo();
dims.add(this);
}
else
{
dims = this.getOverworldInfo().getDimensionInfo();
dims.add(this.getOverworldInfo());
}
Collections.sort(dims);
return dims;
}
}