1352 lines
48 KiB
Python
1352 lines
48 KiB
Python
# This file is part of the Minecraft Overviewer.
|
|
#
|
|
# Minecraft Overviewer is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or (at
|
|
# your option) any later version.
|
|
#
|
|
# Minecraft Overviewer is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
# Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import sys
|
|
import imp
|
|
import os
|
|
import os.path
|
|
import zipfile
|
|
from cStringIO import StringIO
|
|
import math
|
|
from random import randint
|
|
import numpy
|
|
from PIL import Image, ImageEnhance, ImageOps, ImageDraw
|
|
import logging
|
|
import functools
|
|
|
|
import util
|
|
import composite
|
|
|
|
##
|
|
## useful global variables
|
|
##
|
|
|
|
# user-provided path given by --texture-path
|
|
_find_file_local_path = None
|
|
# image background color to use
|
|
bgcolor = None
|
|
# an array of the textures in terrain.png, split up
|
|
terrain_images = None
|
|
|
|
##
|
|
## Helpers for opening textures
|
|
##
|
|
|
|
def _find_file(filename, mode="rb", verbose=False):
|
|
"""Searches for the given file and returns an open handle to it.
|
|
This searches the following locations in this order:
|
|
|
|
* the textures_path given in the config file (if present)
|
|
* The program dir (same dir as overviewer.py)
|
|
* The overviewer_core/data/textures dir
|
|
* On Darwin, in /Applications/Minecraft
|
|
* Inside minecraft.jar, which is looked for at these locations
|
|
|
|
* On Windows, at %APPDATA%/.minecraft/bin/minecraft.jar
|
|
* On Darwin, at $HOME/Library/Application Support/minecraft/bin/minecraft.jar
|
|
* at $HOME/.minecraft/bin/minecraft.jar
|
|
|
|
"""
|
|
|
|
if _find_file_local_path:
|
|
path = os.path.join(_find_file_local_path, filename)
|
|
if os.path.exists(path):
|
|
if verbose: logging.info("Found %s in '%s'", filename, path)
|
|
return open(path, mode)
|
|
|
|
programdir = util.get_program_path()
|
|
path = os.path.join(programdir, filename)
|
|
if os.path.exists(path):
|
|
if verbose: logging.info("Found %s in '%s'", filename, path)
|
|
return open(path, mode)
|
|
|
|
path = os.path.join(programdir, "overviewer_core", "data", "textures", filename)
|
|
if os.path.exists(path):
|
|
return open(path, mode)
|
|
elif hasattr(sys, "frozen") or imp.is_frozen("__main__"):
|
|
# windows special case, when the package dir doesn't exist
|
|
path = os.path.join(programdir, "textures", filename)
|
|
if os.path.exists(path):
|
|
if verbose: logging.info("Found %s in '%s'", filename, path)
|
|
return open(path, mode)
|
|
|
|
if sys.platform == "darwin":
|
|
path = os.path.join("/Applications/Minecraft", filename)
|
|
if os.path.exists(path):
|
|
if verbose: logging.info("Found %s in '%s'", filename, path)
|
|
return open(path, mode)
|
|
|
|
# Find minecraft.jar.
|
|
jarpaths = []
|
|
if "APPDATA" in os.environ:
|
|
jarpaths.append( os.path.join(os.environ['APPDATA'], ".minecraft",
|
|
"bin", "minecraft.jar"))
|
|
if "HOME" in os.environ:
|
|
jarpaths.append(os.path.join(os.environ['HOME'], "Library",
|
|
"Application Support", "minecraft","bin","minecraft.jar"))
|
|
jarpaths.append(os.path.join(os.environ['HOME'], ".minecraft", "bin",
|
|
"minecraft.jar"))
|
|
jarpaths.append(os.path.join(programdir,"minecraft.jar"))
|
|
jarpaths.append(os.path.join(os.getcwd(), "minecraft.jar"))
|
|
if _find_file_local_path:
|
|
jarpaths.append(os.path.join(_find_file_local_path, "minecraft.jar"))
|
|
|
|
for jarpath in jarpaths:
|
|
if os.path.exists(jarpath):
|
|
jar = zipfile.ZipFile(jarpath)
|
|
for jarfilename in [filename, 'misc/' + filename, 'environment/' + filename]:
|
|
try:
|
|
if verbose: logging.info("Found %s in '%s'", jarfilename, jarpath)
|
|
return jar.open(jarfilename)
|
|
except (KeyError, IOError), e:
|
|
pass
|
|
|
|
raise IOError("Could not find the file `{0}'. You can either place it in the same place as overviewer.py, use --textures-path, or install the Minecraft client.".format(filename))
|
|
|
|
def _load_image(filename):
|
|
"""Returns an image object"""
|
|
fileobj = _find_file(filename)
|
|
buffer = StringIO(fileobj.read())
|
|
return Image.open(buffer).convert("RGBA")
|
|
|
|
def _split_terrain(terrain):
|
|
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
|
textures = []
|
|
(terrain_width, terrain_height) = terrain.size
|
|
texture_resolution = terrain_width / 16
|
|
for y in xrange(16):
|
|
for x in xrange(16):
|
|
left = x*texture_resolution
|
|
upper = y*texture_resolution
|
|
right = left+texture_resolution
|
|
lower = upper+texture_resolution
|
|
region = terrain.transform(
|
|
(16, 16),
|
|
Image.EXTENT,
|
|
(left,upper,right,lower),
|
|
Image.BICUBIC)
|
|
textures.append(region)
|
|
|
|
return textures
|
|
|
|
##
|
|
## Image Transformation Functions
|
|
##
|
|
|
|
def transform_image_top(img):
|
|
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
|
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
|
|
|
"""
|
|
|
|
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
|
# even number that can be split in half twice
|
|
img = img.resize((17, 17), Image.ANTIALIAS)
|
|
|
|
# Build the Affine transformation matrix for this perspective
|
|
transform = numpy.matrix(numpy.identity(3))
|
|
# Translate up and left, since rotations are about the origin
|
|
transform *= numpy.matrix([[1,0,8.5],[0,1,8.5],[0,0,1]])
|
|
# Rotate 45 degrees
|
|
ratio = math.cos(math.pi/4)
|
|
#transform *= numpy.matrix("[0.707,-0.707,0;0.707,0.707,0;0,0,1]")
|
|
transform *= numpy.matrix([[ratio,-ratio,0],[ratio,ratio,0],[0,0,1]])
|
|
# Translate back down and right
|
|
transform *= numpy.matrix([[1,0,-12],[0,1,-12],[0,0,1]])
|
|
# scale the image down by a factor of 2
|
|
transform *= numpy.matrix("[1,0,0;0,2,0;0,0,1]")
|
|
|
|
transform = numpy.array(transform)[:2,:].ravel().tolist()
|
|
|
|
newimg = img.transform((24,12), Image.AFFINE, transform)
|
|
return newimg
|
|
|
|
def transform_image_side(img):
|
|
"""Takes an image and shears it for the left side of the cube (reflect for
|
|
the right side)"""
|
|
|
|
# Size of the cube side before shear
|
|
img = img.resize((12,12), Image.ANTIALIAS)
|
|
|
|
# Apply shear
|
|
transform = numpy.matrix(numpy.identity(3))
|
|
transform *= numpy.matrix("[1,0,0;-0.5,1,0;0,0,1]")
|
|
|
|
transform = numpy.array(transform)[:2,:].ravel().tolist()
|
|
|
|
newimg = img.transform((12,18), Image.AFFINE, transform)
|
|
return newimg
|
|
|
|
def transform_image_slope(img):
|
|
"""Takes an image and shears it in the shape of a slope going up
|
|
in the -y direction (reflect for +x direction). Used for minetracks"""
|
|
|
|
# Take the same size as trasform_image_side
|
|
img = img.resize((12,12), Image.ANTIALIAS)
|
|
|
|
# Apply shear
|
|
transform = numpy.matrix(numpy.identity(3))
|
|
transform *= numpy.matrix("[0.75,-0.5,3;0.25,0.5,-3;0,0,1]")
|
|
transform = numpy.array(transform)[:2,:].ravel().tolist()
|
|
|
|
newimg = img.transform((24,24), Image.AFFINE, transform)
|
|
|
|
return newimg
|
|
|
|
|
|
def transform_image_angle(img, angle):
|
|
"""Takes an image an shears it in arbitrary angle with the axis of
|
|
rotation being vertical.
|
|
|
|
WARNING! Don't use angle = pi/2 (or multiplies), it will return
|
|
a blank image (or maybe garbage).
|
|
|
|
NOTE: angle is in the image not in game, so for the left side of a
|
|
block angle = 30 degree.
|
|
"""
|
|
|
|
# Take the same size as trasform_image_side
|
|
img = img.resize((12,12), Image.ANTIALIAS)
|
|
|
|
# some values
|
|
cos_angle = math.cos(angle)
|
|
sin_angle = math.sin(angle)
|
|
|
|
# function_x and function_y are used to keep the result image in the
|
|
# same position, and constant_x and constant_y are the coordinates
|
|
# for the center for angle = 0.
|
|
constant_x = 6.
|
|
constant_y = 6.
|
|
function_x = 6.*(1-cos_angle)
|
|
function_y = -6*sin_angle
|
|
big_term = ( (sin_angle * (function_x + constant_x)) - cos_angle* (function_y + constant_y))/cos_angle
|
|
|
|
# The numpy array is not really used, but is helpful to
|
|
# see the matrix used for the transformation.
|
|
transform = numpy.array([[1./cos_angle, 0, -(function_x + constant_x)/cos_angle],
|
|
[-sin_angle/(cos_angle), 1., big_term ],
|
|
[0, 0, 1.]])
|
|
|
|
transform = tuple(transform[0]) + tuple(transform[1])
|
|
|
|
newimg = img.transform((24,24), Image.AFFINE, transform)
|
|
|
|
return newimg
|
|
|
|
|
|
def build_block(top, side):
|
|
"""From a top texture and a side texture, build a block image.
|
|
top and side should be 16x16 image objects. Returns a 24x24 image
|
|
|
|
"""
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
original_texture = top.copy()
|
|
top = transform_image_top(top)
|
|
|
|
if not side:
|
|
composite.alpha_over(img, top, (0,0), top)
|
|
return img
|
|
|
|
side = transform_image_side(side)
|
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
# Darken the sides slightly. These methods also affect the alpha layer,
|
|
# so save them first (we don't want to "darken" the alpha layer making
|
|
# the block transparent)
|
|
sidealpha = side.split()[3]
|
|
side = ImageEnhance.Brightness(side).enhance(0.9)
|
|
side.putalpha(sidealpha)
|
|
othersidealpha = otherside.split()[3]
|
|
otherside = ImageEnhance.Brightness(otherside).enhance(0.8)
|
|
otherside.putalpha(othersidealpha)
|
|
|
|
composite.alpha_over(img, top, (0,0), top)
|
|
composite.alpha_over(img, side, (0,6), side)
|
|
composite.alpha_over(img, otherside, (12,6), otherside)
|
|
|
|
# Manually touch up 6 pixels that leave a gap because of how the
|
|
# shearing works out. This makes the blocks perfectly tessellate-able
|
|
for x,y in [(13,23), (17,21), (21,19)]:
|
|
# Copy a pixel to x,y from x-1,y
|
|
img.putpixel((x,y), img.getpixel((x-1,y)))
|
|
for x,y in [(3,4), (7,2), (11,0)]:
|
|
# Copy a pixel to x,y from x+1,y
|
|
img.putpixel((x,y), img.getpixel((x+1,y)))
|
|
|
|
return img
|
|
|
|
def build_full_block(top, side1, side2, side3, side4, bottom=None):
|
|
"""From a top texture, a bottom texture and 4 different side textures,
|
|
build a full block with four differnts faces. All images should be 16x16
|
|
image objects. Returns a 24x24 image. Can be used to render any block.
|
|
|
|
side1 is in the -y face of the cube (top left, east)
|
|
side2 is in the +x (top right, south)
|
|
side3 is in the -x (bottom left, north)
|
|
side4 is in the +y (bottom right, west)
|
|
|
|
A non transparent block uses top, side 3 and side 4.
|
|
|
|
If top is a tuple then first item is the top image and the second
|
|
item is an increment (integer) from 0 to 16 (pixels in the
|
|
original minecraft texture). This increment will be used to crop the
|
|
side images and to paste the top image increment pixels lower, so if
|
|
you use an increment of 8, it willll draw a half-block.
|
|
|
|
NOTE: this method uses the bottom of the texture image (as done in
|
|
minecraft with beds and cackes)
|
|
|
|
"""
|
|
|
|
increment = 0
|
|
if isinstance(top, tuple):
|
|
increment = int(round((top[1] / 16.)*12.)) # range increment in the block height in pixels (half texture size)
|
|
crop_height = increment
|
|
top = top[0]
|
|
if side1 != None:
|
|
side1 = side1.copy()
|
|
ImageDraw.Draw(side1).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
if side2 != None:
|
|
side2 = side2.copy()
|
|
ImageDraw.Draw(side2).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
if side3 != None:
|
|
side3 = side3.copy()
|
|
ImageDraw.Draw(side3).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
if side4 != None:
|
|
side4 = side4.copy()
|
|
ImageDraw.Draw(side4).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
# first back sides
|
|
if side1 != None :
|
|
side1 = transform_image_side(side1)
|
|
side1 = side1.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
# Darken this side.
|
|
sidealpha = side1.split()[3]
|
|
side1 = ImageEnhance.Brightness(side1).enhance(0.9)
|
|
side1.putalpha(sidealpha)
|
|
|
|
composite.alpha_over(img, side1, (0,0), side1)
|
|
|
|
|
|
if side2 != None :
|
|
side2 = transform_image_side(side2)
|
|
|
|
# Darken this side.
|
|
sidealpha2 = side2.split()[3]
|
|
side2 = ImageEnhance.Brightness(side2).enhance(0.8)
|
|
side2.putalpha(sidealpha2)
|
|
|
|
composite.alpha_over(img, side2, (12,0), side2)
|
|
|
|
if bottom != None :
|
|
bottom = transform_image_top(bottom)
|
|
composite.alpha_over(img, bottom, (0,12), bottom)
|
|
|
|
# front sides
|
|
if side3 != None :
|
|
side3 = transform_image_side(side3)
|
|
|
|
# Darken this side
|
|
sidealpha = side3.split()[3]
|
|
side3 = ImageEnhance.Brightness(side3).enhance(0.9)
|
|
side3.putalpha(sidealpha)
|
|
|
|
composite.alpha_over(img, side3, (0,6), side3)
|
|
|
|
if side4 != None :
|
|
side4 = transform_image_side(side4)
|
|
side4 = side4.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
# Darken this side
|
|
sidealpha = side4.split()[3]
|
|
side4 = ImageEnhance.Brightness(side4).enhance(0.8)
|
|
side4.putalpha(sidealpha)
|
|
|
|
composite.alpha_over(img, side4, (12,6), side4)
|
|
|
|
if top != None :
|
|
top = transform_image_top(top)
|
|
composite.alpha_over(img, top, (0, increment), top)
|
|
|
|
return img
|
|
|
|
def build_sprite(side):
|
|
"""From a side texture, create a sprite-like texture such as those used
|
|
for spiderwebs or flowers."""
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
side = transform_image_side(side)
|
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
composite.alpha_over(img, side, (6,3), side)
|
|
composite.alpha_over(img, otherside, (6,3), otherside)
|
|
return img
|
|
|
|
def build_billboard(tex):
|
|
"""From a texture, create a billboard-like texture such as those used for
|
|
tall grass or melon stems."""
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
front = tex.resize((14, 11), Image.ANTIALIAS)
|
|
composite.alpha_over(img, front, (5,9))
|
|
return img
|
|
|
|
def generate_opaque_mask(img):
|
|
""" Takes the alpha channel of the image and generates a mask
|
|
(used for lighting the block) that deprecates values of alpha
|
|
smallers than 50, and sets every other value to 255. """
|
|
|
|
alpha = img.split()[3]
|
|
return alpha.point(lambda a: int(min(a, 25.5) * 10))
|
|
|
|
def tintTexture(im, c):
|
|
# apparently converting to grayscale drops the alpha channel?
|
|
i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), c)
|
|
i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA
|
|
return i
|
|
|
|
def generate_texture_tuple(img):
|
|
""" This takes an image and returns the needed tuple for the
|
|
blockmap dictionary."""
|
|
if img is None:
|
|
return None
|
|
return (img, generate_opaque_mask(img))
|
|
|
|
##
|
|
## Biomes
|
|
##
|
|
|
|
currentBiomeFile = None
|
|
currentBiomeData = None
|
|
grasscolor = None
|
|
foliagecolor = None
|
|
watercolor = None
|
|
_north = None
|
|
|
|
def prepareBiomeData(worlddir):
|
|
global grasscolor, foliagecolor, watercolor
|
|
|
|
# skip if the color files are already loaded
|
|
if grasscolor and foliagecolor:
|
|
return
|
|
|
|
biomeDir = os.path.join(worlddir, "biomes")
|
|
if not os.path.exists(biomeDir):
|
|
raise Exception("biomes not found")
|
|
|
|
# try to find the biome color images. If _find_file can't locate them
|
|
# then try looking in the EXTRACTEDBIOMES folder
|
|
try:
|
|
grasscolor = list(_load_image("grasscolor.png").getdata())
|
|
foliagecolor = list(_load_image("foliagecolor.png").getdata())
|
|
# don't force the water color just yet
|
|
# since the biome extractor doesn't know about it
|
|
try:
|
|
watercolor = list(_load_image("watercolor.png").getdata())
|
|
except IOError:
|
|
pass
|
|
except IOError:
|
|
try:
|
|
grasscolor = list(Image.open(os.path.join(biomeDir,"grasscolor.png")).getdata())
|
|
foliagecolor = list(Image.open(os.path.join(biomeDir,"foliagecolor.png")).getdata())
|
|
except Exception:
|
|
# clear anything that managed to get set
|
|
grasscolor = None
|
|
foliagecolor = None
|
|
watercolor = None
|
|
|
|
def getBiomeData(worlddir, chunkX, chunkY):
|
|
'''Opens the worlddir and reads in the biome color information
|
|
from the .biome files. See also:
|
|
http://www.minecraftforum.net/viewtopic.php?f=25&t=80902
|
|
'''
|
|
|
|
global currentBiomeFile, currentBiomeData
|
|
global _north
|
|
biomeX = chunkX // 32
|
|
biomeY = chunkY // 32
|
|
rots = 0
|
|
if _north == 'upper-left':
|
|
temp = biomeX
|
|
biomeX = biomeY
|
|
biomeY = -temp-1
|
|
rots = 3
|
|
elif _north == 'upper-right':
|
|
biomeX = -biomeX-1
|
|
biomeY = -biomeY-1
|
|
rots = 2
|
|
elif _north == 'lower-right':
|
|
temp = biomeX
|
|
biomeX = -biomeY-1
|
|
biomeY = temp
|
|
rots = 1
|
|
|
|
biomeFile = "b.%d.%d.biome" % (biomeX, biomeY)
|
|
if biomeFile == currentBiomeFile:
|
|
return currentBiomeData
|
|
|
|
try:
|
|
with open(os.path.join(worlddir, "biomes", biomeFile), "rb") as f:
|
|
rawdata = f.read()
|
|
# make sure the file size is correct
|
|
if not len(rawdata) == 512 * 512 * 2:
|
|
raise Exception("Biome file %s is not valid." % (biomeFile,))
|
|
data = numpy.reshape(numpy.rot90(numpy.reshape(
|
|
numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")),
|
|
(512,512)),rots), -1)
|
|
except IOError:
|
|
data = None
|
|
pass # no biome data
|
|
|
|
currentBiomeFile = biomeFile
|
|
currentBiomeData = data
|
|
return data
|
|
|
|
##
|
|
## Color Light
|
|
##
|
|
|
|
lightcolor = None
|
|
lightcolor_checked = False
|
|
def loadLightColor():
|
|
global lightcolor, lightcolor_checked
|
|
|
|
if not lightcolor_checked:
|
|
lightcolor_checked = True
|
|
try:
|
|
lightcolor = list(_load_image("light_normal.png").getdata())
|
|
except Exception:
|
|
logging.warning("Light color image could not be found.")
|
|
lightcolor = None
|
|
return lightcolor
|
|
|
|
##
|
|
## The big one: generate() and associated framework
|
|
##
|
|
|
|
# placeholders that are generated in generate()
|
|
texture_dimensions = None
|
|
blockmap_generators = {}
|
|
blockmap = {}
|
|
biome_grass_texture = None
|
|
|
|
transparent_blocks = set([0,])
|
|
solid_blocks = set()
|
|
fluid_blocks = set()
|
|
nospawn_blocks = set()
|
|
|
|
# the material registration decorator
|
|
def material(blockid=[], data=[0], **kwargs):
|
|
# mapping from property name to the set to store them in
|
|
properties = {"transparent" : transparent_blocks, "solid" : solid_blocks, "fluid" : fluid_blocks, "nospawn" : nospawn_blocks}
|
|
|
|
# make sure blockid and data are iterable
|
|
try:
|
|
iter(blockid)
|
|
except:
|
|
blockid = [blockid,]
|
|
try:
|
|
iter(data)
|
|
except:
|
|
data = [data,]
|
|
|
|
def inner_material(func):
|
|
global blockmap_generators
|
|
|
|
# create a wrapper function with a known signature
|
|
@functools.wraps(func)
|
|
def func_wrapper(blockid, data, north):
|
|
try:
|
|
return func(blockid, data, north)
|
|
except TypeError:
|
|
return func(blockid, data)
|
|
|
|
for block in blockid:
|
|
# set the property sets appropriately
|
|
for prop in properties:
|
|
try:
|
|
if block in kwargs.get(prop, []):
|
|
properties[prop].update([block])
|
|
except TypeError:
|
|
if kwargs.get(prop, False):
|
|
properties[prop].update([block])
|
|
|
|
# populate blockmap_generators with our function
|
|
for d in data:
|
|
blockmap_generators[(block, d)] = func_wrapper
|
|
|
|
return func_wrapper
|
|
return inner_material
|
|
|
|
# shortcut function for pure blocks, default to solid
|
|
def block(blockid=[], top_index=None, side_index=None, **kwargs):
|
|
new_kwargs = {'solid' : True}
|
|
new_kwargs.update(kwargs)
|
|
|
|
if top_index is None:
|
|
raise ValueError("top_index was not provided")
|
|
|
|
if side_index is None:
|
|
side_index = top_index
|
|
|
|
@material(blockid=blockid, **new_kwargs)
|
|
def inner_block(unused_id, unused_data):
|
|
return build_block(terrain_images[top_index], terrain_images[side_index])
|
|
return inner_block
|
|
|
|
# shortcut function for sprite blocks, defaults to transparent
|
|
def sprite(blockid=[], index=None, **kwargs):
|
|
new_kwargs = {'transparent' : True}
|
|
new_kwargs.update(kwargs)
|
|
|
|
if index is None:
|
|
raise ValueError("index was not provided")
|
|
|
|
@material(blockid=blockid, **new_kwargs)
|
|
def inner_sprite(unused_id, unused_data):
|
|
return build_sprite(terrain_images[index])
|
|
return inner_sprite
|
|
|
|
# shortcut function for billboard blocks, defaults to transparent
|
|
def billboard(blockid=[], index=None, **kwargs):
|
|
new_kwargs = {'transparent' : True}
|
|
new_kwargs.update(kwargs)
|
|
|
|
if index is None:
|
|
raise ValueError("index was not provided")
|
|
|
|
@material(blockid=blockid, **new_kwargs)
|
|
def inner_billboard(unused_id, unused_data):
|
|
return build_billboard(terrain_images[index])
|
|
return inner_billboard
|
|
|
|
def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower-left'):
|
|
global _find_file_local_path
|
|
global bgcolor
|
|
global texture_dimensions
|
|
global _north
|
|
bgcolor = bgc
|
|
_find_file_local_path = path
|
|
_north = north_direction
|
|
texture_dimensions = (texture_size, texture_size)
|
|
|
|
# This maps terainids to 16x16 images
|
|
global terrain_images
|
|
terrain_images = _split_terrain(_load_image("terrain.png"))
|
|
|
|
# generate biome grass mask
|
|
global biome_grass_texture
|
|
biome_grass_texture = build_block(terrain_images[0], terrain_images[38])
|
|
|
|
# generate the blocks
|
|
global blockmap, blockmap_generators
|
|
blockmap = {}
|
|
for blockid, data in blockmap_generators:
|
|
texgen = blockmap_generators[(blockid, data)]
|
|
tex = texgen(blockid, data, north_direction)
|
|
blockmap[(blockid, data)] = generate_texture_tuple(tex)
|
|
|
|
if texture_size != 24:
|
|
# rescale biome textures.
|
|
biome_grass_texture = biome_grass_texture.resize(texture_dimensions, Image.ANTIALIAS)
|
|
|
|
# rescale the special block images
|
|
for blockid, data in iter(blockmap):
|
|
block = blockmap[(blockid,data)]
|
|
if block != None:
|
|
block = block[0]
|
|
scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS)
|
|
blockmap[(blockid,data)] = generate_texture_tuple(scaled_block, blockid)
|
|
|
|
##
|
|
## and finally: actual texture definitions
|
|
##
|
|
|
|
# stone
|
|
block(blockid=1, top_index=1)
|
|
|
|
@material(blockid=2, data=range(11)+[0x10,], solid=True)
|
|
def grass(blockid, data):
|
|
# 0x10 bit means SNOW
|
|
side_img = terrain_images[3]
|
|
if data & 0x10:
|
|
side_img = terrain_images[68]
|
|
img = build_block(terrain_images[0], side_img)
|
|
if not data & 0x10:
|
|
global biome_grass_texture
|
|
composite.alpha_over(img, biome_grass_texture, (0, 0), biome_grass_texture)
|
|
return img
|
|
|
|
# dirt
|
|
block(blockid=3, top_index=2)
|
|
# cobblestone
|
|
block(blockid=4, top_index=16)
|
|
# wooden plank
|
|
block(blockid=5, top_index=4)
|
|
|
|
@material(blockid=6, data=range(16), transparent=True)
|
|
def saplings(blockid, data):
|
|
# usual saplings
|
|
tex = terrain_images[15]
|
|
|
|
if data & 0x3 == 1: # spruce sapling
|
|
tex = terrain_images[63]
|
|
if data & 0x3 == 2: # birch sapling
|
|
tex = terrain_images[79]
|
|
|
|
return build_sprite(tex)
|
|
|
|
# bedrock
|
|
block(blockid=7, top_index=17)
|
|
|
|
@material(blockid=8, data=range(16), fluid=True, transparent=True)
|
|
def water(blockid, data):
|
|
watertex = _load_image("water.png")
|
|
return build_block(watertex, watertex)
|
|
|
|
# other water, glass, and ice (no inner surfaces)
|
|
# uses pseudo-ancildata found in iterate.c
|
|
@material(blockid=[9, 20, 79], data=range(32), fluid=(9,), transparent=True, nospawn=True)
|
|
def no_inner_surfaces(blockid, data):
|
|
if blockid == 9:
|
|
texture = _load_image("water.png")
|
|
elif blockid == 20:
|
|
texture = terrain_images[49]
|
|
else:
|
|
texture = terrain_images[67]
|
|
|
|
if (data & 0b10000) == 16:
|
|
top = texture
|
|
else:
|
|
top = None
|
|
|
|
if (data & 0b0001) == 1:
|
|
side1 = texture # top left
|
|
else:
|
|
side1 = None
|
|
|
|
if (data & 0b1000) == 8:
|
|
side2 = texture # top right
|
|
else:
|
|
side2 = None
|
|
|
|
if (data & 0b0010) == 2:
|
|
side3 = texture # bottom left
|
|
else:
|
|
side3 = None
|
|
|
|
if (data & 0b0100) == 4:
|
|
side4 = texture # bottom right
|
|
else:
|
|
side4 = None
|
|
|
|
# if nothing shown do not draw at all
|
|
if top is None and side3 is None and side4 is None:
|
|
return None
|
|
|
|
img = build_full_block(top,None,None,side3,side4)
|
|
return img
|
|
|
|
@material(blockid=[10, 11], data=range(16), fluid=True, transparent=False)
|
|
def lava(blockid, data):
|
|
lavatex = _load_image("lava.png")
|
|
return build_block(lavatex, lavatex)
|
|
|
|
# sand
|
|
block(blockid=12, top_index=18)
|
|
# gravel
|
|
block(blockid=13, top_index=19)
|
|
# gold ore
|
|
block(blockid=14, top_index=32)
|
|
# iron ore
|
|
block(blockid=15, top_index=33)
|
|
# coal ore
|
|
block(blockid=16, top_index=34)
|
|
|
|
@material(blockid=17, data=range(3), solid=True)
|
|
def wood(blockid, data):
|
|
top = terrain_images[21]
|
|
if data == 0: # normal
|
|
return build_block(top, terrain_images[20])
|
|
if data == 1: # birch
|
|
return build_block(top, terrain_images[116])
|
|
if data == 2: # pine
|
|
return build_block(top, terrain_images[117])
|
|
|
|
@material(blockid=18, data=range(16), transparent=True, solid=True)
|
|
def leaves(blockid, data):
|
|
t = terrain_images[52]
|
|
if data == 1:
|
|
# pine!
|
|
t = terrain_images[132]
|
|
return build_block(t, t)
|
|
|
|
# sponge
|
|
block(blockid=19, top_index=48)
|
|
# lapis lazuli ore
|
|
block(blockid=21, top_index=160)
|
|
# lapis lazuli block
|
|
block(blockid=22, top_index=144)
|
|
|
|
# dispensers, furnaces, and burning furnaces
|
|
@material(blockid=[23, 61, 62], data=range(6), solid=True)
|
|
def furnaces(blockid, data, north):
|
|
# first, do the north rotation if needed
|
|
if north == 'upper-left':
|
|
if data == 2: data = 5
|
|
elif data == 3: data = 4
|
|
elif data == 4: data = 2
|
|
elif data == 5: data = 3
|
|
elif north == 'upper-right':
|
|
if data == 2: data = 3
|
|
elif data == 3: data = 2
|
|
elif data == 4: data = 5
|
|
elif data == 5: data = 4
|
|
elif north == 'lower-right':
|
|
if data == 2: data = 4
|
|
elif data == 3: data = 5
|
|
elif data == 4: data = 3
|
|
elif data == 5: data = 2
|
|
|
|
top = terrain_images[62]
|
|
side = terrain_images[45]
|
|
|
|
if blockid == 61:
|
|
front = terrain_images[44]
|
|
elif blockid == 62:
|
|
front = terrain_images[61]
|
|
elif blockid == 23:
|
|
front = terrain_images[46]
|
|
|
|
if data == 3: # pointing west
|
|
return build_full_block(top, None, None, side, front)
|
|
elif data == 4: # pointing north
|
|
return build_full_block(top, None, None, front, side)
|
|
else: # in any other direction the front can't be seen
|
|
return build_full_block(top, None, None, side, side)
|
|
|
|
# sandstone
|
|
block(blockid=24, top_index=176, side_index=192)
|
|
# note block
|
|
block(blockid=25, top_index=74)
|
|
|
|
@material(blockid=26, data=range(12), transparent=True)
|
|
def bed(blockid, data, north):
|
|
# first get north rotation done
|
|
# Masked to not clobber block head/foot info
|
|
if north == 'upper-left':
|
|
if (data & 0b0011) == 0: data = data & 0b1100 | 1
|
|
elif (data & 0b0011) == 1: data = data & 0b1100 | 2
|
|
elif (data & 0b0011) == 2: data = data & 0b1100 | 3
|
|
elif (data & 0b0011) == 3: data = data & 0b1100 | 0
|
|
elif north == 'upper-right':
|
|
if (data & 0b0011) == 0: data = data & 0b1100 | 2
|
|
elif (data & 0b0011) == 1: data = data & 0b1100 | 3
|
|
elif (data & 0b0011) == 2: data = data & 0b1100 | 0
|
|
elif (data & 0b0011) == 3: data = data & 0b1100 | 1
|
|
elif north == 'lower-right':
|
|
if (data & 0b0011) == 0: data = data & 0b1100 | 3
|
|
elif (data & 0b0011) == 1: data = data & 0b1100 | 0
|
|
elif (data & 0b0011) == 2: data = data & 0b1100 | 1
|
|
elif (data & 0b0011) == 3: data = data & 0b1100 | 2
|
|
|
|
increment = 8
|
|
left_face = None
|
|
right_face = None
|
|
if data & 0x8 == 0x8: # head of the bed
|
|
top = terrain_images[135]
|
|
if data & 0x00 == 0x00: # head pointing to West
|
|
top = top.copy().rotate(270)
|
|
left_face = terrain_images[151]
|
|
right_face = terrain_images[152]
|
|
if data & 0x01 == 0x01: # ... North
|
|
top = top.rotate(270)
|
|
left_face = terrain_images[152]
|
|
right_face = terrain_images[151]
|
|
if data & 0x02 == 0x02: # East
|
|
top = top.rotate(180)
|
|
left_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
|
|
right_face = None
|
|
if data & 0x03 == 0x03: # South
|
|
right_face = None
|
|
right_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
else: # foot of the bed
|
|
top = terrain_images[134]
|
|
if data & 0x00 == 0x00: # head pointing to West
|
|
top = top.rotate(270)
|
|
left_face = terrain_images[150]
|
|
right_face = None
|
|
if data & 0x01 == 0x01: # ... North
|
|
top = top.rotate(270)
|
|
left_face = None
|
|
right_face = terrain_images[150]
|
|
if data & 0x02 == 0x02: # East
|
|
top = top.rotate(180)
|
|
left_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
|
|
right_face = terrain_images[149].transpose(Image.FLIP_LEFT_RIGHT)
|
|
if data & 0x03 == 0x03: # South
|
|
left_face = terrain_images[149]
|
|
right_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
top = (top, increment)
|
|
return build_full_block(top, None, None, left_face, right_face)
|
|
|
|
# powered, detector, and normal rails
|
|
@material(blockid=[27, 28, 66], data=range(14), transparent=True)
|
|
def rails(blockid, data, north):
|
|
# first, do north rotation
|
|
# Masked to not clobber powered rail on/off info
|
|
# Ascending and flat straight
|
|
if north == 'upper-left':
|
|
if (data & 0b0111) == 0: data = data & 0b1000 | 1
|
|
elif (data & 0b0111) == 1: data = data & 0b1000 | 0
|
|
elif (data & 0b0111) == 2: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 3
|
|
elif north == 'upper-right':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 4
|
|
elif north == 'lower-right':
|
|
if (data & 0b0111) == 0: data = data & 0b1000 | 1
|
|
elif (data & 0b0111) == 1: data = data & 0b1000 | 0
|
|
elif (data & 0b0111) == 2: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 2
|
|
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
if blockid == 27: # powered rail
|
|
if data & 0x8 == 0: # unpowered
|
|
raw_straight = terrain_images[163]
|
|
raw_corner = terrain_images[112] # they don't exist but make the code
|
|
# much simplier
|
|
elif data & 0x8 == 0x8: # powered
|
|
raw_straight = terrain_images[179]
|
|
raw_corner = terrain_images[112] # leave corners for code simplicity
|
|
# filter the 'powered' bit
|
|
data = data & 0x7
|
|
|
|
elif blockid == 28: # detector rail
|
|
raw_straight = terrain_images[195]
|
|
raw_corner = terrain_images[112] # leave corners for code simplicity
|
|
|
|
elif blockid == 66: # normal rail
|
|
raw_straight = terrain_images[128]
|
|
raw_corner = terrain_images[112]
|
|
|
|
## use transform_image to scale and shear
|
|
if data == 0:
|
|
track = transform_image_top(raw_straight)
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
elif data == 6:
|
|
track = transform_image_top(raw_corner)
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
elif data == 7:
|
|
track = transform_image_top(raw_corner.rotate(270))
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
elif data == 8:
|
|
# flip
|
|
track = transform_image_top(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90))
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
elif data == 9:
|
|
track = transform_image_top(raw_corner.transpose(Image.FLIP_TOP_BOTTOM))
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
elif data == 1:
|
|
track = transform_image_top(raw_straight.rotate(90))
|
|
composite.alpha_over(img, track, (0,12), track)
|
|
|
|
#slopes
|
|
elif data == 2: # slope going up in +x direction
|
|
track = transform_image_slope(raw_straight)
|
|
track = track.transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, track, (2,0), track)
|
|
# the 2 pixels move is needed to fit with the adjacent tracks
|
|
|
|
elif data == 3: # slope going up in -x direction
|
|
# tracks are sprites, in this case we are seeing the "side" of
|
|
# the sprite, so draw a line to make it beautiful.
|
|
ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164))
|
|
# grey from track texture (exterior grey).
|
|
# the track doesn't start from image corners, be carefull drawing the line!
|
|
elif data == 4: # slope going up in -y direction
|
|
track = transform_image_slope(raw_straight)
|
|
composite.alpha_over(img, track, (0,0), track)
|
|
|
|
elif data == 5: # slope going up in +y direction
|
|
# same as "data == 3"
|
|
ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164))
|
|
|
|
return img
|
|
|
|
# sticky and normal piston body
|
|
@material(blockid=[29, 33], data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True, solid=True)
|
|
def piston(blockid, data, north):
|
|
# first, north rotation
|
|
# Masked to not clobber block head/foot info
|
|
if north == 'upper-left':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 3
|
|
elif north == 'upper-right':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 4
|
|
elif north == 'lower-right':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 2
|
|
|
|
if blockid == 29: # sticky
|
|
piston_t = terrain_images[106].copy()
|
|
else: # normal
|
|
piston_t = terrain_images[107].copy()
|
|
|
|
# other textures
|
|
side_t = terrain_images[108].copy()
|
|
back_t = terrain_images[109].copy()
|
|
interior_t = terrain_images[110].copy()
|
|
|
|
if data & 0x08 == 0x08: # pushed out, non full blocks, tricky stuff
|
|
# remove piston texture from piston body
|
|
ImageDraw.Draw(side_t).rectangle((0, 0,16,3),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
|
|
if data & 0x07 == 0x0: # down
|
|
side_t = side_t.rotate(180)
|
|
img = build_full_block(back_t ,None ,None ,side_t, side_t)
|
|
|
|
elif data & 0x07 == 0x1: # up
|
|
img = build_full_block((interior_t, 4) ,None ,None ,side_t, side_t)
|
|
|
|
elif data & 0x07 == 0x2: # east
|
|
img = build_full_block(side_t , None, None ,side_t.rotate(90), back_t)
|
|
|
|
elif data & 0x07 == 0x3: # west
|
|
img = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), None)
|
|
temp = transform_image_side(interior_t)
|
|
temp = temp.transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, temp, (9,5), temp)
|
|
|
|
elif data & 0x07 == 0x4: # north
|
|
img = build_full_block(side_t.rotate(90) ,None ,None , None, side_t.rotate(270))
|
|
temp = transform_image_side(interior_t)
|
|
composite.alpha_over(img, temp, (3,5), temp)
|
|
|
|
elif data & 0x07 == 0x5: # south
|
|
img = build_full_block(side_t.rotate(270) ,None , None ,back_t, side_t.rotate(90))
|
|
|
|
else: # pushed in, normal full blocks, easy stuff
|
|
if data & 0x07 == 0x0: # down
|
|
side_t = side_t.rotate(180)
|
|
img = build_full_block(back_t ,None ,None ,side_t, side_t)
|
|
elif data & 0x07 == 0x1: # up
|
|
img = build_full_block(piston_t ,None ,None ,side_t, side_t)
|
|
elif data & 0x07 == 0x2: # east
|
|
img = build_full_block(side_t ,None ,None ,side_t.rotate(90), back_t)
|
|
elif data & 0x07 == 0x3: # west
|
|
img = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t)
|
|
elif data & 0x07 == 0x4: # north
|
|
img = build_full_block(side_t.rotate(90) ,None ,None ,piston_t, side_t.rotate(270))
|
|
elif data & 0x07 == 0x5: # south
|
|
img = build_full_block(side_t.rotate(270) ,None ,None ,back_t, side_t.rotate(90))
|
|
|
|
return img
|
|
|
|
# sticky and normal piston shaft
|
|
@material(blockid=34, data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True)
|
|
def piston_extension(blockid, data, north):
|
|
# first, north rotation
|
|
# Masked to not clobber block head/foot info
|
|
if north == 'upper-left':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 3
|
|
elif north == 'upper-right':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 2
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 4
|
|
elif north == 'lower-right':
|
|
if (data & 0b0111) == 2: data = data & 0b1000 | 4
|
|
elif (data & 0b0111) == 3: data = data & 0b1000 | 5
|
|
elif (data & 0b0111) == 4: data = data & 0b1000 | 3
|
|
elif (data & 0b0111) == 5: data = data & 0b1000 | 2
|
|
|
|
if (data & 0x8) == 0x8: # sticky
|
|
piston_t = terrain_images[106].copy()
|
|
else: # normal
|
|
piston_t = terrain_images[107].copy()
|
|
|
|
# other textures
|
|
side_t = terrain_images[108].copy()
|
|
back_t = terrain_images[107].copy()
|
|
# crop piston body
|
|
ImageDraw.Draw(side_t).rectangle((0, 4,16,16),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
|
|
# generate the horizontal piston extension stick
|
|
h_stick = Image.new("RGBA", (24,24), bgcolor)
|
|
temp = transform_image_side(side_t)
|
|
composite.alpha_over(h_stick, temp, (1,7), temp)
|
|
temp = transform_image_top(side_t.rotate(90))
|
|
composite.alpha_over(h_stick, temp, (1,1), temp)
|
|
# Darken it
|
|
sidealpha = h_stick.split()[3]
|
|
h_stick = ImageEnhance.Brightness(h_stick).enhance(0.85)
|
|
h_stick.putalpha(sidealpha)
|
|
|
|
# generate the vertical piston extension stick
|
|
v_stick = Image.new("RGBA", (24,24), bgcolor)
|
|
temp = transform_image_side(side_t.rotate(90))
|
|
composite.alpha_over(v_stick, temp, (12,6), temp)
|
|
temp = temp.transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(v_stick, temp, (1,6), temp)
|
|
# Darken it
|
|
sidealpha = v_stick.split()[3]
|
|
v_stick = ImageEnhance.Brightness(v_stick).enhance(0.85)
|
|
v_stick.putalpha(sidealpha)
|
|
|
|
# Piston orientation is stored in the 3 first bits
|
|
if data & 0x07 == 0x0: # down
|
|
side_t = side_t.rotate(180)
|
|
img = build_full_block((back_t, 12) ,None ,None ,side_t, side_t)
|
|
composite.alpha_over(img, v_stick, (0,-3), v_stick)
|
|
elif data & 0x07 == 0x1: # up
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
img2 = build_full_block(piston_t ,None ,None ,side_t, side_t)
|
|
composite.alpha_over(img, v_stick, (0,4), v_stick)
|
|
composite.alpha_over(img, img2, (0,0), img2)
|
|
elif data & 0x07 == 0x2: # east
|
|
img = build_full_block(side_t ,None ,None ,side_t.rotate(90), None)
|
|
temp = transform_image_side(back_t).transpose(Image.FLIP_LEFT_RIGHT)
|
|
composite.alpha_over(img, temp, (2,2), temp)
|
|
composite.alpha_over(img, h_stick, (6,3), h_stick)
|
|
elif data & 0x07 == 0x3: # west
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
img2 = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t)
|
|
composite.alpha_over(img, h_stick, (0,0), h_stick)
|
|
composite.alpha_over(img, img2, (0,0), img2)
|
|
elif data & 0x07 == 0x4: # north
|
|
img = build_full_block(side_t.rotate(90) ,None ,None , piston_t, side_t.rotate(270))
|
|
composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (0,0), h_stick.transpose(Image.FLIP_LEFT_RIGHT))
|
|
elif data & 0x07 == 0x5: # south
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
img2 = build_full_block(side_t.rotate(270) ,None ,None ,None, side_t.rotate(90))
|
|
temp = transform_image_side(back_t)
|
|
composite.alpha_over(img2, temp, (10,2), temp)
|
|
composite.alpha_over(img, img2, (0,0), img2)
|
|
composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (-3,2), h_stick.transpose(Image.FLIP_LEFT_RIGHT))
|
|
|
|
return img
|
|
|
|
# cobweb
|
|
sprite(blockid=30, index=11)
|
|
|
|
@material(blockid=31, data=range(3), transparent=True)
|
|
def tall_grass(blockid, data):
|
|
if data == 0: # dead shrub
|
|
texture = terrain_images[55]
|
|
elif data == 1: # tall grass
|
|
texture = terrain_images[39]
|
|
elif data == 2: # fern
|
|
texture = terrain_images[56]
|
|
|
|
return build_billboard(texture)
|
|
|
|
# dead bush
|
|
billboard(blockid=32, index=55)
|
|
|
|
@material(blockid=35, data=range(16), solid=True)
|
|
def wool(blockid, data):
|
|
if data == 0: # white
|
|
texture = terrain_images[64]
|
|
elif data == 1: # orange
|
|
texture = terrain_images[210]
|
|
elif data == 2: # magenta
|
|
texture = terrain_images[194]
|
|
elif data == 3: # light blue
|
|
texture = terrain_images[178]
|
|
elif data == 4: # yellow
|
|
texture = terrain_images[162]
|
|
elif data == 5: # light green
|
|
texture = terrain_images[146]
|
|
elif data == 6: # pink
|
|
texture = terrain_images[130]
|
|
elif data == 7: # grey
|
|
texture = terrain_images[114]
|
|
elif data == 8: # light grey
|
|
texture = terrain_images[225]
|
|
elif data == 9: # cyan
|
|
texture = terrain_images[209]
|
|
elif data == 10: # purple
|
|
texture = terrain_images[193]
|
|
elif data == 11: # blue
|
|
texture = terrain_images[177]
|
|
elif data == 12: # brown
|
|
texture = terrain_images[161]
|
|
elif data == 13: # dark green
|
|
texture = terrain_images[145]
|
|
elif data == 14: # red
|
|
texture = terrain_images[129]
|
|
elif data == 15: # black
|
|
texture = terrain_images[113]
|
|
|
|
return build_block(texture, texture)
|
|
|
|
# dandelion
|
|
sprite(blockid=37, index=13)
|
|
# rose
|
|
sprite(blockid=38, index=12)
|
|
# brown mushroom
|
|
sprite(blockid=39, index=29)
|
|
# red mushroom
|
|
sprite(blockid=40, index=28)
|
|
# block of gold
|
|
block(blockid=41, top_index=23)
|
|
# block of iron
|
|
block(blockid=42, top_index=22)
|
|
|
|
# double slabs and slabs
|
|
@material(blockid=[43, 44], data=range(6), transparent=(44,), solid=(43,))
|
|
def slabs(blockid, data):
|
|
if data == 0: # stone slab
|
|
top = terrain_images[6]
|
|
side = terrain_images[5]
|
|
elif data == 1: # stone slab
|
|
top = terrain_images[176]
|
|
side = terrain_images[192]
|
|
elif data == 2: # wooden slab
|
|
top = side = terrain_images[4]
|
|
elif data == 3: # cobblestone slab
|
|
top = side = terrain_images[16]
|
|
elif data == 4: # brick?
|
|
top = side = terrain_images[7]
|
|
elif data == 5: # stone brick?
|
|
top = side = terrain_images[54]
|
|
|
|
if blockid == 43: # double slab
|
|
return build_block(top, side)
|
|
|
|
# plain slab
|
|
top = transform_image_top(top)
|
|
side = transform_image_side(side)
|
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
sidealpha = side.split()[3]
|
|
side = ImageEnhance.Brightness(side).enhance(0.9)
|
|
side.putalpha(sidealpha)
|
|
othersidealpha = otherside.split()[3]
|
|
otherside = ImageEnhance.Brightness(otherside).enhance(0.8)
|
|
otherside.putalpha(othersidealpha)
|
|
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
composite.alpha_over(img, side, (0,12), side)
|
|
composite.alpha_over(img, otherside, (12,12), otherside)
|
|
composite.alpha_over(img, top, (0,6), top)
|
|
|
|
return img
|
|
|
|
# brick block
|
|
block(blockid=45, top_index=7)
|
|
# TNT
|
|
block(blockid=46, top_index=9, side_index=8)
|
|
# bookshelf
|
|
block(blockid=47, top_index=4, side_index=35)
|
|
# moss stone
|
|
block(blockid=48, top_index=36)
|
|
# obsidian
|
|
block(blockid=49, top_index=37)
|
|
|
|
# torch, redstone torch (off), redstone torch(on)
|
|
@material(blockid=[50, 75, 76], data=[1, 2, 3, 4, 5], transparent=True)
|
|
def torches(blockid, data, north):
|
|
# first, north rotations
|
|
if north == 'upper-left':
|
|
if data == 1: data = 3
|
|
elif data == 2: data = 4
|
|
elif data == 3: data = 2
|
|
elif data == 4: data = 1
|
|
elif north == 'upper-right':
|
|
if data == 1: data = 2
|
|
elif data == 2: data = 1
|
|
elif data == 3: data = 4
|
|
elif data == 4: data = 3
|
|
elif north == 'lower-right':
|
|
if data == 1: data = 4
|
|
elif data == 2: data = 3
|
|
elif data == 3: data = 1
|
|
elif data == 4: data = 2
|
|
|
|
# choose the proper texture
|
|
if blockid == 50: # torch
|
|
small = terrain_images[80]
|
|
elif blockid == 75: # off redstone torch
|
|
small = terrain_images[115]
|
|
else: # on redstone torch
|
|
small = terrain_images[99]
|
|
|
|
# compose a torch bigger than the normal
|
|
# (better for doing transformations)
|
|
torch = Image.new("RGBA", (16,16), bgcolor)
|
|
composite.alpha_over(torch,small,(-4,-3))
|
|
composite.alpha_over(torch,small,(-5,-2))
|
|
composite.alpha_over(torch,small,(-3,-2))
|
|
|
|
# angle of inclination of the texture
|
|
rotation = 15
|
|
|
|
if data == 1: # pointing south
|
|
torch = torch.rotate(-rotation, Image.NEAREST) # nearest filter is more nitid.
|
|
img = build_full_block(None, None, None, torch, None, None)
|
|
|
|
elif data == 2: # pointing north
|
|
torch = torch.rotate(rotation, Image.NEAREST)
|
|
img = build_full_block(None, None, torch, None, None, None)
|
|
|
|
elif data == 3: # pointing west
|
|
torch = torch.rotate(rotation, Image.NEAREST)
|
|
img = build_full_block(None, torch, None, None, None, None)
|
|
|
|
elif data == 4: # pointing east
|
|
torch = torch.rotate(-rotation, Image.NEAREST)
|
|
img = build_full_block(None, None, None, None, torch, None)
|
|
|
|
elif data == 5: # standing on the floor
|
|
# compose a "3d torch".
|
|
img = Image.new("RGBA", (24,24), bgcolor)
|
|
|
|
small_crop = small.crop((2,2,14,14))
|
|
slice = small_crop.copy()
|
|
ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0))
|
|
|
|
composite.alpha_over(img, slice, (7,5))
|
|
composite.alpha_over(img, small_crop, (6,6))
|
|
composite.alpha_over(img, small_crop, (7,6))
|
|
composite.alpha_over(img, slice, (7,7))
|
|
|
|
return img
|