added back biome colors, and misc. cleanup in world.py
This commit is contained in:
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
// increment this value if you've made a change to the c extesion
|
// increment this value if you've made a change to the c extesion
|
||||||
// and want to force users to rebuild
|
// and want to force users to rebuild
|
||||||
#define OVERVIEWER_EXTENSION_VERSION 18
|
#define OVERVIEWER_EXTENSION_VERSION 19
|
||||||
|
|
||||||
/* Python PIL, and numpy headers */
|
/* Python PIL, and numpy headers */
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
/* macro for getting a value out of various numpy arrays */
|
/* macro for getting a value out of various numpy arrays */
|
||||||
#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z))))
|
#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z))))
|
||||||
#define getArrayShort1D(array, x) (*(unsigned short *)(PyArray_GETPTR1((array), (x))))
|
#define getArrayShort2D(array, x,y) (*(unsigned short *)(PyArray_GETPTR2((array), (x), (y))))
|
||||||
|
|
||||||
/* generally useful MAX / MIN macros */
|
/* generally useful MAX / MIN macros */
|
||||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|||||||
@@ -18,9 +18,7 @@
|
|||||||
#include "../overviewer.h"
|
#include "../overviewer.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* coordinates of the chunk, inside its region file */
|
/* biome data for the chunk */
|
||||||
int chunk_x, chunk_y;
|
|
||||||
/* biome data for the region */
|
|
||||||
PyObject *biome_data;
|
PyObject *biome_data;
|
||||||
/* grasscolor and foliagecolor lookup tables */
|
/* grasscolor and foliagecolor lookup tables */
|
||||||
PyObject *grasscolor, *foliagecolor, *watercolor;
|
PyObject *grasscolor, *foliagecolor, *watercolor;
|
||||||
@@ -35,44 +33,15 @@ base_start(void *data, RenderState *state, PyObject *support) {
|
|||||||
/* biome-compliant grass mask (includes sides!) */
|
/* biome-compliant grass mask (includes sides!) */
|
||||||
self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture");
|
self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture");
|
||||||
|
|
||||||
/* careful now -- C's % operator works differently from python's
|
self->biome_data = PyObject_CallMethod(state->regionset, "get_biome_data", "ii", state->chunkx, state->chunkz);
|
||||||
we can't just do x % 32 like we did before */
|
if (self->biome_data == NULL) {
|
||||||
self->chunk_x = state->chunkx;
|
/* error while loading biome info, or no biomes at all */
|
||||||
self->chunk_y = state->chunkz;
|
PyErr_Clear();
|
||||||
|
} else {
|
||||||
while (self->chunk_x < 0)
|
self->foliagecolor = PyObject_CallMethod(state->textures, "load_foliage_color", "");
|
||||||
self->chunk_x += 32;
|
self->grasscolor = PyObject_CallMethod(state->textures, "load_grass_color", "");
|
||||||
while (self->chunk_y < 0)
|
self->watercolor = PyObject_CallMethod(state->textures, "load_water_color", "");
|
||||||
self->chunk_y += 32;
|
}
|
||||||
|
|
||||||
self->chunk_x %= 32;
|
|
||||||
self->chunk_y %= 32;
|
|
||||||
|
|
||||||
/* XXX ignore biomes for now :( */
|
|
||||||
/*if (PyObject_IsTrue(use_biomes)) {
|
|
||||||
self->biome_data = PyObject_CallMethod(state->textures, "getBiomeData", "OOO",
|
|
||||||
worlddir, chunk_x_py, chunk_y_py);
|
|
||||||
if (self->biome_data == Py_None) {
|
|
||||||
Py_DECREF(self->biome_data);
|
|
||||||
self->biome_data = NULL;
|
|
||||||
self->foliagecolor = NULL;
|
|
||||||
self->grasscolor = NULL;
|
|
||||||
} else {
|
|
||||||
self->foliagecolor = PyObject_GetAttrString(state->textures, "foliagecolor");
|
|
||||||
self->grasscolor = PyObject_GetAttrString(state->textures, "grasscolor");
|
|
||||||
self->watercolor = PyObject_GetAttrString(state->textures, "watercolor");
|
|
||||||
if (self->watercolor == Py_None)
|
|
||||||
{
|
|
||||||
Py_DECREF(self->watercolor);
|
|
||||||
self->watercolor = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {*/
|
|
||||||
self->biome_data = NULL;
|
|
||||||
self->foliagecolor = NULL;
|
|
||||||
self->grasscolor = NULL;
|
|
||||||
self->watercolor = NULL;
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -159,8 +128,7 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
|
|||||||
unsigned int index;
|
unsigned int index;
|
||||||
PyObject *color = NULL;
|
PyObject *color = NULL;
|
||||||
|
|
||||||
index = ((self->chunk_y * 16) + state->y) * 16 * 32 + (self->chunk_x * 16) + state->x;
|
index = big_endian_ushort(getArrayShort2D(self->biome_data, state->x, state->y));
|
||||||
index = big_endian_ushort(getArrayShort1D(self->biome_data, index));
|
|
||||||
|
|
||||||
switch (state->block) {
|
switch (state->block) {
|
||||||
case 2:
|
case 2:
|
||||||
@@ -211,7 +179,7 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
|
|||||||
color = PySequence_GetItem(self->grasscolor, index);
|
color = PySequence_GetItem(self->grasscolor, index);
|
||||||
break;
|
break;
|
||||||
case 111:
|
case 111:
|
||||||
/* lily padas */
|
/* lily pads */
|
||||||
color = PySequence_GetItem(self->grasscolor, index);
|
color = PySequence_GetItem(self->grasscolor, index);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Textures(object):
|
|||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
# we must get rid of the huge image lists, and other images
|
# we must get rid of the huge image lists, and other images
|
||||||
attributes = self.__dict__.copy()
|
attributes = self.__dict__.copy()
|
||||||
for attr in ['terrain_images', 'blockmap', 'biome_grass_texture', 'watertexture', 'lavatexture', 'firetexture', 'portaltexture', 'lightcolor']:
|
for attr in ['terrain_images', 'blockmap', 'biome_grass_texture', 'watertexture', 'lavatexture', 'firetexture', 'portaltexture', 'lightcolor', 'grasscolor', 'foliagecolor', 'watercolor']:
|
||||||
try:
|
try:
|
||||||
del attributes[attr]
|
del attributes[attr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -294,6 +294,24 @@ class Textures(object):
|
|||||||
lightcolor = None
|
lightcolor = None
|
||||||
self.lightcolor = lightcolor
|
self.lightcolor = lightcolor
|
||||||
return lightcolor
|
return lightcolor
|
||||||
|
|
||||||
|
def load_grass_color(self):
|
||||||
|
"""Helper function to load the grass color texture."""
|
||||||
|
if not hasattr(self, "grasscolor"):
|
||||||
|
self.grasscolor = list(self.load_image("grasscolor.png").getdata())
|
||||||
|
return self.grasscolor
|
||||||
|
|
||||||
|
def load_foliage_color(self):
|
||||||
|
"""Helper function to load the foliage color texture."""
|
||||||
|
if not hasattr(self, "foliagecolor"):
|
||||||
|
self.foliagecolor = list(self.load_image("foliagecolor.png").getdata())
|
||||||
|
return self.foliagecolor
|
||||||
|
|
||||||
|
def load_water_color(self):
|
||||||
|
"""Helper function to load the water color texture."""
|
||||||
|
if not hasattr(self, "watercolor"):
|
||||||
|
self.watercolor = list(self.load_image("watercolor.png").getdata())
|
||||||
|
return self.watercolor
|
||||||
|
|
||||||
def _split_terrain(self, terrain):
|
def _split_terrain(self, terrain):
|
||||||
"""Builds and returns a length 256 array of each 16x16 chunk
|
"""Builds and returns a length 256 array of each 16x16 chunk
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
from glob import glob
|
from glob import glob
|
||||||
import logging
|
import logging
|
||||||
import collections
|
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
@@ -30,35 +29,17 @@ This module has routines for extracting information about available worlds
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
base36decode = functools.partial(int, base=36)
|
class ChunkDoesntExist(Exception):
|
||||||
cached = collections.defaultdict(dict)
|
pass
|
||||||
|
|
||||||
def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'):
|
class BiomeDataDoesntExist(Exception):
|
||||||
'''
|
pass
|
||||||
Convert an integer to a base36 string.
|
|
||||||
'''
|
|
||||||
if not isinstance(number, (int, long)):
|
|
||||||
raise TypeError('number must be an integer')
|
|
||||||
|
|
||||||
newn = abs(number)
|
|
||||||
|
|
||||||
# Special case for zero
|
|
||||||
if number == 0:
|
|
||||||
return '0'
|
|
||||||
|
|
||||||
base36 = ''
|
|
||||||
while newn != 0:
|
|
||||||
newn, i = divmod(newn, len(alphabet))
|
|
||||||
base36 = alphabet[i] + base36
|
|
||||||
|
|
||||||
if number < 0:
|
|
||||||
return "-" + base36
|
|
||||||
return base36
|
|
||||||
|
|
||||||
def log_other_exceptions(func):
|
def log_other_exceptions(func):
|
||||||
"""A decorator that prints out any errors that are not ChunkDoesntExist
|
"""A decorator that prints out any errors that are not
|
||||||
errors. This decorates get_chunk because the C code is likely to swallow
|
ChunkDoesntExist or BiomeDataDoesntExist errors. This decorates
|
||||||
exceptions, so this will at least make them visible.
|
get_chunk because the C code is likely to swallow exceptions, so
|
||||||
|
this will at least make them visible.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
functools.wraps(func)
|
functools.wraps(func)
|
||||||
@@ -67,6 +48,8 @@ def log_other_exceptions(func):
|
|||||||
return func(*args)
|
return func(*args)
|
||||||
except ChunkDoesntExist:
|
except ChunkDoesntExist:
|
||||||
raise
|
raise
|
||||||
|
except BiomeDataDoesntExist:
|
||||||
|
raise
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logging.exception("%s raised this exception", func.func_name)
|
logging.exception("%s raised this exception", func.func_name)
|
||||||
raise
|
raise
|
||||||
@@ -150,10 +133,6 @@ class World(object):
|
|||||||
|
|
||||||
|
|
||||||
# TODO figure out where to handle regionlists
|
# TODO figure out where to handle regionlists
|
||||||
|
|
||||||
self.useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes'))
|
|
||||||
if not self.useBiomeData:
|
|
||||||
logging.info("Notice: Not using biome data for tinting")
|
|
||||||
|
|
||||||
def get_regionsets(self):
|
def get_regionsets(self):
|
||||||
return self.regionsets
|
return self.regionsets
|
||||||
@@ -259,7 +238,8 @@ class RegionSet(object):
|
|||||||
logging.debug("Done scanning regions")
|
logging.debug("Done scanning regions")
|
||||||
|
|
||||||
# Caching implementaiton: a simple LRU cache
|
# Caching implementaiton: a simple LRU cache
|
||||||
# Decorate the get_chunk method with the cache decorator
|
# Decorate the getter methods with the cache decorator
|
||||||
|
self._get_biome_data_for_region = cache.lru_cache(cachesize)(self._get_biome_data_for_region)
|
||||||
self.get_chunk = cache.lru_cache(cachesize)(self.get_chunk)
|
self.get_chunk = cache.lru_cache(cachesize)(self.get_chunk)
|
||||||
|
|
||||||
# Re-initialize upon unpickling
|
# Re-initialize upon unpickling
|
||||||
@@ -283,7 +263,47 @@ class RegionSet(object):
|
|||||||
return "overworld"
|
return "overworld"
|
||||||
else:
|
else:
|
||||||
raise Exception("Woah, what kind of dimension is this! %r" % self.regiondir)
|
raise Exception("Woah, what kind of dimension is this! %r" % self.regiondir)
|
||||||
|
|
||||||
|
# this is decorated with cache.lru_cache in __init__(). Be aware!
|
||||||
|
@log_other_exceptions
|
||||||
|
def _get_biome_data_for_region(self, regionx, regionz):
|
||||||
|
"""Get the block of biome data for an entire region. Biome
|
||||||
|
data is in the format output by Minecraft Biome Extractor:
|
||||||
|
http://code.google.com/p/minecraft-biome-extractor/"""
|
||||||
|
|
||||||
|
# biomes only make sense for the overworld, right now
|
||||||
|
if self.get_type() != "overworld":
|
||||||
|
raise BiomeDataDoesntExist("Biome data is not available for '%s'." % (self.get_type(),))
|
||||||
|
|
||||||
|
# biomes are, unfortunately, in a different place than regiondir
|
||||||
|
biomefile = os.path.split(self.regiondir)[0]
|
||||||
|
biomefile = os.path.join(biomefile, 'biomes', 'b.%d.%d.biome' % (regionx, regionz))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(biomefile, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
if not len(data) == 512 * 512 * 2:
|
||||||
|
raise BiomeDataDoesntExist("File `%s' does not have correct size." % (biomefile,))
|
||||||
|
data = numpy.frombuffer(data, dtype=numpy.dtype(">u2"))
|
||||||
|
# reshape and transpose to get [x, z] indices
|
||||||
|
return numpy.transpose(numpy.reshape(data, (512, 512)))
|
||||||
|
except IOError:
|
||||||
|
raise BiomeDataDoesntExist("File `%s' could not be read." % (biomefile,))
|
||||||
|
|
||||||
|
@log_other_exceptions
|
||||||
|
def get_biome_data(self, x, z):
|
||||||
|
"""Get the block of biome data for the given chunk. Biome data
|
||||||
|
is returned as a 16x16 numpy array of indices into the
|
||||||
|
corresponding biome color images."""
|
||||||
|
regionx = x // 32
|
||||||
|
regionz = z // 32
|
||||||
|
blockx = (x % 32) * 16
|
||||||
|
blockz = (z % 32) * 16
|
||||||
|
|
||||||
|
region_biomes = self._get_biome_data_for_region(regionx, regionz)
|
||||||
|
return region_biomes[blockx:blockx+16,blockz:blockz+16]
|
||||||
|
|
||||||
|
# this is decorated with cache.lru_cache in __init__(). Be aware!
|
||||||
@log_other_exceptions
|
@log_other_exceptions
|
||||||
def get_chunk(self, x, z):
|
def get_chunk(self, x, z):
|
||||||
"""Returns a dictionary object representing the "Level" NBT Compound
|
"""Returns a dictionary object representing the "Level" NBT Compound
|
||||||
@@ -448,7 +468,12 @@ class RotatedRegionSet(RegionSet):
|
|||||||
return (self.regiondir, self.north_dir)
|
return (self.regiondir, self.north_dir)
|
||||||
def __setstate__(self, args):
|
def __setstate__(self, args):
|
||||||
self.__init__(args[0], args[1])
|
self.__init__(args[0], args[1])
|
||||||
|
|
||||||
|
def get_biome_data(self, x, z):
|
||||||
|
x,z = self.unrotate(x,z)
|
||||||
|
biome_data = super(RotatedRegionSet, self).get_biome_data(x,z)
|
||||||
|
return numpy.rot90(biome_data, self.north_dir)
|
||||||
|
|
||||||
def get_chunk(self, x, z):
|
def get_chunk(self, x, z):
|
||||||
x,z = self.unrotate(x,z)
|
x,z = self.unrotate(x,z)
|
||||||
chunk_data = super(RotatedRegionSet, self).get_chunk(x,z)
|
chunk_data = super(RotatedRegionSet, self).get_chunk(x,z)
|
||||||
@@ -467,9 +492,6 @@ class RotatedRegionSet(RegionSet):
|
|||||||
x,z = self.rotate(x,z)
|
x,z = self.rotate(x,z)
|
||||||
yield x,z,mtime
|
yield x,z,mtime
|
||||||
|
|
||||||
class ChunkDoesntExist(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_save_dir():
|
def get_save_dir():
|
||||||
"""Returns the path to the local saves directory
|
"""Returns the path to the local saves directory
|
||||||
* On Windows, at %APPDATA%/.minecraft/saves/
|
* On Windows, at %APPDATA%/.minecraft/saves/
|
||||||
|
|||||||
Reference in New Issue
Block a user