blob: be73bc61d4706b130f8b697273cd2c2ea048ea9b [file] [log] [blame] [raw]
package com.plusminus.craft;
import java.awt.Point;
import java.awt.Graphics2D;
import java.io.File;
import java.util.HashMap;
import java.util.ArrayList;
import com.plusminus.craft.dtf.ByteArrayTag;
import com.plusminus.craft.dtf.CompoundTag;
import com.plusminus.craft.dtf.DTFReader;
import com.plusminus.craft.dtf.DoubleTag;
import com.plusminus.craft.dtf.FloatTag;
import com.plusminus.craft.dtf.IntTag;
import com.plusminus.craft.dtf.ListTag;
import com.plusminus.craft.dtf.Tag;
/***
* A Minecraft level
* @author Vincent
*/
public class MinecraftLevel {
/**
* A word here about sizes, related to these two facts:
*
* 1) Right now X-Ray only updates the minimap when it loads a chunk from disk,
* and then never touches it again if no further chunks are loaded.
*
* 2) As we "walk" around in X-Ray, the minimap is getting trimmed along the edges so
* that we don't show portions of the map that we don't want to (because the
* minimap now "wraps," much as our internal Chunk representation does here).
*
* We could certainly change some of this behavior around, and in fact I'm sure that
* there's a much better way of dealing with the minimap than we are now. Because I
* haven't gotten around to figuring that out, though, the combination of 1 and 2 means
* that our chunk cache has to be small enough so that chunks are always getting loaded
* wherever the minimap needs to be updated.
*
* The minimap texture is 2048x2048, and there's a noticeable performance hit if we
* push that any higher. Since each pixel in the minimap is a tile, a 2048-pixel
* row can hold 128 chunks. Our effective size of the minimap is actually only
* 1024x1024, though, because of the way we trim to avoid wrapping issues, so there's
* really only 64 chunks in each direction that we can hold, so that's why we're using
* that size instead of something bigger.
*
* Regardless, hopefully I'll take the time to make this Better in the future.
*/
//public static int LEVELDATA_SIZE = 256;
public static int LEVELDATA_SIZE = 128;
public static int LEVELDATA_OFFSET = Integer.MAX_VALUE/2;
public Chunk[][] levelData;
private WorldInfo world;
private Block spawnPoint;
private Block playerPos;
private float playerYaw;
private float playerPitch;
public Texture minecraftTexture;
public Texture portalTexture;
/***
* Create a minecraftLevel from the given world
* @param world
*/
public MinecraftLevel(WorldInfo world, Texture minecraftTexture, Texture portalTexture) {
this.world = world;
this.minecraftTexture = minecraftTexture;
this.portalTexture = portalTexture;
this.levelData = new Chunk[LEVELDATA_SIZE][LEVELDATA_SIZE];
File levelFile = world.getLevelDatFile();
CompoundTag levelData = (CompoundTag) DTFReader.readDTFFile(levelFile);
// System.out.println(levelData.toString());
CompoundTag levelDataData = (CompoundTag) levelData.getTagWithName("Data");
CompoundTag levelPlayerData = (CompoundTag) levelDataData.getTagWithName("Player");
if(levelPlayerData != null) {
// Figure out what dimension the player's in. If it matches, move our camera there.
// TODO: if playerDim is null, perhaps we should move the camera to the spawnpoint...
IntTag playerDim = (IntTag) levelPlayerData.getTagWithName("Dimension");
if (playerDim != null && ((playerDim.value == 0 && !world.isNether()) || (playerDim.value == -1 && world.isNether())))
{
ListTag playerPos = (ListTag) levelPlayerData .getTagWithName("Pos");
ListTag playerRotation = (ListTag) levelPlayerData .getTagWithName("Rotation");
DoubleTag posX = (DoubleTag) playerPos.value.get(0);
DoubleTag posY = (DoubleTag) playerPos.value.get(1);
DoubleTag posZ = (DoubleTag) playerPos.value.get(2);
FloatTag rotYaw = (FloatTag) playerRotation.value.get(0);
FloatTag rotPitch = (FloatTag) playerRotation.value.get(1);
this.playerPos = new Block((int) -posX.value, (int) -posY.value, (int) -posZ.value);
this.playerYaw = rotYaw.value;
this.playerPitch = rotPitch.value;
}
else
{
this.playerPos = new Block(0,65,0);
this.playerYaw =0;
this.playerPitch = 0;
}
// Set the spawn point if we're not in the Nether
if (world.isNether())
{
this.spawnPoint = new Block(0,65,0);
}
else
{
IntTag spawnX = (IntTag) levelDataData.getTagWithName("SpawnX");
IntTag spawnY = (IntTag) levelDataData.getTagWithName("SpawnY");
IntTag spawnZ = (IntTag) levelDataData.getTagWithName("SpawnZ");
this.spawnPoint = new Block(-spawnX.value, -spawnY.value, -spawnZ.value);
}
} else {
this.spawnPoint = new Block(0,65,0);
this.playerPos = new Block(0,65,0);
this.playerYaw =0;
this.playerPitch = 0;
}
}
/***
* returns the spawning point for this level
*/
public Block getSpawnPoint() {
return this.spawnPoint;
}
public Block getPlayerPosition() {
return this.playerPos;
}
public float getPlayerPitch() {
return this.playerPitch;
}
public float getPlayerYaw() {
return this.playerYaw;
}
/***
* correctly calculate the chunk X value given a universal coordinate
* @param x
* @return
*/
public int getChunkX(int x) {
if(x<0) {
return -(((-x)-1) / 16)-1; // otherwise -1 and +1 would return the same chunk
} else {
return x / 16;
}
}
/***
* correctly calculate the block X value given a universal coordinate
* @param x
* @return
*/
public int getBlockX(int x) {
if(x<0) {
return 15-(((-x)-1) % 16); // compensate for different chunk calculation
} else {
return x % 16;
}
}
/***
* correctly calculate the chunk Z value given a universal coordinate
* @param z
* @return
*/
public int getChunkZ(int z) {
if(z<0) {
return -(((-z)-1) / 16)-1; // otherwise -1 and +1 would return the same chunk
} else {
return z / 16;
}
}
/***
* correctly calculate the block Z value given a universal coordinate
* @param z
* @return
*/
public int getBlockZ(int z) {
if(z<0) {
return 15-(((-z)-1) % 16); // compensate for different chunk calculation
} else {
return z % 16;
}
}
/***
* Returns a single byte representing the block data at the given universal coordinates
* @param x
* @param z
* @param y
* @return
*/
public byte getBlockData(int x, int z, int y) {
int chunkX = getChunkX(x);
int chunkZ = getChunkZ(z);
int blockX = getBlockX(x);
int blockZ = getBlockZ(z);
Chunk chunk = this.getChunk(chunkX, chunkZ);
if(chunk == null) { // no chunk for the given coordinate
return 0;
}
int blockOffset = y + (blockZ * 128) + (blockX * 128 * 16);
try {
return chunk.getMapData().value[blockOffset];
} catch(Exception e) {
// dirty, but there was an error with out of range blockvalues O_o
System.out.println(blockOffset);
System.out.println("" + x + ", " + y + ", " + z);
System.out.println("" + blockX + ", " + blockZ );
System.exit(0);
return 0;
}
}
public void invalidateSelected() {
this.invalidateSelected(false);
}
public void invalidateSelected(boolean main_dirty) {
for (Chunk[] chunkrow : this.levelData)
{
for (Chunk chunk : chunkrow)
{
if (chunk != null)
{
chunk.isSelectedDirty = true;
if (main_dirty)
{
chunk.isDirty = true;
}
}
}
}
}
public void markChunkAsDirty(int x, int z) {
Chunk c = this.getChunk(x, z);
if (c != null)
{
c.isDirty = true;
}
}
public Tag loadChunk(int x, int z) {
File chunkFile = MineCraftEnvironment.getChunkFile(world, x,z);
if(!chunkFile.exists()) {
return null;
}
Tag t = DTFReader.readDTFFile(chunkFile);
if (t != null)
{
levelData[(x+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(z+LEVELDATA_OFFSET)%LEVELDATA_SIZE] = new Chunk(this, t);
}
return t;
}
/**
* Gets the specified Chunk object
*
* @param chunkX
* @param chunkZ
* @return
*/
public Chunk getChunk(int chunkX, int chunkZ) {
return this.levelData[(chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE];
}
/**
* Sets a chunk to null
*
* @param chunkX
* @param chunkZ
*/
public void clearChunk(int chunkX, int chunkZ)
{
this.levelData[(chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE] = null;
}
/**
* Sets all chunks in the given X row to be no longer on the minimap
*
* @param chunkX
*/
public ArrayList<Chunk> removeChunkRowXFromMinimap(int chunkX)
{
ArrayList<Chunk> chunks = new ArrayList<Chunk>();
int xval = (chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE;
for (int i=0; i<LEVELDATA_SIZE; i++)
{
if (this.levelData[xval][i] != null)
{
//System.out.println("(" + chunkX + ") Removing from minimap on " + xval + ", " + i);
this.levelData[xval][i].isOnMinimap = false;
chunks.add(this.levelData[xval][i]);
}
}
return chunks;
}
/**
* Sets all chunks in the given Z row to be no longer on the minimap
*
* @param chunkZ
*/
public ArrayList<Chunk> removeChunkRowZFromMinimap(int chunkZ)
{
ArrayList<Chunk> chunks = new ArrayList<Chunk>();
int zval = (chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE;
for (int i=0; i<LEVELDATA_SIZE; i++)
{
if (this.levelData[i][zval] != null)
{
//System.out.println("Removing from minimap on " + i + ", " + zval);
this.levelData[i][zval].isOnMinimap = false;
chunks.add(this.levelData[i][zval]);
}
}
return chunks;
}
/***
* gets the data for a given chunk (coordinates are CHUNK coordinates, not world coordinates!)
* @param chunkX
* @param chunkZ
* @return
*/
public byte[] getChunkData(int chunkX, int chunkZ) {
Chunk c = this.getChunk(chunkX, chunkZ);
if(c == null) {
return new byte[32768];
} else {
return c.getMapData().value;
}
}
public Tag getFullChunk(int chunkX, int chunkZ) {
Chunk c = this.getChunk(chunkX, chunkZ);
if(c == null) {
return null;
}
return c.getChunkData();
}
}