import wx
import os
-import platform
import webbrowser
-import shutil
-import glob
from Cura.gui import configBase
from Cura.gui import expertConfig
from Cura.gui import printWindow
from Cura.gui import simpleMode
from Cura.gui import projectPlanner
-from Cura.gui import batchRun
+from Cura.gui.tools import batchRun
from Cura.gui import flatSlicerWindow
from Cura.gui.util import dropTarget
+from Cura.gui.tools import minecraftImport
from Cura.util import validators
from Cura.util import profile
from Cura.util import version
self.Bind(wx.EVT_MENU, self.OnProjectPlanner, i)
# i = toolsMenu.Append(-1, 'Open SVG (2D) slicer...')
# self.Bind(wx.EVT_MENU, self.OnSVGSlicerOpen, i)
+ i = toolsMenu.Append(-1, 'Minecraft import...')
+ self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
self.menubar.Append(toolsMenu, 'Tools')
expertMenu = wx.Menu()
pp.Centre()
pp.Show(True)
+ def OnMinecraftImport(self, e):
+ mi = minecraftImport.minecraftImportWindow(self)
+ mi.Centre()
+ mi.Show(True)
+
def OnSVGSlicerOpen(self, e):
svgSlicer = flatSlicerWindow.flatSlicerWindow()
svgSlicer.Centre()
--- /dev/null
+from __future__ import absolute_import
+
+import wx
+import glob
+import os
+import numpy
+
+from Cura.util import mesh
+from Cura.util import stl
+from Cura.util.pymclevel import mclevel
+
+class minecraftImportWindow(wx.Frame):
+ def __init__(self, parent):
+ super(minecraftImportWindow, self).__init__(parent, title='Cura - Minecraft import')
+
+ saveFileList = map(os.path.basename, glob.glob(mclevel.saveFileDir + "/*"))
+
+ self.panel = wx.Panel(self, -1)
+ self.SetSizer(wx.BoxSizer())
+ self.GetSizer().Add(self.panel, 1, wx.EXPAND)
+
+ sizer = wx.GridBagSizer(2, 2)
+
+ self.saveListBox = wx.ListBox(self.panel, -1, choices=saveFileList)
+ sizer.Add(self.saveListBox, (0,0), span=(2,1), flag=wx.EXPAND)
+ self.playerListBox = wx.ListBox(self.panel, -1, choices=[])
+ sizer.Add(self.playerListBox, (0,1), span=(2,1), flag=wx.EXPAND)
+
+ self.previewPanel = wx.Panel(self.panel, -1)
+ self.previewPanel.SetMinSize((512, 512))
+ sizer.Add(self.previewPanel, (0,2), flag=wx.EXPAND)
+
+ self.importButton = wx.Button(self.panel, -1, 'Import')
+ sizer.Add(self.importButton, (1,2))
+
+ sizer.AddGrowableRow(1)
+
+ self.panel.SetSizer(sizer)
+
+ self.saveListBox.Bind(wx.EVT_LISTBOX, self.OnSaveSelect)
+ self.playerListBox.Bind(wx.EVT_LISTBOX, self.OnPlayerSelect)
+ self.importButton.Bind(wx.EVT_BUTTON, self.OnImport)
+
+ self.previewPanel.Bind(wx.EVT_PAINT, self.OnPaintPreview)
+ self.previewPanel.Bind(wx.EVT_SIZE, self.OnSizePreview)
+ self.previewPanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackgroundPreview)
+ self.previewPanel.Bind(wx.EVT_MOTION, self.OnMotion)
+
+ self.level = None
+ self.previewImage = None
+ self.renderList = []
+ self.selectArea = None
+ self.draggingArea = False
+
+ self.Layout()
+ self.Fit()
+
+ self.gravelPen = wx.Pen(wx.Colour(128, 128, 128))
+ self.sandPen = wx.Pen(wx.Colour(192, 192, 0))
+ self.grassPen = []
+ self.waterPen = []
+ for z in xrange(0, 256):
+ self.waterPen.append(wx.Pen(wx.Colour(0,0,min(z+64, 255))))
+ self.grassPen.append(wx.Pen(wx.Colour(0,min(64+z,255),0)))
+
+ self.isSolid = [True] * 256
+ self.isSolid[0] = False #Air
+ self.isSolid[8] = False #Water
+ self.isSolid[9] = False #Water
+ self.isSolid[10] = False #Lava
+ self.isSolid[11] = False #Lava
+ self.isSolid[50] = False #Torch
+ self.isSolid[51] = False #Fire
+
+ def OnSaveSelect(self, e):
+ if self.saveListBox.Selection < 0:
+ return
+ self.level = mclevel.loadWorld(self.saveListBox.GetItems()[self.saveListBox.Selection])
+ self.playerListBox.Clear()
+ for player in self.level.players:
+ self.playerListBox.Append(player)
+
+ def OnPlayerSelect(self, e):
+ playerName = self.playerListBox.GetItems()[self.playerListBox.Selection]
+ self.playerPos = map(lambda n: int(n / 16), self.level.getPlayerPosition(playerName))[0::2]
+
+ self.previewImage = wx.EmptyBitmap(512, 512)
+ for i in xrange(0, 16):
+ for j in xrange(1, i * 2 + 1):
+ self.renderList.insert(0, (15 - i, 16 + i - j))
+ for j in xrange(0, i * 2 + 1):
+ self.renderList.insert(0, (15 + j - i, 15 - i))
+ for j in xrange(0, i * 2 + 1):
+ self.renderList.insert(0, (16 + i, 15 + j - i))
+ for j in xrange(0, i * 2 + 2):
+ self.renderList.insert(0, (16 + i - j, 16 + i))
+ self.previewPanel.Refresh()
+
+ def OnPaintPreview(self, e):
+ if len(self.renderList) > 0:
+ cx, cy = self.renderList.pop()
+ dc = wx.MemoryDC()
+ dc.SelectObject(self.previewImage)
+ chunk = self.level.getChunk(cx + self.playerPos[0] - 16, cy + self.playerPos[1] - 16)
+ dc.SetPen(wx.Pen(wx.Colour(255,0,0)))
+ for x in xrange(0, 16):
+ for y in xrange(0, 16):
+ z = numpy.max(numpy.where(chunk.Blocks[x, y] != 0))
+ type = chunk.Blocks[x, y, z]
+ if type == 1: #Stone
+ dc.SetPen(wx.Pen(wx.Colour(z,z,z)))
+ elif type == 2: #Grass
+ dc.SetPen(self.grassPen[z])
+ elif type == 8 or type == 9: #Water
+ dc.SetPen(self.waterPen[z])
+ elif type == 10 or type == 11: #Lava
+ dc.SetPen(wx.Pen(wx.Colour(min(z+64, 255),0,0)))
+ elif type == 12 or type == 24: #Sand/Standstone
+ dc.SetPen(self.sandPen)
+ elif type == 13: #Gravel
+ dc.SetPen(self.gravelPen)
+ elif type == 18: #Leaves
+ dc.SetPen(wx.Pen(wx.Colour(0,max(z-32, 0),0)))
+ else:
+ dc.SetPen(wx.Pen(wx.Colour(z,z,z)))
+ dc.DrawPoint(cx * 16 + x, cy * 16 + y)
+ dc.SelectObject(wx.NullBitmap)
+ wx.CallAfter(self.previewPanel.Refresh)
+
+ dc = wx.BufferedPaintDC(self.previewPanel)
+ dc.SetBackground(wx.Brush(wx.BLACK))
+ dc.Clear()
+ if self.previewImage is not None:
+ dc.DrawBitmap(self.previewImage, 0, 0)
+ if self.selectArea is not None:
+ dc.SetPen(wx.Pen(wx.Colour(255,0,0)))
+ dc.SetBrush(wx.Brush(None, style=wx.TRANSPARENT))
+ dc.DrawRectangle(self.selectArea[0], self.selectArea[1], self.selectArea[2] - self.selectArea[0] + 1, self.selectArea[3] - self.selectArea[1] + 1)
+
+ def OnSizePreview(self, e):
+ self.previewPanel.Refresh()
+ self.previewPanel.Update()
+
+ def OnEraseBackgroundPreview(self, e):
+ pass
+
+ def OnMotion(self, e):
+ if e.Dragging():
+ if not self.draggingArea:
+ self.draggingArea = True
+ self.selectArea = [e.GetX(), e.GetY(), e.GetX(), e.GetY()]
+ self.selectArea[2] = e.GetX()
+ self.selectArea[3] = e.GetY()
+ self.previewPanel.Refresh()
+ else:
+ self.draggingArea = False
+
+ def OnImport(self, e):
+ if self.level is None or self.selectArea is None:
+ return
+
+ xMin = min(self.selectArea[0], self.selectArea[2]) + (self.playerPos[0] - 16) * 16
+ xMax = max(self.selectArea[0], self.selectArea[2]) + (self.playerPos[0] - 16) * 16
+ yMin = min(self.selectArea[1], self.selectArea[3]) + (self.playerPos[1] - 16) * 16
+ yMax = max(self.selectArea[1], self.selectArea[3]) + (self.playerPos[1] - 16) * 16
+
+ sx = (xMax - xMin + 1)
+ sy = (yMax - yMin + 1)
+ blocks = numpy.zeros((sx, sy, 256), numpy.int32)
+
+ cxMin = int(xMin / 16)
+ cxMax = int((xMax + 15) / 16)
+ cyMin = int(yMin / 16)
+ cyMax = int((yMax + 15) / 16)
+
+ for cx in xrange(cxMin, cxMax + 1):
+ for cy in xrange(cyMin, cyMax + 1):
+ chunk = self.level.getChunk(cx, cy)
+ for x in xrange(0, 16):
+ bx = x + cx * 16
+ if xMin <= bx <= xMax:
+ for y in xrange(0, 16):
+ by = y + cy * 16
+ if yMin <= by <= yMax:
+ blocks[bx - xMin, by - yMin] = chunk.Blocks[x, y]
+ minZ = 256
+ maxZ = 0
+ for x in xrange(0, sx):
+ for y in xrange(0, sy):
+ minZ = min(minZ, numpy.max(numpy.where(blocks[x, y] != 0)))
+ maxZ = max(maxZ, numpy.max(numpy.where(blocks[x, y] != 0)))
+ minZ += 1
+
+ faceCount = 0
+ for x in xrange(0, sx):
+ for y in xrange(0, sy):
+ for z in xrange(minZ, maxZ + 1):
+ if self.isSolid[blocks[x, y, z]]:
+ if z == maxZ or not self.isSolid[blocks[x, y, z + 1]]:
+ faceCount += 1
+ if z == minZ or not self.isSolid[blocks[x, y, z - 1]]:
+ faceCount += 1
+ if x == 0 or not self.isSolid[blocks[x - 1, y, z]]:
+ faceCount += 1
+ if x == sx - 1 or not self.isSolid[blocks[x + 1, y, z]]:
+ faceCount += 1
+ if y == 0 or not self.isSolid[blocks[x, y - 1, z]]:
+ faceCount += 1
+ if y == sy - 1 or not self.isSolid[blocks[x, y + 1, z]]:
+ faceCount += 1
+ m = mesh.mesh()
+ m._prepareVertexCount(faceCount * 2 * 3)
+ for x in xrange(0, sx):
+ for y in xrange(0, sy):
+ for z in xrange(minZ, maxZ + 1):
+ if self.isSolid[blocks[x, y, z]]:
+ if z == maxZ or not self.isSolid[blocks[x, y, z + 1]]:
+ m.addVertex(x, y, z+1)
+ m.addVertex(x+1, y, z+1)
+ m.addVertex(x, y+1, z+1)
+
+ m.addVertex(x+1, y+1, z+1)
+ m.addVertex(x, y+1, z+1)
+ m.addVertex(x+1, y, z+1)
+
+ if z == minZ or not self.isSolid[blocks[x, y, z - 1]]:
+ m.addVertex(x, y, z)
+ m.addVertex(x, y+1, z)
+ m.addVertex(x+1, y, z)
+
+ m.addVertex(x+1, y+1, z)
+ m.addVertex(x+1, y, z)
+ m.addVertex(x, y+1, z)
+
+ if x == 0 or not self.isSolid[blocks[x - 1, y, z]]:
+ m.addVertex(x, y, z)
+ m.addVertex(x, y, z+1)
+ m.addVertex(x, y+1, z)
+
+ m.addVertex(x, y+1, z+1)
+ m.addVertex(x, y+1, z)
+ m.addVertex(x, y, z+1)
+
+ if x == sx - 1 or not self.isSolid[blocks[x + 1, y, z]]:
+ m.addVertex(x+1, y, z)
+ m.addVertex(x+1, y+1, z)
+ m.addVertex(x+1, y, z+1)
+
+ m.addVertex(x+1, y+1, z+1)
+ m.addVertex(x+1, y, z+1)
+ m.addVertex(x+1, y+1, z)
+
+ if y == 0 or not self.isSolid[blocks[x, y - 1, z]]:
+ m.addVertex(x, y, z)
+ m.addVertex(x+1, y, z)
+ m.addVertex(x, y, z+1)
+
+ m.addVertex(x+1, y, z+1)
+ m.addVertex(x, y, z+1)
+ m.addVertex(x+1, y, z)
+
+ if y == sy - 1 or not self.isSolid[blocks[x, y + 1, z]]:
+ m.addVertex(x, y+1, z)
+ m.addVertex(x, y+1, z+1)
+ m.addVertex(x+1, y+1, z)
+
+ m.addVertex(x+1, y+1, z+1)
+ m.addVertex(x+1, y+1, z)
+ m.addVertex(x, y+1, z+1)
+
+ stlFilename = os.path.join(os.path.dirname(self.level.filename), 'export.stl')
+ stl.saveAsSTL(m, stlFilename)
+ self.GetParent()._loadModels([stlFilename])
--- /dev/null
+Copyright (c) 2010 David Rio Vierra
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file
--- /dev/null
+Python library for reading Minecraft levels.
+
+Can read Alpha levels, Indev levels, and Creative levels (with help).
+
+Includes a command-line client (mce.py)
+
+Requires numpy and PyYaml.
+
+Read mclevel.py to get started.
+
+See LICENSE.txt for licensing terms.
+
+
+
+mce.py is a command-line editor for SMP maps. It can be used interactively from a terminal, accept editing commands on standard input, or run a single editing command from the shell.
+
+Sample usage:
+
+$ python mce.py
+
+ Usage:
+
+ Block commands:
+ clone <sourcePoint> <sourceSize> <destPoint>
+ fill <blockType> [ <point> <size> ]
+ replace <blockType> [with] <newBlockType> [ <point> <size> ]
+
+ export <filename> <sourcePoint> <sourceSize>
+ import <filename> <destPoint>
+
+ analyze
+
+ Player commands:
+ player [ <player> [ <point> ] ]
+ spawn [ <point> ]
+
+ Entity commands:
+ removeEntities [ <EntityID> ]
+
+ Chunk commands:
+ createChunks <point> <size>
+ deleteChunks <point> <size>
+ prune <point> <size>
+ relight [ <point> <size> ]
+
+ World commands:
+ degrief
+
+ Editor commands:
+ save
+ reload
+ load <filename> | <world number>
+ quit
+
+ Informational:
+ blocks [ <block name> | <block ID> ]
+ help [ <command> ]
+
+ Points and sizes are space-separated triplets of numbers ordered X Y Z.
+ X is position north-south, increasing southward.
+ Y is position up-down, increasing upward.
+ Z is position east-west, increasing westward.
+
+ A player's name can be used as a point - it will use the
+ position of the player's head. Use the keyword 'delta' after
+ the name to specify a point near the player.
+
+ Example:
+ codewarrior delta 0 5 0
+
+ This refers to a point 5 blocks above codewarrior's head.
+
+
+Please enter world number or path to world folder: 4
+INFO:Identifying C:\Users\Rio\AppData\Roaming\.minecraft\saves\World4\level.dat
+INFO:Detected Infdev level.dat
+INFO:Saved 0 chunks
+INFO:Scanning for chunks...
+INFO:Found 6288 chunks.
+World4> fill 20 Player delta -10 0 -10 20 20 20
+
+Filling with Glass
+Filled 8000 blocks.
+World4> player Player
+
+Player Player: [87.658381289724858, 54.620000004768372, 358.64257283335115]
+World4> player Player Player delta 0 25 0
+
+Moved player Player to (87.658381289724858, 79.620000004768372, 358.642572833351
+15)
+World4> save
+
+INFO:Asked to light 6 chunks
+INFO:Batch 1/1
+INFO:Lighting 20 chunks
+INFO:Dispersing light...
+INFO:BlockLight Pass 0: 20 chunks
+INFO:BlockLight Pass 1: 2 chunks
+INFO:BlockLight Pass 2: 0 chunks
+INFO:BlockLight Pass 3: 0 chunks
+INFO:BlockLight Pass 4: 0 chunks
+INFO:BlockLight Pass 5: 0 chunks
+INFO:BlockLight Pass 6: 0 chunks
+INFO:BlockLight Pass 7: 0 chunks
+INFO:BlockLight Pass 8: 0 chunks
+INFO:BlockLight Pass 9: 0 chunks
+INFO:BlockLight Pass 10: 0 chunks
+INFO:BlockLight Pass 11: 0 chunks
+INFO:BlockLight Pass 12: 0 chunks
+INFO:BlockLight Pass 13: 0 chunks
+INFO:SkyLight Pass 0: 20 chunks
+INFO:SkyLight Pass 1: 22 chunks
+INFO:SkyLight Pass 2: 17 chunks
+INFO:SkyLight Pass 3: 9 chunks
+INFO:SkyLight Pass 4: 7 chunks
+INFO:SkyLight Pass 5: 2 chunks
+INFO:SkyLight Pass 6: 0 chunks
+INFO:SkyLight Pass 7: 0 chunks
+INFO:SkyLight Pass 8: 0 chunks
+INFO:SkyLight Pass 9: 0 chunks
+INFO:SkyLight Pass 10: 0 chunks
+INFO:SkyLight Pass 11: 0 chunks
+INFO:SkyLight Pass 12: 0 chunks
+INFO:SkyLight Pass 13: 0 chunks
+INFO:Completed in 0:00:02.024000, 0:00:00.337333 per chunk
+INFO:Saved 20 chunks
+World4>
--- /dev/null
+from box import BoundingBox, FloatBox
+from entity import Entity, TileEntity
+from faces import faceDirections, FaceXDecreasing, FaceXIncreasing, FaceYDecreasing, FaceYIncreasing, FaceZDecreasing, FaceZIncreasing, MaxDirections
+from indev import MCIndevLevel
+from infiniteworld import ChunkedLevelMixin, AnvilChunk, MCAlphaDimension, MCInfdevOldLevel, ZeroChunk
+import items
+from java import MCJavaLevel
+from level import ChunkBase, computeChunkHeightMap, EntityLevel, FakeChunk, LightedChunk, MCLevel
+from materials import alphaMaterials, classicMaterials, indevMaterials, MCMaterials, namedMaterials, pocketMaterials
+from mclevelbase import ChunkNotPresent, saveFileDir, minecraftDir, PlayerNotFound
+from mclevel import fromFile, loadWorld, loadWorldNumber
+from nbt import load, gunzip, TAG_Byte, TAG_Byte_Array, TAG_Compound, TAG_Double, TAG_Float, TAG_Int, TAG_Int_Array, TAG_List, TAG_Long, TAG_Short, TAG_String
+import pocket
+from schematic import INVEditChest, MCSchematic, ZipSchematic
--- /dev/null
+biome_types = {
+ -1: "Will be computed",
+ 0: "Ocean",
+ 1: "Plains",
+ 2: "Desert",
+ 3: "Extreme Hills",
+ 4: "Forest",
+ 5: "Taiga",
+ 6: "Swampland",
+ 7: "River",
+ 8: "Hell",
+ 9: "Sky",
+ 10: "FrozenOcean",
+ 11: "FrozenRiver",
+ 12: "Ice Plains",
+ 13: "Ice Mountains",
+ 14: "MushroomIsland",
+ 15: "MushroomIslandShore",
+ 16: "Beach",
+ 17: "DesertHills",
+ 18: "ForestHills",
+ 19: "TaigaHills",
+ 20: "Extreme Hills Edge",
+ 21: "Jungle",
+ 22: "JungleHills",
+}
--- /dev/null
+from datetime import datetime
+import logging
+log = logging.getLogger(__name__)
+
+import numpy
+from box import BoundingBox, Vector
+from mclevelbase import exhaust
+import materials
+from entity import Entity, TileEntity
+
+
+def convertBlocks(destLevel, sourceLevel, blocks, blockData):
+ return materials.convertBlocks(destLevel.materials, sourceLevel.materials, blocks, blockData)
+
+def sourceMaskFunc(blocksToCopy):
+ if blocksToCopy is not None:
+ typemask = numpy.zeros(256, dtype='bool')
+ typemask[blocksToCopy] = 1
+
+ def maskedSourceMask(sourceBlocks):
+ return typemask[sourceBlocks]
+
+ return maskedSourceMask
+
+ def unmaskedSourceMask(_sourceBlocks):
+ return slice(None, None)
+
+ return unmaskedSourceMask
+
+
+def adjustCopyParameters(destLevel, sourceLevel, sourceBox, destinationPoint):
+ # if the destination box is outside the level, it and the source corners are moved inward to fit.
+ (dx, dy, dz) = map(int, destinationPoint)
+
+ log.debug(u"Asked to copy {} blocks \n\tfrom {} in {}\n\tto {} in {}" .format(
+ sourceBox.volume, sourceBox, sourceLevel, destinationPoint, destLevel))
+ if destLevel.Width == 0:
+ return sourceBox, destinationPoint
+
+ destBox = BoundingBox(destinationPoint, sourceBox.size)
+ actualDestBox = destBox.intersect(destLevel.bounds)
+
+ actualSourceBox = BoundingBox(sourceBox.origin + actualDestBox.origin - destBox.origin, destBox.size)
+ actualDestPoint = actualDestBox.origin
+
+ return actualSourceBox, actualDestPoint
+
+
+
+def copyBlocksFromIter(destLevel, sourceLevel, sourceBox, destinationPoint, blocksToCopy=None, entities=True, create=False):
+ """ copy blocks between two infinite levels by looping through the
+ destination's chunks. make a sub-box of the source level for each chunk
+ and copy block and entities in the sub box to the dest chunk."""
+
+ (lx, ly, lz) = sourceBox.size
+
+ sourceBox, destinationPoint = adjustCopyParameters(destLevel, sourceLevel, sourceBox, destinationPoint)
+ # needs work xxx
+ log.info(u"Copying {0} blocks from {1} to {2}" .format(ly * lz * lx, sourceBox, destinationPoint))
+ startTime = datetime.now()
+
+ destBox = BoundingBox(destinationPoint, sourceBox.size)
+ chunkCount = destBox.chunkCount
+ i = 0
+ e = 0
+ t = 0
+
+ sourceMask = sourceMaskFunc(blocksToCopy)
+
+ copyOffset = [d - s for s, d in zip(sourceBox.origin, destinationPoint)]
+
+ # Visit each chunk in the destination area.
+ # Get the region of the source area corresponding to that chunk
+ # Visit each chunk of the region of the source area
+ # Get the slices of the destination chunk
+ # Get the slices of the source chunk
+ # Copy blocks and data
+
+ for destCpos in destBox.chunkPositions:
+ cx, cz = destCpos
+
+ destChunkBox = BoundingBox((cx << 4, 0, cz << 4), (16, destLevel.Height, 16)).intersect(destBox)
+ destChunkBoxInSourceLevel = BoundingBox([d - o for o, d in zip(copyOffset, destChunkBox.origin)], destChunkBox.size)
+
+ if not destLevel.containsChunk(*destCpos):
+ if create and any(sourceLevel.containsChunk(*c) for c in destChunkBoxInSourceLevel.chunkPositions):
+ # Only create chunks in the destination level if the source level has chunks covering them.
+ destLevel.createChunk(*destCpos)
+ else:
+ continue
+
+ destChunk = destLevel.getChunk(*destCpos)
+
+
+ i += 1
+ yield (i, chunkCount)
+ if i % 100 == 0:
+ log.info("Chunk {0}...".format(i))
+
+ for srcCpos in destChunkBoxInSourceLevel.chunkPositions:
+ if not sourceLevel.containsChunk(*srcCpos):
+ continue
+
+ sourceChunk = sourceLevel.getChunk(*srcCpos)
+
+ sourceChunkBox, sourceSlices = sourceChunk.getChunkSlicesForBox(destChunkBoxInSourceLevel)
+ sourceChunkBoxInDestLevel = BoundingBox([d + o for o, d in zip(copyOffset, sourceChunkBox.origin)], sourceChunkBox.size)
+
+ _, destSlices = destChunk.getChunkSlicesForBox(sourceChunkBoxInDestLevel)
+
+ sourceBlocks = sourceChunk.Blocks[sourceSlices]
+ sourceData = sourceChunk.Data[sourceSlices]
+
+ mask = sourceMask(sourceBlocks)
+ convertedSourceBlocks, convertedSourceData = convertBlocks(destLevel, sourceLevel, sourceBlocks, sourceData)
+
+ destChunk.Blocks[destSlices][mask] = convertedSourceBlocks[mask]
+ if convertedSourceData is not None:
+ destChunk.Data[destSlices][mask] = convertedSourceData[mask]
+
+ if entities:
+ ents = sourceChunk.getEntitiesInBox(destChunkBoxInSourceLevel)
+ e += len(ents)
+ for entityTag in ents:
+ eTag = Entity.copyWithOffset(entityTag, copyOffset)
+ destLevel.addEntity(eTag)
+
+ tileEntities = sourceChunk.getTileEntitiesInBox(destChunkBoxInSourceLevel)
+ t += len(tileEntities)
+ for tileEntityTag in tileEntities:
+ eTag = TileEntity.copyWithOffset(tileEntityTag, copyOffset)
+ destLevel.addTileEntity(eTag)
+
+ destChunk.chunkChanged()
+
+ log.info("Duration: {0}".format(datetime.now() - startTime))
+ log.info("Copied {0} entities and {1} tile entities".format(e, t))
+
+def copyBlocksFrom(destLevel, sourceLevel, sourceBox, destinationPoint, blocksToCopy=None, entities=True, create=False):
+ return exhaust(copyBlocksFromIter(destLevel, sourceLevel, sourceBox, destinationPoint, blocksToCopy, entities, create))
+
+
+
+
+
--- /dev/null
+import logging
+log = logging.getLogger(__name__)
+
+import numpy
+
+from mclevelbase import exhaust
+import blockrotation
+from entity import TileEntity
+
+def blockReplaceTable(blocksToReplace):
+ blocktable = numpy.zeros((256, 16), dtype='bool')
+ for b in blocksToReplace:
+ if b.hasVariants:
+ blocktable[b.ID, b.blockData] = True
+ else:
+ blocktable[b.ID] = True
+
+ return blocktable
+
+def fillBlocks(level, box, blockInfo, blocksToReplace=()):
+ return exhaust(level.fillBlocksIter(box, blockInfo, blocksToReplace))
+
+def fillBlocksIter(level, box, blockInfo, blocksToReplace=()):
+ if box is None:
+ chunkIterator = level.getAllChunkSlices()
+ box = level.bounds
+ else:
+ chunkIterator = level.getChunkSlices(box)
+
+ # shouldRetainData = (not blockInfo.hasVariants and not any([b.hasVariants for b in blocksToReplace]))
+ # if shouldRetainData:
+ # log.info( "Preserving data bytes" )
+ shouldRetainData = False # xxx old behavior overwrote blockdata with 0 when e.g. replacing water with lava
+
+ log.info("Replacing {0} with {1}".format(blocksToReplace, blockInfo))
+
+ changesLighting = True
+ blocktable = None
+ if len(blocksToReplace):
+ blocktable = blockReplaceTable(blocksToReplace)
+ shouldRetainData = all([blockrotation.SameRotationType(blockInfo, b) for b in blocksToReplace])
+
+ newAbsorption = level.materials.lightAbsorption[blockInfo.ID]
+ oldAbsorptions = [level.materials.lightAbsorption[b.ID] for b in blocksToReplace]
+ changesLighting = False
+ for a in oldAbsorptions:
+ if a != newAbsorption:
+ changesLighting = True
+
+ newEmission = level.materials.lightEmission[blockInfo.ID]
+ oldEmissions = [level.materials.lightEmission[b.ID] for b in blocksToReplace]
+ for a in oldEmissions:
+ if a != newEmission:
+ changesLighting = True
+
+ i = 0
+ skipped = 0
+ replaced = 0
+
+ for (chunk, slices, point) in chunkIterator:
+ i += 1
+ if i % 100 == 0:
+ log.info(u"Chunk {0}...".format(i))
+ yield i, box.chunkCount
+
+ blocks = chunk.Blocks[slices]
+ data = chunk.Data[slices]
+ mask = slice(None)
+
+ needsLighting = changesLighting
+
+ if blocktable is not None:
+ mask = blocktable[blocks, data]
+
+ blockCount = mask.sum()
+ replaced += blockCount
+
+ # don't waste time relighting and copying if the mask is empty
+ if blockCount:
+ blocks[:][mask] = blockInfo.ID
+ if not shouldRetainData:
+ data[mask] = blockInfo.blockData
+ else:
+ skipped += 1
+ needsLighting = False
+
+ def include(tileEntity):
+ p = TileEntity.pos(tileEntity)
+ x, y, z = map(lambda a, b, c: (a - b) - c, p, point, box.origin)
+ return not ((p in box) and mask[x, z, y])
+
+ chunk.TileEntities[:] = filter(include, chunk.TileEntities)
+
+ else:
+ blocks[:] = blockInfo.ID
+ if not shouldRetainData:
+ data[:] = blockInfo.blockData
+ chunk.removeTileEntitiesInBox(box)
+
+ chunk.chunkChanged(needsLighting)
+
+ if len(blocksToReplace):
+ log.info(u"Replace: Skipped {0} chunks, replaced {1} blocks".format(skipped, replaced))
--- /dev/null
+from materials import alphaMaterials
+from numpy import arange, zeros
+
+
+def genericVerticalFlip(cls):
+ rotation = arange(16, dtype='uint8')
+ if hasattr(cls, "Up") and hasattr(cls, "Down"):
+ rotation[cls.Up] = cls.Down
+ rotation[cls.Down] = cls.Up
+
+ if hasattr(cls, "TopNorth") and hasattr(cls, "TopWest") and hasattr(cls, "TopSouth") and hasattr(cls, "TopEast"):
+ rotation[cls.North] = cls.TopNorth
+ rotation[cls.West] = cls.TopWest
+ rotation[cls.South] = cls.TopSouth
+ rotation[cls.East] = cls.TopEast
+ rotation[cls.TopNorth] = cls.North
+ rotation[cls.TopWest] = cls.West
+ rotation[cls.TopSouth] = cls.South
+ rotation[cls.TopEast] = cls.East
+
+ return rotation
+
+
+def genericRotation(cls):
+ rotation = arange(16, dtype='uint8')
+ rotation[cls.North] = cls.West
+ rotation[cls.West] = cls.South
+ rotation[cls.South] = cls.East
+ rotation[cls.East] = cls.North
+ if hasattr(cls, "TopNorth") and hasattr(cls, "TopWest") and hasattr(cls, "TopSouth") and hasattr(cls, "TopEast"):
+ rotation[cls.TopNorth] = cls.TopWest
+ rotation[cls.TopWest] = cls.TopSouth
+ rotation[cls.TopSouth] = cls.TopEast
+ rotation[cls.TopEast] = cls.TopNorth
+
+ return rotation
+
+
+def genericEastWestFlip(cls):
+ rotation = arange(16, dtype='uint8')
+ rotation[cls.West] = cls.East
+ rotation[cls.East] = cls.West
+ if hasattr(cls, "TopWest") and hasattr(cls, "TopEast"):
+ rotation[cls.TopWest] = cls.TopEast
+ rotation[cls.TopEast] = cls.TopWest
+
+ return rotation
+
+
+def genericNorthSouthFlip(cls):
+ rotation = arange(16, dtype='uint8')
+ rotation[cls.South] = cls.North
+ rotation[cls.North] = cls.South
+ if hasattr(cls, "TopNorth") and hasattr(cls, "TopSouth"):
+ rotation[cls.TopSouth] = cls.TopNorth
+ rotation[cls.TopNorth] = cls.TopSouth
+
+ return rotation
+
+rotationClasses = []
+
+
+def genericFlipRotation(cls):
+ cls.rotateLeft = genericRotation(cls)
+
+ cls.flipVertical = genericVerticalFlip(cls)
+ cls.flipEastWest = genericEastWestFlip(cls)
+ cls.flipNorthSouth = genericNorthSouthFlip(cls)
+ rotationClasses.append(cls)
+
+
+class Torch:
+ blocktypes = [
+ alphaMaterials.Torch.ID,
+ alphaMaterials.RedstoneTorchOn.ID,
+ alphaMaterials.RedstoneTorchOff.ID,
+ ]
+
+ South = 1
+ North = 2
+ West = 3
+ East = 4
+
+genericFlipRotation(Torch)
+
+
+class Ladder:
+ blocktypes = [alphaMaterials.Ladder.ID]
+
+ East = 2
+ West = 3
+ North = 4
+ South = 5
+genericFlipRotation(Ladder)
+
+
+class Stair:
+ blocktypes = [b.ID for b in alphaMaterials.AllStairs]
+
+ South = 0
+ North = 1
+ West = 2
+ East = 3
+ TopSouth = 4
+ TopNorth = 5
+ TopWest = 6
+ TopEast = 7
+genericFlipRotation(Stair)
+
+
+class HalfSlab:
+ blocktypes = [alphaMaterials.StoneSlab.ID]
+
+ StoneSlab = 0
+ SandstoneSlab = 1
+ WoodenSlab = 2
+ CobblestoneSlab = 3
+ BrickSlab = 4
+ StoneBrickSlab = 5
+ TopStoneSlab = 8
+ TopSandstoneSlab = 9
+ TopWoodenSlab = 10
+ TopCobblestoneSlab = 11
+ TopBrickSlab = 12
+ TopStoneBrickSlab = 13
+
+HalfSlab.flipVertical = arange(16, dtype='uint8')
+HalfSlab.flipVertical[HalfSlab.StoneSlab] = HalfSlab.TopStoneSlab
+HalfSlab.flipVertical[HalfSlab.SandstoneSlab] = HalfSlab.TopSandstoneSlab
+HalfSlab.flipVertical[HalfSlab.WoodenSlab] = HalfSlab.TopWoodenSlab
+HalfSlab.flipVertical[HalfSlab.CobblestoneSlab] = HalfSlab.TopCobblestoneSlab
+HalfSlab.flipVertical[HalfSlab.BrickSlab] = HalfSlab.TopBrickSlab
+HalfSlab.flipVertical[HalfSlab.StoneBrickSlab] = HalfSlab.TopStoneBrickSlab
+HalfSlab.flipVertical[HalfSlab.TopStoneSlab] = HalfSlab.StoneSlab
+HalfSlab.flipVertical[HalfSlab.TopSandstoneSlab] = HalfSlab.SandstoneSlab
+HalfSlab.flipVertical[HalfSlab.TopWoodenSlab] = HalfSlab.WoodenSlab
+HalfSlab.flipVertical[HalfSlab.TopCobblestoneSlab] = HalfSlab.CobblestoneSlab
+HalfSlab.flipVertical[HalfSlab.TopBrickSlab] = HalfSlab.BrickSlab
+HalfSlab.flipVertical[HalfSlab.TopStoneBrickSlab] = HalfSlab.StoneBrickSlab
+rotationClasses.append(HalfSlab)
+
+
+class WallSign:
+ blocktypes = [alphaMaterials.WallSign.ID]
+
+ East = 2
+ West = 3
+ North = 4
+ South = 5
+genericFlipRotation(WallSign)
+
+
+class FurnaceDispenserChest:
+ blocktypes = [
+ alphaMaterials.Furnace.ID,
+ alphaMaterials.LitFurnace.ID,
+ alphaMaterials.Dispenser.ID,
+ alphaMaterials.Chest.ID,
+ ]
+ East = 2
+ West = 3
+ North = 4
+ South = 5
+genericFlipRotation(FurnaceDispenserChest)
+
+
+class Pumpkin:
+ blocktypes = [
+ alphaMaterials.Pumpkin.ID,
+ alphaMaterials.JackOLantern.ID,
+ ]
+
+ East = 0
+ South = 1
+ West = 2
+ North = 3
+genericFlipRotation(Pumpkin)
+
+
+class Rail:
+ blocktypes = [alphaMaterials.Rail.ID]
+
+ EastWest = 0
+ NorthSouth = 1
+ South = 2
+ North = 3
+ East = 4
+ West = 5
+
+ Northeast = 6
+ Southeast = 7
+ Southwest = 8
+ Northwest = 9
+
+
+def generic8wayRotation(cls):
+
+ cls.rotateLeft = genericRotation(cls)
+ cls.rotateLeft[cls.Northeast] = cls.Northwest
+ cls.rotateLeft[cls.Southeast] = cls.Northeast
+ cls.rotateLeft[cls.Southwest] = cls.Southeast
+ cls.rotateLeft[cls.Northwest] = cls.Southwest
+
+ cls.flipEastWest = genericEastWestFlip(cls)
+ cls.flipEastWest[cls.Northeast] = cls.Northwest
+ cls.flipEastWest[cls.Northwest] = cls.Northeast
+ cls.flipEastWest[cls.Southwest] = cls.Southeast
+ cls.flipEastWest[cls.Southeast] = cls.Southwest
+
+ cls.flipNorthSouth = genericNorthSouthFlip(cls)
+ cls.flipNorthSouth[cls.Northeast] = cls.Southeast
+ cls.flipNorthSouth[cls.Southeast] = cls.Northeast
+ cls.flipNorthSouth[cls.Southwest] = cls.Northwest
+ cls.flipNorthSouth[cls.Northwest] = cls.Southwest
+ rotationClasses.append(cls)
+
+generic8wayRotation(Rail)
+Rail.rotateLeft[Rail.NorthSouth] = Rail.EastWest
+Rail.rotateLeft[Rail.EastWest] = Rail.NorthSouth
+
+
+def applyBit(apply):
+ def _applyBit(class_or_array):
+ if hasattr(class_or_array, "rotateLeft"):
+ for a in (class_or_array.flipEastWest,
+ class_or_array.flipNorthSouth,
+ class_or_array.rotateLeft):
+ apply(a)
+ else:
+ array = class_or_array
+ apply(array)
+
+ return _applyBit
+
+
+@applyBit
+def applyBit8(array):
+ array[8:16] = array[0:8] | 0x8
+
+
+@applyBit
+def applyBit4(array):
+ array[4:8] = array[0:4] | 0x4
+ array[12:16] = array[8:12] | 0x4
+
+
+@applyBit
+def applyBits48(array):
+ array[4:8] = array[0:4] | 0x4
+ array[8:16] = array[0:8] | 0x8
+
+applyThrownBit = applyBit8
+
+
+class PoweredDetectorRail(Rail):
+ blocktypes = [alphaMaterials.PoweredRail.ID, alphaMaterials.DetectorRail.ID]
+PoweredDetectorRail.rotateLeft = genericRotation(PoweredDetectorRail)
+
+PoweredDetectorRail.rotateLeft[PoweredDetectorRail.NorthSouth] = PoweredDetectorRail.EastWest
+PoweredDetectorRail.rotateLeft[PoweredDetectorRail.EastWest] = PoweredDetectorRail.NorthSouth
+
+PoweredDetectorRail.flipEastWest = genericEastWestFlip(PoweredDetectorRail)
+PoweredDetectorRail.flipNorthSouth = genericNorthSouthFlip(PoweredDetectorRail)
+applyThrownBit(PoweredDetectorRail)
+rotationClasses.append(PoweredDetectorRail)
+
+
+class Lever:
+ blocktypes = [alphaMaterials.Lever.ID]
+ ThrownBit = 0x8
+ South = 1
+ North = 2
+ West = 3
+ East = 4
+ EastWest = 5
+ NorthSouth = 6
+Lever.rotateLeft = genericRotation(Lever)
+Lever.rotateLeft[Lever.NorthSouth] = Lever.EastWest
+Lever.rotateLeft[Lever.EastWest] = Lever.NorthSouth
+Lever.flipEastWest = genericEastWestFlip(Lever)
+Lever.flipNorthSouth = genericNorthSouthFlip(Lever)
+applyThrownBit(Lever)
+rotationClasses.append(Lever)
+
+
+class Button:
+ blocktypes = [alphaMaterials.Button.ID]
+ PressedBit = 0x8
+ South = 1
+ North = 2
+ West = 3
+ East = 4
+Button.rotateLeft = genericRotation(Button)
+Button.flipEastWest = genericEastWestFlip(Button)
+Button.flipNorthSouth = genericNorthSouthFlip(Button)
+applyThrownBit(Button)
+rotationClasses.append(Button)
+
+
+class SignPost:
+ blocktypes = [alphaMaterials.Sign.ID]
+ #west is 0, increasing clockwise
+
+ rotateLeft = arange(16, dtype='uint8')
+ rotateLeft -= 4
+ rotateLeft &= 0xf
+
+ flipEastWest = arange(16, dtype='uint8')
+ flipNorthSouth = arange(16, dtype='uint8')
+ pass
+
+rotationClasses.append(SignPost)
+
+
+class Bed:
+ blocktypes = [alphaMaterials.Bed.ID]
+ West = 0
+ North = 1
+ East = 2
+ South = 3
+
+genericFlipRotation(Bed)
+applyBit8(Bed)
+applyBit4(Bed)
+
+
+class Door:
+ blocktypes = [
+ alphaMaterials.IronDoor.ID,
+ alphaMaterials.WoodenDoor.ID,
+ ]
+ TopHalfBit = 0x8
+ SwungCCWBit = 0x4
+
+ Northeast = 0
+ Southeast = 1
+ Southwest = 2
+ Northwest = 3
+
+ rotateLeft = arange(16, dtype='uint8')
+
+Door.rotateLeft[Door.Northeast] = Door.Northwest
+Door.rotateLeft[Door.Southeast] = Door.Northeast
+Door.rotateLeft[Door.Southwest] = Door.Southeast
+Door.rotateLeft[Door.Northwest] = Door.Southwest
+
+applyBit4(Door.rotateLeft)
+
+#when flipping horizontally, swing the doors so they at least look the same
+
+Door.flipEastWest = arange(16, dtype='uint8')
+Door.flipEastWest[Door.Northeast] = Door.Northwest
+Door.flipEastWest[Door.Northwest] = Door.Northeast
+Door.flipEastWest[Door.Southwest] = Door.Southeast
+Door.flipEastWest[Door.Southeast] = Door.Southwest
+Door.flipEastWest[4:8] = Door.flipEastWest[0:4]
+Door.flipEastWest[0:4] = Door.flipEastWest[4:8] | 0x4
+Door.flipEastWest[8:16] = Door.flipEastWest[0:8] | 0x8
+
+Door.flipNorthSouth = arange(16, dtype='uint8')
+Door.flipNorthSouth[Door.Northeast] = Door.Southeast
+Door.flipNorthSouth[Door.Northwest] = Door.Southwest
+Door.flipNorthSouth[Door.Southwest] = Door.Northwest
+Door.flipNorthSouth[Door.Southeast] = Door.Northeast
+Door.flipNorthSouth[4:8] = Door.flipNorthSouth[0:4]
+Door.flipNorthSouth[0:4] = Door.flipNorthSouth[4:8] | 0x4
+Door.flipNorthSouth[8:16] = Door.flipNorthSouth[0:8] | 0x8
+
+rotationClasses.append(Door)
+
+
+class RedstoneRepeater:
+ blocktypes = [
+ alphaMaterials.RedstoneRepeaterOff.ID,
+ alphaMaterials.RedstoneRepeaterOn.ID,
+
+ ]
+
+ East = 0
+ South = 1
+ West = 2
+ North = 3
+
+genericFlipRotation(RedstoneRepeater)
+
+#high bits of the repeater indicate repeater delay, and should be preserved
+applyBits48(RedstoneRepeater)
+
+
+class Trapdoor:
+ blocktypes = [alphaMaterials.Trapdoor.ID]
+
+ West = 0
+ East = 1
+ South = 2
+ North = 3
+
+genericFlipRotation(Trapdoor)
+applyOpenedBit = applyBit4
+applyOpenedBit(Trapdoor)
+
+
+class PistonBody:
+ blocktypes = [alphaMaterials.StickyPiston.ID, alphaMaterials.Piston.ID]
+
+ Down = 0
+ Up = 1
+ East = 2
+ West = 3
+ North = 4
+ South = 5
+
+genericFlipRotation(PistonBody)
+applyPistonBit = applyBit8
+applyPistonBit(PistonBody)
+
+
+class PistonHead(PistonBody):
+ blocktypes = [alphaMaterials.PistonHead.ID]
+rotationClasses.append(PistonHead)
+
+
+class Vines:
+ blocktypes = [alphaMaterials.Vines.ID]
+
+ WestBit = 1
+ NorthBit = 2
+ EastBit = 4
+ SouthBit = 8
+
+ rotateLeft = arange(16, dtype='uint8')
+ flipEastWest = arange(16, dtype='uint8')
+ flipNorthSouth = arange(16, dtype='uint8')
+
+
+#Mushroom types:
+#Value Description Textures
+#0 Fleshy piece Pores on all sides
+#1 Corner piece Cap texture on top, directions 1 (cloud direction) and 2 (sunrise)
+#2 Side piece Cap texture on top and direction 2 (sunrise)
+#3 Corner piece Cap texture on top, directions 2 (sunrise) and 3 (cloud origin)
+#4 Side piece Cap texture on top and direction 1 (cloud direction)
+#5 Top piece Cap texture on top
+#6 Side piece Cap texture on top and direction 3 (cloud origin)
+#7 Corner piece Cap texture on top, directions 0 (sunset) and 1 (cloud direction)
+#8 Side piece Cap texture on top and direction 0 (sunset)
+#9 Corner piece Cap texture on top, directions 3 (cloud origin) and 0 (sunset)
+#10 Stem piece Stem texture on all four sides, pores on top and bottom
+
+
+class HugeMushroom:
+ blocktypes = [alphaMaterials.HugeRedMushroom.ID, alphaMaterials.HugeBrownMushroom.ID]
+ Northeast = 1
+ East = 2
+ Southeast = 3
+ South = 6
+ Southwest = 9
+ West = 8
+ Northwest = 7
+ North = 4
+
+generic8wayRotation(HugeMushroom)
+
+#Hmm... Since each bit is a direction, we can rotate by shifting!
+Vines.rotateLeft = 0xf & ((Vines.rotateLeft >> 1) | (Vines.rotateLeft << 3))
+# Wherever each bit is set, clear it and set the opposite bit
+EastWestBits = (Vines.EastBit | Vines.WestBit)
+Vines.flipEastWest[(Vines.flipEastWest & EastWestBits) > 0] ^= EastWestBits
+
+NorthSouthBits = (Vines.NorthBit | Vines.SouthBit)
+Vines.flipNorthSouth[(Vines.flipNorthSouth & NorthSouthBits) > 0] ^= NorthSouthBits
+
+rotationClasses.append(Vines)
+
+
+def masterRotationTable(attrname):
+ # compute a 256x16 table mapping each possible blocktype/data combination to
+ # the resulting data when the block is rotated
+ table = zeros((256, 16), dtype='uint8')
+ table[:] = arange(16, dtype='uint8')
+ for cls in rotationClasses:
+ if hasattr(cls, attrname):
+ blocktable = getattr(cls, attrname)
+ for blocktype in cls.blocktypes:
+ table[blocktype] = blocktable
+
+ return table
+
+
+def rotationTypeTable():
+ table = {}
+ for cls in rotationClasses:
+ for b in cls.blocktypes:
+ table[b] = cls
+
+ return table
+
+
+class BlockRotation:
+ rotateLeft = masterRotationTable("rotateLeft")
+ flipEastWest = masterRotationTable("flipEastWest")
+ flipNorthSouth = masterRotationTable("flipNorthSouth")
+ flipVertical = masterRotationTable("flipVertical")
+ typeTable = rotationTypeTable()
+
+
+def SameRotationType(blocktype1, blocktype2):
+ #use different default values for typeTable.get() to make it return false when neither blocktype is present
+ return BlockRotation.typeTable.get(blocktype1.ID) == BlockRotation.typeTable.get(blocktype2.ID, BlockRotation)
+
+
+def FlipVertical(blocks, data):
+ data[:] = BlockRotation.flipVertical[blocks, data]
+
+
+def FlipNorthSouth(blocks, data):
+ data[:] = BlockRotation.flipNorthSouth[blocks, data]
+
+
+def FlipEastWest(blocks, data):
+ data[:] = BlockRotation.flipEastWest[blocks, data]
+
+
+def RotateLeft(blocks, data):
+ data[:] = BlockRotation.rotateLeft[blocks, data]
--- /dev/null
+from collections import namedtuple
+import itertools
+
+_Vector = namedtuple("_Vector", ("x", "y", "z"))
+
+class Vector(_Vector):
+
+ __slots__ = ()
+
+ def __add__(self, other):
+ return Vector(self[0] + other[0], self[1] + other[1], self[2] + other[2])
+ def __sub__(self, other):
+ return Vector(self[0] - other[0], self[1] - other[1], self[2] - other[2])
+ def __mul__(self, other):
+ return Vector(self[0] * other[0], self[1] * other[1], self[2] * other[2])
+
+class BoundingBox (object):
+ type = int
+
+ def __init__(self, origin=(0, 0, 0), size=(0, 0, 0)):
+ if isinstance(origin, BoundingBox):
+ self._origin = origin._origin
+ self._size = origin._size
+ else:
+ self._origin, self._size = Vector(*(self.type(a) for a in origin)), Vector(*(self.type(a) for a in size))
+
+ def __repr__(self):
+ return "BoundingBox({0}, {1})".format(self.origin, self.size)
+
+ @property
+ def origin(self):
+ "The smallest position in the box"
+ return self._origin
+
+ @property
+ def size(self):
+ "The size of the box"
+ return self._size
+
+ @property
+ def width(self):
+ "The dimension along the X axis"
+ return self._size.x
+
+ @property
+ def height(self):
+ "The dimension along the Y axis"
+ return self._size.y
+
+ @property
+ def length(self):
+ "The dimension along the Z axis"
+ return self._size.z
+
+ @property
+ def minx(self):
+ return self.origin.x
+
+ @property
+ def miny(self):
+ return self.origin.y
+
+ @property
+ def minz(self):
+ return self.origin.z
+
+ @property
+ def maxx(self):
+ return self.origin.x + self.size.x
+
+ @property
+ def maxy(self):
+ return self.origin.y + self.size.y
+
+ @property
+ def maxz(self):
+ return self.origin.z + self.size.z
+
+ @property
+ def maximum(self):
+ "The largest point of the box; origin plus size."
+ return self._origin + self._size
+
+ @property
+ def volume(self):
+ "The volume of the box in blocks"
+ return self.size.x * self.size.y * self.size.z
+
+ @property
+ def positions(self):
+ """iterate through all of the positions within this selection box"""
+ return itertools.product(
+ xrange(self.minx, self.maxx),
+ xrange(self.miny, self.maxy),
+ xrange(self.minz, self.maxz)
+ )
+
+ def intersect(self, box):
+ """
+ Return a box containing the area self and box have in common. Box will have zero volume
+ if there is no common area.
+ """
+ if (self.minx > box.maxx or self.maxx < box.minx or
+ self.miny > box.maxy or self.maxy < box.miny or
+ self.minz > box.maxz or self.maxz < box.minz):
+ #Zero size intersection.
+ return BoundingBox()
+
+ origin = Vector(
+ max(self.minx, box.minx),
+ max(self.miny, box.miny),
+ max(self.minz, box.minz),
+ )
+ maximum = Vector(
+ min(self.maxx, box.maxx),
+ min(self.maxy, box.maxy),
+ min(self.maxz, box.maxz),
+ )
+
+ #print "Intersect of {0} and {1}: {2}".format(self, box, newbox)
+ return BoundingBox(origin, maximum - origin)
+
+ def union(self, box):
+ """
+ Return a box large enough to contain both self and box.
+ """
+ origin = Vector(
+ min(self.minx, box.minx),
+ min(self.miny, box.miny),
+ min(self.minz, box.minz),
+ )
+ maximum = Vector(
+ max(self.maxx, box.maxx),
+ max(self.maxy, box.maxy),
+ max(self.maxz, box.maxz),
+ )
+ return BoundingBox(origin, maximum - origin)
+
+ def expand(self, dx, dy=None, dz=None):
+ """
+ Return a new box with boundaries expanded by dx, dy, dz.
+ If only dx is passed, expands by dx in all dimensions.
+ """
+ if dz is None:
+ dz = dx
+ if dy is None:
+ dy = dx
+
+ origin = self.origin - (dx, dy, dz)
+ size = self.size + (dx * 2, dy * 2, dz * 2)
+
+ return BoundingBox(origin, size)
+
+ def __contains__(self, pos):
+ x, y, z = pos
+ if x < self.minx or x >= self.maxx:
+ return False
+ if y < self.miny or y >= self.maxy:
+ return False
+ if z < self.minz or z >= self.maxz:
+ return False
+
+ return True
+
+ def __cmp__(self, b):
+ return cmp((self.origin, self.size), (b.origin, b.size))
+
+
+ # --- Chunk positions ---
+
+ @property
+ def mincx(self):
+ "The smallest chunk position contained in this box"
+ return self.origin.x >> 4
+
+ @property
+ def mincz(self):
+ "The smallest chunk position contained in this box"
+ return self.origin.z >> 4
+
+ @property
+ def maxcx(self):
+ "The largest chunk position contained in this box"
+ return ((self.origin.x + self.size.x - 1) >> 4) + 1
+
+ @property
+ def maxcz(self):
+ "The largest chunk position contained in this box"
+ return ((self.origin.z + self.size.z - 1) >> 4) + 1
+
+ def chunkBox(self, level):
+ """Returns this box extended to the chunk boundaries of the given level"""
+ box = self
+ return BoundingBox((box.mincx << 4, 0, box.mincz << 4),
+ (box.maxcx - box.mincx << 4, level.Height, box.maxcz - box.mincz << 4))
+
+ @property
+ def chunkPositions(self):
+ #iterate through all of the chunk positions within this selection box
+ return itertools.product(xrange(self.mincx, self.maxcx), xrange(self.mincz, self.maxcz))
+
+ @property
+ def chunkCount(self):
+ return (self.maxcx - self.mincx) * (self.maxcz - self.mincz)
+
+ @property
+ def isChunkAligned(self):
+ return (self.origin.x & 0xf == 0) and (self.origin.z & 0xf == 0)
+
+class FloatBox (BoundingBox):
+ type = float
--- /dev/null
+# From http://code.activestate.com/recipes/498245/
+import collections
+import functools
+from itertools import ifilterfalse
+from heapq import nsmallest
+from operator import itemgetter
+
+
+class Counter(dict):
+ 'Mapping where default values are zero'
+
+ def __missing__(self, key):
+ return 0
+
+
+def lru_cache(maxsize=100):
+ '''Least-recently-used cache decorator.
+
+ Arguments to the cached function must be hashable.
+ Cache performance statistics stored in f.hits and f.misses.
+ Clear the cache with f.clear().
+ http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+ '''
+ maxqueue = maxsize * 10
+
+ def decorating_function(user_function,
+ len=len, iter=iter, tuple=tuple, sorted=sorted, KeyError=KeyError):
+ cache = {} # mapping of args to results
+ queue = collections.deque() # order that keys have been used
+ refcount = Counter() # times each key is in the queue
+ sentinel = object() # marker for looping around the queue
+ kwd_mark = object() # separate positional and keyword args
+
+ # lookup optimizations (ugly but fast)
+ queue_append, queue_popleft = queue.append, queue.popleft
+ queue_appendleft, queue_pop = queue.appendleft, queue.pop
+
+ @functools.wraps(user_function)
+ def wrapper(*args, **kwds):
+ # cache key records both positional and keyword args
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+
+ # record recent use of this key
+ queue_append(key)
+ refcount[key] += 1
+
+ # get cache entry or compute if not found
+ try:
+ result = cache[key]
+ wrapper.hits += 1
+ except KeyError:
+ result = user_function(*args, **kwds)
+ cache[key] = result
+ wrapper.misses += 1
+
+ # purge least recently used cache entry
+ if len(cache) > maxsize:
+ key = queue_popleft()
+ refcount[key] -= 1
+ while refcount[key]:
+ key = queue_popleft()
+ refcount[key] -= 1
+ del cache[key], refcount[key]
+
+ # periodically compact the queue by eliminating duplicate keys
+ # while preserving order of most recent access
+ if len(queue) > maxqueue:
+ refcount.clear()
+ queue_appendleft(sentinel)
+ for key in ifilterfalse(refcount.__contains__,
+ iter(queue_pop, sentinel)):
+ queue_appendleft(key)
+ refcount[key] = 1
+
+ return result
+
+ def clear():
+ cache.clear()
+ queue.clear()
+ refcount.clear()
+ wrapper.hits = wrapper.misses = 0
+
+ wrapper.hits = wrapper.misses = 0
+ wrapper.clear = clear
+ return wrapper
+ return decorating_function
+
+
+def lfu_cache(maxsize=100):
+ '''Least-frequenty-used cache decorator.
+
+ Arguments to the cached function must be hashable.
+ Cache performance statistics stored in f.hits and f.misses.
+ Clear the cache with f.clear().
+ http://en.wikipedia.org/wiki/Least_Frequently_Used
+
+ '''
+
+ def decorating_function(user_function):
+ cache = {} # mapping of args to results
+ use_count = Counter() # times each key has been accessed
+ kwd_mark = object() # separate positional and keyword args
+
+ @functools.wraps(user_function)
+ def wrapper(*args, **kwds):
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ use_count[key] += 1
+
+ # get cache entry or compute if not found
+ try:
+ result = cache[key]
+ wrapper.hits += 1
+ except KeyError:
+ result = user_function(*args, **kwds)
+ cache[key] = result
+ wrapper.misses += 1
+
+ # purge least frequently used cache entry
+ if len(cache) > maxsize:
+ for key, _ in nsmallest(maxsize // 10,
+ use_count.iteritems(),
+ key=itemgetter(1)):
+ del cache[key], use_count[key]
+
+ return result
+
+ def clear():
+ cache.clear()
+ use_count.clear()
+ wrapper.hits = wrapper.misses = 0
+
+ wrapper.hits = wrapper.misses = 0
+ wrapper.clear = clear
+ return wrapper
+ return decorating_function
+
+if __name__ == '__main__':
+
+ @lru_cache(maxsize=20)
+ def f_lru(x, y):
+ return 3 * x + y
+
+ domain = range(5)
+ from random import choice
+ for i in range(1000):
+ r = f_lru(choice(domain), choice(domain))
+
+ print(f_lru.hits, f_lru.misses)
+
+ @lfu_cache(maxsize=20)
+ def f_lfu(x, y):
+ return 3 * x + y
+
+ domain = range(5)
+ from random import choice
+ for i in range(1000):
+ r = f_lfu(choice(domain), choice(domain))
+
+ print(f_lfu.hits, f_lfu.misses)
--- /dev/null
+
+blocks:
+
+ - id: 1
+ name: Stone
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+
+ - id: 2
+ name: Grass
+ mapcolor: [117, 176, 73]
+ tex: [0, 0]
+ tex_direction:
+ FORWARD: [3, 0]
+ SIDES: [3, 0]
+ BACKWARD: [3, 0]
+ BOTTOM: [2, 0]
+
+ - id: 3
+ name: Dirt
+ mapcolor: [134, 96, 67]
+ tex: [2, 0]
+
+ - id: 4
+ name: Cobblestone
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+
+ - id: 5
+ name: Wood Planks
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+
+ - id: 6
+ name: Sapling
+ mapcolor: [120, 120, 120]
+ tex: [15, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 7
+ name: Bedrock
+ aka: Adminium
+ mapcolor: [84, 84, 84]
+ tex: [1, 1]
+
+ - id: 8
+ name: Water (active)
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 9
+ name: Water
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 10
+ name: Lava (active)
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+
+ - id: 11
+ name: Lava
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+
+ - id: 12
+ name: Sand
+ mapcolor: [218, 210, 158]
+ tex: [2, 1]
+
+ - id: 13
+ name: Gravel
+ mapcolor: [136, 126, 126]
+ tex: [3, 1]
+
+ - id: 14
+ name: Gold Ore
+ mapcolor: [143, 140, 125]
+ tex: [0, 2]
+
+ - id: 15
+ name: Iron Ore
+ mapcolor: [136, 130, 127]
+ tex: [1, 2]
+
+ - id: 16
+ name: Coal Ore
+ mapcolor: [115, 115, 115]
+ tex: [2, 2]
+
+ - id: 17
+ name: Wood
+ mapcolor: [102, 81, 51]
+ tex: [4, 1]
+ tex_direction:
+ TOP: [5, 1]
+ BOTTOM: [5, 1]
+
+ - id: 18
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+
+ - id: 19
+ name: Sponge
+ mapcolor: [193, 193, 57]
+ tex: [0, 3]
+
+ - id: 20
+ name: Glass
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: GLASS
+ opacity: 0
+
+ - id: 21
+ name: Red Wool
+ tex: [0, 4]
+ mapcolor: [164, 45, 41]
+
+ - id: 22
+ name: Orange Wool
+ tex: [1, 4]
+ mapcolor: [234, 127, 55]
+
+ - id: 23
+ name: Yellow Wool
+ tex: [2, 4]
+ mapcolor: [104, 139, 212]
+
+ - id: 24
+ name: Lime Wool
+ tex: [3, 4]
+ mapcolor: [59, 189, 48]
+
+ - id: 25
+ name: Green Wool
+ tex: [4, 4]
+ mapcolor: [56, 77, 24]
+
+ - id: 26
+ name: Aqua Wool
+ tex: [5, 4]
+ mapcolor: [104, 139, 212]
+
+ - id: 27
+ name: Cyan Wool
+ tex: [6, 4]
+ mapcolor: [39, 117, 149]
+
+ - id: 28
+ name: Blue Wool
+ tex: [7, 4]
+ mapcolor: [39, 51, 161]
+
+ - id: 29
+ name: Purple Wool
+ tex: [8, 4]
+ mapcolor: [88, 54, 122]
+
+ - id: 30
+ name: Indigo Wool
+ tex: [9, 4]
+
+ - id: 31
+ name: Violet Wool
+ tex: [10, 4]
+ mapcolor: [129, 54, 196]
+
+ - id: 32
+ name: Magenta Wool
+ tex: [11, 4]
+ mapcolor: [191, 75, 201]
+
+ - id: 33
+ name: Pink Wool
+ tex: [12, 4]
+ mapcolor: [217, 131, 155]
+
+ - id: 34
+ name: Black Wool
+ tex: [13, 4]
+ mapcolor: [0, 0, 0]
+
+ - id: 35
+ name: Gray Wool
+ tex: [14, 4]
+ mapcolor: [66, 66, 66]
+
+ - id: 36
+ name: White Wool
+ tex: [15, 4]
+ mapcolor: [222, 222, 222]
+
+ - id: 37
+ name: Flower
+ mapcolor: [255, 255, 0]
+ tex: [13, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 38
+ name: Rose
+ mapcolor: [255, 0, 0]
+ tex: [12, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 39
+ name: Brown Mushroom
+ mapcolor: [145, 109, 85]
+ tex: [13, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 40
+ name: Red Mushroom
+ mapcolor: [226, 18, 18]
+ tex: [12, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 41
+ name: Block of Gold
+ mapcolor: [231, 165, 45]
+ tex: [7, 1]
+
+ - id: 42
+ name: Block of Iron
+ mapcolor: [191, 191, 191]
+ tex: [6, 1]
+
+ - id: 43
+ name: Double Stone Slab
+ aka: Step
+ mapcolor: [200, 200, 200]
+ tex: [5, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+
+ - id: 44
+ name: Stone Slab
+ aka: Single Half Step
+ mapcolor: [200, 200, 200]
+ tex: [6, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+
+ - id: 45
+ name: Brick
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+
+ - id: 46
+ name: TNT
+ mapcolor: [160, 83, 65]
+ tex: [8, 0]
+ tex_direction:
+ TOP: [9, 0]
+ BOTTOM: [10, 0]
+
+ - id: 47
+ name: Bookshelf
+ mapcolor: [188, 152, 98]
+ tex: [3, 2]
+ tex_direction:
+ TOP: [4, 0]
+ BOTTOM: [4, 0]
+
+ - id: 48
+ name: Moss Stone
+ aka: Mossy Cobblestone
+ mapcolor: [115, 169, 115]
+ tex: [4, 2]
+
+ - id: 49
+ name: Obsidian
+ mapcolor: [26, 11, 43]
+ tex: [5, 2]
+
\ No newline at end of file
--- /dev/null
+'''
+Created on Jul 23, 2011
+
+@author: Rio
+'''
+from math import isnan
+
+import nbt
+from copy import deepcopy
+
+__all__ = ["Entity", "TileEntity"]
+
+class TileEntity(object):
+ baseStructures = {
+ "Furnace": (
+ ("BurnTime", nbt.TAG_Short),
+ ("CookTime", nbt.TAG_Short),
+ ("Items", nbt.TAG_List),
+ ),
+ "Sign": (
+ ("Items", nbt.TAG_List),
+ ),
+ "MobSpawner": (
+ ("Items", nbt.TAG_List),
+ ),
+ "Chest": (
+ ("Items", nbt.TAG_List),
+ ),
+ "Music": (
+ ("note", nbt.TAG_Byte),
+ ),
+ "Trap": (
+ ("Items", nbt.TAG_List),
+ ),
+ "RecordPlayer": (
+ ("Record", nbt.TAG_Int),
+ ),
+ "Piston": (
+ ("blockId", nbt.TAG_Int),
+ ("blockData", nbt.TAG_Int),
+ ("facing", nbt.TAG_Int),
+ ("progress", nbt.TAG_Float),
+ ("extending", nbt.TAG_Byte),
+ ),
+ "Cauldron": (
+ ("Items", nbt.TAG_List),
+ ("BrewTime", nbt.TAG_Int),
+ ),
+ }
+
+ knownIDs = baseStructures.keys()
+ maxItems = {
+ "Furnace": 3,
+ "Chest": 27,
+ "Trap": 9,
+ "Cauldron": 4,
+ }
+ slotNames = {
+ "Furnace": {
+ 0: "Raw",
+ 1: "Fuel",
+ 2: "Product"
+ },
+ "Cauldron": {
+ 0: "Potion",
+ 1: "Potion",
+ 2: "Potion",
+ 3: "Reagent",
+ }
+ }
+
+ @classmethod
+ def Create(cls, tileEntityID, **kw):
+ tileEntityTag = nbt.TAG_Compound()
+ tileEntityTag["id"] = nbt.TAG_String(tileEntityID)
+ base = cls.baseStructures.get(tileEntityID, None)
+ if base:
+ for (name, tag) in base:
+ tileEntityTag[name] = tag()
+
+ cls.setpos(tileEntityTag, (0, 0, 0))
+ return tileEntityTag
+
+ @classmethod
+ def pos(cls, tag):
+ return [tag[a].value for a in 'xyz']
+
+ @classmethod
+ def setpos(cls, tag, pos):
+ for a, p in zip('xyz', pos):
+ tag[a] = nbt.TAG_Int(p)
+
+ @classmethod
+ def copyWithOffset(cls, tileEntity, copyOffset):
+ eTag = deepcopy(tileEntity)
+ eTag['x'] = nbt.TAG_Int(tileEntity['x'].value + copyOffset[0])
+ eTag['y'] = nbt.TAG_Int(tileEntity['y'].value + copyOffset[1])
+ eTag['z'] = nbt.TAG_Int(tileEntity['z'].value + copyOffset[2])
+ return eTag
+
+
+class Entity(object):
+ monsters = ["Creeper",
+ "Skeleton",
+ "Spider",
+ "CaveSpider",
+ "Giant",
+ "Zombie",
+ "Slime",
+ "PigZombie",
+ "Ghast",
+ "Pig",
+ "Sheep",
+ "Cow",
+ "Chicken",
+ "Squid",
+ "Wolf",
+ "Monster",
+ "Enderman",
+ "Silverfish",
+ "Blaze",
+ "Villager",
+ "LavaSlime",
+ "WitherBoss",
+ ]
+ projectiles = ["Arrow",
+ "Snowball",
+ "Egg",
+ "Fireball",
+ "SmallFireball",
+ "ThrownEnderpearl",
+ ]
+
+ items = ["Item",
+ "XPOrb",
+ "Painting",
+ "EnderCrystal",
+ "ItemFrame",
+ "WitherSkull",
+ ]
+ vehicles = ["Minecart", "Boat"]
+ tiles = ["PrimedTnt", "FallingSand"]
+
+ @classmethod
+ def Create(cls, entityID, **kw):
+ entityTag = nbt.TAG_Compound()
+ entityTag["id"] = nbt.TAG_String(entityID)
+ Entity.setpos(entityTag, (0, 0, 0))
+ return entityTag
+
+ @classmethod
+ def pos(cls, tag):
+ if "Pos" not in tag:
+ raise InvalidEntity(tag)
+ values = [a.value for a in tag["Pos"]]
+
+ if isnan(values[0]) and 'xTile' in tag :
+ values[0] = tag['xTile'].value
+ if isnan(values[1]) and 'yTile' in tag:
+ values[1] = tag['yTile'].value
+ if isnan(values[2]) and 'zTile' in tag:
+ values[2] = tag['zTile'].value
+
+ return values
+
+ @classmethod
+ def setpos(cls, tag, pos):
+ tag["Pos"] = nbt.TAG_List([nbt.TAG_Double(p) for p in pos])
+
+ @classmethod
+ def copyWithOffset(cls, entity, copyOffset):
+ eTag = deepcopy(entity)
+
+ positionTags = map(lambda p, co: nbt.TAG_Double(p.value + co), eTag["Pos"], copyOffset)
+ eTag["Pos"] = nbt.TAG_List(positionTags)
+
+ if eTag["id"].value in ("Painting", "ItemFrame"):
+ eTag["TileX"].value += copyOffset[0]
+ eTag["TileY"].value += copyOffset[1]
+ eTag["TileZ"].value += copyOffset[2]
+
+ return eTag
+
+
+class InvalidEntity(ValueError):
+ pass
+
+
+class InvalidTileEntity(ValueError):
+ pass
--- /dev/null
+
+FaceXIncreasing = 0
+FaceXDecreasing = 1
+FaceYIncreasing = 2
+FaceYDecreasing = 3
+FaceZIncreasing = 4
+FaceZDecreasing = 5
+MaxDirections = 6
+
+faceDirections = (
+ (FaceXIncreasing, (1, 0, 0)),
+ (FaceXDecreasing, (-1, 0, 0)),
+ (FaceYIncreasing, (0, 1, 0)),
+ (FaceYDecreasing, (0, -1, 0)),
+ (FaceZIncreasing, (0, 0, 1)),
+ (FaceZDecreasing, (0, 0, -1))
+ )
--- /dev/null
+"""
+Created on Jul 22, 2011
+
+@author: Rio
+
+Indev levels:
+
+TAG_Compound "MinecraftLevel"
+{
+ TAG_Compound "Environment"
+ {
+ TAG_Short "SurroundingGroundHeight"// Height of surrounding ground (in blocks)
+ TAG_Byte "SurroundingGroundType" // Block ID of surrounding ground
+ TAG_Short "SurroundingWaterHeight" // Height of surrounding water (in blocks)
+ TAG_Byte "SurroundingWaterType" // Block ID of surrounding water
+ TAG_Short "CloudHeight" // Height of the cloud layer (in blocks)
+ TAG_Int "CloudColor" // Hexadecimal value for the color of the clouds
+ TAG_Int "SkyColor" // Hexadecimal value for the color of the sky
+ TAG_Int "FogColor" // Hexadecimal value for the color of the fog
+ TAG_Byte "SkyBrightness" // The brightness of the sky, from 0 to 100
+ }
+
+ TAG_List "Entities"
+ {
+ TAG_Compound
+ {
+ // One of these per entity on the map.
+ // These can change a lot, and are undocumented.
+ // Feel free to play around with them, though.
+ // The most interesting one might be the one with ID "LocalPlayer", which contains the player inventory
+ }
+ }
+
+ TAG_Compound "Map"
+ {
+ // To access a specific block from either byte array, use the following algorithm:
+ // Index = x + (y * Depth + z) * Width
+
+ TAG_Short "Width" // Width of the level (along X)
+ TAG_Short "Height" // Height of the level (along Y)
+ TAG_Short "Length" // Length of the level (along Z)
+ TAG_Byte_Array "Blocks" // An array of Length*Height*Width bytes specifying the block types
+ TAG_Byte_Array "Data" // An array of Length*Height*Width bytes with data for each blocks
+
+ TAG_List "Spawn" // Default spawn position
+ {
+ TAG_Short x // These values are multiplied by 32 before being saved
+ TAG_Short y // That means that the actual values are x/32.0, y/32.0, z/32.0
+ TAG_Short z
+ }
+ }
+
+ TAG_Compound "About"
+ {
+ TAG_String "Name" // Level name
+ TAG_String "Author" // Name of the player who made the level
+ TAG_Long "CreatedOn" // Timestamp when the level was first created
+ }
+}
+"""
+
+from entity import TileEntity
+from level import MCLevel
+from logging import getLogger
+from materials import indevMaterials
+from numpy import array, swapaxes
+import nbt
+import os
+
+log = getLogger(__name__)
+
+MinecraftLevel = "MinecraftLevel"
+
+Environment = "Environment"
+SurroundingGroundHeight = "SurroundingGroundHeight"
+SurroundingGroundType = "SurroundingGroundType"
+SurroundingWaterHeight = "SurroundingWaterHeight"
+SurroundingWaterType = "SurroundingWaterType"
+CloudHeight = "CloudHeight"
+CloudColor = "CloudColor"
+SkyColor = "SkyColor"
+FogColor = "FogColor"
+SkyBrightness = "SkyBrightness"
+
+About = "About"
+Name = "Name"
+Author = "Author"
+CreatedOn = "CreatedOn"
+Spawn = "Spawn"
+
+__all__ = ["MCIndevLevel"]
+
+from level import EntityLevel
+
+
+class MCIndevLevel(EntityLevel):
+ """ IMPORTANT: self.Blocks and self.Data are indexed with [x,z,y] via axis
+ swapping to be consistent with infinite levels."""
+
+ materials = indevMaterials
+
+ def setPlayerSpawnPosition(self, pos, player=None):
+ assert len(pos) == 3
+ self.Spawn = array(pos)
+
+ def playerSpawnPosition(self, player=None):
+ return self.Spawn
+
+ def setPlayerPosition(self, pos, player="Ignored"):
+ self.LocalPlayer["Pos"] = nbt.TAG_List([nbt.TAG_Float(p) for p in pos])
+
+ def getPlayerPosition(self, player="Ignored"):
+ return array(map(lambda x: x.value, self.LocalPlayer["Pos"]))
+
+ def setPlayerOrientation(self, yp, player="Ignored"):
+ self.LocalPlayer["Rotation"] = nbt.TAG_List([nbt.TAG_Float(p) for p in yp])
+
+ def getPlayerOrientation(self, player="Ignored"):
+ """ returns (yaw, pitch) """
+ return array(map(lambda x: x.value, self.LocalPlayer["Rotation"]))
+
+ def setBlockDataAt(self, x, y, z, newdata):
+ if x < 0 or y < 0 or z < 0:
+ return 0
+ if x >= self.Width or y >= self.Height or z >= self.Length:
+ return 0
+ self.Data[x, z, y] = (newdata & 0xf)
+
+ def blockDataAt(self, x, y, z):
+ if x < 0 or y < 0 or z < 0:
+ return 0
+ if x >= self.Width or y >= self.Height or z >= self.Length:
+ return 0
+ return self.Data[x, z, y]
+
+ def blockLightAt(self, x, y, z):
+ if x < 0 or y < 0 or z < 0:
+ return 0
+ if x >= self.Width or y >= self.Height or z >= self.Length:
+ return 0
+ return self.BlockLight[x, z, y]
+
+ def __repr__(self):
+ return u"MCIndevLevel({0}): {1}W {2}L {3}H".format(self.filename, self.Width, self.Length, self.Height)
+
+ @classmethod
+ def _isTagLevel(cls, root_tag):
+ return "MinecraftLevel" == root_tag.name
+
+ def __init__(self, root_tag=None, filename=""):
+ self.Width = 0
+ self.Height = 0
+ self.Length = 0
+ self.Blocks = array([], "uint8")
+ self.Data = array([], "uint8")
+ self.Spawn = (0, 0, 0)
+ self.filename = filename
+
+ if root_tag:
+
+ self.root_tag = root_tag
+ mapTag = root_tag["Map"]
+ self.Width = mapTag["Width"].value
+ self.Length = mapTag["Length"].value
+ self.Height = mapTag["Height"].value
+
+ mapTag["Blocks"].value.shape = (self.Height, self.Length, self.Width)
+
+ self.Blocks = swapaxes(mapTag["Blocks"].value, 0, 2)
+
+ mapTag["Data"].value.shape = (self.Height, self.Length, self.Width)
+
+ self.Data = swapaxes(mapTag["Data"].value, 0, 2)
+
+ self.BlockLight = self.Data & 0xf
+
+ self.Data >>= 4
+
+ self.Spawn = [mapTag[Spawn][i].value for i in range(3)]
+
+ if "Entities" not in root_tag:
+ root_tag["Entities"] = nbt.TAG_List()
+ self.Entities = root_tag["Entities"]
+
+ # xxx fixup Motion and Pos to match infdev format
+ def numbersToDoubles(ent):
+ for attr in "Motion", "Pos":
+ if attr in ent:
+ ent[attr] = nbt.TAG_List([nbt.TAG_Double(t.value) for t in ent[attr]])
+ for ent in self.Entities:
+ numbersToDoubles(ent)
+
+ if "TileEntities" not in root_tag:
+ root_tag["TileEntities"] = nbt.TAG_List()
+ self.TileEntities = root_tag["TileEntities"]
+ # xxx fixup TileEntities positions to match infdev format
+ for te in self.TileEntities:
+ pos = te["Pos"].value
+
+ (x, y, z) = self.decodePos(pos)
+
+ TileEntity.setpos(te, (x, y, z))
+
+
+ localPlayerList = [tag for tag in root_tag["Entities"] if tag['id'].value == 'LocalPlayer']
+ if len(localPlayerList) == 0: # omen doesn't make a player entity
+ playerTag = nbt.TAG_Compound()
+ playerTag['id'] = nbt.TAG_String('LocalPlayer')
+ playerTag['Pos'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(64.), nbt.TAG_Float(0.)])
+ playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(45.)])
+ self.LocalPlayer = playerTag
+
+ else:
+ self.LocalPlayer = localPlayerList[0]
+
+ else:
+ log.info(u"Creating new Indev levels is not yet implemented.!")
+ raise ValueError("Can't do that yet")
+# self.SurroundingGroundHeight = root_tag[Environment][SurroundingGroundHeight].value
+# self.SurroundingGroundType = root_tag[Environment][SurroundingGroundType].value
+# self.SurroundingWaterHeight = root_tag[Environment][SurroundingGroundHeight].value
+# self.SurroundingWaterType = root_tag[Environment][SurroundingWaterType].value
+# self.CloudHeight = root_tag[Environment][CloudHeight].value
+# self.CloudColor = root_tag[Environment][CloudColor].value
+# self.SkyColor = root_tag[Environment][SkyColor].value
+# self.FogColor = root_tag[Environment][FogColor].value
+# self.SkyBrightness = root_tag[Environment][SkyBrightness].value
+# self.TimeOfDay = root_tag[Environment]["TimeOfDay"].value
+#
+#
+# self.Name = self.root_tag[About][Name].value
+# self.Author = self.root_tag[About][Author].value
+# self.CreatedOn = self.root_tag[About][CreatedOn].value
+
+ def rotateLeft(self):
+ MCLevel.rotateLeft(self)
+
+ self.Data = swapaxes(self.Data, 1, 0)[:, ::-1, :] # x=y; y=-x
+
+ torchRotation = array([0, 4, 3, 1, 2, 5,
+ 6, 7,
+
+ 8, 9, 10, 11, 12, 13, 14, 15])
+
+ torchIndexes = (self.Blocks == self.materials.Torch.ID)
+ log.info(u"Rotating torches: {0}".format(len(torchIndexes.nonzero()[0])))
+ self.Data[torchIndexes] = torchRotation[self.Data[torchIndexes]]
+
+ def decodePos(self, v):
+ b = 10
+ m = (1 << b) - 1
+ return v & m, (v >> b) & m, (v >> (2 * b))
+
+ def encodePos(self, x, y, z):
+ b = 10
+ return x + (y << b) + (z << (2 * b))
+
+ def saveToFile(self, filename=None):
+ if filename is None:
+ filename = self.filename
+ if filename is None:
+ log.warn(u"Attempted to save an unnamed file in place")
+ return # you fool!
+
+ self.Data <<= 4
+ self.Data |= (self.BlockLight & 0xf)
+
+ self.Blocks = swapaxes(self.Blocks, 0, 2)
+ self.Data = swapaxes(self.Data, 0, 2)
+
+ mapTag = nbt.TAG_Compound()
+ mapTag["Width"] = nbt.TAG_Short(self.Width)
+ mapTag["Height"] = nbt.TAG_Short(self.Height)
+ mapTag["Length"] = nbt.TAG_Short(self.Length)
+ mapTag["Blocks"] = nbt.TAG_Byte_Array(self.Blocks)
+ mapTag["Data"] = nbt.TAG_Byte_Array(self.Data)
+
+ self.Blocks = swapaxes(self.Blocks, 0, 2)
+ self.Data = swapaxes(self.Data, 0, 2)
+
+ mapTag[Spawn] = nbt.TAG_List([nbt.TAG_Short(i) for i in self.Spawn])
+
+ self.root_tag["Map"] = mapTag
+
+ self.Entities.append(self.LocalPlayer)
+ # fix up Entities imported from Alpha worlds
+ def numbersToFloats(ent):
+ for attr in "Motion", "Pos":
+ if attr in ent:
+ ent[attr] = nbt.TAG_List([nbt.TAG_Double(t.value) for t in ent[attr]])
+ for ent in self.Entities:
+ numbersToFloats(ent)
+
+ # fix up TileEntities imported from Alpha worlds.
+ for ent in self.TileEntities:
+ if "Pos" not in ent and all(c in ent for c in 'xyz'):
+ ent["Pos"] = nbt.TAG_Int(self.encodePos(ent['x'].value, ent['y'].value, ent['z'].value))
+ # output_file = gzip.open(self.filename, "wb", compresslevel=1)
+ try:
+ os.rename(filename, filename + ".old")
+ except Exception:
+ pass
+
+ try:
+ self.root_tag.save(filename)
+ except:
+ os.rename(filename + ".old", filename)
+
+ try:
+ os.remove(filename + ".old")
+ except Exception:
+ pass
+
+ self.Entities.remove(self.LocalPlayer)
+
+ self.BlockLight = self.Data & 0xf
+
+ self.Data >>= 4
--- /dev/null
+
+blocks:
+
+ - id: 1
+ name: Stone
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+
+ - id: 2
+ name: Grass
+ mapcolor: [117, 176, 73]
+ tex: [0, 0]
+ tex_direction:
+ FORWARD: [3, 0]
+ SIDES: [3, 0]
+ BACKWARD: [3, 0]
+ BOTTOM: [2, 0]
+
+ - id: 3
+ name: Dirt
+ mapcolor: [134, 96, 67]
+ tex: [2, 0]
+
+ - id: 4
+ name: Cobblestone
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+
+ - id: 5
+ name: Wood Planks
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+
+ - id: 6
+ name: Sapling
+ mapcolor: [120, 120, 120]
+ tex: [15, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 7
+ name: Bedrock
+ aka: Adminium
+ mapcolor: [84, 84, 84]
+ tex: [1, 1]
+
+ - id: 8
+ name: Water (active)
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 9
+ name: Water
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 10
+ name: Lava (active)
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+
+ - id: 11
+ name: Lava
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+
+ - id: 12
+ name: Sand
+ mapcolor: [218, 210, 158]
+ tex: [2, 1]
+
+ - id: 13
+ name: Gravel
+ mapcolor: [136, 126, 126]
+ tex: [3, 1]
+
+ - id: 14
+ name: Gold Ore
+ mapcolor: [143, 140, 125]
+ tex: [0, 2]
+
+ - id: 15
+ name: Iron Ore
+ mapcolor: [136, 130, 127]
+ tex: [1, 2]
+
+ - id: 16
+ name: Coal Ore
+ mapcolor: [115, 115, 115]
+ tex: [2, 2]
+
+ - id: 17
+ name: Wood
+ mapcolor: [102, 81, 51]
+ tex: [4, 1]
+ tex_direction:
+ TOP: [5, 1]
+ BOTTOM: [5, 1]
+
+ - id: 18
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+
+ - id: 19
+ name: Sponge
+ mapcolor: [193, 193, 57]
+ tex: [0, 3]
+
+ - id: 20
+ name: Glass
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: GLASS
+ opacity: 0
+
+ - id: 21
+ name: Red Wool
+ tex: [0, 4]
+ mapcolor: [164, 45, 41]
+
+ - id: 22
+ name: Orange Wool
+ tex: [1, 4]
+ mapcolor: [234, 127, 55]
+
+ - id: 23
+ name: Yellow Wool
+ tex: [2, 4]
+ mapcolor: [104, 139, 212]
+
+ - id: 24
+ name: Lime Wool
+ tex: [3, 4]
+ mapcolor: [59, 189, 48]
+
+ - id: 25
+ name: Green Wool
+ tex: [4, 4]
+ mapcolor: [56, 77, 24]
+
+ - id: 26
+ name: Aqua Wool
+ tex: [5, 4]
+ mapcolor: [104, 139, 212]
+
+ - id: 27
+ name: Cyan Wool
+ tex: [6, 4]
+ mapcolor: [39, 117, 149]
+
+ - id: 28
+ name: Blue Wool
+ tex: [7, 4]
+ mapcolor: [39, 51, 161]
+
+ - id: 29
+ name: Purple Wool
+ tex: [8, 4]
+ mapcolor: [88, 54, 122]
+
+ - id: 30
+ name: Indigo Wool
+ tex: [9, 4]
+
+ - id: 31
+ name: Violet Wool
+ tex: [10, 4]
+ mapcolor: [129, 54, 196]
+
+ - id: 32
+ name: Magenta Wool
+ tex: [11, 4]
+ mapcolor: [191, 75, 201]
+
+ - id: 33
+ name: Pink Wool
+ tex: [12, 4]
+ mapcolor: [217, 131, 155]
+
+ - id: 34
+ name: Black Wool
+ tex: [13, 4]
+ mapcolor: [0, 0, 0]
+
+ - id: 35
+ name: Gray Wool
+ tex: [14, 4]
+ mapcolor: [66, 66, 66]
+
+ - id: 36
+ name: White Wool
+ tex: [15, 4]
+ mapcolor: [222, 222, 222]
+
+ - id: 37
+ name: Flower
+ mapcolor: [255, 255, 0]
+ tex: [13, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 38
+ name: Rose
+ mapcolor: [255, 0, 0]
+ tex: [12, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 39
+ name: Brown Mushroom
+ mapcolor: [145, 109, 85]
+ tex: [13, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 40
+ name: Red Mushroom
+ mapcolor: [226, 18, 18]
+ tex: [12, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 41
+ name: Block of Gold
+ mapcolor: [231, 165, 45]
+ tex: [7, 1]
+
+ - id: 42
+ name: Block of Iron
+ mapcolor: [191, 191, 191]
+ tex: [6, 1]
+
+ - id: 43
+ name: Double Stone Slab
+ aka: Step
+ mapcolor: [200, 200, 200]
+ tex: [5, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+
+ - id: 44
+ name: Stone Slab
+ aka: Single Half Step
+ mapcolor: [200, 200, 200]
+ tex: [6, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+
+ - id: 45
+ name: Brick
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+
+ - id: 46
+ name: TNT
+ mapcolor: [160, 83, 65]
+ tex: [8, 0]
+ tex_direction:
+ TOP: [9, 0]
+ BOTTOM: [10, 0]
+
+ - id: 47
+ name: Bookshelf
+ mapcolor: [188, 152, 98]
+ tex: [3, 2]
+ tex_direction:
+ TOP: [4, 0]
+ BOTTOM: [4, 0]
+
+ - id: 48
+ name: Moss Stone
+ aka: Mossy Cobblestone
+ mapcolor: [115, 169, 115]
+ tex: [4, 2]
+
+ - id: 49
+ name: Obsidian
+ mapcolor: [26, 11, 43]
+ tex: [5, 2]
+
+ - id: 50
+ name: Torch
+ mapcolor: [245, 220, 50]
+ tex: [0, 5]
+ type: TORCH
+ opacity: 0
+ brightness: 14
+
+ - id: 51
+ name: Fire
+ mapcolor: [255, 170, 30]
+ type: SEMISOLID
+ tex: [15, 1]
+ opacity: 0
+ brightness: 15
+
+ - id: 52
+ name: Infinite water source
+ mapcolor: [38, 92, 255]
+ tex: [1, 4]
+ type: SEMISOLID
+ opacity: 0
+
+ - id: 53
+ name: Infinite lava source
+ mapcolor: [255, 90, 0]
+ tex: [4, 0]
+ type: SEMISOLID
+ brightness: 15
+
+ - id: 54
+ name: Chest
+ mapcolor: [125, 91, 38]
+ tex: [11, 1]
+ type: CHEST
+ tex_extra:
+ side_small: [10, 1]
+ top: [9, 1]
+ front_big_left: [9, 2]
+ front_big_right: [10, 2]
+ back_big_left: [9, 3]
+ back_big_right: [10, 3]
+
+ - id: 55
+ name: Cog
+ tex: [15, 3]
+
+ - id: 56
+ name: Diamond Ore
+ mapcolor: [129, 140, 143]
+ tex: [2, 3]
+
+ - id: 57
+ name: Block of Diamond
+ mapcolor: [45, 166, 152]
+ tex: [8, 1]
+
+ - id: 58
+ name: Crafting Table
+ aka: Workbench
+ mapcolor: [114, 88, 56]
+ tex: [12, 3]
+ tex_direction:
+ SIDES: [11, 3]
+ TOP: [11, 2]
+ BOTTOM: [4, 0]
+
+ - id: 59
+ name: Crops
+ aka: Wheat
+ mapcolor: [146, 192, 0]
+ tex: [15, 5]
+ type: CROPS
+ tex_extra:
+ smaller_1: [14, 5]
+ smaller_2: [13, 5]
+ smaller_3: [12, 5]
+ smaller_4: [11, 5]
+ smaller_5: [10, 5]
+ smaller_6: [9, 5]
+ smaller_7: [8, 5]
+ opacity: 0
+
+ - id: 60
+ name: Farmland
+ aka: Soil
+ mapcolor: [95, 58, 30]
+ tex: [6, 5]
+ tex_direction:
+ FORWARD: [2, 0]
+ SIDES: [2, 0]
+ BACKWARD: [2, 0]
+ BOTTOM: [2, 0]
+
+ - id: 61
+ name: Furnace
+ mapcolor: [96, 96, 96]
+ tex: [12, 2]
+ tex_direction:
+ FORWARD: [12, 2]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [1, 0]
+ BOTTOM: [1, 0]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+
+ - id: 62
+ name: Lit Furnace
+ mapcolor: [96, 96, 96]
+ tex: [13, 3]
+ tex_direction:
+ FORWARD: [13, 3]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [1, 0]
+ BOTTOM: [1, 0]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+ brightness: 14
--- /dev/null
+'''
+Created on Jul 22, 2011
+
+@author: Rio
+'''
+
+import copy
+from datetime import datetime
+import itertools
+from logging import getLogger
+from math import floor
+import os
+import re
+import random
+import shutil
+import struct
+import time
+import traceback
+import weakref
+import zlib
+import sys
+
+import blockrotation
+from box import BoundingBox
+from entity import Entity, TileEntity
+from faces import FaceXDecreasing, FaceXIncreasing, FaceZDecreasing, FaceZIncreasing
+from level import LightedChunk, EntityLevel, computeChunkHeightMap, MCLevel, ChunkBase
+from materials import alphaMaterials
+from mclevelbase import ChunkMalformed, ChunkNotPresent, exhaust, PlayerNotFound
+import nbt
+from numpy import array, clip, maximum, zeros
+from regionfile import MCRegionFile
+
+log = getLogger(__name__)
+
+
+DIM_NETHER = -1
+DIM_END = 1
+
+__all__ = ["ZeroChunk", "AnvilChunk", "ChunkedLevelMixin", "MCInfdevOldLevel", "MCAlphaDimension", "ZipSchematic"]
+_zeros = {}
+
+class SessionLockLost(IOError):
+ pass
+
+
+
+def ZeroChunk(height=512):
+ z = _zeros.get(height)
+ if z is None:
+ z = _zeros[height] = _ZeroChunk(height)
+ return z
+
+
+class _ZeroChunk(ChunkBase):
+ " a placebo for neighboring-chunk routines "
+
+ def __init__(self, height=512):
+ zeroChunk = zeros((16, 16, height), 'uint8')
+ whiteLight = zeroChunk + 15
+ self.Blocks = zeroChunk
+ self.BlockLight = whiteLight
+ self.SkyLight = whiteLight
+ self.Data = zeroChunk
+
+
+def unpackNibbleArray(dataArray):
+ s = dataArray.shape
+ unpackedData = zeros((s[0], s[1], s[2] * 2), dtype='uint8')
+
+ unpackedData[:, :, ::2] = dataArray
+ unpackedData[:, :, ::2] &= 0xf
+ unpackedData[:, :, 1::2] = dataArray
+ unpackedData[:, :, 1::2] >>= 4
+ return unpackedData
+
+
+def packNibbleArray(unpackedData):
+ packedData = array(unpackedData.reshape(16, 16, unpackedData.shape[2] / 2, 2))
+ packedData[..., 1] <<= 4
+ packedData[..., 1] |= packedData[..., 0]
+ return array(packedData[:, :, :, 1])
+
+def sanitizeBlocks(chunk):
+ # change grass to dirt where needed so Minecraft doesn't flip out and die
+ grass = chunk.Blocks == chunk.materials.Grass.ID
+ grass |= chunk.Blocks == chunk.materials.Dirt.ID
+ badgrass = grass[:, :, 1:] & grass[:, :, :-1]
+
+ chunk.Blocks[:, :, :-1][badgrass] = chunk.materials.Dirt.ID
+
+ # remove any thin snow layers immediately above other thin snow layers.
+ # minecraft doesn't flip out, but it's almost never intended
+ if hasattr(chunk.materials, "SnowLayer"):
+ snowlayer = chunk.Blocks == chunk.materials.SnowLayer.ID
+ badsnow = snowlayer[:, :, 1:] & snowlayer[:, :, :-1]
+
+ chunk.Blocks[:, :, 1:][badsnow] = chunk.materials.Air.ID
+
+
+class AnvilChunkData(object):
+ """ This is the chunk data backing an AnvilChunk. Chunk data is retained by the MCInfdevOldLevel until its
+ AnvilChunk is no longer used, then it is either cached in memory, discarded, or written to disk according to
+ resource limits.
+
+ AnvilChunks are stored in a WeakValueDictionary so we can find out when they are no longer used by clients. The
+ AnvilChunkData for an unused chunk may safely be discarded or written out to disk. The client should probably
+ not keep references to a whole lot of chunks or else it will run out of memory.
+ """
+ def __init__(self, world, chunkPosition, root_tag = None, create = False):
+ self.chunkPosition = chunkPosition
+ self.world = world
+ self.root_tag = root_tag
+ self.dirty = False
+
+ self.Blocks = zeros((16, 16, world.Height), 'uint8') # xxx uint16?
+ self.Data = zeros((16, 16, world.Height), 'uint8')
+ self.BlockLight = zeros((16, 16, world.Height), 'uint8')
+ self.SkyLight = zeros((16, 16, world.Height), 'uint8')
+ self.SkyLight[:] = 15
+
+
+ if create:
+ self._create()
+ else:
+ self._load(root_tag)
+
+ def _create(self):
+ (cx, cz) = self.chunkPosition
+ chunkTag = nbt.TAG_Compound()
+ chunkTag.name = ""
+
+ levelTag = nbt.TAG_Compound()
+ chunkTag["Level"] = levelTag
+
+ levelTag["HeightMap"] = nbt.TAG_Int_Array(zeros((16, 16), 'uint32').newbyteorder())
+ levelTag["TerrainPopulated"] = nbt.TAG_Byte(1)
+ levelTag["xPos"] = nbt.TAG_Int(cx)
+ levelTag["zPos"] = nbt.TAG_Int(cz)
+
+ levelTag["LastUpdate"] = nbt.TAG_Long(0)
+
+ levelTag["Entities"] = nbt.TAG_List()
+ levelTag["TileEntities"] = nbt.TAG_List()
+
+ self.root_tag = chunkTag
+
+ self.dirty = True
+
+ def _load(self, root_tag):
+ self.root_tag = root_tag
+
+ for sec in self.root_tag["Level"].pop("Sections", []):
+ y = sec["Y"].value * 16
+ for name in "Blocks", "Data", "SkyLight", "BlockLight":
+ arr = getattr(self, name)
+ secarray = sec[name].value
+ if name == "Blocks":
+ secarray.shape = (16, 16, 16)
+ else:
+ secarray.shape = (16, 16, 8)
+ secarray = unpackNibbleArray(secarray)
+
+ arr[..., y:y + 16] = secarray.swapaxes(0, 2)
+
+
+ def savedTagData(self):
+ """ does not recalculate any data or light """
+
+ log.debug(u"Saving chunk: {0}".format(self))
+ sanitizeBlocks(self)
+
+ sections = nbt.TAG_List()
+ for y in range(0, self.world.Height, 16):
+ section = nbt.TAG_Compound()
+
+ Blocks = self.Blocks[..., y:y + 16].swapaxes(0, 2)
+ Data = self.Data[..., y:y + 16].swapaxes(0, 2)
+ BlockLight = self.BlockLight[..., y:y + 16].swapaxes(0, 2)
+ SkyLight = self.SkyLight[..., y:y + 16].swapaxes(0, 2)
+
+ if (not Blocks.any() and
+ not BlockLight.any() and
+ (SkyLight == 15).all()):
+ continue
+
+ Data = packNibbleArray(Data)
+ BlockLight = packNibbleArray(BlockLight)
+ SkyLight = packNibbleArray(SkyLight)
+
+ section['Blocks'] = nbt.TAG_Byte_Array(array(Blocks))
+ section['Data'] = nbt.TAG_Byte_Array(array(Data))
+ section['BlockLight'] = nbt.TAG_Byte_Array(array(BlockLight))
+ section['SkyLight'] = nbt.TAG_Byte_Array(array(SkyLight))
+
+ section["Y"] = nbt.TAG_Byte(y / 16)
+ sections.append(section)
+
+ self.root_tag["Level"]["Sections"] = sections
+ data = self.root_tag.save(compressed=False)
+ del self.root_tag["Level"]["Sections"]
+
+ log.debug(u"Saved chunk {0}".format(self))
+ return data
+
+ @property
+ def materials(self):
+ return self.world.materials
+
+
+class AnvilChunk(LightedChunk):
+ """ This is a 16x16xH chunk in an (infinite) world.
+ The properties Blocks, Data, SkyLight, BlockLight, and Heightmap
+ are ndarrays containing the respective blocks in the chunk file.
+ Each array is indexed [x,z,y]. The Data, Skylight, and BlockLight
+ arrays are automatically unpacked from nibble arrays into byte arrays
+ for better handling.
+ """
+
+ def __init__(self, chunkData):
+ self.world = chunkData.world
+ self.chunkPosition = chunkData.chunkPosition
+ self.chunkData = chunkData
+
+
+ def savedTagData(self):
+ return self.chunkData.savedTagData()
+
+
+ def __str__(self):
+ return u"AnvilChunk, coords:{0}, world: {1}, D:{2}, L:{3}".format(self.chunkPosition, self.world.displayName, self.dirty, self.needsLighting)
+
+ @property
+ def needsLighting(self):
+ return self.chunkPosition in self.world.chunksNeedingLighting
+
+ @needsLighting.setter
+ def needsLighting(self, value):
+ if value:
+ self.world.chunksNeedingLighting.add(self.chunkPosition)
+ else:
+ self.world.chunksNeedingLighting.discard(self.chunkPosition)
+
+ def generateHeightMap(self):
+ if self.world.dimNo == DIM_NETHER:
+ self.HeightMap[:] = 0
+ else:
+ computeChunkHeightMap(self.materials, self.Blocks, self.HeightMap)
+
+ def addEntity(self, entityTag):
+
+ def doubleize(name):
+ # This is needed for compatibility with Indev levels. Those levels use TAG_Float for entity motion and pos
+ if name in entityTag:
+ m = entityTag[name]
+ entityTag[name] = nbt.TAG_List([nbt.TAG_Double(i.value) for i in m])
+
+ doubleize("Motion")
+ doubleize("Position")
+
+ self.dirty = True
+ return super(AnvilChunk, self).addEntity(entityTag)
+
+ def removeEntitiesInBox(self, box):
+ self.dirty = True
+ return super(AnvilChunk, self).removeEntitiesInBox(box)
+
+ def removeTileEntitiesInBox(self, box):
+ self.dirty = True
+ return super(AnvilChunk, self).removeTileEntitiesInBox(box)
+
+ # --- AnvilChunkData accessors ---
+
+ @property
+ def root_tag(self):
+ return self.chunkData.root_tag
+
+ @property
+ def dirty(self):
+ return self.chunkData.dirty
+
+ @dirty.setter
+ def dirty(self, val):
+ self.chunkData.dirty = val
+
+ # --- Chunk attributes ---
+
+ @property
+ def materials(self):
+ return self.world.materials
+
+ @property
+ def Blocks(self):
+ return self.chunkData.Blocks
+
+ @property
+ def Data(self):
+ return self.chunkData.Data
+
+ @property
+ def SkyLight(self):
+ return self.chunkData.SkyLight
+
+ @property
+ def BlockLight(self):
+ return self.chunkData.BlockLight
+
+ @property
+ def Biomes(self):
+ return self.root_tag["Level"]["Biomes"].value.reshape((16, 16))
+
+ @property
+ def HeightMap(self):
+ return self.root_tag["Level"]["HeightMap"].value.reshape((16, 16))
+
+ @property
+ def Entities(self):
+ return self.root_tag["Level"]["Entities"]
+
+ @property
+ def TileEntities(self):
+ return self.root_tag["Level"]["TileEntities"]
+
+ @property
+ def TerrainPopulated(self):
+ return self.root_tag["Level"]["TerrainPopulated"].value
+
+ @TerrainPopulated.setter
+ def TerrainPopulated(self, val):
+ """True or False. If False, the game will populate the chunk with
+ ores and vegetation on next load"""
+ self.root_tag["Level"]["TerrainPopulated"].value = val
+ self.dirty = True
+
+
+base36alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
+
+
+def decbase36(s):
+ return int(s, 36)
+
+
+def base36(n):
+ global base36alphabet
+
+ n = int(n)
+ if 0 == n:
+ return '0'
+ neg = ""
+ if n < 0:
+ neg = "-"
+ n = -n
+
+ work = []
+
+ while n:
+ n, digit = divmod(n, 36)
+ work.append(base36alphabet[digit])
+
+ return neg + ''.join(reversed(work))
+
+
+def deflate(data):
+ # zobj = zlib.compressobj(6,zlib.DEFLATED,-zlib.MAX_WBITS,zlib.DEF_MEM_LEVEL,0)
+ # zdata = zobj.compress(data)
+ # zdata += zobj.flush()
+ # return zdata
+ return zlib.compress(data)
+
+
+def inflate(data):
+ return zlib.decompress(data)
+
+
+class ChunkedLevelMixin(MCLevel):
+ def blockLightAt(self, x, y, z):
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+ ch = self.getChunk(xc, zc)
+
+ return ch.BlockLight[xInChunk, zInChunk, y]
+
+ def setBlockLightAt(self, x, y, z, newLight):
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ ch = self.getChunk(xc, zc)
+ ch.BlockLight[xInChunk, zInChunk, y] = newLight
+ ch.chunkChanged(False)
+
+ def blockDataAt(self, x, y, z):
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ try:
+ ch = self.getChunk(xc, zc)
+ except ChunkNotPresent:
+ return 0
+
+ return ch.Data[xInChunk, zInChunk, y]
+
+ def setBlockDataAt(self, x, y, z, newdata):
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ try:
+ ch = self.getChunk(xc, zc)
+ except ChunkNotPresent:
+ return 0
+
+ ch.Data[xInChunk, zInChunk, y] = newdata
+ ch.dirty = True
+ ch.needsLighting = True
+
+ def blockAt(self, x, y, z):
+ """returns 0 for blocks outside the loadable chunks. automatically loads chunks."""
+ if y < 0 or y >= self.Height:
+ return 0
+
+ zc = z >> 4
+ xc = x >> 4
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ try:
+ ch = self.getChunk(xc, zc)
+ except ChunkNotPresent:
+ return 0
+
+ return ch.Blocks[xInChunk, zInChunk, y]
+
+ def setBlockAt(self, x, y, z, blockID):
+ """returns 0 for blocks outside the loadable chunks. automatically loads chunks."""
+ if y < 0 or y >= self.Height:
+ return 0
+
+ zc = z >> 4
+ xc = x >> 4
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ try:
+ ch = self.getChunk(xc, zc)
+ except ChunkNotPresent:
+ return 0
+
+ ch.Blocks[xInChunk, zInChunk, y] = blockID
+ ch.dirty = True
+ ch.needsLighting = True
+
+ def skylightAt(self, x, y, z):
+
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ ch = self.getChunk(xc, zc)
+
+ return ch.SkyLight[xInChunk, zInChunk, y]
+
+ def setSkylightAt(self, x, y, z, lightValue):
+ if y < 0 or y >= self.Height:
+ return 0
+ zc = z >> 4
+ xc = x >> 4
+
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ ch = self.getChunk(xc, zc)
+ skyLight = ch.SkyLight
+
+ oldValue = skyLight[xInChunk, zInChunk, y]
+
+ ch.chunkChanged(False)
+ if oldValue < lightValue:
+ skyLight[xInChunk, zInChunk, y] = lightValue
+ return oldValue < lightValue
+
+ createChunk = NotImplemented
+
+
+
+ def generateLights(self, dirtyChunkPositions=None):
+ return exhaust(self.generateLightsIter(dirtyChunkPositions))
+
+ def generateLightsIter(self, dirtyChunkPositions=None):
+ """ dirtyChunks may be an iterable yielding (xPos,zPos) tuples
+ if none, generate lights for all chunks that need lighting
+ """
+
+ startTime = datetime.now()
+
+ if dirtyChunkPositions is None:
+ dirtyChunkPositions = self.chunksNeedingLighting
+ else:
+ dirtyChunkPositions = (c for c in dirtyChunkPositions if self.containsChunk(*c))
+
+ dirtyChunkPositions = sorted(dirtyChunkPositions)
+
+ maxLightingChunks = getattr(self, 'loadedChunkLimit', 400)
+
+ log.info(u"Asked to light {0} chunks".format(len(dirtyChunkPositions)))
+ chunkLists = [dirtyChunkPositions]
+
+ def reverseChunkPosition((cx, cz)):
+ return cz, cx
+
+ def splitChunkLists(chunkLists):
+ newChunkLists = []
+ for l in chunkLists:
+
+ # list is already sorted on x position, so this splits into left and right
+
+ smallX = l[:len(l) / 2]
+ bigX = l[len(l) / 2:]
+
+ # sort halves on z position
+ smallX = sorted(smallX, key=reverseChunkPosition)
+ bigX = sorted(bigX, key=reverseChunkPosition)
+
+ # add quarters to list
+
+ newChunkLists.append(smallX[:len(smallX) / 2])
+ newChunkLists.append(smallX[len(smallX) / 2:])
+
+ newChunkLists.append(bigX[:len(bigX) / 2])
+ newChunkLists.append(bigX[len(bigX) / 2:])
+
+ return newChunkLists
+
+ while len(chunkLists[0]) > maxLightingChunks:
+ chunkLists = splitChunkLists(chunkLists)
+
+ if len(chunkLists) > 1:
+ log.info(u"Using {0} batches to conserve memory.".format(len(chunkLists)))
+ # batchSize = min(len(a) for a in chunkLists)
+ estimatedTotals = [len(a) * 32 for a in chunkLists]
+ workDone = 0
+
+ for i, dc in enumerate(chunkLists):
+ log.info(u"Batch {0}/{1}".format(i, len(chunkLists)))
+
+ dc = sorted(dc)
+ workTotal = sum(estimatedTotals)
+ t = 0
+ for c, t, p in self._generateLightsIter(dc):
+
+ yield c + workDone, t + workTotal - estimatedTotals[i], p
+
+ estimatedTotals[i] = t
+ workDone += t
+
+ timeDelta = datetime.now() - startTime
+
+ if len(dirtyChunkPositions):
+ log.info(u"Completed in {0}, {1} per chunk".format(timeDelta, dirtyChunkPositions and timeDelta / len(dirtyChunkPositions) or 0))
+
+ return
+
+ def _generateLightsIter(self, dirtyChunkPositions):
+ la = array(self.materials.lightAbsorption)
+ clip(la, 1, 15, la)
+
+ dirtyChunks = set(self.getChunk(*cPos) for cPos in dirtyChunkPositions)
+
+ workDone = 0
+ workTotal = len(dirtyChunks) * 29
+
+ progressInfo = (u"Lighting {0} chunks".format(len(dirtyChunks)))
+ log.info(progressInfo)
+
+ for i, chunk in enumerate(dirtyChunks):
+
+ chunk.chunkChanged()
+ yield i, workTotal, progressInfo
+ assert chunk.dirty and chunk.needsLighting
+
+ workDone += len(dirtyChunks)
+ workTotal = len(dirtyChunks)
+
+ for ch in list(dirtyChunks):
+ # relight all blocks in neighboring chunks in case their light source disappeared.
+ cx, cz = ch.chunkPosition
+ for dx, dz in itertools.product((-1, 0, 1), (-1, 0, 1)):
+ try:
+ ch = self.getChunk(cx + dx, cz + dz)
+ except (ChunkNotPresent, ChunkMalformed):
+ continue
+ dirtyChunks.add(ch)
+ ch.dirty = True
+
+ dirtyChunks = sorted(dirtyChunks, key=lambda x: x.chunkPosition)
+ workTotal += len(dirtyChunks) * 28
+
+ for i, chunk in enumerate(dirtyChunks):
+ chunk.BlockLight[:] = self.materials.lightEmission[chunk.Blocks]
+ chunk.dirty = True
+
+ zeroChunk = ZeroChunk(self.Height)
+ zeroChunk.BlockLight[:] = 0
+ zeroChunk.SkyLight[:] = 0
+
+ startingDirtyChunks = dirtyChunks
+
+ oldLeftEdge = zeros((1, 16, self.Height), 'uint8')
+ oldBottomEdge = zeros((16, 1, self.Height), 'uint8')
+ oldChunk = zeros((16, 16, self.Height), 'uint8')
+ if self.dimNo in (-1, 1):
+ lights = ("BlockLight",)
+ else:
+ lights = ("BlockLight", "SkyLight")
+ log.info(u"Dispersing light...")
+
+ def clipLight(light):
+ # light arrays are all uint8 by default, so when results go negative
+ # they become large instead. reinterpret as signed int using view()
+ # and then clip to range
+ light.view('int8').clip(0, 15, light)
+
+ for j, light in enumerate(lights):
+ zerochunkLight = getattr(zeroChunk, light)
+ newDirtyChunks = list(startingDirtyChunks)
+
+ work = 0
+
+ for i in range(14):
+ if len(newDirtyChunks) == 0:
+ workTotal -= len(startingDirtyChunks) * (14 - i)
+ break
+
+ progressInfo = u"{0} Pass {1}: {2} chunks".format(light, i, len(newDirtyChunks))
+ log.info(progressInfo)
+
+# propagate light!
+# for each of the six cardinal directions, figure a new light value for
+# adjoining blocks by reducing this chunk's light by light absorption and fall off.
+# compare this new light value against the old light value and update with the maximum.
+#
+# we calculate all chunks one step before moving to the next step, to ensure all gaps at chunk edges are filled.
+# we do an extra cycle because lights sent across edges may lag by one cycle.
+#
+# xxx this can be optimized by finding the highest and lowest blocks
+# that changed after one pass, and only calculating changes for that
+# vertical slice on the next pass. newDirtyChunks would have to be a
+# list of (cPos, miny, maxy) tuples or a cPos : (miny, maxy) dict
+
+ newDirtyChunks = set(newDirtyChunks)
+ newDirtyChunks.discard(zeroChunk)
+
+ dirtyChunks = sorted(newDirtyChunks, key=lambda x: x.chunkPosition)
+
+ newDirtyChunks = list()
+
+ for chunk in dirtyChunks:
+ (cx, cz) = chunk.chunkPosition
+ neighboringChunks = {}
+
+ for dir, dx, dz in ((FaceXDecreasing, -1, 0),
+ (FaceXIncreasing, 1, 0),
+ (FaceZDecreasing, 0, -1),
+ (FaceZIncreasing, 0, 1)):
+ try:
+ neighboringChunks[dir] = self.getChunk(cx + dx, cz + dz)
+ except (ChunkNotPresent, ChunkMalformed):
+ neighboringChunks[dir] = zeroChunk
+ neighboringChunks[dir].dirty = True
+
+ chunkLa = la[chunk.Blocks]
+ chunkLight = getattr(chunk, light)
+ oldChunk[:] = chunkLight[:]
+
+ ### Spread light toward -X
+
+ nc = neighboringChunks[FaceXDecreasing]
+ ncLight = getattr(nc, light)
+ oldLeftEdge[:] = ncLight[15:16, :, 0:self.Height] # save the old left edge
+
+ # left edge
+ newlight = (chunkLight[0:1, :, :self.Height] - la[nc.Blocks[15:16, :, 0:self.Height]])
+ clipLight(newlight)
+
+ maximum(ncLight[15:16, :, 0:self.Height], newlight, ncLight[15:16, :, 0:self.Height])
+
+ # chunk body
+ newlight = (chunkLight[1:16, :, 0:self.Height] - chunkLa[0:15, :, 0:self.Height])
+ clipLight(newlight)
+
+ maximum(chunkLight[0:15, :, 0:self.Height], newlight, chunkLight[0:15, :, 0:self.Height])
+
+ # right edge
+ nc = neighboringChunks[FaceXIncreasing]
+ ncLight = getattr(nc, light)
+
+ newlight = ncLight[0:1, :, :self.Height] - chunkLa[15:16, :, 0:self.Height]
+ clipLight(newlight)
+
+ maximum(chunkLight[15:16, :, 0:self.Height], newlight, chunkLight[15:16, :, 0:self.Height])
+
+ ### Spread light toward +X
+
+ # right edge
+ nc = neighboringChunks[FaceXIncreasing]
+ ncLight = getattr(nc, light)
+
+ newlight = (chunkLight[15:16, :, 0:self.Height] - la[nc.Blocks[0:1, :, 0:self.Height]])
+ clipLight(newlight)
+
+ maximum(ncLight[0:1, :, 0:self.Height], newlight, ncLight[0:1, :, 0:self.Height])
+
+ # chunk body
+ newlight = (chunkLight[0:15, :, 0:self.Height] - chunkLa[1:16, :, 0:self.Height])
+ clipLight(newlight)
+
+ maximum(chunkLight[1:16, :, 0:self.Height], newlight, chunkLight[1:16, :, 0:self.Height])
+
+ # left edge
+ nc = neighboringChunks[FaceXDecreasing]
+ ncLight = getattr(nc, light)
+
+ newlight = ncLight[15:16, :, :self.Height] - chunkLa[0:1, :, 0:self.Height]
+ clipLight(newlight)
+
+ maximum(chunkLight[0:1, :, 0:self.Height], newlight, chunkLight[0:1, :, 0:self.Height])
+
+ zerochunkLight[:] = 0 # zero the zero chunk after each direction
+ # so the lights it absorbed don't affect the next pass
+
+ # check if the left edge changed and dirty or compress the chunk appropriately
+ if (oldLeftEdge != ncLight[15:16, :, :self.Height]).any():
+ # chunk is dirty
+ newDirtyChunks.append(nc)
+
+ ### Spread light toward -Z
+
+ # bottom edge
+ nc = neighboringChunks[FaceZDecreasing]
+ ncLight = getattr(nc, light)
+ oldBottomEdge[:] = ncLight[:, 15:16, :self.Height] # save the old bottom edge
+
+ newlight = (chunkLight[:, 0:1, :self.Height] - la[nc.Blocks[:, 15:16, :self.Height]])
+ clipLight(newlight)
+
+ maximum(ncLight[:, 15:16, :self.Height], newlight, ncLight[:, 15:16, :self.Height])
+
+ # chunk body
+ newlight = (chunkLight[:, 1:16, :self.Height] - chunkLa[:, 0:15, :self.Height])
+ clipLight(newlight)
+
+ maximum(chunkLight[:, 0:15, :self.Height], newlight, chunkLight[:, 0:15, :self.Height])
+
+ # top edge
+ nc = neighboringChunks[FaceZIncreasing]
+ ncLight = getattr(nc, light)
+
+ newlight = ncLight[:, 0:1, :self.Height] - chunkLa[:, 15:16, 0:self.Height]
+ clipLight(newlight)
+
+ maximum(chunkLight[:, 15:16, 0:self.Height], newlight, chunkLight[:, 15:16, 0:self.Height])
+
+ ### Spread light toward +Z
+
+ # top edge
+ nc = neighboringChunks[FaceZIncreasing]
+
+ ncLight = getattr(nc, light)
+
+ newlight = (chunkLight[:, 15:16, :self.Height] - la[nc.Blocks[:, 0:1, :self.Height]])
+ clipLight(newlight)
+
+ maximum(ncLight[:, 0:1, :self.Height], newlight, ncLight[:, 0:1, :self.Height])
+
+ # chunk body
+ newlight = (chunkLight[:, 0:15, :self.Height] - chunkLa[:, 1:16, :self.Height])
+ clipLight(newlight)
+
+ maximum(chunkLight[:, 1:16, :self.Height], newlight, chunkLight[:, 1:16, :self.Height])
+
+ # bottom edge
+ nc = neighboringChunks[FaceZDecreasing]
+ ncLight = getattr(nc, light)
+
+ newlight = ncLight[:, 15:16, :self.Height] - chunkLa[:, 0:1, 0:self.Height]
+ clipLight(newlight)
+
+ maximum(chunkLight[:, 0:1, 0:self.Height], newlight, chunkLight[:, 0:1, 0:self.Height])
+
+ zerochunkLight[:] = 0
+
+ if (oldBottomEdge != ncLight[:, 15:16, :self.Height]).any():
+ newDirtyChunks.append(nc)
+
+ newlight = (chunkLight[:, :, 0:self.Height - 1] - chunkLa[:, :, 1:self.Height])
+ clipLight(newlight)
+ maximum(chunkLight[:, :, 1:self.Height], newlight, chunkLight[:, :, 1:self.Height])
+
+ newlight = (chunkLight[:, :, 1:self.Height] - chunkLa[:, :, 0:self.Height - 1])
+ clipLight(newlight)
+ maximum(chunkLight[:, :, 0:self.Height - 1], newlight, chunkLight[:, :, 0:self.Height - 1])
+
+ if (oldChunk != chunkLight).any():
+ newDirtyChunks.append(chunk)
+
+ work += 1
+ yield workDone + work, workTotal, progressInfo
+
+ workDone += work
+ workTotal -= len(startingDirtyChunks)
+ workTotal += work
+
+ work = 0
+
+ for ch in startingDirtyChunks:
+ ch.needsLighting = False
+
+
+def TagProperty(tagName, tagType, default_or_func=None):
+ def getter(self):
+ if tagName not in self.root_tag["Data"]:
+ if hasattr(default_or_func, "__call__"):
+ default = default_or_func(self)
+ else:
+ default = default_or_func
+
+ self.root_tag["Data"][tagName] = tagType(default)
+ return self.root_tag["Data"][tagName].value
+
+ def setter(self, val):
+ self.root_tag["Data"][tagName] = tagType(value=val)
+
+ return property(getter, setter)
+
+class AnvilWorldFolder(object):
+ def __init__(self, filename):
+ if not os.path.exists(filename):
+ os.mkdir(filename)
+
+ elif not os.path.isdir(filename):
+ raise IOError, "AnvilWorldFolder: Not a folder: %s" % filename
+
+ self.filename = filename
+ self.regionFiles = {}
+
+ # --- File paths ---
+
+ def getFilePath(self, path):
+ path = path.replace("/", os.path.sep)
+ return os.path.join(self.filename, path)
+
+ def getFolderPath(self, path):
+ path = self.getFilePath(path)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ return path
+
+ # --- Region files ---
+
+ def getRegionFilename(self, rx, rz):
+ return os.path.join(self.getFolderPath("region"), "r.%s.%s.%s" % (rx, rz, "mca"))
+
+ def getRegionFile(self, rx, rz):
+ regionFile = self.regionFiles.get((rx, rz))
+ if regionFile:
+ return regionFile
+ regionFile = MCRegionFile(self.getRegionFilename(rx, rz), (rx, rz))
+ self.regionFiles[rx, rz] = regionFile
+ return regionFile
+
+ def getRegionForChunk(self, cx, cz):
+ rx = cx >> 5
+ rz = cz >> 5
+ return self.getRegionFile(rx, rz)
+
+ def closeRegions(self):
+ for rf in self.regionFiles.values():
+ rf.close()
+
+ self.regionFiles = {}
+
+ # --- Chunks and chunk listing ---
+
+ def tryLoadRegionFile(self, filepath):
+ filename = os.path.basename(filepath)
+ bits = filename.split('.')
+ if len(bits) < 4 or bits[0] != 'r' or bits[3] != "mca":
+ return None
+
+ try:
+ rx, rz = map(int, bits[1:3])
+ except ValueError:
+ return None
+
+ return MCRegionFile(filepath, (rx, rz))
+
+ def findRegionFiles(self):
+ regionDir = self.getFolderPath("region")
+
+ regionFiles = os.listdir(regionDir)
+ for filename in regionFiles:
+ yield os.path.join(regionDir, filename)
+
+ def listChunks(self):
+ chunks = set()
+
+ for filepath in self.findRegionFiles():
+ regionFile = self.tryLoadRegionFile(filepath)
+ if regionFile is None:
+ continue
+
+ if regionFile.offsets.any():
+ rx, rz = regionFile.regionCoords
+ self.regionFiles[rx, rz] = regionFile
+
+ for index, offset in enumerate(regionFile.offsets):
+ if offset:
+ cx = index & 0x1f
+ cz = index >> 5
+
+ cx += rx << 5
+ cz += rz << 5
+
+ chunks.add((cx, cz))
+ else:
+ log.info(u"Removing empty region file {0}".format(filepath))
+ regionFile.close()
+ os.unlink(regionFile.path)
+
+ return chunks
+
+ def containsChunk(self, cx, cz):
+ rx = cx >> 5
+ rz = cz >> 5
+ if not os.path.exists(self.getRegionFilename(rx, rz)):
+ return False
+
+ return self.getRegionForChunk(cx, cz).containsChunk(cx, cz)
+
+ def deleteChunk(self, cx, cz):
+ r = cx >> 5, cz >> 5
+ rf = self.getRegionFile(*r)
+ if rf:
+ rf.setOffset(cx & 0x1f, cz & 0x1f, 0)
+ if (rf.offsets == 0).all():
+ rf.close()
+ os.unlink(rf.path)
+ del self.regionFiles[r]
+
+ def readChunk(self, cx, cz):
+ if not self.containsChunk(cx, cz):
+ raise ChunkNotPresent((cx, cz))
+
+ return self.getRegionForChunk(cx, cz).readChunk(cx, cz)
+
+ def saveChunk(self, cx, cz, data):
+ regionFile = self.getRegionForChunk(cx, cz)
+ regionFile.saveChunk(cx, cz, data)
+
+ def copyChunkFrom(self, worldFolder, cx, cz):
+ fromRF = worldFolder.getRegionForChunk(cx, cz)
+ rf = self.getRegionForChunk(cx, cz)
+ rf.copyChunkFrom(fromRF, cx, cz)
+
+class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
+
+ def __init__(self, filename=None, create=False, random_seed=None, last_played=None, readonly=False):
+ """
+ Load an Alpha level from the given filename. It can point to either
+ a level.dat or a folder containing one. If create is True, it will
+ also create the world using the random_seed and last_played arguments.
+ If they are none, a random 64-bit seed will be selected for RandomSeed
+ and long(time.time() * 1000) will be used for LastPlayed.
+
+ If you try to create an existing world, its level.dat will be replaced.
+ """
+
+ self.Length = 0
+ self.Width = 0
+ self.Height = 256
+
+ self.playerTagCache = {}
+ self.players = []
+ assert not (create and readonly)
+
+ if os.path.basename(filename) in ("level.dat", "level.dat_old"):
+ filename = os.path.dirname(filename)
+
+ if not os.path.exists(filename):
+ if not create:
+ raise IOError('File not found')
+
+ os.mkdir(filename)
+
+ if not os.path.isdir(filename):
+ raise IOError('File is not a Minecraft Alpha world')
+
+
+ self.worldFolder = AnvilWorldFolder(filename)
+ self.filename = self.worldFolder.getFilePath("level.dat")
+ self.readonly = readonly
+ if not readonly:
+ self.acquireSessionLock()
+
+ workFolderPath = self.worldFolder.getFolderPath("##MCEDIT.TEMP##")
+ if os.path.exists(workFolderPath):
+ # xxxxxxx Opening a world a second time deletes the first world's work folder and crashes when the first
+ # world tries to read a modified chunk from the work folder. This mainly happens when importing a world
+ # into itself after modifying it.
+ shutil.rmtree(workFolderPath, True)
+
+ self.unsavedWorkFolder = AnvilWorldFolder(workFolderPath)
+
+ # maps (cx, cz) pairs to AnvilChunk
+ self._loadedChunks = weakref.WeakValueDictionary()
+
+ # maps (cx, cz) pairs to AnvilChunkData
+ self._loadedChunkData = {}
+
+ self.chunksNeedingLighting = set()
+ self._allChunks = None
+ self.dimensions = {}
+
+ self.loadLevelDat(create, random_seed, last_played)
+
+ assert self.version == self.VERSION_ANVIL, "Pre-Anvil world formats are not supported (for now)"
+
+
+ self.playersFolder = self.worldFolder.getFolderPath("players")
+ self.players = [x[:-4] for x in os.listdir(self.playersFolder) if x.endswith(".dat")]
+ if "Player" in self.root_tag["Data"]:
+ self.players.append("Player")
+
+ self.preloadDimensions()
+
+ # --- Load, save, create ---
+
+ def _create(self, filename, random_seed, last_played):
+
+ # create a new level
+ root_tag = nbt.TAG_Compound()
+ root_tag["Data"] = nbt.TAG_Compound()
+ root_tag["Data"]["SpawnX"] = nbt.TAG_Int(0)
+ root_tag["Data"]["SpawnY"] = nbt.TAG_Int(2)
+ root_tag["Data"]["SpawnZ"] = nbt.TAG_Int(0)
+
+ if last_played is None:
+ last_played = long(time.time() * 1000)
+ if random_seed is None:
+ random_seed = long(random.random() * 0xffffffffffffffffL) - 0x8000000000000000L
+
+ self.root_tag = root_tag
+ root_tag["Data"]['version'] = nbt.TAG_Int(self.VERSION_ANVIL)
+
+ self.LastPlayed = long(last_played)
+ self.RandomSeed = long(random_seed)
+ self.SizeOnDisk = 0
+ self.Time = 1
+ self.LevelName = os.path.basename(self.worldFolder.filename)
+
+ ### if singleplayer:
+
+ self.createPlayer("Player")
+
+ def acquireSessionLock(self):
+ lockfile = self.worldFolder.getFilePath("session.lock")
+ self.initTime = int(time.time() * 1000)
+ with file(lockfile, "wb") as f:
+ f.write(struct.pack(">q", self.initTime))
+
+
+ def checkSessionLock(self):
+ if self.readonly:
+ raise SessionLockLost, "World is opened read only."
+
+ lockfile = self.worldFolder.getFilePath("session.lock")
+ try:
+ (lock, ) = struct.unpack(">q", file(lockfile, "rb").read())
+ except struct.error:
+ lock = -1
+ if lock != self.initTime:
+ raise SessionLockLost, "Session lock lost. This world is being accessed from another location."
+
+ def loadLevelDat(self, create=False, random_seed=None, last_played=None):
+
+ if create:
+ self._create(self.filename, random_seed, last_played)
+ self.saveInPlace()
+ else:
+ try:
+ self.root_tag = nbt.load(self.filename)
+ except Exception, e:
+ filename_old = self.worldFolder.getFilePath("level.dat_old")
+ log.info("Error loading level.dat, trying level.dat_old ({0})".format(e))
+ try:
+ self.root_tag = nbt.load(filename_old)
+ log.info("level.dat restored from backup.")
+ self.saveInPlace()
+ except Exception, e:
+ traceback.print_exc()
+ print repr(e)
+ log.info("Error loading level.dat_old. Initializing with defaults.")
+ self._create(self.filename, random_seed, last_played)
+
+ def saveInPlace(self):
+ if self.readonly:
+ raise IOError, "World is opened read only."
+
+ self.checkSessionLock()
+
+ for level in self.dimensions.itervalues():
+ level.saveInPlace(True)
+
+ dirtyChunkCount = 0
+ for chunk in self._loadedChunkData.itervalues():
+ cx, cz = chunk.chunkPosition
+ if chunk.dirty:
+ data = chunk.savedTagData()
+ dirtyChunkCount += 1
+ self.worldFolder.saveChunk(cx, cz, data)
+ chunk.dirty = False
+
+ for cx, cz in self.unsavedWorkFolder.listChunks():
+ if (cx, cz) not in self._loadedChunkData:
+ data = self.unsavedWorkFolder.readChunk(cx, cz)
+ self.worldFolder.saveChunk(cx, cz, data)
+ dirtyChunkCount += 1
+
+
+ self.unsavedWorkFolder.closeRegions()
+ shutil.rmtree(self.unsavedWorkFolder.filename, True)
+ os.mkdir(self.unsavedWorkFolder.filename)
+
+ for path, tag in self.playerTagCache.iteritems():
+ tag.save(path)
+
+ self.playerTagCache.clear()
+
+ self.root_tag.save(self.filename)
+ log.info(u"Saved {0} chunks (dim {1})".format(dirtyChunkCount, self.dimNo))
+
+ def unload(self):
+ """
+ Unload all chunks and close all open filehandles.
+ """
+ self.worldFolder.closeRegions()
+ if not self.readonly:
+ self.unsavedWorkFolder.closeRegions()
+
+ self._allChunks = None
+ self._loadedChunks.clear()
+ self._loadedChunkData.clear()
+
+ def close(self):
+ """
+ Unload all chunks and close all open filehandles. Discard any unsaved data.
+ """
+ self.unload()
+ try:
+ self.checkSessionLock()
+ shutil.rmtree(self.unsavedWorkFolder.filename, True)
+ except SessionLockLost:
+ pass
+
+ # --- Resource limits ---
+
+ loadedChunkLimit = 400
+
+ # --- Constants ---
+
+ GAMETYPE_SURVIVAL = 0
+ GAMETYPE_CREATIVE = 1
+
+ VERSION_MCR = 19132
+ VERSION_ANVIL = 19133
+
+ # --- Instance variables ---
+
+ materials = alphaMaterials
+ isInfinite = True
+ parentWorld = None
+ dimNo = 0
+ Height = 256
+ _bounds = None
+
+ # --- NBT Tag variables ---
+
+ SizeOnDisk = TagProperty('SizeOnDisk', nbt.TAG_Long, 0)
+ RandomSeed = TagProperty('RandomSeed', nbt.TAG_Long, 0)
+ Time = TagProperty('Time', nbt.TAG_Long, 0) # Age of the world in ticks. 20 ticks per second; 24000 ticks per day.
+ LastPlayed = TagProperty('LastPlayed', nbt.TAG_Long, lambda self: long(time.time() * 1000))
+
+ LevelName = TagProperty('LevelName', nbt.TAG_String, lambda self: self.displayName)
+
+ MapFeatures = TagProperty('MapFeatures', nbt.TAG_Byte, 1)
+
+ GameType = TagProperty('GameType', nbt.TAG_Int, 0) # 0 for survival, 1 for creative
+
+ version = TagProperty('version', nbt.TAG_Int, VERSION_ANVIL)
+
+ # --- World info ---
+
+ def __str__(self):
+ return "MCInfdevOldLevel(\"%s\")" % os.path.basename(self.worldFolder.filename)
+
+ @property
+ def displayName(self):
+ # shortname = os.path.basename(self.filename)
+ # if shortname == "level.dat":
+ shortname = os.path.basename(os.path.dirname(self.filename))
+
+ return shortname
+
+ @property
+ def bounds(self):
+ if self._bounds is None:
+ self._bounds = self.getWorldBounds()
+ return self._bounds
+
+ def getWorldBounds(self):
+ if self.chunkCount == 0:
+ return BoundingBox((0, 0, 0), (0, 0, 0))
+
+ allChunks = array(list(self.allChunks))
+ mincx = (allChunks[:, 0]).min()
+ maxcx = (allChunks[:, 0]).max()
+ mincz = (allChunks[:, 1]).min()
+ maxcz = (allChunks[:, 1]).max()
+
+ origin = (mincx << 4, 0, mincz << 4)
+ size = ((maxcx - mincx + 1) << 4, self.Height, (maxcz - mincz + 1) << 4)
+
+ return BoundingBox(origin, size)
+
+ @property
+ def size(self):
+ return self.bounds.size
+
+ # --- Format detection ---
+
+ @classmethod
+ def _isLevel(cls, filename):
+
+ if os.path.exists(os.path.join(filename, "chunks.dat")):
+ return False # exclude Pocket Edition folders
+
+ if not os.path.isdir(filename):
+ f = os.path.basename(filename)
+ if f not in ("level.dat", "level.dat_old"):
+ return False
+ filename = os.path.dirname(filename)
+
+ files = os.listdir(filename)
+ if "level.dat" in files or "level.dat_old" in files:
+ return True
+
+ return False
+
+ # --- Dimensions ---
+
+ def preloadDimensions(self):
+ worldDirs = os.listdir(self.worldFolder.filename)
+
+ for dirname in worldDirs:
+ if dirname.startswith("DIM"):
+ try:
+ dimNo = int(dirname[3:])
+ log.info("Found dimension {0}".format(dirname))
+ dim = MCAlphaDimension(self, dimNo)
+ self.dimensions[dimNo] = dim
+ except Exception, e:
+ log.error(u"Error loading dimension {0}: {1}".format(dirname, e))
+
+ def getDimension(self, dimNo):
+ if self.dimNo != 0:
+ return self.parentWorld.getDimension(dimNo)
+
+ if dimNo in self.dimensions:
+ return self.dimensions[dimNo]
+ dim = MCAlphaDimension(self, dimNo, create=True)
+ self.dimensions[dimNo] = dim
+ return dim
+
+ # --- Region I/O ---
+
+ def preloadChunkPositions(self):
+ log.info(u"Scanning for regions...")
+ self._allChunks = self.worldFolder.listChunks()
+ if not self.readonly:
+ self._allChunks.update(self.unsavedWorkFolder.listChunks())
+ self._allChunks.update(self._loadedChunkData.iterkeys())
+
+ def getRegionForChunk(self, cx, cz):
+ return self.worldFolder.getRegionFile(cx, cz)
+
+ # --- Chunk I/O ---
+
+ def dirhash(self, n):
+ return self.dirhashes[n % 64]
+
+ def _dirhash(self):
+ n = self
+ n = n % 64
+ s = u""
+ if n >= 36:
+ s += u"1"
+ n -= 36
+ s += u"0123456789abcdefghijklmnopqrstuvwxyz"[n]
+
+ return s
+
+ dirhashes = [_dirhash(n) for n in range(64)]
+
+ def _oldChunkFilename(self, cx, cz):
+ return self.worldFolder.getFilePath("%s/%s/c.%s.%s.dat" % (self.dirhash(cx), self.dirhash(cz), base36(cx), base36(cz)))
+
+ def extractChunksInBox(self, box, parentFolder):
+ for cx, cz in box.chunkPositions:
+ if self.containsChunk(cx, cz):
+ self.extractChunk(cx, cz, parentFolder)
+
+ def extractChunk(self, cx, cz, parentFolder):
+ if not os.path.exists(parentFolder):
+ os.mkdir(parentFolder)
+
+ chunkFilename = self._oldChunkFilename(cx, cz)
+ outputFile = os.path.join(parentFolder, os.path.basename(chunkFilename))
+
+ chunk = self.getChunk(cx, cz)
+
+ chunk.root_tag.save(outputFile)
+
+ @property
+ def chunkCount(self):
+ """Returns the number of chunks in the level. May initiate a costly
+ chunk scan."""
+ if self._allChunks is None:
+ self.preloadChunkPositions()
+ return len(self._allChunks)
+
+ @property
+ def allChunks(self):
+ """Iterates over (xPos, zPos) tuples, one for each chunk in the level.
+ May initiate a costly chunk scan."""
+ if self._allChunks is None:
+ self.preloadChunkPositions()
+ return self._allChunks.__iter__()
+
+ def copyChunkFrom(self, world, cx, cz):
+ """
+ Copy a chunk from world into the same chunk position in self.
+ """
+ assert isinstance(world, MCInfdevOldLevel)
+ if self.readonly:
+ raise IOError, "World is opened read only."
+ self.checkSessionLock()
+
+ destChunk = self._loadedChunks.get((cx, cz))
+ sourceChunk = world._loadedChunks.get((cx, cz))
+
+ if sourceChunk:
+ if destChunk:
+ log.debug("Both chunks loaded. Using block copy.")
+ # Both chunks loaded. Use block copy.
+ self.copyBlocksFrom(world, destChunk.bounds, destChunk.bounds.origin)
+ return
+ else:
+ log.debug("Source chunk loaded. Saving into work folder.")
+
+ # Only source chunk loaded. Discard destination chunk and save source chunk in its place.
+ self._loadedChunkData.pop((cx, cz), None)
+ self.unsavedWorkFolder.saveChunk(cx, cz, sourceChunk.savedTagData())
+ return
+ else:
+ if destChunk:
+ log.debug("Destination chunk loaded. Using block copy.")
+ # Only destination chunk loaded. Use block copy.
+ self.copyBlocksFrom(world, destChunk.bounds, destChunk.bounds.origin)
+ else:
+ log.debug("No chunk loaded. Using world folder.copyChunkFrom")
+ # Neither chunk loaded. Copy via world folders.
+ self._loadedChunkData.pop((cx, cz), None)
+
+ # If the source chunk is dirty, write it to the work folder.
+ chunkData = world._loadedChunkData.pop((cx, cz), None)
+ if chunkData and chunkData.dirty:
+ data = chunkData.savedTagData()
+ world.unsavedWorkFolder.saveChunk(cx, cz, data)
+
+ if world.unsavedWorkFolder.containsChunk(cx, cz):
+ sourceFolder = world.unsavedWorkFolder
+ else:
+ sourceFolder = world.worldFolder
+
+ self.unsavedWorkFolder.copyChunkFrom(sourceFolder, cx, cz)
+
+ def _getChunkBytes(self, cx, cz):
+ if not self.readonly and self.unsavedWorkFolder.containsChunk(cx, cz):
+ return self.unsavedWorkFolder.readChunk(cx, cz)
+ else:
+ return self.worldFolder.readChunk(cx, cz)
+
+ def _getChunkData(self, cx, cz):
+ chunkData = self._loadedChunkData.get((cx, cz))
+ if chunkData is not None: return chunkData
+
+ try:
+ data = self._getChunkBytes(cx, cz)
+ root_tag = nbt.load(buf=data)
+ chunkData = AnvilChunkData(self, (cx, cz), root_tag)
+ except (MemoryError, ChunkNotPresent):
+ raise
+ except Exception, e:
+ raise ChunkMalformed, "Chunk {0} had an error: {1!r}".format((cx, cz), e), sys.exc_info()[2]
+
+ if not self.readonly and self.unsavedWorkFolder.containsChunk(cx, cz):
+ chunkData.dirty = True
+
+ self._storeLoadedChunkData(chunkData)
+
+ return chunkData
+
+ def _storeLoadedChunkData(self, chunkData):
+ if len(self._loadedChunkData) > self.loadedChunkLimit:
+ # Try to find a chunk to unload. The chunk must not be in _loadedChunks, which contains only chunks that
+ # are in use by another object. If the chunk is dirty, save it to the temporary folder.
+ if not self.readonly:
+ self.checkSessionLock()
+ for (ocx, ocz), oldChunkData in self._loadedChunkData.items():
+ if (ocx, ocz) not in self._loadedChunks:
+ if oldChunkData.dirty and not self.readonly:
+ data = oldChunkData.savedTagData()
+ self.unsavedWorkFolder.saveChunk(ocx, ocz, data)
+
+ del self._loadedChunkData[ocx, ocz]
+ break
+
+ self._loadedChunkData[chunkData.chunkPosition] = chunkData
+
+ def getChunk(self, cx, cz):
+ """ read the chunk from disk, load it, and return it."""
+
+ chunk = self._loadedChunks.get((cx, cz))
+ if chunk is not None:
+ return chunk
+
+ chunkData = self._getChunkData(cx, cz)
+ chunk = AnvilChunk(chunkData)
+
+ self._loadedChunks[cx, cz] = chunk
+ return chunk
+
+ def markDirtyChunk(self, cx, cz):
+ self.getChunk(cx, cz).chunkChanged()
+
+ def markDirtyBox(self, box):
+ for cx, cz in box.chunkPositions:
+ self.markDirtyChunk(cx, cz)
+
+ def listDirtyChunks(self):
+ for cPos, chunkData in self._loadedChunkData.iteritems():
+ if chunkData.dirty:
+ yield cPos
+
+ # --- HeightMaps ---
+
+ def heightMapAt(self, x, z):
+ zc = z >> 4
+ xc = x >> 4
+ xInChunk = x & 0xf
+ zInChunk = z & 0xf
+
+ ch = self.getChunk(xc, zc)
+
+ heightMap = ch.HeightMap
+
+ return heightMap[zInChunk, xInChunk] # HeightMap indices are backwards
+
+ # --- Entities and TileEntities ---
+
+ def addEntity(self, entityTag):
+ assert isinstance(entityTag, nbt.TAG_Compound)
+ x, y, z = map(lambda x: int(floor(x)), Entity.pos(entityTag))
+
+ try:
+ chunk = self.getChunk(x >> 4, z >> 4)
+ except (ChunkNotPresent, ChunkMalformed):
+ return None
+ # raise Error, can't find a chunk?
+ chunk.addEntity(entityTag)
+ chunk.dirty = True
+
+ def tileEntityAt(self, x, y, z):
+ chunk = self.getChunk(x >> 4, z >> 4)
+ return chunk.tileEntityAt(x, y, z)
+
+ def addTileEntity(self, tileEntityTag):
+ assert isinstance(tileEntityTag, nbt.TAG_Compound)
+ if not 'x' in tileEntityTag:
+ return
+ x, y, z = TileEntity.pos(tileEntityTag)
+
+ try:
+ chunk = self.getChunk(x >> 4, z >> 4)
+ except (ChunkNotPresent, ChunkMalformed):
+ return
+ # raise Error, can't find a chunk?
+ chunk.addTileEntity(tileEntityTag)
+ chunk.dirty = True
+
+ def getEntitiesInBox(self, box):
+ entities = []
+ for chunk, slices, point in self.getChunkSlices(box):
+ entities += chunk.getEntitiesInBox(box)
+
+ return entities
+
+ def removeEntitiesInBox(self, box):
+ count = 0
+ for chunk, slices, point in self.getChunkSlices(box):
+ count += chunk.removeEntitiesInBox(box)
+
+ log.info("Removed {0} entities".format(count))
+ return count
+
+ def removeTileEntitiesInBox(self, box):
+ count = 0
+ for chunk, slices, point in self.getChunkSlices(box):
+ count += chunk.removeTileEntitiesInBox(box)
+
+ log.info("Removed {0} tile entities".format(count))
+ return count
+
+ # --- Chunk manipulation ---
+
+ def containsChunk(self, cx, cz):
+ if self._allChunks is not None:
+ return (cx, cz) in self._allChunks
+ if (cx, cz) in self._loadedChunkData:
+ return True
+
+ return self.worldFolder.containsChunk(cx, cz)
+
+ def containsPoint(self, x, y, z):
+ if y < 0 or y > 127:
+ return False
+ return self.containsChunk(x >> 4, z >> 4)
+
+ def createChunk(self, cx, cz):
+ if self.containsChunk(cx, cz):
+ raise ValueError("{0}:Chunk {1} already present!".format(self, (cx, cz)))
+ if self._allChunks is not None:
+ self._allChunks.add((cx, cz))
+
+ self._storeLoadedChunkData(AnvilChunkData(self, (cx, cz), create=True))
+ self._bounds = None
+
+ def createChunks(self, chunks):
+
+ i = 0
+ ret = []
+ for cx, cz in chunks:
+ i += 1
+ if not self.containsChunk(cx, cz):
+ ret.append((cx, cz))
+ self.createChunk(cx, cz)
+ assert self.containsChunk(cx, cz), "Just created {0} but it didn't take".format((cx, cz))
+ if i % 100 == 0:
+ log.info(u"Chunk {0}...".format(i))
+
+ log.info("Created {0} chunks.".format(len(ret)))
+
+ return ret
+
+ def createChunksInBox(self, box):
+ log.info(u"Creating {0} chunks in {1}".format((box.maxcx - box.mincx) * (box.maxcz - box.mincz), ((box.mincx, box.mincz), (box.maxcx, box.maxcz))))
+ return self.createChunks(box.chunkPositions)
+
+ def deleteChunk(self, cx, cz):
+ self.worldFolder.deleteChunk(cx, cz)
+ if self._allChunks is not None:
+ self._allChunks.discard((cx, cz))
+
+ self._bounds = None
+
+
+ def deleteChunksInBox(self, box):
+ log.info(u"Deleting {0} chunks in {1}".format((box.maxcx - box.mincx) * (box.maxcz - box.mincz), ((box.mincx, box.mincz), (box.maxcx, box.maxcz))))
+ i = 0
+ ret = []
+ for cx, cz in itertools.product(xrange(box.mincx, box.maxcx), xrange(box.mincz, box.maxcz)):
+ i += 1
+ if self.containsChunk(cx, cz):
+ self.deleteChunk(cx, cz)
+ ret.append((cx, cz))
+
+ assert not self.containsChunk(cx, cz), "Just deleted {0} but it didn't take".format((cx, cz))
+
+ if i % 100 == 0:
+ log.info(u"Chunk {0}...".format(i))
+
+ return ret
+
+ # --- Player and spawn manipulation ---
+
+ def playerSpawnPosition(self, player=None):
+ """
+ xxx if player is None then it gets the default spawn position for the world
+ if player hasn't used a bed then it gets the default spawn position
+ """
+ dataTag = self.root_tag["Data"]
+ if player is None:
+ playerSpawnTag = dataTag
+ else:
+ playerSpawnTag = self.getPlayerTag(player)
+
+ return [playerSpawnTag.get(i, dataTag[i]).value for i in ("SpawnX", "SpawnY", "SpawnZ")]
+
+ def setPlayerSpawnPosition(self, pos, player=None):
+ """ xxx if player is None then it sets the default spawn position for the world """
+ if player is None:
+ playerSpawnTag = self.root_tag["Data"]
+ else:
+ playerSpawnTag = self.getPlayerTag(player)
+ for name, val in zip(("SpawnX", "SpawnY", "SpawnZ"), pos):
+ playerSpawnTag[name] = nbt.TAG_Int(val)
+
+ def getPlayerPath(self, player):
+ assert player != "Player"
+ return os.path.join(self.playersFolder, "%s.dat" % player)
+
+ def getPlayerTag(self, player="Player"):
+ if player == "Player":
+ if player in self.root_tag["Data"]:
+ # single-player world
+ return self.root_tag["Data"]["Player"]
+ raise PlayerNotFound(player)
+ else:
+ playerFilePath = self.getPlayerPath(player)
+ if os.path.exists(playerFilePath):
+ # multiplayer world, found this player
+ playerTag = self.playerTagCache.get(playerFilePath)
+ if playerTag is None:
+ playerTag = nbt.load(playerFilePath)
+ self.playerTagCache[playerFilePath] = playerTag
+ return playerTag
+ else:
+ raise PlayerNotFound(player)
+
+ def getPlayerDimension(self, player="Player"):
+ playerTag = self.getPlayerTag(player)
+ if "Dimension" not in playerTag:
+ return 0
+ return playerTag["Dimension"].value
+
+ def setPlayerDimension(self, d, player="Player"):
+ playerTag = self.getPlayerTag(player)
+ if "Dimension" not in playerTag:
+ playerTag["Dimension"] = nbt.TAG_Int(0)
+ playerTag["Dimension"].value = d
+
+ def setPlayerPosition(self, pos, player="Player"):
+ posList = nbt.TAG_List([nbt.TAG_Double(p) for p in pos])
+ playerTag = self.getPlayerTag(player)
+
+ playerTag["Pos"] = posList
+
+ def getPlayerPosition(self, player="Player"):
+ playerTag = self.getPlayerTag(player)
+ posList = playerTag["Pos"]
+
+ pos = map(lambda x: x.value, posList)
+ return pos
+
+ def setPlayerOrientation(self, yp, player="Player"):
+ self.getPlayerTag(player)["Rotation"] = nbt.TAG_List([nbt.TAG_Float(p) for p in yp])
+
+ def getPlayerOrientation(self, player="Player"):
+ """ returns (yaw, pitch) """
+ yp = map(lambda x: x.value, self.getPlayerTag(player)["Rotation"])
+ y, p = yp
+ if p == 0:
+ p = 0.000000001
+ if p == 180.0:
+ p -= 0.000000001
+ yp = y, p
+ return array(yp)
+
+ def setPlayerAbilities(self, gametype, player="Player"):
+ playerTag = self.getPlayerTag(player)
+
+ # Check for the Abilities tag. It will be missing in worlds from before
+ # Beta 1.9 Prerelease 5.
+ if not 'abilities' in playerTag:
+ playerTag['abilities'] = nbt.TAG_Compound()
+
+ # Assumes creative (1) is the only mode with these abilities set,
+ # which is true for now. Future game modes may not hold this to be
+ # true, however.
+ if gametype == 1:
+ playerTag['abilities']['instabuild'] = nbt.TAG_Byte(1)
+ playerTag['abilities']['mayfly'] = nbt.TAG_Byte(1)
+ playerTag['abilities']['invulnerable'] = nbt.TAG_Byte(1)
+ else:
+ playerTag['abilities']['flying'] = nbt.TAG_Byte(0)
+ playerTag['abilities']['instabuild'] = nbt.TAG_Byte(0)
+ playerTag['abilities']['mayfly'] = nbt.TAG_Byte(0)
+ playerTag['abilities']['invulnerable'] = nbt.TAG_Byte(0)
+
+ def setPlayerGameType(self, gametype, player="Player"):
+ playerTag = self.getPlayerTag(player)
+ # This annoyingly works differently between single- and multi-player.
+ if player == "Player":
+ self.GameType = gametype
+ self.setPlayerAbilities(gametype, player)
+ else:
+ playerTag['playerGameType'] = nbt.TAG_Int(gametype)
+ self.setPlayerAbilities(gametype, player)
+
+ def getPlayerGameType(self, player="Player"):
+ if player == "Player":
+ return self.GameType
+ else:
+ playerTag = self.getPlayerTag(player)
+ return playerTag["playerGameType"].value
+
+ def createPlayer(self, playerName):
+ if playerName == "Player":
+ playerTag = self.root_tag["Data"].setdefault(playerName, nbt.TAG_Compound())
+ else:
+ playerTag = nbt.TAG_Compound()
+
+ playerTag['Air'] = nbt.TAG_Short(300)
+ playerTag['AttackTime'] = nbt.TAG_Short(0)
+ playerTag['DeathTime'] = nbt.TAG_Short(0)
+ playerTag['Fire'] = nbt.TAG_Short(-20)
+ playerTag['Health'] = nbt.TAG_Short(20)
+ playerTag['HurtTime'] = nbt.TAG_Short(0)
+ playerTag['Score'] = nbt.TAG_Int(0)
+ playerTag['FallDistance'] = nbt.TAG_Float(0)
+ playerTag['OnGround'] = nbt.TAG_Byte(0)
+
+ playerTag["Inventory"] = nbt.TAG_List()
+
+ playerTag['Motion'] = nbt.TAG_List([nbt.TAG_Double(0) for i in range(3)])
+ playerTag['Pos'] = nbt.TAG_List([nbt.TAG_Double([0.5, 2.8, 0.5][i]) for i in range(3)])
+ playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0), nbt.TAG_Float(0)])
+
+ if playerName != "Player":
+ if self.readonly:
+ raise IOError, "World is opened read only."
+ self.checkSessionLock()
+ playerTag.save(self.getPlayerPath(playerName))
+
+
+class MCAlphaDimension (MCInfdevOldLevel):
+ def __init__(self, parentWorld, dimNo, create=False):
+ filename = parentWorld.worldFolder.getFolderPath("DIM" + str(int(dimNo)))
+
+ self.parentWorld = parentWorld
+ MCInfdevOldLevel.__init__(self, filename, create)
+ self.dimNo = dimNo
+ self.filename = parentWorld.filename
+ self.players = self.parentWorld.players
+ self.playersFolder = self.parentWorld.playersFolder
+ self.playerTagCache = self.parentWorld.playerTagCache
+
+ @property
+ def root_tag(self):
+ return self.parentWorld.root_tag
+
+ def __str__(self):
+ return u"MCAlphaDimension({0}, {1})".format(self.parentWorld, self.dimNo)
+
+ def loadLevelDat(self, create=False, random_seed=None, last_played=None):
+ pass
+
+ def preloadDimensions(self):
+ pass
+
+ def _create(self, *args, **kw):
+ pass
+
+ def acquireSessionLock(self):
+ pass
+
+ def checkSessionLock(self):
+ self.parentWorld.checkSessionLock()
+
+ dimensionNames = {-1: "Nether", 1: "The End"}
+
+ @property
+ def displayName(self):
+ return u"{0} ({1})".format(self.parentWorld.displayName,
+ self.dimensionNames.get(self.dimNo, "Dimension %d" % self.dimNo))
+
+ def saveInPlace(self, saveSelf=False):
+ """saving the dimension will save the parent world, which will save any
+ other dimensions that need saving. the intent is that all of them can
+ stay loaded at once for fast switching """
+
+ if saveSelf:
+ MCInfdevOldLevel.saveInPlace(self)
+ else:
+ self.parentWorld.saveInPlace()
+
--- /dev/null
+from logging import getLogger
+logger = getLogger(__file__)
+
+items_txt = """
+:version 34
+:mc-version Minecraft 1.4
+
+# Blocks
+# ID NAME FILE CORDS DAMAGE
+ 1 Stone terrain.png 1,0
+ 2 Grass terrain.png 3,0
+ 3 Dirt terrain.png 2,0
+ 4 Cobblestone terrain.png 0,1
+ 5 Oak_Wooden_Planks terrain.png 4,0 0
+ 5 Spruce_Wooden_Planks terrain.png 6,12 1
+ 5 Birch_Wooden_Planks terrain.png 6,13 2
+ 5 Jungle_Wooden_Planks terrain.png 7,12 3
+ 6 Oak_Sapling terrain.png 15,0 0
+ 6 Spruce_Sapling terrain.png 15,3 1
+ 6 Birch_Sapling terrain.png 15,4 2
+ 6 Jungle_Sapling terrain.png 14,1 3
+ 7 Bedrock terrain.png 1,1
+ 8 Water terrain.png 15,13
+ 9 Still_Water terrain.png 15,13
+ 10 Lava terrain.png 15,15
+ 11 Still_Lava terrain.png 15,15
+ 12 Sand terrain.png 2,1
+ 13 Gravel terrain.png 3,1
+ 14 Gold_Ore terrain.png 0,2
+ 15 Iron_Ore terrain.png 1,2
+ 16 Coal_Ore terrain.png 2,2
+ 17 Oak_Wood terrain.png 4,1 0
+ 17 Dark_Wood terrain.png 4,7 1
+ 17 Birch_Wood terrain.png 5,7 2
+ 17 Jungle_Wood terrain.png 9,9 3
+ 18 Oak_Leaves special.png 15,0 0
+ 18 Dark_Leaves special.png 14,1 1
+ 18 Birch_Leaves special.png 14,2 2
+ 18 Jungle_Leaves special.png 14,3 3
+ 19 Sponge terrain.png 0,3
+ 20 Glass terrain.png 1,3
+ 21 Lapis_Lazuli_Ore terrain.png 0,10
+ 22 Lapis_Lazuli_Block terrain.png 0,9
+ 23 Dispenser terrain.png 14,2
+ 24 Sandstone terrain.png 0,12 0
+ 24 Chiseled_Sandstone terrain.png 5,14 1
+ 24 Smooth_Sandstone terrain.png 6,14 2
+ 25 Note_Block terrain.png 10,4
+ 26 Bed_Block terrain.png 6,8
+ 27 Powered_Rail terrain.png 3,10
+ 28 Detector_Rail terrain.png 3,12
+ 29 Sticky_Piston terrain.png 10,6
+ 30 Cobweb terrain.png 11,0
+ 31 Dead_Bush terrain.png 7,3 0
+ 31 Tall_Grass special.png 15,0 1
+ 31 Fern special.png 15,1 2
+ 32 Dead_Bush terrain.png 7,3
+ 33 Piston terrain.png 11,6
+ 34 Piston_(head) terrain.png 11,6
+ 35 Wool terrain.png 0,4 0
+ 35 Orange_Wool terrain.png 2,13 1
+ 35 Magenta_Wool terrain.png 2,12 2
+ 35 Light_Blue_Wool terrain.png 2,11 3
+ 35 Yellow_Wool terrain.png 2,10 4
+ 35 Lime_Wool terrain.png 2,9 5
+ 35 Pink_Wool terrain.png 2,8 6
+ 35 Gray_Wool terrain.png 2,7 7
+ 35 Light_Gray_Wool terrain.png 1,14 8
+ 35 Cyan_Wool terrain.png 1,13 9
+ 35 Purple_Wool terrain.png 1,12 10
+ 35 Blue_Wool terrain.png 1,11 11
+ 35 Brown_Wool terrain.png 1,10 12
+ 35 Green_Wool terrain.png 1,9 13
+ 35 Red_Wool terrain.png 1,8 14
+ 35 Black_Wool terrain.png 1,7 15
+ 37 Flower terrain.png 13,0
+ 38 Rose terrain.png 12,0
+ 39 Brown_Mushroom terrain.png 13,1
+ 40 Red_Mushroom terrain.png 12,1
+ 41 Block_of_Gold terrain.png 7,1
+ 42 Block_of_Iron terrain.png 6,1
+ 43 Double_Stone_Slab terrain.png 5,0 0
+ 43 Double_Sandstone_Slab terrain.png 0,12 1
+ 43 Double_Wooden_Slab terrain.png 4,0 2
+ 43 Double_Stone_Slab terrain.png 0,1 3
+ 44 Stone_Slab special.png 2,2 0
+ 44 Sandstone_Slab special.png 8,0 1
+ 44 Wooden_Slab special.png 3,0 2
+ 44 Stone_Slab special.png 1,0 3
+ 44 Brick_Slab special.png 0,0 4
+ 44 Stone_Brick_Slab special.png 2,0 5
+ 45 Bricks terrain.png 7,0
+ 46 TNT terrain.png 8,0
+ 47 Bookshelf terrain.png 3,2
+ 48 Moss_Stone terrain.png 4,2
+ 49 Obsidian terrain.png 5,2
+ 50 Torch terrain.png 0,5
+ 51 Fire special.png 0,5
+ 52 Monster_Spawner terrain.png 1,4
+ 53 Oak_Wood_Stair special.png 3,1
+ 54 Chest special.png 0,6
+ 55 Redstone_Dust terrain.png 4,5
+ 56 Diamond_Ore terrain.png 2,3
+ 57 Block_of_Diamond terrain.png 8,1
+ 58 Workbench terrain.png 12,3 (x1)
+ 59 Crops terrain.png 15,5
+ 60 Farmland terrain.png 7,5
+ 61 Furnace terrain.png 12,2
+ 62 Lit_Furnace terrain.png 13,3
+ 63 Sign_Block terrain.png 0,0
+ 64 Wooden_Door_Block terrain.png 1,6
+ 65 Ladder terrain.png 3,5
+ 66 Rail terrain.png 0,8
+ 67 Stone_Stairs special.png 1,1
+ 68 Wall_Sign terrain.png 4,0
+ 69 Lever terrain.png 0,6
+ 70 Stone_Pressure_Plate special.png 2,4
+ 71 Iron_Door_Block terrain.png 2,6
+ 72 Wooden_Pressure_Plate special.png 3,4
+ 73 Redstone_Ore terrain.png 3,3
+ 74 Glowing_Redstone_Ore terrain.png 3,3
+ 75 Redstone_Torch_(off) terrain.png 3,7
+ 76 Redstone_Torch terrain.png 3,6
+ 77 Stone_Button special.png 2,3
+ 78 Snow_Layer special.png 1,4
+ 79 Ice terrain.png 3,4
+ 80 Snow terrain.png 2,4
+ 81 Cactus terrain.png 6,4
+ 82 Clay terrain.png 8,4
+ 83 Sugar_cane terrain.png 9,4
+ 84 Jukebox terrain.png 10,4
+ 85 Fence special.png 3,2
+ 86 Pumpkin terrain.png 7,7
+ 87 Netherrack terrain.png 7,6
+ 88 Soul_Sand terrain.png 8,6
+ 89 Glowstone terrain.png 9,6
+ 90 Portal special.png 1,5
+ 91 Jack-o'-lantern terrain.png 8,7
+ 92 Cake special.png 0,4
+ 93 Repeater_Block_(off) terrain.png 3,8
+ 94 Repeater_Block terrain.png 3,9
+ 95 Locked_Chest special.png 0,2
+ 96 Trapdoor terrain.png 4,5
+ 97 Silverfish_Block terrain.png 1,0
+ 98 Stone_Brick terrain.png 6,3 0
+ 98 Mossy_Stone_Brick terrain.png 4,6 1
+ 98 Cracked_Stone_Brick terrain.png 5,6 2
+ 98 Chiseled_Stone_Brick terrain.png 5,13 3
+ 99 Brown_Mushroom_Block terrain.png 13,7
+ 100 Red_Mushroom_Block terrain.png 14,7
+ 101 Iron_Bars terrain.png 5,5
+ 102 Glass_Pane special.png 1,3
+ 103 Melon terrain.png 8,8
+ 104 Pumpkin_Stem special.png 15,4
+ 105 Melon_Stem special.png 15,4
+ 106 Vines special.png 15,2
+ 107 Fence_Gate special.png 4,3
+ 108 Brick_Stairs special.png 0,1
+ 109 Stone_Brick_Stairs special.png 2,1
+ 110 Mycelium terrain.png 13,4
+ 111 Lily_Pad special.png 15,3
+ 112 Nether_Brick terrain.png 0,14
+ 113 Nether_Brick_Fence special.png 7,2
+ 114 Nether_Brick_Stairs special.png 7,1
+ 115 Nether_Wart terrain.png 2,14
+ 116 Enchantment_Table terrain.png 6,11 (x1)
+ 117 Brewing_Stand terrain.png 13,9
+ 118 Cauldron terrain.png 10,9
+ 119 End_Portal special.png 2,5
+ 120 End_Portal_Frame terrain.png 15,9
+ 121 End_Stone terrain.png 15,10
+ 122 Dragon_Egg special.png 0,7
+ 123 Redstone_Lamp terrain.png 3,13
+ 124 Redstone_Lamp_(on) terrain.png 4,13
+ 125 Oak_Wooden_D._Slab terrain.png 4,0 0
+ 125 Spruce_Wooden_D._Slab terrain.png 6,12 1
+ 125 Birch_Wooden_D._Slab terrain.png 6,13 2
+ 125 Jungle_Wooden_D._Slab terrain.png 7,12 3
+ 126 Oak_Wooden_Slab special.png 3,0 0
+ 126 Spruce_Wooden_Slab special.png 4,0 1
+ 126 Birch_Wooden_Slab special.png 5,0 2
+ 126 Jungle_Wooden_Slab special.png 6,0 3
+ 127 Cocoa_Plant special.png 15,5
+ 128 Sandstone_Stairs special.png 8,1
+ 129 Emerald_Ore terrain.png 11,10
+ 130 Ender_Chest special.png 1,6
+ 131 Tripwire_Hook terrain.png 12,10
+ 132 Tripwire terrain.png 5,11
+ 133 Block_of_Emerald terrain.png 9,1
+ 134 Spruce_Wood_Stairs special.png 4,1
+ 135 Birch_Wood_Stairs special.png 5,1
+ 136 Jungle_Wood_Stairs special.png 6,1
+ 137 Command_Block terrain.png 8,11
+ 138 Beacon special.png 2,6
+ 139 Cobblestone_Wall special.png 1,2 0
+ 140 Moss_Stone_Wall special.png 0,2 1
+ 141 Flower_Pot terrain.png 9,11
+ 142 Carrots terrain.png 11,12
+ 143 Potatoes terrain.png 12,12
+ 144 Wooden_Button special.png 3,3
+ 145 Head items.png 0,14
+ 146 Anvil special.png 3,6 0
+ 146 Slightly_Damaged_Anvil special.png 4,6 1
+ 146 Very_Damaged_Anvil special.png 5,6 2
+
+# Items
+# ID NAME FILE CORDS DAMAGE
+ 256 Iron_Shovel items.png 2,5 +250
+ 257 Iron_Pickaxe items.png 2,6 +250
+ 258 Iron_Axe items.png 2,7 +250
+ 259 Flint_and_Steel items.png 5,0 +64
+ 260 Apple items.png 10,0
+ 261 Bow items.png 5,1 +384
+ 262 Arrow items.png 5,2
+ 263 Coal items.png 7,0 0
+ 263 Charcoal items.png 7,0 1
+ 264 Diamond items.png 7,3
+ 265 Iron_Ingot items.png 7,1
+ 266 Gold_Ingot items.png 7,2
+ 267 Iron_Sword items.png 2,4 +250
+ 268 Wooden_Sword items.png 0,4 +59
+ 269 Wooden_Shovel items.png 0,5 +59
+ 270 Wooden_Pickaxe items.png 0,6 +59
+ 271 Wooden_Axe items.png 0,7 +59
+ 272 Stone_Sword items.png 1,4 +131
+ 273 Stone_Shovel items.png 1,5 +131
+ 274 Stone_Pickaxe items.png 1,6 +131
+ 275 Stone_Axe items.png 1,7 +131
+ 276 Diamond_Sword items.png 3,4 +1561
+ 277 Diamond_Shovel items.png 3,5 +1561
+ 278 Diamond_Pickaxe items.png 3,6 +1561
+ 279 Diamond_Axe items.png 3,7 +1561
+ 280 Stick items.png 5,3
+ 281 Bowl items.png 7,4
+ 282 Mushroom_Stew items.png 8,4 x1
+ 283 Golden_Sword items.png 4,4 +32
+ 284 Golden_Shovel items.png 4,5 +32
+ 285 Golden_Pickaxe items.png 4,6 +32
+ 286 Golden_Axe items.png 4,7 +32
+ 287 String items.png 8,0
+ 288 Feather items.png 8,1
+ 289 Gunpowder items.png 8,2
+ 290 Wooden_Hoe items.png 0,8 +59
+ 291 Stone_Hoe items.png 1,8 +131
+ 292 Iron_Hoe items.png 2,8 +250
+ 293 Diamond_Hoe items.png 3,8 +1561
+ 294 Golden_Hoe items.png 4,8 +32
+ 295 Seeds items.png 9,0
+ 296 Wheat items.png 9,1
+ 297 Bread items.png 9,2
+ 298 Leather_Cap items.png 0,0 +34
+ 299 Leather_Tunic items.png 0,1 +48
+ 300 Leather_Pants items.png 0,2 +46
+ 301 Leather_Boots items.png 0,3 +40
+ 302 Chainmail_Helmet items.png 1,0 +68
+ 303 Chainmail_Chestplate items.png 1,1 +96
+ 304 Chainmail_Leggings items.png 1,2 +92
+ 305 Chainmail_Boots items.png 1,3 +80
+ 306 Iron_Helmet items.png 2,0 +136
+ 307 Iron_Chestplate items.png 2,1 +192
+ 308 Iron_Leggings items.png 2,2 +184
+ 309 Iron_Boots items.png 2,3 +160
+ 310 Diamond_Helmet items.png 3,0 +272
+ 311 Diamond_Chestplate items.png 3,1 +384
+ 312 Diamond_Leggings items.png 3,2 +368
+ 313 Diamond_Boots items.png 3,3 +320
+ 314 Golden_Helmet items.png 4,0 +68
+ 315 Golden_Chestplate items.png 4,1 +96
+ 316 Golden_Leggings items.png 4,2 +92
+ 317 Golden_Boots items.png 4,3 +80
+ 318 Flint items.png 6,0
+ 319 Raw_Porkchop items.png 7,5
+ 320 Cooked_Porkchop items.png 8,5
+ 321 Painting items.png 10,1
+ 322 Golden_Apple items.png 11,0
+ 322 Ench._Golden_Apple special.png 0,3 1
+ 323 Sign items.png 10,2 x16
+ 324 Wooden_Door items.png 11,2 x1
+ 325 Bucket items.png 10,4 x16
+ 326 Water_Bucket items.png 11,4 x1
+ 327 Lava_Bucket items.png 12,4 x1
+ 328 Minecart items.png 7,8 x1
+ 329 Saddle items.png 8,6 x1
+ 330 Iron_Door items.png 12,2 x1
+ 331 Redstone items.png 8,3
+ 332 Snowball items.png 14,0 x16
+ 333 Boat items.png 8,8 x1
+ 334 Leather items.png 7,6
+ 335 Milk items.png 13,4 x1
+ 336 Brick items.png 6,1
+ 337 Clay items.png 9,3
+ 338 Sugar_Canes items.png 11,1
+ 339 Paper items.png 10,3
+ 340 Book items.png 11,3
+ 341 Slimeball items.png 14,1
+ 342 Minecart_with_Chest items.png 7,9 x1
+ 343 Minecart_with_Furnace items.png 7,10 x1
+ 344 Egg items.png 12,0
+ 345 Compass items.png 6,3 (x1)
+ 346 Fishing_Rod items.png 5,4 +64
+ 347 Clock items.png 6,4 (x1)
+ 348 Glowstone_Dust items.png 9,4
+ 349 Raw_Fish items.png 9,5
+ 350 Cooked_Fish items.png 10,5
+ 351 Ink_Sack items.png 14,4 0
+ 351 Rose_Red items.png 14,5 1
+ 351 Cactus_Green items.png 14,6 2
+ 351 Coco_Beans items.png 14,7 3
+ 351 Lapis_Lazuli items.png 14,8 4
+ 351 Purple_Dye items.png 14,9 5
+ 351 Cyan_Dye items.png 14,10 6
+ 351 Light_Gray_Dye items.png 14,11 7
+ 351 Gray_Dye items.png 15,4 8
+ 351 Pink_Dye items.png 15,5 9
+ 351 Lime_Dye items.png 15,6 10
+ 351 Dandelion_Yellow items.png 15,7 11
+ 351 Light_Blue_Dye items.png 15,8 12
+ 351 Magenta_Dye items.png 15,9 13
+ 351 Orange_Dye items.png 15,10 14
+ 351 Bone_Meal items.png 15,11 15
+ 352 Bone items.png 12,1
+ 353 Sugar items.png 13,0
+ 354 Cake items.png 13,1 x1
+ 355 Bed items.png 13,2 x1
+ 356 Redstone_Repeater items.png 6,5
+ 357 Cookie items.png 12,5
+ 358 Map items.png 12,3 x1
+ 359 Shears items.png 13,5 +238
+ 360 Melon items.png 13,6
+ 361 Pumpkin_Seeds items.png 13,3
+ 362 Melon_Seeds items.png 14,3
+ 363 Raw_Beef items.png 9,6
+ 364 Steak items.png 10,6
+ 365 Raw_Chicken items.png 9,7
+ 366 Cooked_Chicken items.png 10,7
+ 367 Rotten_Flesh items.png 11,5
+ 368 Ender_Pearl items.png 11,6
+ 369 Blaze_Rod items.png 12,6
+ 370 Ghast_Tear items.png 11,7
+ 371 Gold_Nugget items.png 12,7
+ 372 Nether_Wart items.png 13,7
+ 374 Glass_Bottle items.png 12,8
+ 375 Spider_Eye items.png 11,8
+ 376 Fermented_Spider_Eye items.png 10,8
+ 377 Blaze_Powder items.png 13,9
+ 378 Magma_Cream items.png 13,10
+ 379 Brewing_Stand items.png 12,10 (x1)
+ 380 Cauldron items.png 12,9 (x1)
+ 381 Eye_of_Ender items.png 11,9
+ 382 Glistering_Melon items.png 9,8
+ 383 Spawn_Egg items.png 9,9
+ 384 Bottle_o'_Enchanting items.png 11,10
+ 385 Fire_Charge items.png 14,2
+ 386 Book_and_Quill items.png 11,11 x1
+ 387 Written_Book items.png 12,11 x1
+ 388 Emerald items.png 10,11
+ 389 Item_Frame items.png 14,12
+ 390 Flower_Pot items.png 13,11
+ 391 Carrot items.png 8,7
+ 392 Potato items.png 7,7
+ 393 Baked_Potato items.png 6,7
+ 394 Poisonous_Potato items.png 6,8
+ 395 Empty_Map items.png 13,12 x1
+ 396 Golden_Carrot items.png 6,9
+ 397 Skeleton_Head items.png 0,14 0
+ 397 Wither_Skeleton_Head items.png 1,14 1
+ 397 Zombie_Head items.png 2,14 2
+ 397 Human_Head items.png 3,14 3
+ 397 Creeper_Head items.png 4,14 4
+ 398 Carrot_on_a_Stick items.png 6,6 +25
+ 399 Nether_Star items.png 9,11
+ 400 Pumpkin_Pie items.png 8,9
+2256 C418_-_13 items.png 0,15 x1
+2257 C418_-_cat items.png 1,15 x1
+2258 C418_-_blocks items.png 2,15 x1
+2259 C418_-_chirp items.png 3,15 x1
+2260 C418_-_far items.png 4,15 x1
+2261 C418_-_mall items.png 5,15 x1
+2262 C418_-_mellohi items.png 6,15 x1
+2263 C418_-_stal items.png 7,15 x1
+2264 C418_-_strad items.png 8,15 x1
+2265 C418_-_ward items.png 9,15 x1
+2266 C418_-_11 items.png 10,15 x1
+
+# Potions
+# ID NAME FILE CORDS DAMAGE
+ 373 Water_Bottle special.png 0,14 0
+ 373 Awkward_Potion special.png 1,14 16
+ 373 Thick_Potion special.png 1,14 32
+ 373 Mundane_Potion special.png 1,14 64
+ 373 Mundane_Potion special.png 1,14 8192
+ 373 Regeneration_(0:45) special.png 2,14 8193
+ 373 Regeneration_(2:00) special.png 2,14 8257
+ 373 Regeneration_II_(0:22) special.png 2,14 8225
+ 373 Swiftness_(3:00) special.png 3,14 8194
+ 373 Swiftness_(8:00) special.png 3,14 8258
+ 373 Swiftness_II_(1:30) special.png 3,14 8226
+ 373 Fire_Resistance_(3:00) special.png 4,14 8195
+ 373 Fire_Resistance_(3:00) special.png 4,14 8227
+ 373 Fire_Resistance_(8:00) special.png 4,14 8259
+ 373 Healing special.png 6,14 8197
+ 373 Healing special.png 6,14 8261
+ 373 Healing_II special.png 6,14 8229
+ 373 Strength_(3:00) special.png 8,14 8201
+ 373 Strength_(8:00) special.png 8,14 8265
+ 373 Strength_II_(1:30) special.png 8,14 8233
+ 373 Poison_(0:45) special.png 5,14 8196
+ 373 Poison_(2:00) special.png 5,14 8260
+ 373 Poison_II_(0:22) special.png 5,14 8228
+ 373 Weakness_(1:30) special.png 7,14 8200
+ 373 Weakness_(1:30) special.png 7,14 8332
+ 373 Weakness_(4:00) special.png 7,14 8264
+ 373 Slowness_(1:30) special.png 9,14 8202
+ 373 Slowness_(1:30) special.png 9,14 8234
+ 373 Slowness_(4:00) special.png 9,14 8266
+ 373 Harming special.png 10,14 8204
+ 373 Harming special.png 10,14 8268
+ 373 Harming_II special.png 10,14 8236
+# Unbrewable:
+ 373 Regeneration_II_(1:00) special.png 2,14 8289
+ 373 Swiftness_II_(4:00) special.png 3,14 8290
+ 373 Strength_II_(4:00) special.png 8,14 8297
+ 373 Poison_II_(1:00) special.png 5,14 8292
+
+# Splash Potions
+# ID NAME FILE CORDS DAMAGE
+ 373 Splash_Mundane special.png 1,13 16384
+ 373 Regeneration_(0:33) special.png 2,13 16385
+ 373 Regeneration_(1:30) special.png 2,13 16499
+ 373 Regeneration_II_(0:16) special.png 2,13 16417
+ 373 Swiftness_(2:15) special.png 3,13 16386
+ 373 Swiftness_(6:00) special.png 3,13 16450
+ 373 Swiftness_II_(1:07) special.png 3,13 16418
+ 373 Fire_Resistance_(2:15) special.png 4,13 16387
+ 373 Fire_Resistance_(2:15) special.png 4,13 16419
+ 373 Fire_Resistance_(6:00) special.png 4,13 16451
+ 373 Healing special.png 6,13 16389
+ 373 Healing special.png 6,13 16453
+ 373 Healing_II special.png 6,13 16421
+ 373 Strength_(2:15) special.png 8,13 16393
+ 373 Strength_(6:00) special.png 8,13 16457
+ 373 Strength_II_(1:07) special.png 8,13 16425
+ 373 Poison_(0:33) special.png 5,13 16388
+ 373 Poison_(1:30) special.png 5,13 16452
+ 373 Poison_II_(0:16) special.png 5,13 16420
+ 373 Weakness_(1:07) special.png 7,13 16392
+ 373 Weakness_(1:07) special.png 7,13 16424
+ 373 Weakness_(3:00) special.png 7,13 16456
+ 373 Slowness_(1:07) special.png 9,13 16394
+ 373 Slowness_(1:07) special.png 9,13 16426
+ 373 Slowness_(3:00) special.png 9,13 16458
+ 373 Harming special.png 10,13 16396
+ 373 Harming special.png 10,13 16460
+ 373 Harming_II special.png 10,13 16428
+# Unbrewable:
+ 373 Regeneration_II_(0:45) special.png 2,13 16481
+ 373 Swiftness_II_(3:00) special.png 3,13 16482
+ 373 Strength_II_(3:00) special.png 8,13 16489
+ 373 Poison_II_(0:45) special.png 5,13 16484
+
+# Spawn Eggs
+# ID NAME FILE CORDS DAMAGE
+ 383 Spawn_Creeper special.png 0,9 50
+ 383 Spawn_Skeleton special.png 1,9 51
+ 383 Spawn_Spider special.png 2,9 52
+ 383 Spawn_Zombie special.png 3,9 54
+ 383 Spawn_Slime special.png 4,9 55
+ 383 Spawn_Ghast special.png 0,10 56
+ 383 Spawn_Zombie_Pigmen special.png 1,10 57
+ 383 Spawn_Enderman special.png 2,10 58
+ 383 Spawn_Cave_Spider special.png 3,10 59
+ 383 Spawn_Silverfish special.png 4,10 60
+ 383 Spawn_Blaze special.png 0,11 61
+ 383 Spawn_Magma_Cube special.png 1,11 62
+ 383 Spawn_Bat special.png 5,9 65
+ 383 Spawn_Witch special.png 5,10 66
+ 383 Spawn_Pig special.png 2,11 90
+ 383 Spawn_Sheep special.png 3,11 91
+ 383 Spawn_Cow special.png 4,11 92
+ 383 Spawn_Chicken special.png 0,12 93
+ 383 Spawn_Squid special.png 1,12 94
+ 383 Spawn_Wolf special.png 2,12 95
+ 383 Spawn_Mooshroom special.png 3,12 96
+ 383 Spawn_Villager special.png 4,12 120
+
+# Groups
+# NAME ICON ITEMS
+# Column 1
+~ Natural 2 2,3,12,24,128,44~1,13,82,79,80,78
+~ Stone 1 1,4,48,67,44~3,139,140,98,109,44~5,44~0,45,108,44~4,101
+~ Wood 5 17,5,53,134,135,136,126,47,85,107,20,102,30
+~ NetherEnd 87 87,88,89,348,112,114,113,372,121,122
+~ Ores 56 16,15,14,56,129,73,21,49,42,41,57,133,22,263~0,265,266,264,388
+~ Special 54 46,52,58,54,130,61,23,25,84,116,379,380,138,146~0,321,389,323,324,330,355,65,96,390,397
+~ Plants1 81 31~1,31~2,106,111,18,81,86,91,103,110
+~ Plants2 6 295,361,362,6,296,338,37,38,39,40,32
+~ Transport 328 66,27,28,328,342,343,333,329,398
+~ Logic 331 331,76,356,69,70,72,131,77,144,33,29,123,137
+~ Wool 35 35~0,35~8,35~7,35~15,35~14,35~12,35~1,35~4,35~5,35~13,35~11,35~3,35~9,35~10,35~2,35~6
+~ Dye 351 351~15,351~7,351~8,351~0,351~1,351~3,351~14,351~11,351~10,351~2,351~4,351~12,351~6,351~5,351~13,351~9
+# Column 2
+~ TierWood 299 298,299,300,301,269,270,271,290,268
+~ TierStone 303 302,303,304,305,273,274,275,291,272
+~ TierIron 307 306,307,308,309,256,257,258,292,267
+~ TierDiam 311 310,311,312,313,277,278,279,293,276
+~ TierGold 315 314,315,316,317,284,285,286,294,283
+~ Tools 261 50,261,262,259,346,359,345,347,395,358,325,326,327,335,384,385,386,387
+~ Food 297 260,322,282,297,360,319,320,363,364,365,366,349,350,354,357,391,396,392,393,394,400
+~ Items 318 280,281,318,337,336,353,339,340,332,376,377,382,381
+~ Drops 341 344,288,334,287,352,289,367,375,341,368,369,370,371,378,399
+~ Music 2257 2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266
+# New
+~ Potion 373 373~0,373~16,373~32,373~8192,373~8193,373~8257,373~8225,373~8289,373~8194,373~8258,373~8226,373~8290,373~8195,373~8259,373~8197,373~8229,373~8201,373~8265,373~8233,373~8297,373~8196,373~8260,373~8228,373~8292,373~8200,373~8264,373~8202,373~8266,373~8204,373~8236,373~16384,373~16385,373~16499,373~16417,373~16481,373~16386,373~16450,373~16418,373~16482,373~16387,373~16451,373~16389,373~16421,373~16393,373~16457,373~16425,373~16489,373~16388,373~16452,373~16420,373~16484,373~16392,373~16456,373~16394,373~16458,373~16396,373~16428
+~ Eggs 383 383~50,383~51,383~52,383~54,383~55,383~56,383~57,383~58,383~59,383~60,383~61,383~62,383~65,383~66,383~90,383~91,383~92,383~93,383~94,383~95,383~96,383~120
+
+# Enchantments
+# EID NAME MAX ITEMS
++ 0 Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 1 Fire_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 2 Feather_Falling 4 301,305,309,313,317
++ 3 Blast_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 4 Projectile_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 5 Respiration 3 298,302,306,310,314
++ 6 Aqua_Affinity 1 298,302,306,310,314
++ 16 Sharpness 5 268,272,267,276,283
++ 17 Smite 5 268,272,267,276,283
++ 18 Bane_of_Arthropods 5 268,272,267,276,283
++ 19 Knockback 2 268,272,267,276,283
++ 20 Fire_Aspect 2 268,272,267,276,283
++ 21 Looting 3 268,272,267,276,283
++ 32 Efficiency 5 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 33 Silk_Touch 1 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 34 Unbreaking 3 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 35 Fortune 3 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 48 Power 5 261
++ 49 Punch 2 261
++ 50 Flame 1 261
++ 51 Infinity 1 261
+"""
+
+
+class ItemType (object):
+ def __init__(self, id, name, imagefile=None, imagecoords=None, maxdamage=0, damagevalue=0, stacksize=64):
+ self.id = id
+ self.name = name
+ self.imagefile = imagefile
+ self.imagecoords = imagecoords
+ self.maxdamage = maxdamage
+ self.damagevalue = damagevalue
+ self.stacksize = stacksize
+
+ def __repr__(self):
+ return "ItemType({0}, '{1}')".format(self.id, self.name)
+
+ def __str__(self):
+ return "ItemType {0}: {1}".format(self.id, self.name)
+
+
+class Items (object):
+ items_txt = items_txt
+
+ def __init__(self, filename=None):
+ if filename is None:
+ items_txt = self.items_txt
+ else:
+ try:
+ with file(filename) as f:
+ items_txt = f.read()
+ except Exception, e:
+ logger.info("Error reading items.txt: %s", e)
+ logger.info("Using internal data.")
+ items_txt = self.items_txt
+
+ self.itemtypes = {}
+ self.itemgroups = []
+
+ for line in items_txt.split("\n"):
+ try:
+ line = line.strip()
+ if len(line) == 0:
+ continue
+ if line[0] == "#": # comment
+ continue
+ if line[0] == "+": # enchantment
+ continue
+ if line[0] == "~": # category
+ fields = line.split()
+ name, icon, items = fields[1:4]
+ items = items.split(",")
+ self.itemgroups.append((name, icon, items))
+ continue
+
+ stacksize = 64
+ damagevalue = None
+ maxdamage = 0
+
+ fields = line.split()
+ if len(fields) >= 4:
+ maxdamage = None
+ id, name, imagefile, imagecoords = fields[0:4]
+ if len(fields) > 4:
+ info = fields[4]
+ if info[0] == '(':
+ info = info[1:-1]
+ if info[0] == 'x':
+ stacksize = int(info[1:])
+ elif info[0] == '+':
+ maxdamage = int(info[1:])
+ else:
+ damagevalue = int(info)
+ id = int(id)
+ name = name.replace("_", " ")
+ imagecoords = imagecoords.split(",")
+
+ self.itemtypes[(id, damagevalue)] = ItemType(id, name, imagefile, imagecoords, maxdamage, damagevalue, stacksize)
+ except Exception, e:
+ print "Error reading line:", e
+ print "Line: ", line
+ print
+
+ self.names = dict((item.name, item.id) for item in self.itemtypes.itervalues())
+
+ def findItem(self, id=0, damage=None):
+ item = self.itemtypes.get((id, damage))
+ if item:
+ return item
+
+ item = self.itemtypes.get((id, None))
+ if item:
+ return item
+
+ item = self.itemtypes.get((id, 0))
+ if item:
+ return item
+
+ return ItemType(id, "Unknown Item {0}:{1}".format(id, damage), damagevalue=damage)
+ #raise ItemNotFound, "Item {0}:{1} not found".format(id, damage)
+
+
+class ItemNotFound(KeyError):
+ pass
+
+items = Items()
--- /dev/null
+:version 34
+:mc-version Minecraft 1.4
+
+# Blocks
+# ID NAME FILE CORDS DAMAGE
+ 1 Stone terrain.png 1,0
+ 2 Grass terrain.png 3,0
+ 3 Dirt terrain.png 2,0
+ 4 Cobblestone terrain.png 0,1
+ 5 Oak_Wooden_Planks terrain.png 4,0 0
+ 5 Spruce_Wooden_Planks terrain.png 6,12 1
+ 5 Birch_Wooden_Planks terrain.png 6,13 2
+ 5 Jungle_Wooden_Planks terrain.png 7,12 3
+ 6 Oak_Sapling terrain.png 15,0 0
+ 6 Spruce_Sapling terrain.png 15,3 1
+ 6 Birch_Sapling terrain.png 15,4 2
+ 6 Jungle_Sapling terrain.png 14,1 3
+ 7 Bedrock terrain.png 1,1
+ 8 Water terrain.png 15,13
+ 9 Still_Water terrain.png 15,13
+ 10 Lava terrain.png 15,15
+ 11 Still_Lava terrain.png 15,15
+ 12 Sand terrain.png 2,1
+ 13 Gravel terrain.png 3,1
+ 14 Gold_Ore terrain.png 0,2
+ 15 Iron_Ore terrain.png 1,2
+ 16 Coal_Ore terrain.png 2,2
+ 17 Oak_Wood terrain.png 4,1 0
+ 17 Dark_Wood terrain.png 4,7 1
+ 17 Birch_Wood terrain.png 5,7 2
+ 17 Jungle_Wood terrain.png 9,9 3
+ 18 Oak_Leaves special.png 15,0 0
+ 18 Dark_Leaves special.png 14,1 1
+ 18 Birch_Leaves special.png 14,2 2
+ 18 Jungle_Leaves special.png 14,3 3
+ 19 Sponge terrain.png 0,3
+ 20 Glass terrain.png 1,3
+ 21 Lapis_Lazuli_Ore terrain.png 0,10
+ 22 Lapis_Lazuli_Block terrain.png 0,9
+ 23 Dispenser terrain.png 14,2
+ 24 Sandstone terrain.png 0,12 0
+ 24 Chiseled_Sandstone terrain.png 5,14 1
+ 24 Smooth_Sandstone terrain.png 6,14 2
+ 25 Note_Block terrain.png 10,4
+ 26 Bed_Block terrain.png 6,8
+ 27 Powered_Rail terrain.png 3,10
+ 28 Detector_Rail terrain.png 3,12
+ 29 Sticky_Piston terrain.png 10,6
+ 30 Cobweb terrain.png 11,0
+ 31 Dead_Bush terrain.png 7,3 0
+ 31 Tall_Grass special.png 15,0 1
+ 31 Fern special.png 15,1 2
+ 32 Dead_Bush terrain.png 7,3
+ 33 Piston terrain.png 11,6
+ 34 Piston_(head) terrain.png 11,6
+ 35 Wool terrain.png 0,4 0
+ 35 Orange_Wool terrain.png 2,13 1
+ 35 Magenta_Wool terrain.png 2,12 2
+ 35 Light_Blue_Wool terrain.png 2,11 3
+ 35 Yellow_Wool terrain.png 2,10 4
+ 35 Lime_Wool terrain.png 2,9 5
+ 35 Pink_Wool terrain.png 2,8 6
+ 35 Gray_Wool terrain.png 2,7 7
+ 35 Light_Gray_Wool terrain.png 1,14 8
+ 35 Cyan_Wool terrain.png 1,13 9
+ 35 Purple_Wool terrain.png 1,12 10
+ 35 Blue_Wool terrain.png 1,11 11
+ 35 Brown_Wool terrain.png 1,10 12
+ 35 Green_Wool terrain.png 1,9 13
+ 35 Red_Wool terrain.png 1,8 14
+ 35 Black_Wool terrain.png 1,7 15
+ 37 Flower terrain.png 13,0
+ 38 Rose terrain.png 12,0
+ 39 Brown_Mushroom terrain.png 13,1
+ 40 Red_Mushroom terrain.png 12,1
+ 41 Block_of_Gold terrain.png 7,1
+ 42 Block_of_Iron terrain.png 6,1
+ 43 Double_Stone_Slab terrain.png 5,0 0
+ 43 Double_Sandstone_Slab terrain.png 0,12 1
+ 43 Double_Wooden_Slab terrain.png 4,0 2
+ 43 Double_Stone_Slab terrain.png 0,1 3
+ 44 Stone_Slab special.png 2,2 0
+ 44 Sandstone_Slab special.png 8,0 1
+ 44 Wooden_Slab special.png 3,0 2
+ 44 Stone_Slab special.png 1,0 3
+ 44 Brick_Slab special.png 0,0 4
+ 44 Stone_Brick_Slab special.png 2,0 5
+ 45 Bricks terrain.png 7,0
+ 46 TNT terrain.png 8,0
+ 47 Bookshelf terrain.png 3,2
+ 48 Moss_Stone terrain.png 4,2
+ 49 Obsidian terrain.png 5,2
+ 50 Torch terrain.png 0,5
+ 51 Fire special.png 0,5
+ 52 Monster_Spawner terrain.png 1,4
+ 53 Oak_Wood_Stair special.png 3,1
+ 54 Chest special.png 0,6
+ 55 Redstone_Dust terrain.png 4,5
+ 56 Diamond_Ore terrain.png 2,3
+ 57 Block_of_Diamond terrain.png 8,1
+ 58 Workbench terrain.png 12,3 (x1)
+ 59 Crops terrain.png 15,5
+ 60 Farmland terrain.png 7,5
+ 61 Furnace terrain.png 12,2
+ 62 Lit_Furnace terrain.png 13,3
+ 63 Sign_Block terrain.png 0,0
+ 64 Wooden_Door_Block terrain.png 1,6
+ 65 Ladder terrain.png 3,5
+ 66 Rail terrain.png 0,8
+ 67 Stone_Stairs special.png 1,1
+ 68 Wall_Sign terrain.png 4,0
+ 69 Lever terrain.png 0,6
+ 70 Stone_Pressure_Plate special.png 2,4
+ 71 Iron_Door_Block terrain.png 2,6
+ 72 Wooden_Pressure_Plate special.png 3,4
+ 73 Redstone_Ore terrain.png 3,3
+ 74 Glowing_Redstone_Ore terrain.png 3,3
+ 75 Redstone_Torch_(off) terrain.png 3,7
+ 76 Redstone_Torch terrain.png 3,6
+ 77 Stone_Button special.png 2,3
+ 78 Snow_Layer special.png 1,4
+ 79 Ice terrain.png 3,4
+ 80 Snow terrain.png 2,4
+ 81 Cactus terrain.png 6,4
+ 82 Clay terrain.png 8,4
+ 83 Sugar_cane terrain.png 9,4
+ 84 Jukebox terrain.png 10,4
+ 85 Fence special.png 3,2
+ 86 Pumpkin terrain.png 7,7
+ 87 Netherrack terrain.png 7,6
+ 88 Soul_Sand terrain.png 8,6
+ 89 Glowstone terrain.png 9,6
+ 90 Portal special.png 1,5
+ 91 Jack-o'-lantern terrain.png 8,7
+ 92 Cake special.png 0,4
+ 93 Repeater_Block_(off) terrain.png 3,8
+ 94 Repeater_Block terrain.png 3,9
+ 95 Locked_Chest special.png 0,2
+ 96 Trapdoor terrain.png 4,5
+ 97 Silverfish_Block terrain.png 1,0
+ 98 Stone_Brick terrain.png 6,3 0
+ 98 Mossy_Stone_Brick terrain.png 4,6 1
+ 98 Cracked_Stone_Brick terrain.png 5,6 2
+ 98 Chiseled_Stone_Brick terrain.png 5,13 3
+ 99 Brown_Mushroom_Block terrain.png 13,7
+ 100 Red_Mushroom_Block terrain.png 14,7
+ 101 Iron_Bars terrain.png 5,5
+ 102 Glass_Pane special.png 1,3
+ 103 Melon terrain.png 8,8
+ 104 Pumpkin_Stem special.png 15,4
+ 105 Melon_Stem special.png 15,4
+ 106 Vines special.png 15,2
+ 107 Fence_Gate special.png 4,3
+ 108 Brick_Stairs special.png 0,1
+ 109 Stone_Brick_Stairs special.png 2,1
+ 110 Mycelium terrain.png 13,4
+ 111 Lily_Pad special.png 15,3
+ 112 Nether_Brick terrain.png 0,14
+ 113 Nether_Brick_Fence special.png 7,2
+ 114 Nether_Brick_Stairs special.png 7,1
+ 115 Nether_Wart terrain.png 2,14
+ 116 Enchantment_Table terrain.png 6,11 (x1)
+ 117 Brewing_Stand terrain.png 13,9
+ 118 Cauldron terrain.png 10,9
+ 119 End_Portal special.png 2,5
+ 120 End_Portal_Frame terrain.png 15,9
+ 121 End_Stone terrain.png 15,10
+ 122 Dragon_Egg special.png 0,7
+ 123 Redstone_Lamp terrain.png 3,13
+ 124 Redstone_Lamp_(on) terrain.png 4,13
+ 125 Oak_Wooden_D._Slab terrain.png 4,0 0
+ 125 Spruce_Wooden_D._Slab terrain.png 6,12 1
+ 125 Birch_Wooden_D._Slab terrain.png 6,13 2
+ 125 Jungle_Wooden_D._Slab terrain.png 7,12 3
+ 126 Oak_Wooden_Slab special.png 3,0 0
+ 126 Spruce_Wooden_Slab special.png 4,0 1
+ 126 Birch_Wooden_Slab special.png 5,0 2
+ 126 Jungle_Wooden_Slab special.png 6,0 3
+ 127 Cocoa_Plant special.png 15,5
+ 128 Sandstone_Stairs special.png 8,1
+ 129 Emerald_Ore terrain.png 11,10
+ 130 Ender_Chest special.png 1,6
+ 131 Tripwire_Hook terrain.png 12,10
+ 132 Tripwire terrain.png 5,11
+ 133 Block_of_Emerald terrain.png 9,1
+ 134 Spruce_Wood_Stairs special.png 4,1
+ 135 Birch_Wood_Stairs special.png 5,1
+ 136 Jungle_Wood_Stairs special.png 6,1
+ 137 Command_Block terrain.png 8,11
+ 138 Beacon special.png 2,6
+ 139 Cobblestone_Wall special.png 1,2 0
+ 140 Moss_Stone_Wall special.png 0,2 1
+ 141 Flower_Pot terrain.png 9,11
+ 142 Carrots terrain.png 11,12
+ 143 Potatoes terrain.png 12,12
+ 144 Wooden_Button special.png 3,3
+ 145 Head items.png 0,14
+ 146 Anvil special.png 3,6 0
+ 146 Slightly_Damaged_Anvil special.png 4,6 1
+ 146 Very_Damaged_Anvil special.png 5,6 2
+
+# Items
+# ID NAME FILE CORDS DAMAGE
+ 256 Iron_Shovel items.png 2,5 +250
+ 257 Iron_Pickaxe items.png 2,6 +250
+ 258 Iron_Axe items.png 2,7 +250
+ 259 Flint_and_Steel items.png 5,0 +64
+ 260 Apple items.png 10,0
+ 261 Bow items.png 5,1 +384
+ 262 Arrow items.png 5,2
+ 263 Coal items.png 7,0 0
+ 263 Charcoal items.png 7,0 1
+ 264 Diamond items.png 7,3
+ 265 Iron_Ingot items.png 7,1
+ 266 Gold_Ingot items.png 7,2
+ 267 Iron_Sword items.png 2,4 +250
+ 268 Wooden_Sword items.png 0,4 +59
+ 269 Wooden_Shovel items.png 0,5 +59
+ 270 Wooden_Pickaxe items.png 0,6 +59
+ 271 Wooden_Axe items.png 0,7 +59
+ 272 Stone_Sword items.png 1,4 +131
+ 273 Stone_Shovel items.png 1,5 +131
+ 274 Stone_Pickaxe items.png 1,6 +131
+ 275 Stone_Axe items.png 1,7 +131
+ 276 Diamond_Sword items.png 3,4 +1561
+ 277 Diamond_Shovel items.png 3,5 +1561
+ 278 Diamond_Pickaxe items.png 3,6 +1561
+ 279 Diamond_Axe items.png 3,7 +1561
+ 280 Stick items.png 5,3
+ 281 Bowl items.png 7,4
+ 282 Mushroom_Stew items.png 8,4 x1
+ 283 Golden_Sword items.png 4,4 +32
+ 284 Golden_Shovel items.png 4,5 +32
+ 285 Golden_Pickaxe items.png 4,6 +32
+ 286 Golden_Axe items.png 4,7 +32
+ 287 String items.png 8,0
+ 288 Feather items.png 8,1
+ 289 Gunpowder items.png 8,2
+ 290 Wooden_Hoe items.png 0,8 +59
+ 291 Stone_Hoe items.png 1,8 +131
+ 292 Iron_Hoe items.png 2,8 +250
+ 293 Diamond_Hoe items.png 3,8 +1561
+ 294 Golden_Hoe items.png 4,8 +32
+ 295 Seeds items.png 9,0
+ 296 Wheat items.png 9,1
+ 297 Bread items.png 9,2
+ 298 Leather_Cap items.png 0,0 +34
+ 299 Leather_Tunic items.png 0,1 +48
+ 300 Leather_Pants items.png 0,2 +46
+ 301 Leather_Boots items.png 0,3 +40
+ 302 Chainmail_Helmet items.png 1,0 +68
+ 303 Chainmail_Chestplate items.png 1,1 +96
+ 304 Chainmail_Leggings items.png 1,2 +92
+ 305 Chainmail_Boots items.png 1,3 +80
+ 306 Iron_Helmet items.png 2,0 +136
+ 307 Iron_Chestplate items.png 2,1 +192
+ 308 Iron_Leggings items.png 2,2 +184
+ 309 Iron_Boots items.png 2,3 +160
+ 310 Diamond_Helmet items.png 3,0 +272
+ 311 Diamond_Chestplate items.png 3,1 +384
+ 312 Diamond_Leggings items.png 3,2 +368
+ 313 Diamond_Boots items.png 3,3 +320
+ 314 Golden_Helmet items.png 4,0 +68
+ 315 Golden_Chestplate items.png 4,1 +96
+ 316 Golden_Leggings items.png 4,2 +92
+ 317 Golden_Boots items.png 4,3 +80
+ 318 Flint items.png 6,0
+ 319 Raw_Porkchop items.png 7,5
+ 320 Cooked_Porkchop items.png 8,5
+ 321 Painting items.png 10,1
+ 322 Golden_Apple items.png 11,0
+ 322 Ench._Golden_Apple special.png 0,3 1
+ 323 Sign items.png 10,2 x16
+ 324 Wooden_Door items.png 11,2 x1
+ 325 Bucket items.png 10,4 x16
+ 326 Water_Bucket items.png 11,4 x1
+ 327 Lava_Bucket items.png 12,4 x1
+ 328 Minecart items.png 7,8 x1
+ 329 Saddle items.png 8,6 x1
+ 330 Iron_Door items.png 12,2 x1
+ 331 Redstone items.png 8,3
+ 332 Snowball items.png 14,0 x16
+ 333 Boat items.png 8,8 x1
+ 334 Leather items.png 7,6
+ 335 Milk items.png 13,4 x1
+ 336 Brick items.png 6,1
+ 337 Clay items.png 9,3
+ 338 Sugar_Canes items.png 11,1
+ 339 Paper items.png 10,3
+ 340 Book items.png 11,3
+ 341 Slimeball items.png 14,1
+ 342 Minecart_with_Chest items.png 7,9 x1
+ 343 Minecart_with_Furnace items.png 7,10 x1
+ 344 Egg items.png 12,0
+ 345 Compass items.png 6,3 (x1)
+ 346 Fishing_Rod items.png 5,4 +64
+ 347 Clock items.png 6,4 (x1)
+ 348 Glowstone_Dust items.png 9,4
+ 349 Raw_Fish items.png 9,5
+ 350 Cooked_Fish items.png 10,5
+ 351 Ink_Sack items.png 14,4 0
+ 351 Rose_Red items.png 14,5 1
+ 351 Cactus_Green items.png 14,6 2
+ 351 Coco_Beans items.png 14,7 3
+ 351 Lapis_Lazuli items.png 14,8 4
+ 351 Purple_Dye items.png 14,9 5
+ 351 Cyan_Dye items.png 14,10 6
+ 351 Light_Gray_Dye items.png 14,11 7
+ 351 Gray_Dye items.png 15,4 8
+ 351 Pink_Dye items.png 15,5 9
+ 351 Lime_Dye items.png 15,6 10
+ 351 Dandelion_Yellow items.png 15,7 11
+ 351 Light_Blue_Dye items.png 15,8 12
+ 351 Magenta_Dye items.png 15,9 13
+ 351 Orange_Dye items.png 15,10 14
+ 351 Bone_Meal items.png 15,11 15
+ 352 Bone items.png 12,1
+ 353 Sugar items.png 13,0
+ 354 Cake items.png 13,1 x1
+ 355 Bed items.png 13,2 x1
+ 356 Redstone_Repeater items.png 6,5
+ 357 Cookie items.png 12,5
+ 358 Map items.png 12,3 x1
+ 359 Shears items.png 13,5 +238
+ 360 Melon items.png 13,6
+ 361 Pumpkin_Seeds items.png 13,3
+ 362 Melon_Seeds items.png 14,3
+ 363 Raw_Beef items.png 9,6
+ 364 Steak items.png 10,6
+ 365 Raw_Chicken items.png 9,7
+ 366 Cooked_Chicken items.png 10,7
+ 367 Rotten_Flesh items.png 11,5
+ 368 Ender_Pearl items.png 11,6
+ 369 Blaze_Rod items.png 12,6
+ 370 Ghast_Tear items.png 11,7
+ 371 Gold_Nugget items.png 12,7
+ 372 Nether_Wart items.png 13,7
+ 374 Glass_Bottle items.png 12,8
+ 375 Spider_Eye items.png 11,8
+ 376 Fermented_Spider_Eye items.png 10,8
+ 377 Blaze_Powder items.png 13,9
+ 378 Magma_Cream items.png 13,10
+ 379 Brewing_Stand items.png 12,10 (x1)
+ 380 Cauldron items.png 12,9 (x1)
+ 381 Eye_of_Ender items.png 11,9
+ 382 Glistering_Melon items.png 9,8
+ 383 Spawn_Egg items.png 9,9
+ 384 Bottle_o'_Enchanting items.png 11,10
+ 385 Fire_Charge items.png 14,2
+ 386 Book_and_Quill items.png 11,11 x1
+ 387 Written_Book items.png 12,11 x1
+ 388 Emerald items.png 10,11
+ 389 Item_Frame items.png 14,12
+ 390 Flower_Pot items.png 13,11
+ 391 Carrot items.png 8,7
+ 392 Potato items.png 7,7
+ 393 Baked_Potato items.png 6,7
+ 394 Poisonous_Potato items.png 6,8
+ 395 Empty_Map items.png 13,12 x1
+ 396 Golden_Carrot items.png 6,9
+ 397 Skeleton_Head items.png 0,14 0
+ 397 Wither_Skeleton_Head items.png 1,14 1
+ 397 Zombie_Head items.png 2,14 2
+ 397 Human_Head items.png 3,14 3
+ 397 Creeper_Head items.png 4,14 4
+ 398 Carrot_on_a_Stick items.png 6,6 +25
+ 399 Nether_Star items.png 9,11
+ 400 Pumpkin_Pie items.png 8,9
+2256 C418_-_13 items.png 0,15 x1
+2257 C418_-_cat items.png 1,15 x1
+2258 C418_-_blocks items.png 2,15 x1
+2259 C418_-_chirp items.png 3,15 x1
+2260 C418_-_far items.png 4,15 x1
+2261 C418_-_mall items.png 5,15 x1
+2262 C418_-_mellohi items.png 6,15 x1
+2263 C418_-_stal items.png 7,15 x1
+2264 C418_-_strad items.png 8,15 x1
+2265 C418_-_ward items.png 9,15 x1
+2266 C418_-_11 items.png 10,15 x1
+
+# Potions
+# ID NAME FILE CORDS DAMAGE
+ 373 Water_Bottle special.png 0,14 0
+ 373 Awkward_Potion special.png 1,14 16
+ 373 Thick_Potion special.png 1,14 32
+ 373 Mundane_Potion special.png 1,14 64
+ 373 Mundane_Potion special.png 1,14 8192
+ 373 Regeneration_(0:45) special.png 2,14 8193
+ 373 Regeneration_(2:00) special.png 2,14 8257
+ 373 Regeneration_II_(0:22) special.png 2,14 8225
+ 373 Swiftness_(3:00) special.png 3,14 8194
+ 373 Swiftness_(8:00) special.png 3,14 8258
+ 373 Swiftness_II_(1:30) special.png 3,14 8226
+ 373 Fire_Resistance_(3:00) special.png 4,14 8195
+ 373 Fire_Resistance_(3:00) special.png 4,14 8227
+ 373 Fire_Resistance_(8:00) special.png 4,14 8259
+ 373 Healing special.png 6,14 8197
+ 373 Healing special.png 6,14 8261
+ 373 Healing_II special.png 6,14 8229
+ 373 Strength_(3:00) special.png 8,14 8201
+ 373 Strength_(8:00) special.png 8,14 8265
+ 373 Strength_II_(1:30) special.png 8,14 8233
+ 373 Poison_(0:45) special.png 5,14 8196
+ 373 Poison_(2:00) special.png 5,14 8260
+ 373 Poison_II_(0:22) special.png 5,14 8228
+ 373 Weakness_(1:30) special.png 7,14 8200
+ 373 Weakness_(1:30) special.png 7,14 8332
+ 373 Weakness_(4:00) special.png 7,14 8264
+ 373 Slowness_(1:30) special.png 9,14 8202
+ 373 Slowness_(1:30) special.png 9,14 8234
+ 373 Slowness_(4:00) special.png 9,14 8266
+ 373 Harming special.png 10,14 8204
+ 373 Harming special.png 10,14 8268
+ 373 Harming_II special.png 10,14 8236
+# Unbrewable:
+ 373 Regeneration_II_(1:00) special.png 2,14 8289
+ 373 Swiftness_II_(4:00) special.png 3,14 8290
+ 373 Strength_II_(4:00) special.png 8,14 8297
+ 373 Poison_II_(1:00) special.png 5,14 8292
+
+# Splash Potions
+# ID NAME FILE CORDS DAMAGE
+ 373 Splash_Mundane special.png 1,13 16384
+ 373 Regeneration_(0:33) special.png 2,13 16385
+ 373 Regeneration_(1:30) special.png 2,13 16499
+ 373 Regeneration_II_(0:16) special.png 2,13 16417
+ 373 Swiftness_(2:15) special.png 3,13 16386
+ 373 Swiftness_(6:00) special.png 3,13 16450
+ 373 Swiftness_II_(1:07) special.png 3,13 16418
+ 373 Fire_Resistance_(2:15) special.png 4,13 16387
+ 373 Fire_Resistance_(2:15) special.png 4,13 16419
+ 373 Fire_Resistance_(6:00) special.png 4,13 16451
+ 373 Healing special.png 6,13 16389
+ 373 Healing special.png 6,13 16453
+ 373 Healing_II special.png 6,13 16421
+ 373 Strength_(2:15) special.png 8,13 16393
+ 373 Strength_(6:00) special.png 8,13 16457
+ 373 Strength_II_(1:07) special.png 8,13 16425
+ 373 Poison_(0:33) special.png 5,13 16388
+ 373 Poison_(1:30) special.png 5,13 16452
+ 373 Poison_II_(0:16) special.png 5,13 16420
+ 373 Weakness_(1:07) special.png 7,13 16392
+ 373 Weakness_(1:07) special.png 7,13 16424
+ 373 Weakness_(3:00) special.png 7,13 16456
+ 373 Slowness_(1:07) special.png 9,13 16394
+ 373 Slowness_(1:07) special.png 9,13 16426
+ 373 Slowness_(3:00) special.png 9,13 16458
+ 373 Harming special.png 10,13 16396
+ 373 Harming special.png 10,13 16460
+ 373 Harming_II special.png 10,13 16428
+# Unbrewable:
+ 373 Regeneration_II_(0:45) special.png 2,13 16481
+ 373 Swiftness_II_(3:00) special.png 3,13 16482
+ 373 Strength_II_(3:00) special.png 8,13 16489
+ 373 Poison_II_(0:45) special.png 5,13 16484
+
+# Spawn Eggs
+# ID NAME FILE CORDS DAMAGE
+ 383 Spawn_Creeper special.png 0,9 50
+ 383 Spawn_Skeleton special.png 1,9 51
+ 383 Spawn_Spider special.png 2,9 52
+ 383 Spawn_Zombie special.png 3,9 54
+ 383 Spawn_Slime special.png 4,9 55
+ 383 Spawn_Ghast special.png 0,10 56
+ 383 Spawn_Zombie_Pigmen special.png 1,10 57
+ 383 Spawn_Enderman special.png 2,10 58
+ 383 Spawn_Cave_Spider special.png 3,10 59
+ 383 Spawn_Silverfish special.png 4,10 60
+ 383 Spawn_Blaze special.png 0,11 61
+ 383 Spawn_Magma_Cube special.png 1,11 62
+ 383 Spawn_Bat special.png 5,9 65
+ 383 Spawn_Witch special.png 5,10 66
+ 383 Spawn_Pig special.png 2,11 90
+ 383 Spawn_Sheep special.png 3,11 91
+ 383 Spawn_Cow special.png 4,11 92
+ 383 Spawn_Chicken special.png 0,12 93
+ 383 Spawn_Squid special.png 1,12 94
+ 383 Spawn_Wolf special.png 2,12 95
+ 383 Spawn_Mooshroom special.png 3,12 96
+ 383 Spawn_Villager special.png 4,12 120
+
+# Groups
+# NAME ICON ITEMS
+# Column 1
+~ Natural 2 2,3,12,24,128,44~1,13,82,79,80,78
+~ Stone 1 1,4,48,67,44~3,139,140,98,109,44~5,44~0,45,108,44~4,101
+~ Wood 5 17,5,53,134,135,136,126,47,85,107,20,102,30
+~ NetherEnd 87 87,88,89,348,112,114,113,372,121,122
+~ Ores 56 16,15,14,56,129,73,21,49,42,41,57,133,22,263~0,265,266,264,388
+~ Special 54 46,52,58,54,130,61,23,25,84,116,379,380,138,146~0,321,389,323,324,330,355,65,96,390,397
+~ Plants1 81 31~1,31~2,106,111,18,81,86,91,103,110
+~ Plants2 6 295,361,362,6,296,338,37,38,39,40,32
+~ Transport 328 66,27,28,328,342,343,333,329,398
+~ Logic 331 331,76,356,69,70,72,131,77,144,33,29,123,137
+~ Wool 35 35~0,35~8,35~7,35~15,35~14,35~12,35~1,35~4,35~5,35~13,35~11,35~3,35~9,35~10,35~2,35~6
+~ Dye 351 351~15,351~7,351~8,351~0,351~1,351~3,351~14,351~11,351~10,351~2,351~4,351~12,351~6,351~5,351~13,351~9
+# Column 2
+~ TierWood 299 298,299,300,301,269,270,271,290,268
+~ TierStone 303 302,303,304,305,273,274,275,291,272
+~ TierIron 307 306,307,308,309,256,257,258,292,267
+~ TierDiam 311 310,311,312,313,277,278,279,293,276
+~ TierGold 315 314,315,316,317,284,285,286,294,283
+~ Tools 261 50,261,262,259,346,359,345,347,395,358,325,326,327,335,384,385,386,387
+~ Food 297 260,322,282,297,360,319,320,363,364,365,366,349,350,354,357,391,396,392,393,394,400
+~ Items 318 280,281,318,337,336,353,339,340,332,376,377,382,381
+~ Drops 341 344,288,334,287,352,289,367,375,341,368,369,370,371,378,399
+~ Music 2257 2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266
+# New
+~ Potion 373 373~0,373~16,373~32,373~8192,373~8193,373~8257,373~8225,373~8289,373~8194,373~8258,373~8226,373~8290,373~8195,373~8259,373~8197,373~8229,373~8201,373~8265,373~8233,373~8297,373~8196,373~8260,373~8228,373~8292,373~8200,373~8264,373~8202,373~8266,373~8204,373~8236,373~16384,373~16385,373~16499,373~16417,373~16481,373~16386,373~16450,373~16418,373~16482,373~16387,373~16451,373~16389,373~16421,373~16393,373~16457,373~16425,373~16489,373~16388,373~16452,373~16420,373~16484,373~16392,373~16456,373~16394,373~16458,373~16396,373~16428
+~ Eggs 383 383~50,383~51,383~52,383~54,383~55,383~56,383~57,383~58,383~59,383~60,383~61,383~62,383~65,383~66,383~90,383~91,383~92,383~93,383~94,383~95,383~96,383~120
+
+# Enchantments
+# EID NAME MAX ITEMS
++ 0 Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 1 Fire_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 2 Feather_Falling 4 301,305,309,313,317
++ 3 Blast_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 4 Projectile_Protection 4 298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317
++ 5 Respiration 3 298,302,306,310,314
++ 6 Aqua_Affinity 1 298,302,306,310,314
++ 16 Sharpness 5 268,272,267,276,283
++ 17 Smite 5 268,272,267,276,283
++ 18 Bane_of_Arthropods 5 268,272,267,276,283
++ 19 Knockback 2 268,272,267,276,283
++ 20 Fire_Aspect 2 268,272,267,276,283
++ 21 Looting 3 268,272,267,276,283
++ 32 Efficiency 5 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 33 Silk_Touch 1 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 34 Unbreaking 3 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 35 Fortune 3 269,270,271,273,274,275,256,257,258,277,278,279,284,285,286
++ 48 Power 5 261
++ 49 Punch 2 261
++ 50 Flame 1 261
++ 51 Infinity 1 261
\ No newline at end of file
--- /dev/null
+'''
+Created on Jul 22, 2011
+
+@author: Rio
+'''
+
+__all__ = ["MCJavaLevel"]
+
+from cStringIO import StringIO
+import gzip
+from level import MCLevel
+from logging import getLogger
+from numpy import fromstring
+import os
+import re
+
+log = getLogger(__name__)
+
+class MCJavaLevel(MCLevel):
+ def setBlockDataAt(self, *args):
+ pass
+
+ def blockDataAt(self, *args):
+ return 0
+
+ @property
+ def Height(self):
+ return self.Blocks.shape[2]
+
+ @property
+ def Length(self):
+ return self.Blocks.shape[1]
+
+ @property
+ def Width(self):
+ return self.Blocks.shape[0]
+
+ def guessSize(self, data):
+ Width = 64
+ Length = 64
+ Height = 64
+ if data.shape[0] <= (32 * 32 * 64) * 2:
+ log.warn(u"Can't guess the size of a {0} byte level".format(data.shape[0]))
+ raise IOError("MCJavaLevel attempted for smaller than 64 blocks cubed")
+ if data.shape[0] > (64 * 64 * 64) * 2:
+ Width = 128
+ Length = 128
+ Height = 64
+ if data.shape[0] > (128 * 128 * 64) * 2:
+ Width = 256
+ Length = 256
+ Height = 64
+ if data.shape[0] > (256 * 256 * 64) * 2: # could also be 256*256*256
+ Width = 512
+ Length = 512
+ Height = 64
+ if data.shape[0] > 512 * 512 * 64 * 2: # just to load shadowmarch castle
+ Width = 512
+ Length = 512
+ Height = 256
+ return Width, Length, Height
+
+ @classmethod
+ def _isDataLevel(cls, data):
+ return (data[0] == 0x27 and
+ data[1] == 0x1B and
+ data[2] == 0xb7 and
+ data[3] == 0x88)
+
+ def __init__(self, filename, data):
+ self.filename = filename
+ if isinstance(data, basestring):
+ data = fromstring(data, dtype='uint8')
+ self.filedata = data
+
+ # try to take x,z,y from the filename
+ r = re.findall("\d+", os.path.basename(filename))
+ if r and len(r) >= 3:
+ (w, l, h) = map(int, r[-3:])
+ if w * l * h > data.shape[0]:
+ log.info("Not enough blocks for size " + str((w, l, h)))
+ w, l, h = self.guessSize(data)
+ else:
+ w, l, h = self.guessSize(data)
+
+ log.info(u"MCJavaLevel created for potential level of size " + str((w, l, h)))
+
+ blockCount = h * l * w
+ if blockCount > data.shape[0]:
+ raise ValueError("Level file does not contain enough blocks! (size {s}) Try putting the size into the filename, e.g. server_level_{w}_{l}_{h}.dat".format(w=w, l=l, h=h, s=data.shape))
+
+ blockOffset = data.shape[0] - blockCount
+ blocks = data[blockOffset:blockOffset + blockCount]
+
+ maxBlockType = 64 # maximum allowed in classic
+ while max(blocks[-4096:]) > maxBlockType:
+ # guess the block array by starting at the end of the file
+ # and sliding the blockCount-sized window back until it
+ # looks like every block has a valid blockNumber
+ blockOffset -= 1
+ blocks = data[blockOffset:blockOffset + blockCount]
+
+ if blockOffset <= -data.shape[0]:
+ raise IOError("Can't find a valid array of blocks <= #%d" % maxBlockType)
+
+ self.Blocks = blocks
+ self.blockOffset = blockOffset
+ blocks.shape = (w, l, h)
+ blocks.strides = (1, w, w * l)
+
+ def saveInPlace(self):
+
+ s = StringIO()
+ g = gzip.GzipFile(fileobj=s, mode='wb')
+
+
+ g.write(self.filedata.tostring())
+ g.flush()
+ g.close()
+
+ try:
+ os.rename(self.filename, self.filename + ".old")
+ except Exception, e:
+ pass
+
+ try:
+ with open(self.filename, 'wb') as f:
+ f.write(s.getvalue())
+ except Exception, e:
+ log.info(u"Error while saving java level in place: {0}".format(e))
+ try:
+ os.remove(self.filename)
+ except:
+ pass
+ os.rename(self.filename + ".old", self.filename)
+
+ try:
+ os.remove(self.filename + ".old")
+ except Exception, e:
+ pass
+
+
+class MCSharpLevel(MCLevel):
+ """ int magic = convert(data.readShort())
+ logger.trace("Magic number: {}", magic)
+ if (magic != 1874)
+ throw new IOException("Only version 1 MCSharp levels supported (magic number was "+magic+")")
+
+ int width = convert(data.readShort())
+ int height = convert(data.readShort())
+ int depth = convert(data.readShort())
+ logger.trace("Width: {}", width)
+ logger.trace("Depth: {}", depth)
+ logger.trace("Height: {}", height)
+
+ int spawnX = convert(data.readShort())
+ int spawnY = convert(data.readShort())
+ int spawnZ = convert(data.readShort())
+
+ int spawnRotation = data.readUnsignedByte()
+ int spawnPitch = data.readUnsignedByte()
+
+ int visitRanks = data.readUnsignedByte()
+ int buildRanks = data.readUnsignedByte()
+
+ byte[][][] blocks = new byte[width][height][depth]
+ int i = 0
+ BlockManager manager = BlockManager.getBlockManager()
+ for(int z = 0;z<depth;z++) {
+ for(int y = 0;y<height;y++) {
+ byte[] row = new byte[height]
+ data.readFully(row)
+ for(int x = 0;x<width;x++) {
+ blocks[x][y][z] = translateBlock(row[x])
+ }
+ }
+ }
+
+ lvl.setBlocks(blocks, new byte[width][height][depth], width, height, depth)
+ lvl.setSpawnPosition(new Position(spawnX, spawnY, spawnZ))
+ lvl.setSpawnRotation(new Rotation(spawnRotation, spawnPitch))
+ lvl.setEnvironment(new Environment())
+
+ return lvl
+ }"""
--- /dev/null
+'''
+Created on Jul 22, 2011
+
+@author: Rio
+'''
+
+import blockrotation
+from box import BoundingBox
+from collections import defaultdict
+from entity import Entity, TileEntity
+import itertools
+from logging import getLogger
+import materials
+from math import floor
+from mclevelbase import ChunkMalformed, ChunkNotPresent, exhaust
+import nbt
+from numpy import argmax, swapaxes, zeros, zeros_like
+import os.path
+
+log = getLogger(__name__)
+
+def computeChunkHeightMap(materials, blocks, HeightMap=None):
+ """Computes the HeightMap array for a chunk, which stores the lowest
+ y-coordinate of each column where the sunlight is still at full strength.
+ The HeightMap array is indexed z,x contrary to the blocks array which is x,z,y.
+
+ If HeightMap is passed, fills it with the result and returns it. Otherwise, returns a
+ new array.
+ """
+
+ lightAbsorption = materials.lightAbsorption[blocks]
+ heights = extractHeights(lightAbsorption)
+ heights = heights.swapaxes(0, 1)
+ if HeightMap is None:
+ return heights.astype('uint8')
+ else:
+ HeightMap[:] = heights
+ return HeightMap
+
+
+def extractHeights(array):
+ """ Given an array of bytes shaped (x, z, y), return the coordinates of the highest
+ non-zero value in each y-column into heightMap
+ """
+
+ # The fastest way I've found to do this is to make a boolean array with >0,
+ # then turn it upside down with ::-1 and use argmax to get the _first_ nonzero
+ # from each column.
+
+ w, h = array.shape[:2]
+ heightMap = zeros((w, h), 'int16')
+
+ heights = argmax((array > 0)[..., ::-1], 2)
+ heights = array.shape[2] - heights
+
+ # if the entire column is air, argmax finds the first air block and the result is a top height column
+ # top height columns won't ever have air in the top block so we can find air columns by checking for both
+ heights[(array[..., -1] == 0) & (heights == array.shape[2])] = 0
+
+ heightMap[:] = heights
+
+ return heightMap
+
+
+def getSlices(box, height):
+ """ call this method to iterate through a large slice of the world by
+ visiting each chunk and indexing its data with a subslice.
+
+ this returns an iterator, which yields 3-tuples containing:
+ + a pair of chunk coordinates (cx, cz),
+ + a x,z,y triplet of slices that can be used to index the AnvilChunk's data arrays,
+ + a x,y,z triplet representing the relative location of this subslice within the requested world slice.
+
+ Note the different order of the coordinates between the 'slices' triplet
+ and the 'offset' triplet. x,z,y ordering is used only
+ to index arrays, since it reflects the order of the blocks in memory.
+ In all other places, including an entity's 'Pos', the order is x,y,z.
+ """
+
+ # when yielding slices of chunks on the edge of the box, adjust the
+ # slices by an offset
+ minxoff, minzoff = box.minx - (box.mincx << 4), box.minz - (box.mincz << 4)
+ maxxoff, maxzoff = box.maxx - (box.maxcx << 4) + 16, box.maxz - (box.maxcz << 4) + 16
+
+ newMinY = 0
+ if box.miny < 0:
+ newMinY = -box.miny
+ miny = max(0, box.miny)
+ maxy = min(height, box.maxy)
+
+ for cx in range(box.mincx, box.maxcx):
+ localMinX = 0
+ localMaxX = 16
+ if cx == box.mincx:
+ localMinX = minxoff
+
+ if cx == box.maxcx - 1:
+ localMaxX = maxxoff
+ newMinX = localMinX + (cx << 4) - box.minx
+
+ for cz in range(box.mincz, box.maxcz):
+ localMinZ = 0
+ localMaxZ = 16
+ if cz == box.mincz:
+ localMinZ = minzoff
+ if cz == box.maxcz - 1:
+ localMaxZ = maxzoff
+ newMinZ = localMinZ + (cz << 4) - box.minz
+ slices, point = (
+ (slice(localMinX, localMaxX), slice(localMinZ, localMaxZ), slice(miny, maxy)),
+ (newMinX, newMinY, newMinZ)
+ )
+
+ yield (cx, cz), slices, point
+
+
+class MCLevel(object):
+ """ MCLevel is an abstract class providing many routines to the different level types,
+ including a common copyEntitiesFrom built on class-specific routines, and
+ a dummy getChunk/allChunks for the finite levels.
+
+ MCLevel subclasses must have Width, Length, and Height attributes. The first two are always zero for infinite levels.
+ Subclasses must also have Blocks, and optionally Data and BlockLight.
+ """
+
+ ### common to Creative, Survival and Indev. these routines assume
+ ### self has Width, Height, Length, and Blocks
+
+ materials = materials.classicMaterials
+ isInfinite = False
+
+ root_tag = None
+
+ Height = None
+ Length = None
+ Width = None
+
+ players = ["Player"]
+ dimNo = 0
+ parentWorld = None
+ world = None
+
+ @classmethod
+ def isLevel(cls, filename):
+ """Tries to find out whether the given filename can be loaded
+ by this class. Returns True or False.
+
+ Subclasses should implement _isLevel, _isDataLevel, or _isTagLevel.
+ """
+ if hasattr(cls, "_isLevel"):
+ return cls._isLevel(filename)
+
+ with file(filename) as f:
+ data = f.read()
+
+ if hasattr(cls, "_isDataLevel"):
+ return cls._isDataLevel(data)
+
+ if hasattr(cls, "_isTagLevel"):
+ try:
+ root_tag = nbt.load(filename, data)
+ except:
+ return False
+
+ return cls._isTagLevel(root_tag)
+
+ return False
+
+ def getWorldBounds(self):
+ return BoundingBox((0, 0, 0), self.size)
+
+ @property
+ def displayName(self):
+ return os.path.basename(self.filename)
+
+ @property
+ def size(self):
+ "Returns the level's dimensions as a tuple (X,Y,Z)"
+ return self.Width, self.Height, self.Length
+
+ @property
+ def bounds(self):
+ return BoundingBox((0, 0, 0), self.size)
+
+ def close(self):
+ pass
+
+ # --- Entity Methods ---
+ def addEntity(self, entityTag):
+ pass
+
+ def addEntities(self, entities):
+ pass
+
+ def tileEntityAt(self, x, y, z):
+ return None
+
+ def addTileEntity(self, entityTag):
+ pass
+
+ def getEntitiesInBox(self, box):
+ return []
+
+ def getTileEntitiesInBox(self, box):
+ return []
+
+ def removeEntitiesInBox(self, box):
+ pass
+
+ def removeTileEntitiesInBox(self, box):
+ pass
+
+ @property
+ def chunkCount(self):
+ return (self.Width + 15 >> 4) * (self.Length + 15 >> 4)
+
+ @property
+ def allChunks(self):
+ """Returns a synthetic list of chunk positions (xPos, zPos), to fake
+ being a chunked level format."""
+ return itertools.product(xrange(0, self.Width + 15 >> 4), xrange(0, self.Length + 15 >> 4))
+
+ def getChunks(self, chunks=None):
+ """ pass a list of chunk coordinate tuples to get an iterator yielding
+ AnvilChunks. pass nothing for an iterator of every chunk in the level.
+ the chunks are automatically loaded."""
+ if chunks is None:
+ chunks = self.allChunks
+ return (self.getChunk(cx, cz) for (cx, cz) in chunks if self.containsChunk(cx, cz))
+
+ def _getFakeChunkEntities(self, cx, cz):
+ """Returns Entities, TileEntities"""
+ return nbt.TAG_List(), nbt.TAG_List()
+
+ def getChunk(self, cx, cz):
+ """Synthesize a FakeChunk object representing the chunk at the given
+ position. Subclasses override fakeBlocksForChunk and fakeDataForChunk
+ to fill in the chunk arrays"""
+
+ f = FakeChunk()
+ f.world = self
+ f.chunkPosition = (cx, cz)
+
+ f.Blocks = self.fakeBlocksForChunk(cx, cz)
+
+ f.Data = self.fakeDataForChunk(cx, cz)
+
+ whiteLight = zeros_like(f.Blocks)
+ whiteLight[:] = 15
+
+ f.BlockLight = whiteLight
+ f.SkyLight = whiteLight
+
+ f.Entities, f.TileEntities = self._getFakeChunkEntities(cx, cz)
+
+ f.root_tag = nbt.TAG_Compound()
+
+ return f
+
+ def getAllChunkSlices(self):
+ slices = (slice(None), slice(None), slice(None),)
+ box = self.bounds
+ x, y, z = box.origin
+
+ for cpos in self.allChunks:
+ xPos, zPos = cpos
+ try:
+ chunk = self.getChunk(xPos, zPos)
+ except (ChunkMalformed, ChunkNotPresent):
+ continue
+
+ yield (chunk, slices, (xPos * 16 - x, 0, zPos * 16 - z))
+
+ def _getSlices(self, box):
+ if box == self.bounds:
+ log.info("All chunks selected! Selecting %s chunks instead of %s", self.chunkCount, box.chunkCount)
+ y = box.miny
+ slices = slice(0, 16), slice(0, 16), slice(0, box.maxy)
+
+ def getAllSlices():
+ for cPos in self.allChunks:
+ x, z = cPos
+ x *= 16
+ z *= 16
+ x -= box.minx
+ z -= box.minz
+ yield cPos, slices, (x, y, z)
+ return getAllSlices()
+ else:
+ return getSlices(box, self.Height)
+
+ def getChunkSlices(self, box):
+ return ((self.getChunk(*cPos), slices, point)
+ for cPos, slices, point in self._getSlices(box)
+ if self.containsChunk(*cPos))
+
+ def containsPoint(self, x, y, z):
+ return (x, y, z) in self.bounds
+
+ def containsChunk(self, cx, cz):
+ bounds = self.bounds
+ return ((bounds.mincx <= cx < bounds.maxcx) and
+ (bounds.mincz <= cz < bounds.maxcz))
+
+ def fakeBlocksForChunk(self, cx, cz):
+ # return a 16x16xH block array for rendering. Alpha levels can
+ # just return the chunk data. other levels need to reorder the
+ # indices and return a slice of the blocks.
+
+ cxOff = cx << 4
+ czOff = cz << 4
+ b = self.Blocks[cxOff:cxOff + 16, czOff:czOff + 16, 0:self.Height, ]
+ # (w, l, h) = b.shape
+ # if w<16 or l<16:
+ # b = resize(b, (16,16,h) )
+ return b
+
+ def fakeDataForChunk(self, cx, cz):
+ # Data is emulated for flexibility
+ cxOff = cx << 4
+ czOff = cz << 4
+
+ if hasattr(self, "Data"):
+ return self.Data[cxOff:cxOff + 16, czOff:czOff + 16, 0:self.Height, ]
+
+ else:
+ return zeros(shape=(16, 16, self.Height), dtype='uint8')
+
+ # --- Block accessors ---
+ def skylightAt(self, *args):
+ return 15
+
+ def setSkylightAt(self, *args):
+ pass
+
+ def setBlockDataAt(self, x, y, z, newdata):
+ pass
+
+ def blockDataAt(self, x, y, z):
+ return 0
+
+ def blockLightAt(self, x, y, z):
+ return 15
+
+ def blockAt(self, x, y, z):
+ if (x, y, z) not in self.bounds:
+ return 0
+ return self.Blocks[x, z, y]
+
+ def setBlockAt(self, x, y, z, blockID):
+ if (x, y, z) not in self.bounds:
+ return 0
+ self.Blocks[x, z, y] = blockID
+
+ # --- Fill and Replace ---
+
+ from block_fill import fillBlocks, fillBlocksIter
+
+ # --- Transformations ---
+ def rotateLeft(self):
+ self.Blocks = swapaxes(self.Blocks, 1, 0)[:, ::-1, :] # x=z; z=-x
+ pass
+
+ def roll(self):
+ self.Blocks = swapaxes(self.Blocks, 2, 0)[:, :, ::-1] # x=y; y=-x
+ pass
+
+ def flipVertical(self):
+ self.Blocks = self.Blocks[:, :, ::-1] # y=-y
+ pass
+
+ def flipNorthSouth(self):
+ self.Blocks = self.Blocks[::-1, :, :] # x=-x
+ pass
+
+ def flipEastWest(self):
+ self.Blocks = self.Blocks[:, ::-1, :] # z=-z
+ pass
+
+ # --- Copying ---
+
+ from block_copy import copyBlocksFrom, copyBlocksFromIter
+
+
+ def saveInPlace(self):
+ self.saveToFile(self.filename)
+
+ # --- Player Methods ---
+ def setPlayerPosition(self, pos, player="Player"):
+ pass
+
+ def getPlayerPosition(self, player="Player"):
+ return 8, self.Height * 0.75, 8
+
+ def getPlayerDimension(self, player="Player"):
+ return 0
+
+ def setPlayerDimension(self, d, player="Player"):
+ return
+
+ def setPlayerSpawnPosition(self, pos, player=None):
+ pass
+
+ def playerSpawnPosition(self, player=None):
+ return self.getPlayerPosition()
+
+ def setPlayerOrientation(self, yp, player="Player"):
+ pass
+
+ def getPlayerOrientation(self, player="Player"):
+ return -45., 0.
+
+ # --- Dummy Lighting Methods ---
+ def generateLights(self, dirtyChunks=None):
+ pass
+
+ def generateLightsIter(self, dirtyChunks=None):
+ yield 0
+
+
+class EntityLevel(MCLevel):
+ """Abstract subclass of MCLevel that adds default entity behavior"""
+
+ def getEntitiesInBox(self, box):
+ """Returns a list of references to entities in this chunk, whose positions are within box"""
+ return [ent for ent in self.Entities if Entity.pos(ent) in box]
+
+ def getTileEntitiesInBox(self, box):
+ """Returns a list of references to tile entities in this chunk, whose positions are within box"""
+ return [ent for ent in self.TileEntities if TileEntity.pos(ent) in box]
+
+ def removeEntitiesInBox(self, box):
+
+ newEnts = []
+ for ent in self.Entities:
+ if Entity.pos(ent) in box:
+ continue
+ newEnts.append(ent)
+
+ entsRemoved = len(self.Entities) - len(newEnts)
+ log.debug("Removed {0} entities".format(entsRemoved))
+
+ self.Entities.value[:] = newEnts
+
+ return entsRemoved
+
+ def removeTileEntitiesInBox(self, box):
+
+ if not hasattr(self, "TileEntities"):
+ return
+ newEnts = []
+ for ent in self.TileEntities:
+ if TileEntity.pos(ent) in box:
+ continue
+ newEnts.append(ent)
+
+ entsRemoved = len(self.TileEntities) - len(newEnts)
+ log.debug("Removed {0} tile entities".format(entsRemoved))
+
+ self.TileEntities.value[:] = newEnts
+
+ return entsRemoved
+
+ def addEntities(self, entities):
+ for e in entities:
+ self.addEntity(e)
+
+ def addEntity(self, entityTag):
+ assert isinstance(entityTag, nbt.TAG_Compound)
+ self.Entities.append(entityTag)
+ self._fakeEntities = None
+
+ def tileEntityAt(self, x, y, z):
+ entities = []
+ for entityTag in self.TileEntities:
+ if TileEntity.pos(entityTag) == [x, y, z]:
+ entities.append(entityTag)
+
+ if len(entities) > 1:
+ log.info("Multiple tile entities found: {0}".format(entities))
+ if len(entities) == 0:
+ return None
+
+ return entities[0]
+
+ def addTileEntity(self, tileEntityTag):
+ assert isinstance(tileEntityTag, nbt.TAG_Compound)
+
+ def differentPosition(a):
+
+ return not ((tileEntityTag is a) or TileEntity.pos(a) == TileEntity.pos(tileEntityTag))
+
+ self.TileEntities.value[:] = filter(differentPosition, self.TileEntities)
+
+ self.TileEntities.append(tileEntityTag)
+ self._fakeEntities = None
+
+ _fakeEntities = None
+
+ def _getFakeChunkEntities(self, cx, cz):
+ """distribute entities into sublists based on fake chunk position
+ _fakeEntities keys are (cx, cz) and values are (Entities, TileEntities)"""
+ if self._fakeEntities is None:
+ self._fakeEntities = defaultdict(lambda: (nbt.TAG_List(), nbt.TAG_List()))
+ for i, e in enumerate((self.Entities, self.TileEntities)):
+ for ent in e:
+ x, y, z = [Entity, TileEntity][i].pos(ent)
+ ecx, ecz = map(lambda x: (int(floor(x)) >> 4), (x, z))
+
+ self._fakeEntities[ecx, ecz][i].append(ent)
+
+ return self._fakeEntities[cx, cz]
+
+
+class ChunkBase(EntityLevel):
+ dirty = False
+ needsLighting = False
+
+ chunkPosition = NotImplemented
+ Blocks = Data = SkyLight = BlockLight = HeightMap = NotImplemented # override these!
+
+ Width = Length = 16
+
+ @property
+ def Height(self):
+ return self.world.Height
+
+ @property
+ def bounds(self):
+ cx, cz = self.chunkPosition
+ return BoundingBox((cx << 4, 0, cz << 4), self.size)
+
+
+ def chunkChanged(self, needsLighting=True):
+ self.dirty = True
+ self.needsLighting = needsLighting or self.needsLighting
+
+ @property
+ def materials(self):
+ return self.world.materials
+
+
+ def getChunkSlicesForBox(self, box):
+ """
+ Given a BoundingBox enclosing part of the world, return a smaller box enclosing the part of this chunk
+ intersecting the given box, and a tuple of slices that can be used to select the corresponding parts
+ of this chunk's block and data arrays.
+ """
+ bounds = self.bounds
+ localBox = box.intersect(bounds)
+
+ slices = (
+ slice(localBox.minx - bounds.minx, localBox.maxx - bounds.minx),
+ slice(localBox.minz - bounds.minz, localBox.maxz - bounds.minz),
+ slice(localBox.miny - bounds.miny, localBox.maxy - bounds.miny),
+ )
+ return localBox, slices
+
+
+class FakeChunk(ChunkBase):
+ @property
+ def HeightMap(self):
+ if hasattr(self, "_heightMap"):
+ return self._heightMap
+
+ self._heightMap = computeChunkHeightMap(self.materials, self.Blocks)
+ return self._heightMap
+
+
+class LightedChunk(ChunkBase):
+ def generateHeightMap(self):
+ computeChunkHeightMap(self.materials, self.Blocks, self.HeightMap)
+
+ def chunkChanged(self, calcLighting=True):
+ """ You are required to call this function after you are done modifying
+ the chunk. Pass False for calcLighting if you know your changes will
+ not change any lights."""
+
+ self.dirty = True
+ self.needsLighting = calcLighting or self.needsLighting
+ self.generateHeightMap()
+ if calcLighting:
+ self.genFastLights()
+
+ def genFastLights(self):
+ self.SkyLight[:] = 0
+ if self.world.dimNo in (-1, 1):
+ return # no light in nether or the end
+
+ blocks = self.Blocks
+ la = self.world.materials.lightAbsorption
+ skylight = self.SkyLight
+ heightmap = self.HeightMap
+
+ for x, z in itertools.product(xrange(16), xrange(16)):
+
+ skylight[x, z, heightmap[z, x]:] = 15
+ lv = 15
+ for y in reversed(range(heightmap[z, x])):
+ lv -= (la[blocks[x, z, y]] or 1)
+
+ if lv <= 0:
+ break
+ skylight[x, z, y] = lv
--- /dev/null
+
+from logging import getLogger
+from numpy import zeros, rollaxis, indices
+import traceback
+from os.path import join
+from collections import defaultdict
+from pprint import pformat
+
+import os
+
+NOTEX = (0x90, 0xD0)
+
+try:
+ import yaml
+except:
+ yaml = None
+
+log = getLogger(__file__)
+
+
+class Block(object):
+ """
+ Value object representing an (id, data) pair.
+ Provides elements of its parent material's block arrays.
+ Blocks will have (name, ID, blockData, aka, color, brightness, opacity, blockTextures)
+ """
+
+ def __str__(self):
+ return "<Block {name} ({id}:{data}) hasVariants:{ha}>".format(
+ name=self.name, id=self.ID, data=self.blockData, ha=self.hasVariants)
+
+ def __repr__(self):
+ return str(self)
+
+ def __cmp__(self, other):
+ if not isinstance(other, Block):
+ return -1
+ key = lambda a: a and (a.ID, a.blockData)
+ return cmp(key(self), key(other))
+
+ hasVariants = False # True if blockData defines additional blocktypes
+
+ def __init__(self, materials, blockID, blockData=0):
+ self.materials = materials
+ self.ID = blockID
+ self.blockData = blockData
+
+ def __getattr__(self, attr):
+ if attr in self.__dict__:
+ return self.__dict__[attr]
+ if attr == "name":
+ r = self.materials.names[self.ID]
+ else:
+ r = getattr(self.materials, attr)[self.ID]
+ if attr in ("name", "aka", "color", "type"):
+ r = r[self.blockData]
+ return r
+
+
+class MCMaterials(object):
+ defaultColor = (0xc9, 0x77, 0xf0, 0xff)
+ defaultBrightness = 0
+ defaultOpacity = 15
+ defaultTexture = NOTEX
+ defaultTex = [t // 16 for t in defaultTexture]
+
+ def __init__(self, defaultName="Unused Block"):
+ object.__init__(self)
+ self.yamlDatas = []
+
+ self.defaultName = defaultName
+
+ self.blockTextures = zeros((256, 16, 6, 2), dtype='uint8')
+ self.blockTextures[:] = self.defaultTexture
+ self.names = [[defaultName] * 16 for i in range(256)]
+ self.aka = [[""] * 16 for i in range(256)]
+
+ self.type = [["NORMAL"] * 16] * 256
+ self.blocksByType = defaultdict(list)
+ self.allBlocks = []
+ self.blocksByID = {}
+
+ self.lightEmission = zeros(256, dtype='uint8')
+ self.lightEmission[:] = self.defaultBrightness
+ self.lightAbsorption = zeros(256, dtype='uint8')
+ self.lightAbsorption[:] = self.defaultOpacity
+ self.flatColors = zeros((256, 16, 4), dtype='uint8')
+ self.flatColors[:] = self.defaultColor
+
+ self.idStr = {}
+
+ self.color = self.flatColors
+ self.brightness = self.lightEmission
+ self.opacity = self.lightAbsorption
+
+ self.Air = self.addBlock(0,
+ name="Air",
+ texture=(0x80, 0xB0),
+ opacity=0,
+ )
+
+ def __repr__(self):
+ return "<MCMaterials ({0})>".format(self.name)
+
+ @property
+ def AllStairs(self):
+ return [b for b in self.allBlocks if b.name.endswith("Stairs")]
+
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def __len__(self):
+ return len(self.allBlocks)
+
+ def __iter__(self):
+ return iter(self.allBlocks)
+
+ def __getitem__(self, key):
+ """ Let's be magic. If we get a string, return the first block whose
+ name matches exactly. If we get a (id, data) pair or an id, return
+ that block. for example:
+
+ level.materials[0] # returns Air
+ level.materials["Air"] # also returns Air
+ level.materials["Powered Rail"] # returns Powered Rail
+ level.materials["Lapis Lazuli Block"] # in Classic
+
+ """
+ if isinstance(key, basestring):
+ for b in self.allBlocks:
+ if b.name == key:
+ return b
+ raise KeyError("No blocks named: " + key)
+ if isinstance(key, (tuple, list)):
+ id, blockData = key
+ return self.blockWithID(id, blockData)
+ return self.blockWithID(key)
+
+ def blocksMatching(self, name):
+ name = name.lower()
+ return [v for v in self.allBlocks if name in v.name.lower() or name in v.aka.lower()]
+
+ def blockWithID(self, id, data=0):
+ if (id, data) in self.blocksByID:
+ return self.blocksByID[id, data]
+ else:
+ bl = Block(self, id, blockData=data)
+ bl.hasVariants = True
+ return bl
+
+ def addYamlBlocksFromFile(self, filename):
+ if yaml is None:
+ return
+
+ try:
+ import pkg_resources
+
+ f = pkg_resources.resource_stream(__name__, filename)
+ except (ImportError, IOError):
+ root = os.environ.get("PYMCLEVEL_YAML_ROOT", "pymclevel") # fall back to cwd as last resort
+ path = join(root, filename)
+
+ log.exception("Failed to read %s using pkg_resources. Trying %s instead." % (filename, path))
+
+ f = file(path)
+ try:
+ log.info(u"Loading block info from %s", f)
+ blockyaml = yaml.load(f)
+ self.addYamlBlocks(blockyaml)
+
+ except Exception, e:
+ log.warn(u"Exception while loading block info from %s: %s", f, e)
+ traceback.print_exc()
+
+ def addYamlBlocks(self, blockyaml):
+ self.yamlDatas.append(blockyaml)
+ for block in blockyaml['blocks']:
+ try:
+ self.addYamlBlock(block)
+ except Exception, e:
+ log.warn(u"Exception while parsing block: %s", e)
+ traceback.print_exc()
+ log.warn(u"Block definition: \n%s", pformat(block))
+
+ def addYamlBlock(self, kw):
+ blockID = kw['id']
+
+ # xxx unused_yaml_properties variable unused; needed for
+ # documentation purpose of some sort? -zothar
+ #unused_yaml_properties = \
+ #['explored',
+ # # 'id',
+ # # 'idStr',
+ # # 'mapcolor',
+ # # 'name',
+ # # 'tex',
+ # ### 'tex_data',
+ # # 'tex_direction',
+ # ### 'tex_direction_data',
+ # 'tex_extra',
+ # # 'type'
+ # ]
+
+ for val, data in kw.get('data', {0: {}}).items():
+ datakw = dict(kw)
+ datakw.update(data)
+ idStr = datakw.get('idStr', "")
+ tex = [t * 16 for t in datakw.get('tex', self.defaultTex)]
+ texture = [tex] * 6
+ texDirs = {
+ "FORWARD": 5,
+ "BACKWARD": 4,
+ "LEFT": 1,
+ "RIGHT": 0,
+ "TOP": 2,
+ "BOTTOM": 3,
+ }
+ for dirname, dirtex in datakw.get('tex_direction', {}).items():
+ if dirname == "SIDES":
+ for dirname in ("LEFT", "RIGHT"):
+ texture[texDirs[dirname]] = [t * 16 for t in dirtex]
+ if dirname in texDirs:
+ texture[texDirs[dirname]] = [t * 16 for t in dirtex]
+ datakw['texture'] = texture
+ # print datakw
+ block = self.addBlock(blockID, val, **datakw)
+ block.yaml = datakw
+ if idStr not in self.idStr:
+ self.idStr[idStr] = block
+
+ tex_direction_data = kw.get('tex_direction_data')
+ if tex_direction_data:
+ texture = datakw['texture']
+ # X+0, X-1, Y+, Y-, Z+b, Z-f
+ texDirMap = {
+ "NORTH": 0,
+ "EAST": 1,
+ "SOUTH": 2,
+ "WEST": 3,
+ }
+
+ def rot90cw():
+ rot = (5, 0, 2, 3, 4, 1)
+ texture[:] = [texture[r] for r in rot]
+
+ for data, dir in tex_direction_data.items():
+ for _i in range(texDirMap.get(dir, 0)):
+ rot90cw()
+ self.blockTextures[blockID][data] = texture
+
+ def addBlock(self, blockID, blockData=0, **kw):
+ name = kw.pop('name', self.names[blockID][blockData])
+
+ self.lightEmission[blockID] = kw.pop('brightness', self.defaultBrightness)
+ self.lightAbsorption[blockID] = kw.pop('opacity', self.defaultOpacity)
+ self.aka[blockID][blockData] = kw.pop('aka', "")
+ type = kw.pop('type', 'NORMAL')
+
+ color = kw.pop('mapcolor', self.flatColors[blockID, blockData])
+ self.flatColors[blockID, (blockData or slice(None))] = (tuple(color) + (255,))[:4]
+
+ texture = kw.pop('texture', None)
+
+ if texture:
+ self.blockTextures[blockID, (blockData or slice(None))] = texture
+
+ if blockData is 0:
+ self.names[blockID] = [name] * 16
+ self.type[blockID] = [type] * 16
+ else:
+ self.names[blockID][blockData] = name
+ self.type[blockID][blockData] = type
+
+ block = Block(self, blockID, blockData)
+
+ self.allBlocks.append(block)
+ self.blocksByType[type].append(block)
+
+ if (blockID, 0) in self.blocksByID:
+ self.blocksByID[blockID, 0].hasVariants = True
+ block.hasVariants = True
+
+ self.blocksByID[blockID, blockData] = block
+
+ return block
+
+alphaMaterials = MCMaterials(defaultName="Future Block!")
+alphaMaterials.name = "Alpha"
+alphaMaterials.addYamlBlocksFromFile("minecraft.yaml")
+
+# --- Special treatment for some blocks ---
+
+HugeMushroomTypes = {
+ "Northwest": 1,
+ "North": 2,
+ "Northeast": 3,
+ "East": 6,
+ "Southeast": 9,
+ "South": 8,
+ "Southwest": 7,
+ "West": 4,
+ "Stem": 10,
+ "Top": 5,
+}
+from faces import FaceXDecreasing, FaceXIncreasing, FaceYIncreasing, FaceZDecreasing, FaceZIncreasing
+
+Red = (0xD0, 0x70)
+Brown = (0xE0, 0x70)
+Pore = (0xE0, 0x80)
+Stem = (0xD0, 0x80)
+
+
+def defineShroomFaces(Shroom, id, name):
+ for way, data in sorted(HugeMushroomTypes.items(), key=lambda a: a[1]):
+ loway = way.lower()
+ if way is "Stem":
+ tex = [Stem, Stem, Pore, Pore, Stem, Stem]
+ elif way is "Pore":
+ tex = Pore
+ else:
+ tex = [Pore] * 6
+ tex[FaceYIncreasing] = Shroom
+ if "north" in loway:
+ tex[FaceZDecreasing] = Shroom
+ if "south" in loway:
+ tex[FaceZIncreasing] = Shroom
+ if "west" in loway:
+ tex[FaceXDecreasing] = Shroom
+ if "east" in loway:
+ tex[FaceXIncreasing] = Shroom
+
+ alphaMaterials.addBlock(id, blockData=data,
+ name="Huge " + name + " Mushroom (" + way + ")",
+ texture=tex,
+ )
+
+defineShroomFaces(Brown, 99, "Brown")
+defineShroomFaces(Red, 100, "Red")
+
+classicMaterials = MCMaterials(defaultName="Not present in Classic")
+classicMaterials.name = "Classic"
+classicMaterials.addYamlBlocksFromFile("classic.yaml")
+
+indevMaterials = MCMaterials(defaultName="Not present in Indev")
+indevMaterials.name = "Indev"
+indevMaterials.addYamlBlocksFromFile("indev.yaml")
+
+pocketMaterials = MCMaterials()
+pocketMaterials.name = "Pocket"
+pocketMaterials.addYamlBlocksFromFile("pocket.yaml")
+
+# --- Static block defs ---
+
+alphaMaterials.Stone = alphaMaterials[1, 0]
+alphaMaterials.Grass = alphaMaterials[2, 0]
+alphaMaterials.Dirt = alphaMaterials[3, 0]
+alphaMaterials.Cobblestone = alphaMaterials[4, 0]
+alphaMaterials.WoodPlanks = alphaMaterials[5, 0]
+alphaMaterials.Sapling = alphaMaterials[6, 0]
+alphaMaterials.SpruceSapling = alphaMaterials[6, 1]
+alphaMaterials.BirchSapling = alphaMaterials[6, 2]
+alphaMaterials.Bedrock = alphaMaterials[7, 0]
+alphaMaterials.WaterActive = alphaMaterials[8, 0]
+alphaMaterials.Water = alphaMaterials[9, 0]
+alphaMaterials.LavaActive = alphaMaterials[10, 0]
+alphaMaterials.Lava = alphaMaterials[11, 0]
+alphaMaterials.Sand = alphaMaterials[12, 0]
+alphaMaterials.Gravel = alphaMaterials[13, 0]
+alphaMaterials.GoldOre = alphaMaterials[14, 0]
+alphaMaterials.IronOre = alphaMaterials[15, 0]
+alphaMaterials.CoalOre = alphaMaterials[16, 0]
+alphaMaterials.Wood = alphaMaterials[17, 0]
+alphaMaterials.Ironwood = alphaMaterials[17, 1]
+alphaMaterials.BirchWood = alphaMaterials[17, 2]
+alphaMaterials.Leaves = alphaMaterials[18, 0]
+alphaMaterials.PineLeaves = alphaMaterials[18, 1]
+alphaMaterials.BirchLeaves = alphaMaterials[18, 2]
+alphaMaterials.JungleLeaves = alphaMaterials[18, 3]
+alphaMaterials.LeavesPermanent = alphaMaterials[18, 4]
+alphaMaterials.PineLeavesPermanent = alphaMaterials[18, 5]
+alphaMaterials.BirchLeavesPermanent = alphaMaterials[18, 6]
+alphaMaterials.JungleLeavesPermanent = alphaMaterials[18, 7]
+alphaMaterials.LeavesDecaying = alphaMaterials[18, 8]
+alphaMaterials.PineLeavesDecaying = alphaMaterials[18, 9]
+alphaMaterials.BirchLeavesDecaying = alphaMaterials[18, 10]
+alphaMaterials.JungleLeavesDecaying = alphaMaterials[18, 11]
+alphaMaterials.Sponge = alphaMaterials[19, 0]
+alphaMaterials.Glass = alphaMaterials[20, 0]
+
+alphaMaterials.LapisLazuliOre = alphaMaterials[21, 0]
+alphaMaterials.LapisLazuliBlock = alphaMaterials[22, 0]
+alphaMaterials.Dispenser = alphaMaterials[23, 0]
+alphaMaterials.Sandstone = alphaMaterials[24, 0]
+alphaMaterials.NoteBlock = alphaMaterials[25, 0]
+alphaMaterials.Bed = alphaMaterials[26, 0]
+alphaMaterials.PoweredRail = alphaMaterials[27, 0]
+alphaMaterials.DetectorRail = alphaMaterials[28, 0]
+alphaMaterials.StickyPiston = alphaMaterials[29, 0]
+alphaMaterials.Web = alphaMaterials[30, 0]
+alphaMaterials.UnusedShrub = alphaMaterials[31, 0]
+alphaMaterials.TallGrass = alphaMaterials[31, 1]
+alphaMaterials.Shrub = alphaMaterials[31, 2]
+alphaMaterials.DesertShrub2 = alphaMaterials[32, 0]
+alphaMaterials.Piston = alphaMaterials[33, 0]
+alphaMaterials.PistonHead = alphaMaterials[34, 0]
+alphaMaterials.WhiteWool = alphaMaterials[35, 0]
+alphaMaterials.OrangeWool = alphaMaterials[35, 1]
+alphaMaterials.MagentaWool = alphaMaterials[35, 2]
+alphaMaterials.LightBlueWool = alphaMaterials[35, 3]
+alphaMaterials.YellowWool = alphaMaterials[35, 4]
+alphaMaterials.LightGreenWool = alphaMaterials[35, 5]
+alphaMaterials.PinkWool = alphaMaterials[35, 6]
+alphaMaterials.GrayWool = alphaMaterials[35, 7]
+alphaMaterials.LightGrayWool = alphaMaterials[35, 8]
+alphaMaterials.CyanWool = alphaMaterials[35, 9]
+alphaMaterials.PurpleWool = alphaMaterials[35, 10]
+alphaMaterials.BlueWool = alphaMaterials[35, 11]
+alphaMaterials.BrownWool = alphaMaterials[35, 12]
+alphaMaterials.DarkGreenWool = alphaMaterials[35, 13]
+alphaMaterials.RedWool = alphaMaterials[35, 14]
+alphaMaterials.BlackWool = alphaMaterials[35, 15]
+
+alphaMaterials.Flower = alphaMaterials[37, 0]
+alphaMaterials.Rose = alphaMaterials[38, 0]
+alphaMaterials.BrownMushroom = alphaMaterials[39, 0]
+alphaMaterials.RedMushroom = alphaMaterials[40, 0]
+alphaMaterials.BlockofGold = alphaMaterials[41, 0]
+alphaMaterials.BlockofIron = alphaMaterials[42, 0]
+alphaMaterials.DoubleStoneSlab = alphaMaterials[43, 0]
+alphaMaterials.DoubleSandstoneSlab = alphaMaterials[43, 1]
+alphaMaterials.DoubleWoodenSlab = alphaMaterials[43, 2]
+alphaMaterials.DoubleCobblestoneSlab = alphaMaterials[43, 3]
+alphaMaterials.DoubleBrickSlab = alphaMaterials[43, 4]
+alphaMaterials.DoubleStoneBrickSlab = alphaMaterials[43, 5]
+alphaMaterials.StoneSlab = alphaMaterials[44, 0]
+alphaMaterials.SandstoneSlab = alphaMaterials[44, 1]
+alphaMaterials.WoodenSlab = alphaMaterials[44, 2]
+alphaMaterials.CobblestoneSlab = alphaMaterials[44, 3]
+alphaMaterials.BrickSlab = alphaMaterials[44, 4]
+alphaMaterials.StoneBrickSlab = alphaMaterials[44, 5]
+alphaMaterials.Brick = alphaMaterials[45, 0]
+alphaMaterials.TNT = alphaMaterials[46, 0]
+alphaMaterials.Bookshelf = alphaMaterials[47, 0]
+alphaMaterials.MossStone = alphaMaterials[48, 0]
+alphaMaterials.Obsidian = alphaMaterials[49, 0]
+
+alphaMaterials.Torch = alphaMaterials[50, 0]
+alphaMaterials.Fire = alphaMaterials[51, 0]
+alphaMaterials.MonsterSpawner = alphaMaterials[52, 0]
+alphaMaterials.WoodenStairs = alphaMaterials[53, 0]
+alphaMaterials.Chest = alphaMaterials[54, 0]
+alphaMaterials.RedstoneWire = alphaMaterials[55, 0]
+alphaMaterials.DiamondOre = alphaMaterials[56, 0]
+alphaMaterials.BlockofDiamond = alphaMaterials[57, 0]
+alphaMaterials.CraftingTable = alphaMaterials[58, 0]
+alphaMaterials.Crops = alphaMaterials[59, 0]
+alphaMaterials.Farmland = alphaMaterials[60, 0]
+alphaMaterials.Furnace = alphaMaterials[61, 0]
+alphaMaterials.LitFurnace = alphaMaterials[62, 0]
+alphaMaterials.Sign = alphaMaterials[63, 0]
+alphaMaterials.WoodenDoor = alphaMaterials[64, 0]
+alphaMaterials.Ladder = alphaMaterials[65, 0]
+alphaMaterials.Rail = alphaMaterials[66, 0]
+alphaMaterials.StoneStairs = alphaMaterials[67, 0]
+alphaMaterials.WallSign = alphaMaterials[68, 0]
+alphaMaterials.Lever = alphaMaterials[69, 0]
+alphaMaterials.StoneFloorPlate = alphaMaterials[70, 0]
+alphaMaterials.IronDoor = alphaMaterials[71, 0]
+alphaMaterials.WoodFloorPlate = alphaMaterials[72, 0]
+alphaMaterials.RedstoneOre = alphaMaterials[73, 0]
+alphaMaterials.RedstoneOreGlowing = alphaMaterials[74, 0]
+alphaMaterials.RedstoneTorchOff = alphaMaterials[75, 0]
+alphaMaterials.RedstoneTorchOn = alphaMaterials[76, 0]
+alphaMaterials.Button = alphaMaterials[77, 0]
+alphaMaterials.SnowLayer = alphaMaterials[78, 0]
+alphaMaterials.Ice = alphaMaterials[79, 0]
+alphaMaterials.Snow = alphaMaterials[80, 0]
+
+alphaMaterials.Cactus = alphaMaterials[81, 0]
+alphaMaterials.Clay = alphaMaterials[82, 0]
+alphaMaterials.SugarCane = alphaMaterials[83, 0]
+alphaMaterials.Jukebox = alphaMaterials[84, 0]
+alphaMaterials.Fence = alphaMaterials[85, 0]
+alphaMaterials.Pumpkin = alphaMaterials[86, 0]
+alphaMaterials.Netherrack = alphaMaterials[87, 0]
+alphaMaterials.SoulSand = alphaMaterials[88, 0]
+alphaMaterials.Glowstone = alphaMaterials[89, 0]
+alphaMaterials.NetherPortal = alphaMaterials[90, 0]
+alphaMaterials.JackOLantern = alphaMaterials[91, 0]
+alphaMaterials.Cake = alphaMaterials[92, 0]
+alphaMaterials.RedstoneRepeaterOff = alphaMaterials[93, 0]
+alphaMaterials.RedstoneRepeaterOn = alphaMaterials[94, 0]
+alphaMaterials.AprilFoolsChest = alphaMaterials[95, 0]
+alphaMaterials.Trapdoor = alphaMaterials[96, 0]
+
+alphaMaterials.HiddenSilverfishStone = alphaMaterials[97, 0]
+alphaMaterials.HiddenSilverfishCobblestone = alphaMaterials[97, 1]
+alphaMaterials.HiddenSilverfishStoneBrick = alphaMaterials[97, 2]
+alphaMaterials.StoneBricks = alphaMaterials[98, 0]
+alphaMaterials.MossyStoneBricks = alphaMaterials[98, 1]
+alphaMaterials.CrackedStoneBricks = alphaMaterials[98, 2]
+alphaMaterials.HugeBrownMushroom = alphaMaterials[99, 0]
+alphaMaterials.HugeRedMushroom = alphaMaterials[100, 0]
+alphaMaterials.IronBars = alphaMaterials[101, 0]
+alphaMaterials.GlassPane = alphaMaterials[102, 0]
+alphaMaterials.Watermelon = alphaMaterials[103, 0]
+alphaMaterials.PumpkinStem = alphaMaterials[104, 0]
+alphaMaterials.MelonStem = alphaMaterials[105, 0]
+alphaMaterials.Vines = alphaMaterials[106, 0]
+alphaMaterials.FenceGate = alphaMaterials[107, 0]
+alphaMaterials.BrickStairs = alphaMaterials[108, 0]
+alphaMaterials.StoneBrickStairs = alphaMaterials[109, 0]
+alphaMaterials.Mycelium = alphaMaterials[110, 0]
+alphaMaterials.Lilypad = alphaMaterials[111, 0]
+alphaMaterials.NetherBrick = alphaMaterials[112, 0]
+alphaMaterials.NetherBrickFence = alphaMaterials[113, 0]
+alphaMaterials.NetherBrickStairs = alphaMaterials[114, 0]
+alphaMaterials.NetherWart = alphaMaterials[115, 0]
+
+# --- Classic static block defs ---
+classicMaterials.Stone = classicMaterials[1]
+classicMaterials.Grass = classicMaterials[2]
+classicMaterials.Dirt = classicMaterials[3]
+classicMaterials.Cobblestone = classicMaterials[4]
+classicMaterials.WoodPlanks = classicMaterials[5]
+classicMaterials.Sapling = classicMaterials[6]
+classicMaterials.Bedrock = classicMaterials[7]
+classicMaterials.WaterActive = classicMaterials[8]
+classicMaterials.Water = classicMaterials[9]
+classicMaterials.LavaActive = classicMaterials[10]
+classicMaterials.Lava = classicMaterials[11]
+classicMaterials.Sand = classicMaterials[12]
+classicMaterials.Gravel = classicMaterials[13]
+classicMaterials.GoldOre = classicMaterials[14]
+classicMaterials.IronOre = classicMaterials[15]
+classicMaterials.CoalOre = classicMaterials[16]
+classicMaterials.Wood = classicMaterials[17]
+classicMaterials.Leaves = classicMaterials[18]
+classicMaterials.Sponge = classicMaterials[19]
+classicMaterials.Glass = classicMaterials[20]
+
+classicMaterials.RedWool = classicMaterials[21]
+classicMaterials.OrangeWool = classicMaterials[22]
+classicMaterials.YellowWool = classicMaterials[23]
+classicMaterials.LimeWool = classicMaterials[24]
+classicMaterials.GreenWool = classicMaterials[25]
+classicMaterials.AquaWool = classicMaterials[26]
+classicMaterials.CyanWool = classicMaterials[27]
+classicMaterials.BlueWool = classicMaterials[28]
+classicMaterials.PurpleWool = classicMaterials[29]
+classicMaterials.IndigoWool = classicMaterials[30]
+classicMaterials.VioletWool = classicMaterials[31]
+classicMaterials.MagentaWool = classicMaterials[32]
+classicMaterials.PinkWool = classicMaterials[33]
+classicMaterials.BlackWool = classicMaterials[34]
+classicMaterials.GrayWool = classicMaterials[35]
+classicMaterials.WhiteWool = classicMaterials[36]
+
+classicMaterials.Flower = classicMaterials[37]
+classicMaterials.Rose = classicMaterials[38]
+classicMaterials.BrownMushroom = classicMaterials[39]
+classicMaterials.RedMushroom = classicMaterials[40]
+classicMaterials.BlockofGold = classicMaterials[41]
+classicMaterials.BlockofIron = classicMaterials[42]
+classicMaterials.DoubleStoneSlab = classicMaterials[43]
+classicMaterials.StoneSlab = classicMaterials[44]
+classicMaterials.Brick = classicMaterials[45]
+classicMaterials.TNT = classicMaterials[46]
+classicMaterials.Bookshelf = classicMaterials[47]
+classicMaterials.MossStone = classicMaterials[48]
+classicMaterials.Obsidian = classicMaterials[49]
+
+# --- Indev static block defs ---
+indevMaterials.Stone = indevMaterials[1]
+indevMaterials.Grass = indevMaterials[2]
+indevMaterials.Dirt = indevMaterials[3]
+indevMaterials.Cobblestone = indevMaterials[4]
+indevMaterials.WoodPlanks = indevMaterials[5]
+indevMaterials.Sapling = indevMaterials[6]
+indevMaterials.Bedrock = indevMaterials[7]
+indevMaterials.WaterActive = indevMaterials[8]
+indevMaterials.Water = indevMaterials[9]
+indevMaterials.LavaActive = indevMaterials[10]
+indevMaterials.Lava = indevMaterials[11]
+indevMaterials.Sand = indevMaterials[12]
+indevMaterials.Gravel = indevMaterials[13]
+indevMaterials.GoldOre = indevMaterials[14]
+indevMaterials.IronOre = indevMaterials[15]
+indevMaterials.CoalOre = indevMaterials[16]
+indevMaterials.Wood = indevMaterials[17]
+indevMaterials.Leaves = indevMaterials[18]
+indevMaterials.Sponge = indevMaterials[19]
+indevMaterials.Glass = indevMaterials[20]
+
+indevMaterials.RedWool = indevMaterials[21]
+indevMaterials.OrangeWool = indevMaterials[22]
+indevMaterials.YellowWool = indevMaterials[23]
+indevMaterials.LimeWool = indevMaterials[24]
+indevMaterials.GreenWool = indevMaterials[25]
+indevMaterials.AquaWool = indevMaterials[26]
+indevMaterials.CyanWool = indevMaterials[27]
+indevMaterials.BlueWool = indevMaterials[28]
+indevMaterials.PurpleWool = indevMaterials[29]
+indevMaterials.IndigoWool = indevMaterials[30]
+indevMaterials.VioletWool = indevMaterials[31]
+indevMaterials.MagentaWool = indevMaterials[32]
+indevMaterials.PinkWool = indevMaterials[33]
+indevMaterials.BlackWool = indevMaterials[34]
+indevMaterials.GrayWool = indevMaterials[35]
+indevMaterials.WhiteWool = indevMaterials[36]
+
+indevMaterials.Flower = indevMaterials[37]
+indevMaterials.Rose = indevMaterials[38]
+indevMaterials.BrownMushroom = indevMaterials[39]
+indevMaterials.RedMushroom = indevMaterials[40]
+indevMaterials.BlockofGold = indevMaterials[41]
+indevMaterials.BlockofIron = indevMaterials[42]
+indevMaterials.DoubleStoneSlab = indevMaterials[43]
+indevMaterials.StoneSlab = indevMaterials[44]
+indevMaterials.Brick = indevMaterials[45]
+indevMaterials.TNT = indevMaterials[46]
+indevMaterials.Bookshelf = indevMaterials[47]
+indevMaterials.MossStone = indevMaterials[48]
+indevMaterials.Obsidian = indevMaterials[49]
+
+indevMaterials.Torch = indevMaterials[50, 0]
+indevMaterials.Fire = indevMaterials[51, 0]
+indevMaterials.InfiniteWater = indevMaterials[52, 0]
+indevMaterials.InfiniteLava = indevMaterials[53, 0]
+indevMaterials.Chest = indevMaterials[54, 0]
+indevMaterials.Cog = indevMaterials[55, 0]
+indevMaterials.DiamondOre = indevMaterials[56, 0]
+indevMaterials.BlockofDiamond = indevMaterials[57, 0]
+indevMaterials.CraftingTable = indevMaterials[58, 0]
+indevMaterials.Crops = indevMaterials[59, 0]
+indevMaterials.Farmland = indevMaterials[60, 0]
+indevMaterials.Furnace = indevMaterials[61, 0]
+indevMaterials.LitFurnace = indevMaterials[62, 0]
+
+# --- Pocket static block defs ---
+
+pocketMaterials.Air = pocketMaterials[0, 0]
+pocketMaterials.Stone = pocketMaterials[1, 0]
+pocketMaterials.Grass = pocketMaterials[2, 0]
+pocketMaterials.Dirt = pocketMaterials[3, 0]
+pocketMaterials.Cobblestone = pocketMaterials[4, 0]
+pocketMaterials.WoodPlanks = pocketMaterials[5, 0]
+pocketMaterials.Sapling = pocketMaterials[6, 0]
+pocketMaterials.SpruceSapling = pocketMaterials[6, 1]
+pocketMaterials.BirchSapling = pocketMaterials[6, 2]
+pocketMaterials.Bedrock = pocketMaterials[7, 0]
+pocketMaterials.Wateractive = pocketMaterials[8, 0]
+pocketMaterials.Water = pocketMaterials[9, 0]
+pocketMaterials.Lavaactive = pocketMaterials[10, 0]
+pocketMaterials.Lava = pocketMaterials[11, 0]
+pocketMaterials.Sand = pocketMaterials[12, 0]
+pocketMaterials.Gravel = pocketMaterials[13, 0]
+pocketMaterials.GoldOre = pocketMaterials[14, 0]
+pocketMaterials.IronOre = pocketMaterials[15, 0]
+pocketMaterials.CoalOre = pocketMaterials[16, 0]
+pocketMaterials.Wood = pocketMaterials[17, 0]
+pocketMaterials.PineWood = pocketMaterials[17, 1]
+pocketMaterials.BirchWood = pocketMaterials[17, 2]
+pocketMaterials.Leaves = pocketMaterials[18, 0]
+pocketMaterials.Glass = pocketMaterials[20, 0]
+
+pocketMaterials.LapisLazuliOre = pocketMaterials[21, 0]
+pocketMaterials.LapisLazuliBlock = pocketMaterials[22, 0]
+pocketMaterials.Sandstone = pocketMaterials[24, 0]
+pocketMaterials.Bed = pocketMaterials[26, 0]
+pocketMaterials.Web = pocketMaterials[30, 0]
+pocketMaterials.UnusedShrub = pocketMaterials[31, 0]
+pocketMaterials.TallGrass = pocketMaterials[31, 1]
+pocketMaterials.Shrub = pocketMaterials[31, 2]
+pocketMaterials.WhiteWool = pocketMaterials[35, 0]
+pocketMaterials.OrangeWool = pocketMaterials[35, 1]
+pocketMaterials.MagentaWool = pocketMaterials[35, 2]
+pocketMaterials.LightBlueWool = pocketMaterials[35, 3]
+pocketMaterials.YellowWool = pocketMaterials[35, 4]
+pocketMaterials.LightGreenWool = pocketMaterials[35, 5]
+pocketMaterials.PinkWool = pocketMaterials[35, 6]
+pocketMaterials.GrayWool = pocketMaterials[35, 7]
+pocketMaterials.LightGrayWool = pocketMaterials[35, 8]
+pocketMaterials.CyanWool = pocketMaterials[35, 9]
+pocketMaterials.PurpleWool = pocketMaterials[35, 10]
+pocketMaterials.BlueWool = pocketMaterials[35, 11]
+pocketMaterials.BrownWool = pocketMaterials[35, 12]
+pocketMaterials.DarkGreenWool = pocketMaterials[35, 13]
+pocketMaterials.RedWool = pocketMaterials[35, 14]
+pocketMaterials.BlackWool = pocketMaterials[35, 15]
+pocketMaterials.Flower = pocketMaterials[37, 0]
+pocketMaterials.Rose = pocketMaterials[38, 0]
+pocketMaterials.BrownMushroom = pocketMaterials[39, 0]
+pocketMaterials.RedMushroom = pocketMaterials[40, 0]
+pocketMaterials.BlockofGold = pocketMaterials[41, 0]
+pocketMaterials.BlockofIron = pocketMaterials[42, 0]
+pocketMaterials.DoubleStoneSlab = pocketMaterials[43, 0]
+pocketMaterials.DoubleSandstoneSlab = pocketMaterials[43, 1]
+pocketMaterials.DoubleWoodenSlab = pocketMaterials[43, 2]
+pocketMaterials.DoubleCobblestoneSlab = pocketMaterials[43, 3]
+pocketMaterials.DoubleBrickSlab = pocketMaterials[43, 4]
+pocketMaterials.StoneSlab = pocketMaterials[44, 0]
+pocketMaterials.SandstoneSlab = pocketMaterials[44, 1]
+pocketMaterials.WoodenSlab = pocketMaterials[44, 2]
+pocketMaterials.CobblestoneSlab = pocketMaterials[44, 3]
+pocketMaterials.BrickSlab = pocketMaterials[44, 4]
+pocketMaterials.Brick = pocketMaterials[45, 0]
+pocketMaterials.TNT = pocketMaterials[46, 0]
+pocketMaterials.Bookshelf = pocketMaterials[47, 0]
+pocketMaterials.MossStone = pocketMaterials[48, 0]
+pocketMaterials.Obsidian = pocketMaterials[49, 0]
+
+pocketMaterials.Torch = pocketMaterials[50, 0]
+pocketMaterials.Fire = pocketMaterials[51, 0]
+pocketMaterials.WoodenStairs = pocketMaterials[53, 0]
+pocketMaterials.Chest = pocketMaterials[54, 0]
+pocketMaterials.DiamondOre = pocketMaterials[56, 0]
+pocketMaterials.BlockofDiamond = pocketMaterials[57, 0]
+pocketMaterials.CraftingTable = pocketMaterials[58, 0]
+pocketMaterials.Crops = pocketMaterials[59, 0]
+pocketMaterials.Farmland = pocketMaterials[60, 0]
+pocketMaterials.Furnace = pocketMaterials[61, 0]
+pocketMaterials.LitFurnace = pocketMaterials[62, 0]
+pocketMaterials.WoodenDoor = pocketMaterials[64, 0]
+pocketMaterials.Ladder = pocketMaterials[65, 0]
+pocketMaterials.StoneStairs = pocketMaterials[67, 0]
+pocketMaterials.IronDoor = pocketMaterials[71, 0]
+pocketMaterials.RedstoneOre = pocketMaterials[73, 0]
+pocketMaterials.RedstoneOreGlowing = pocketMaterials[74, 0]
+pocketMaterials.SnowLayer = pocketMaterials[78, 0]
+pocketMaterials.Ice = pocketMaterials[79, 0]
+
+pocketMaterials.Snow = pocketMaterials[80, 0]
+pocketMaterials.Cactus = pocketMaterials[81, 0]
+pocketMaterials.Clay = pocketMaterials[82, 0]
+pocketMaterials.SugarCane = pocketMaterials[83, 0]
+pocketMaterials.Fence = pocketMaterials[85, 0]
+pocketMaterials.Glowstone = pocketMaterials[89, 0]
+pocketMaterials.InvisibleBedrock = pocketMaterials[95, 0]
+pocketMaterials.Trapdoor = pocketMaterials[96, 0]
+
+pocketMaterials.StoneBricks = pocketMaterials[98, 0]
+pocketMaterials.GlassPane = pocketMaterials[102, 0]
+pocketMaterials.Watermelon = pocketMaterials[103, 0]
+pocketMaterials.MelonStem = pocketMaterials[105, 0]
+pocketMaterials.FenceGate = pocketMaterials[107, 0]
+pocketMaterials.BrickStairs = pocketMaterials[108, 0]
+
+pocketMaterials.GlowingObsidian = pocketMaterials[246, 0]
+pocketMaterials.NetherReactor = pocketMaterials[247, 0]
+pocketMaterials.NetherReactorUsed = pocketMaterials[247, 1]
+
+# print "\n".join(["pocketMaterials.{0} = pocketMaterials[{1},{2}]".format(
+# b.name.replace(" ", "").replace("(","").replace(")",""),
+# b.ID, b.blockData)
+# for b in sorted(mats.pocketMaterials.allBlocks)])
+
+_indices = rollaxis(indices((256, 16)), 0, 3)
+
+
+def _filterTable(filters, unavailable, default=(0, 0)):
+ # a filter table is a 256x16 table of (ID, data) pairs.
+ table = zeros((256, 16, 2), dtype='uint8')
+ table[:] = _indices
+ for u in unavailable:
+ try:
+ if u[1] == 0:
+ u = u[0]
+ except TypeError:
+ pass
+ table[u] = default
+ for f, t in filters:
+ try:
+ if f[1] == 0:
+ f = f[0]
+ except TypeError:
+ pass
+ table[f] = t
+ return table
+
+nullConversion = lambda b, d: (b, d)
+
+
+def filterConversion(table):
+ def convert(blocks, data):
+ if data is None:
+ data = 0
+ t = table[blocks, data]
+ return t[..., 0], t[..., 1]
+
+ return convert
+
+
+def guessFilterTable(matsFrom, matsTo):
+ """ Returns a pair (filters, unavailable)
+ filters is a list of (from, to) pairs; from and to are (ID, data) pairs
+ unavailable is a list of (ID, data) pairs in matsFrom not found in matsTo.
+
+ Searches the 'name' and 'aka' fields to find matches.
+ """
+ filters = []
+ unavailable = []
+ toByName = dict(((b.name, b) for b in sorted(matsTo.allBlocks, reverse=True)))
+ for fromBlock in matsFrom.allBlocks:
+ block = toByName.get(fromBlock.name)
+ if block is None:
+ for b in matsTo.allBlocks:
+ if b.name.startswith(fromBlock.name):
+ block = b
+ break
+ if block is None:
+ for b in matsTo.allBlocks:
+ if fromBlock.name in b.name:
+ block = b
+ break
+ if block is None:
+ for b in matsTo.allBlocks:
+ if fromBlock.name in b.aka:
+ block = b
+ break
+ if block is None:
+ if "Indigo Wool" == fromBlock.name:
+ block = toByName.get("Purple Wool")
+ elif "Violet Wool" == fromBlock.name:
+ block = toByName.get("Purple Wool")
+
+ if block:
+ if block != fromBlock:
+ filters.append(((fromBlock.ID, fromBlock.blockData), (block.ID, block.blockData)))
+ else:
+ unavailable.append((fromBlock.ID, fromBlock.blockData))
+
+ return filters, unavailable
+
+allMaterials = (alphaMaterials, classicMaterials, pocketMaterials, indevMaterials)
+
+_conversionFuncs = {}
+
+
+def conversionFunc(destMats, sourceMats):
+ if destMats is sourceMats:
+ return nullConversion
+ func = _conversionFuncs.get((destMats, sourceMats))
+ if func:
+ return func
+
+ filters, unavailable = guessFilterTable(sourceMats, destMats)
+ log.debug("")
+ log.debug("%s %s %s", sourceMats.name, "=>", destMats.name)
+ for a, b in [(sourceMats.blockWithID(*a), destMats.blockWithID(*b)) for a, b in filters]:
+ log.debug("{0:20}: \"{1}\"".format('"' + a.name + '"', b.name))
+
+ log.debug("")
+ log.debug("Missing blocks: %s", [sourceMats.blockWithID(*a).name for a in unavailable])
+
+ table = _filterTable(filters, unavailable, (35, 0))
+ func = filterConversion(table)
+ _conversionFuncs[(destMats, sourceMats)] = func
+ return func
+
+
+def convertBlocks(destMats, sourceMats, blocks, blockData):
+ if sourceMats == destMats:
+ return blocks, blockData
+
+ return conversionFunc(destMats, sourceMats)(blocks, blockData)
+
+namedMaterials = dict((i.name, i) for i in allMaterials)
+
+__all__ = "indevMaterials, pocketMaterials, alphaMaterials, classicMaterials, namedMaterials, MCMaterials".split(", ")
--- /dev/null
+#!/usr/bin/env python
+import mclevelbase
+import mclevel
+import infiniteworld
+import sys
+import os
+from box import BoundingBox, Vector
+import numpy
+from numpy import zeros, bincount
+import logging
+import itertools
+import traceback
+import shlex
+import operator
+import codecs
+
+from math import floor
+try:
+ import readline # if available, used by raw_input()
+except:
+ pass
+
+
+class UsageError(RuntimeError):
+ pass
+
+
+class BlockMatchError(RuntimeError):
+ pass
+
+
+class PlayerNotFound(RuntimeError):
+ pass
+
+
+class mce(object):
+ """
+ Block commands:
+ {commandPrefix}clone <sourceBox> <destPoint> [noair] [nowater]
+ {commandPrefix}fill <blockType> [ <box> ]
+ {commandPrefix}replace <blockType> [with] <newBlockType> [ <box> ]
+
+ {commandPrefix}export <filename> <sourceBox>
+ {commandPrefix}import <filename> <destPoint> [noair] [nowater]
+
+ {commandPrefix}createChest <point> <item> [ <count> ]
+ {commandPrefix}analyze
+
+ Player commands:
+ {commandPrefix}player [ <player> [ <point> ] ]
+ {commandPrefix}spawn [ <point> ]
+
+ Entity commands:
+ {commandPrefix}removeEntities [ <EntityID> ]
+ {commandPrefix}dumpSigns [ <filename> ]
+ {commandPrefix}dumpChests [ <filename> ]
+
+ Chunk commands:
+ {commandPrefix}createChunks <box>
+ {commandPrefix}deleteChunks <box>
+ {commandPrefix}prune <box>
+ {commandPrefix}relight [ <box> ]
+
+ World commands:
+ {commandPrefix}create <filename>
+ {commandPrefix}dimension [ <dim> ]
+ {commandPrefix}degrief
+ {commandPrefix}time [ <time> ]
+ {commandPrefix}worldsize
+ {commandPrefix}heightmap <filename>
+ {commandPrefix}randomseed [ <seed> ]
+ {commandPrefix}gametype [ <player> [ <gametype> ] ]
+
+ Editor commands:
+ {commandPrefix}save
+ {commandPrefix}reload
+ {commandPrefix}load <filename> | <world number>
+ {commandPrefix}execute <filename>
+ {commandPrefix}quit
+
+ Informational:
+ {commandPrefix}blocks [ <block name> | <block ID> ]
+ {commandPrefix}help [ <command> ]
+
+ **IMPORTANT**
+ {commandPrefix}box
+
+ Type 'box' to learn how to specify points and areas.
+
+ """
+ random_seed = os.getenv('MCE_RANDOM_SEED', None)
+ last_played = os.getenv("MCE_LAST_PLAYED", None)
+
+ def commandUsage(self, command):
+ " returns usage info for the named command - just give the docstring for the handler func "
+ func = getattr(self, "_" + command)
+ return func.__doc__
+
+ commands = [
+ "clone",
+ "fill",
+ "replace",
+
+ "export",
+ "execute",
+ "import",
+
+ "createchest",
+
+ "player",
+ "spawn",
+
+ "removeentities",
+ "dumpsigns",
+ "dumpchests",
+
+ "createchunks",
+ "deletechunks",
+ "prune",
+ "relight",
+
+ "create",
+ "degrief",
+ "time",
+ "worldsize",
+ "heightmap",
+ "randomseed",
+ "gametype",
+
+ "save",
+ "load",
+ "reload",
+ "dimension",
+ "repair",
+
+ "quit",
+ "exit",
+
+ "help",
+ "blocks",
+ "analyze",
+ "region",
+
+ "debug",
+ "log",
+ "box",
+ ]
+ debug = False
+ needsSave = False
+
+ def readInt(self, command):
+ try:
+ val = int(command.pop(0))
+ except ValueError:
+ raise UsageError("Cannot understand numeric input")
+ return val
+
+ def prettySplit(self, command):
+ cmdstring = " ".join(command)
+
+ lex = shlex.shlex(cmdstring)
+ lex.whitespace_split = True
+ lex.whitespace += "(),"
+
+ command[:] = list(lex)
+
+ def readBox(self, command):
+ self.prettySplit(command)
+
+ sourcePoint = self.readIntPoint(command)
+ if command[0].lower() == "to":
+ command.pop(0)
+ sourcePoint2 = self.readIntPoint(command)
+ sourceSize = sourcePoint2 - sourcePoint
+ else:
+ sourceSize = self.readIntPoint(command, isPoint=False)
+ if len([p for p in sourceSize if p <= 0]):
+ raise UsageError("Box size cannot be zero or negative")
+ box = BoundingBox(sourcePoint, sourceSize)
+ return box
+
+ def readIntPoint(self, command, isPoint=True):
+ point = self.readPoint(command, isPoint)
+ point = map(int, map(floor, point))
+ return Vector(*point)
+
+ def readPoint(self, command, isPoint=True):
+ self.prettySplit(command)
+ try:
+ word = command.pop(0)
+ if isPoint and (word in self.level.players):
+ x, y, z = self.level.getPlayerPosition(word)
+ if len(command) and command[0].lower() == "delta":
+ command.pop(0)
+ try:
+ x += int(command.pop(0))
+ y += int(command.pop(0))
+ z += int(command.pop(0))
+
+ except ValueError:
+ raise UsageError("Error decoding point input (expected a number).")
+ return x, y, z
+
+ except IndexError:
+ raise UsageError("Error decoding point input (expected more values).")
+
+ try:
+ try:
+ x = float(word)
+ except ValueError:
+ if isPoint:
+ raise PlayerNotFound(word)
+ raise
+ y = float(command.pop(0))
+ z = float(command.pop(0))
+ except ValueError:
+ raise UsageError("Error decoding point input (expected a number).")
+ except IndexError:
+ raise UsageError("Error decoding point input (expected more values).")
+
+ return x, y, z
+
+ def readBlockInfo(self, command):
+ keyword = command.pop(0)
+
+ matches = self.level.materials.blocksMatching(keyword)
+ blockInfo = None
+
+ if len(matches):
+ if len(matches) == 1:
+ blockInfo = matches[0]
+
+ # eat up more words that possibly specify a block. stop eating when 0 matching blocks.
+ while len(command):
+ newMatches = self.level.materials.blocksMatching(keyword + " " + command[0])
+
+ if len(newMatches) == 1:
+ blockInfo = newMatches[0]
+ if len(newMatches) > 0:
+ matches = newMatches
+ keyword = keyword + " " + command.pop(0)
+ else:
+ break
+
+ else:
+ try:
+ data = 0
+ if ":" in keyword:
+ blockID, data = map(int, keyword.split(":"))
+ else:
+ blockID = int(keyword)
+ blockInfo = self.level.materials.blockWithID(blockID, data)
+
+ except ValueError:
+ blockInfo = None
+
+ if blockInfo is None:
+ print "Ambiguous block specifier: ", keyword
+ if len(matches):
+ print "Matches: "
+ for m in matches:
+ if m == self.level.materials.defaultName:
+ continue
+ print "{0:3}:{1:<2} : {2}".format(m.ID, m.blockData, m.name)
+ else:
+ print "No blocks matched."
+ raise BlockMatchError
+
+ return blockInfo
+
+ def readBlocksToCopy(self, command):
+ blocksToCopy = range(256)
+ while len(command):
+ word = command.pop()
+ if word == "noair":
+ blocksToCopy.remove(0)
+ if word == "nowater":
+ blocksToCopy.remove(8)
+ blocksToCopy.remove(9)
+
+ return blocksToCopy
+
+ def _box(self, command):
+ """
+ Boxes:
+
+ Many commands require a <box> as arguments. A box can be specified with
+ a point and a size:
+ (12, 5, 15), (5, 5, 5)
+
+ or with two points, making sure to put the keyword "to" between them:
+ (12, 5, 15) to (17, 10, 20)
+
+ The commas and parentheses are not important.
+ You may add them for improved readability.
+
+
+ Points:
+
+ Points and sizes are triplets of numbers ordered X Y Z.
+ X is position north-south, increasing southward.
+ Y is position up-down, increasing upward.
+ Z is position east-west, increasing westward.
+
+
+ Players:
+
+ A player's name can be used as a point - it will use the
+ position of the player's head. Use the keyword 'delta' after
+ the name to specify a point near the player.
+
+ Example:
+ codewarrior delta 0 5 0
+
+ This refers to a point 5 blocks above codewarrior's head.
+
+ """
+ raise UsageError
+
+ def _debug(self, command):
+ self.debug = not self.debug
+ print "Debug", ("disabled", "enabled")[self.debug]
+
+ def _log(self, command):
+ """
+ log [ <number> ]
+
+ Get or set the log threshold. 0 logs everything; 50 only logs major errors.
+ """
+ if len(command):
+ try:
+ logging.getLogger().level = int(command[0])
+ except ValueError:
+ raise UsageError("Cannot understand numeric input.")
+ else:
+ print "Log level: {0}".format(logging.getLogger().level)
+
+ def _clone(self, command):
+ """
+ clone <sourceBox> <destPoint> [noair] [nowater]
+
+ Clone blocks in a cuboid starting at sourcePoint and extending for
+ sourceSize blocks in each direction. Blocks and entities in the area
+ are cloned at destPoint.
+ """
+ if len(command) == 0:
+ self.printUsage("clone")
+ return
+
+ box = self.readBox(command)
+
+ destPoint = self.readPoint(command)
+
+ destPoint = map(int, map(floor, destPoint))
+ blocksToCopy = self.readBlocksToCopy(command)
+
+ tempSchematic = self.level.extractSchematic(box)
+ self.level.copyBlocksFrom(tempSchematic, BoundingBox((0, 0, 0), box.origin), destPoint, blocksToCopy)
+
+ self.needsSave = True
+ print "Cloned 0 blocks."
+
+ def _fill(self, command):
+ """
+ fill <blockType> [ <box> ]
+
+ Fill blocks with blockType in a cuboid starting at point and
+ extending for size blocks in each direction. Without a
+ destination, fills the whole world. blockType and may be a
+ number from 0-255 or a name listed by the 'blocks' command.
+ """
+ if len(command) == 0:
+ self.printUsage("fill")
+ return
+
+ blockInfo = self.readBlockInfo(command)
+
+ if len(command):
+ box = self.readBox(command)
+ else:
+ box = None
+
+ print "Filling with {0}".format(blockInfo.name)
+
+ self.level.fillBlocks(box, blockInfo)
+
+ self.needsSave = True
+ print "Filled {0} blocks.".format("all" if box is None else box.volume)
+
+ def _replace(self, command):
+ """
+ replace <blockType> [with] <newBlockType> [ <box> ]
+
+ Replace all blockType blocks with newBlockType in a cuboid
+ starting at point and extending for size blocks in
+ each direction. Without a destination, replaces blocks over
+ the whole world. blockType and newBlockType may be numbers
+ from 0-255 or names listed by the 'blocks' command.
+ """
+ if len(command) == 0:
+ self.printUsage("replace")
+ return
+
+ blockInfo = self.readBlockInfo(command)
+
+ if command[0].lower() == "with":
+ command.pop(0)
+ newBlockInfo = self.readBlockInfo(command)
+
+ if len(command):
+ box = self.readBox(command)
+ else:
+ box = None
+
+ print "Replacing {0} with {1}".format(blockInfo.name, newBlockInfo.name)
+
+ self.level.fillBlocks(box, newBlockInfo, blocksToReplace=[blockInfo])
+
+ self.needsSave = True
+ print "Done."
+
+ def _createchest(self, command):
+ """
+ createChest <point> <item> [ <count> ]
+
+ Create a chest filled with the specified item.
+ Stacks are 64 if count is not given.
+ """
+ point = map(lambda x: int(floor(float(x))), self.readPoint(command))
+ itemID = self.readInt(command)
+ count = 64
+ if len(command):
+ count = self.readInt(command)
+
+ chest = mclevel.MCSchematic.chestWithItemID(itemID, count)
+ self.level.copyBlocksFrom(chest, chest.bounds, point)
+ self.needsSave = True
+
+ def _analyze(self, command):
+ """
+ analyze
+
+ Counts all of the block types in every chunk of the world.
+ """
+ blockCounts = zeros((4096,), 'uint64')
+ sizeOnDisk = 0
+
+ print "Analyzing {0} chunks...".format(self.level.chunkCount)
+ # for input to bincount, create an array of uint16s by
+ # shifting the data left and adding the blocks
+
+ for i, cPos in enumerate(self.level.allChunks, 1):
+ ch = self.level.getChunk(*cPos)
+ btypes = numpy.array(ch.Data.ravel(), dtype='uint16')
+ btypes <<= 8
+ btypes += ch.Blocks.ravel()
+ counts = bincount(btypes)
+
+ blockCounts[:counts.shape[0]] += counts
+ if i % 100 == 0:
+ logging.info("Chunk {0}...".format(i))
+
+ for blockID in range(256):
+ block = self.level.materials.blockWithID(blockID, 0)
+ if block.hasVariants:
+ for data in range(16):
+ i = (data << 8) + blockID
+ if blockCounts[i]:
+ idstring = "({id}:{data})".format(id=blockID, data=data)
+
+ print "{idstring:9} {name:30}: {count:<10}".format(
+ idstring=idstring, name=self.level.materials.blockWithID(blockID, data).name, count=blockCounts[i])
+
+ else:
+ count = int(sum(blockCounts[(d << 8) + blockID] for d in range(16)))
+ if count:
+ idstring = "({id})".format(id=blockID)
+ print "{idstring:9} {name:30}: {count:<10}".format(
+ idstring=idstring, name=self.level.materials.blockWithID(blockID, 0).name, count=count)
+
+ self.needsSave = True
+
+ def _export(self, command):
+ """
+ export <filename> <sourceBox>
+
+ Exports blocks in the specified region to a file in schematic format.
+ This file can be imported with mce or MCEdit.
+ """
+ if len(command) == 0:
+ self.printUsage("export")
+ return
+
+ filename = command.pop(0)
+ box = self.readBox(command)
+
+ tempSchematic = self.level.extractSchematic(box)
+
+ tempSchematic.saveToFile(filename)
+
+ print "Exported {0} blocks.".format(tempSchematic.bounds.volume)
+
+ def _import(self, command):
+ """
+ import <filename> <destPoint> [noair] [nowater]
+
+ Imports a level or schematic into this world, beginning at destPoint.
+ Supported formats include
+ - Alpha single or multiplayer world folder containing level.dat,
+ - Zipfile containing Alpha world folder,
+ - Classic single-player .mine,
+ - Classic multiplayer server_level.dat,
+ - Indev .mclevel
+ - Schematic from RedstoneSim, MCEdit, mce
+ - .inv from INVEdit (appears as a chest)
+ """
+ if len(command) == 0:
+ self.printUsage("import")
+ return
+
+ filename = command.pop(0)
+ destPoint = self.readPoint(command)
+ blocksToCopy = self.readBlocksToCopy(command)
+
+ importLevel = mclevel.fromFile(filename)
+
+ self.level.copyBlocksFrom(importLevel, importLevel.bounds, destPoint, blocksToCopy, create=True)
+
+ self.needsSave = True
+ print "Imported {0} blocks.".format(importLevel.bounds.volume)
+
+ def _player(self, command):
+ """
+ player [ <player> [ <point> ] ]
+
+ Move the named player to the specified point.
+ Without a point, prints the named player's position.
+ Without a player, prints all players and positions.
+
+ In a single-player world, the player is named Player.
+ """
+ if len(command) == 0:
+ print "Players: "
+ for player in self.level.players:
+ print " {0}: {1}".format(player, self.level.getPlayerPosition(player))
+ return
+
+ player = command.pop(0)
+ if len(command) == 0:
+ print "Player {0}: {1}".format(player, self.level.getPlayerPosition(player))
+ return
+
+ point = self.readPoint(command)
+ self.level.setPlayerPosition(point, player)
+
+ self.needsSave = True
+ print "Moved player {0} to {1}".format(player, point)
+
+ def _spawn(self, command):
+ """
+ spawn [ <point> ]
+
+ Move the world's spawn point.
+ Without a point, prints the world's spawn point.
+ """
+ if len(command):
+ point = self.readPoint(command)
+ point = map(int, map(floor, point))
+
+ self.level.setPlayerSpawnPosition(point)
+
+ self.needsSave = True
+ print "Moved spawn point to ", point
+ else:
+ print "Spawn point: ", self.level.playerSpawnPosition()
+
+ def _dumpsigns(self, command):
+ """
+ dumpSigns [ <filename> ]
+
+ Saves the text and location of every sign in the world to a text file.
+ With no filename, saves signs to <worldname>.signs
+
+ Output is newline-delimited. 5 lines per sign. Coordinates are
+ on the first line, followed by four lines of sign text. For example:
+
+ [229, 118, -15]
+ "To boldy go
+ where no man
+ has gone
+ before."
+
+ Coordinates are ordered the same as point inputs:
+ [North/South, Down/Up, East/West]
+
+ """
+ if len(command):
+ filename = command[0]
+ else:
+ filename = self.level.displayName + ".signs"
+
+ # It appears that Minecraft interprets the sign text as UTF-8,
+ # so we should decode it as such too.
+ decodeSignText = codecs.getdecoder('utf-8')
+ # We happen to encode the output file in UTF-8 too, although
+ # we could use another UTF encoding. The '-sig' encoding puts
+ # a signature at the start of the output file that tools such
+ # as Microsoft Windows Notepad and Emacs understand to mean
+ # the file has UTF-8 encoding.
+ outFile = codecs.open(filename, "w", encoding='utf-8-sig')
+
+ print "Dumping signs..."
+ signCount = 0
+
+ for i, cPos in enumerate(self.level.allChunks):
+ try:
+ chunk = self.level.getChunk(*cPos)
+ except mclevelbase.ChunkMalformed:
+ continue
+
+ for tileEntity in chunk.TileEntities:
+ if tileEntity["id"].value == "Sign":
+ signCount += 1
+
+ outFile.write(str(map(lambda x: tileEntity[x].value, "xyz")) + "\n")
+ for i in range(4):
+ signText = tileEntity["Text{0}".format(i + 1)].value
+ outFile.write(decodeSignText(signText)[0] + u"\n")
+
+ if i % 100 == 0:
+ print "Chunk {0}...".format(i)
+
+
+ print "Dumped {0} signs to {1}".format(signCount, filename)
+
+ outFile.close()
+
+ def _region(self, command):
+ """
+ region [rx rz]
+
+ List region files in this world.
+ """
+ level = self.level
+ assert(isinstance(level, mclevel.MCInfdevOldLevel))
+ assert level.version
+
+ def getFreeSectors(rf):
+ runs = []
+ start = None
+ count = 0
+ for i, free in enumerate(rf.freeSectors):
+ if free:
+ if start is None:
+ start = i
+ count = 1
+ else:
+ count += 1
+ else:
+ if start is None:
+ pass
+ else:
+ runs.append((start, count))
+ start = None
+ count = 0
+
+ return runs
+
+ def printFreeSectors(runs):
+
+ for i, (start, count) in enumerate(runs):
+ if i % 4 == 3:
+ print ""
+ print "{start:>6}+{count:<4}".format(**locals()),
+
+ print ""
+
+ if len(command):
+ if len(command) > 1:
+ rx, rz = map(int, command[:2])
+ print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks)
+ rf = level.regionFiles.get((rx, rz))
+ if rf is None:
+ print "Region {rx},{rz} not found.".format(**locals())
+ return
+
+ print "Region {rx:6}, {rz:6}: {used}/{sectors} sectors".format(used=rf.usedSectors, sectors=rf.sectorCount)
+ print "Offset Table:"
+ for cx in range(32):
+ for cz in range(32):
+ if cz % 4 == 0:
+ print ""
+ print "{0:3}, {1:3}: ".format(cx, cz),
+ off = rf.getOffset(cx, cz)
+ sector, length = off >> 8, off & 0xff
+ print "{sector:>6}+{length:<2} ".format(**locals()),
+ print ""
+
+ runs = getFreeSectors(rf)
+ if len(runs):
+ print "Free sectors:",
+
+ printFreeSectors(runs)
+
+ else:
+ if command[0] == "free":
+ print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks)
+ for (rx, rz), rf in level.regionFiles.iteritems():
+
+ runs = getFreeSectors(rf)
+ if len(runs):
+ print "R {0:3}, {1:3}:".format(rx, rz),
+ printFreeSectors(runs)
+
+ else:
+ print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks)
+ coords = (r for r in level.regionFiles)
+ for i, (rx, rz) in enumerate(coords):
+ print "({rx:6}, {rz:6}): {count}, ".format(count=level.regionFiles[rx, rz].chunkCount),
+ if i % 5 == 4:
+ print ""
+
+ def _repair(self, command):
+ """
+ repair
+
+ Attempt to repair inconsistent region files.
+ MAKE A BACKUP. WILL DELETE YOUR DATA.
+
+ Scans for and repairs errors in region files:
+ Deletes chunks whose sectors overlap with another chunk
+ Rearranges chunks that are in the wrong slot in the offset table
+ Deletes completely unreadable chunks
+
+ Only usable with region-format saves.
+ """
+ if self.level.version:
+ self.level.preloadRegions()
+ for rf in self.level.regionFiles.itervalues():
+ rf.repair()
+
+ def _dumpchests(self, command):
+ """
+ dumpChests [ <filename> ]
+
+ Saves the content and location of every chest in the world to a text file.
+ With no filename, saves signs to <worldname>.chests
+
+ Output is delimited by brackets and newlines. A set of coordinates in
+ brackets begins a chest, followed by a line for each inventory slot.
+ For example:
+
+ [222, 51, 22]
+ 2 String
+ 3 String
+ 3 Iron bar
+
+ Coordinates are ordered the same as point inputs:
+ [North/South, Down/Up, East/West]
+
+ """
+ from items import items
+ if len(command):
+ filename = command[0]
+ else:
+ filename = self.level.displayName + ".chests"
+
+ outFile = file(filename, "w")
+
+ print "Dumping chests..."
+ chestCount = 0
+
+ for i, cPos in enumerate(self.level.allChunks):
+ try:
+ chunk = self.level.getChunk(*cPos)
+ except mclevelbase.ChunkMalformed:
+ continue
+
+ for tileEntity in chunk.TileEntities:
+ if tileEntity["id"].value == "Chest":
+ chestCount += 1
+
+ outFile.write(str(map(lambda x: tileEntity[x].value, "xyz")) + "\n")
+ itemsTag = tileEntity["Items"]
+ if len(itemsTag):
+ for itemTag in itemsTag:
+ try:
+ id = itemTag["id"].value
+ damage = itemTag["Damage"].value
+ item = items.findItem(id, damage)
+ itemname = item.name
+ except KeyError:
+ itemname = "Unknown Item {0}".format(itemTag)
+ except Exception, e:
+ itemname = repr(e)
+ outFile.write("{0} {1}\n".format(itemTag["Count"].value, itemname))
+ else:
+ outFile.write("Empty Chest\n")
+
+ if i % 100 == 0:
+ print "Chunk {0}...".format(i)
+
+
+ print "Dumped {0} chests to {1}".format(chestCount, filename)
+
+ outFile.close()
+
+ def _removeentities(self, command):
+ """
+ removeEntities [ [except] [ <EntityID> [ <EntityID> ... ] ] ]
+
+ Remove all entities matching one or more entity IDs.
+ With the except keyword, removes all entities not
+ matching one or more entity IDs.
+
+ Without any IDs, removes all entities in the world,
+ except for Paintings.
+
+ Known Mob Entity IDs:
+ Mob Monster Creeper Skeleton Spider Giant
+ Zombie Slime Pig Sheep Cow Chicken
+
+ Known Item Entity IDs: Item Arrow Snowball Painting
+
+ Known Vehicle Entity IDs: Minecart Boat
+
+ Known Dynamic Tile Entity IDs: PrimedTnt FallingSand
+ """
+ ENT_MATCHTYPE_ANY = 0
+ ENT_MATCHTYPE_EXCEPT = 1
+ ENT_MATCHTYPE_NONPAINTING = 2
+
+ def match(entityID, matchType, matchWords):
+ if ENT_MATCHTYPE_ANY == matchType:
+ return entityID.lower() in matchWords
+ elif ENT_MATCHTYPE_EXCEPT == matchType:
+ return not (entityID.lower() in matchWords)
+ else:
+ # ENT_MATCHTYPE_EXCEPT == matchType
+ return entityID != "Painting"
+
+ removedEntities = {}
+ match_words = []
+
+ if len(command):
+ if command[0].lower() == "except":
+ command.pop(0)
+ print "Removing all entities except ", command
+ match_type = ENT_MATCHTYPE_EXCEPT
+ else:
+ print "Removing {0}...".format(", ".join(command))
+ match_type = ENT_MATCHTYPE_ANY
+
+ match_words = map(lambda x: x.lower(), command)
+
+ else:
+ print "Removing all entities except Painting..."
+ match_type = ENT_MATCHTYPE_NONPAINTING
+
+ for cx, cz in self.level.allChunks:
+ chunk = self.level.getChunk(cx, cz)
+ entitiesRemoved = 0
+
+ for entity in list(chunk.Entities):
+ entityID = entity["id"].value
+
+ if match(entityID, match_type, match_words):
+ removedEntities[entityID] = removedEntities.get(entityID, 0) + 1
+
+ chunk.Entities.remove(entity)
+ entitiesRemoved += 1
+
+ if entitiesRemoved:
+ chunk.chunkChanged(False)
+
+
+ if len(removedEntities) == 0:
+ print "No entities to remove."
+ else:
+ print "Removed entities:"
+ for entityID in sorted(removedEntities.keys()):
+ print " {0}: {1:6}".format(entityID, removedEntities[entityID])
+
+ self.needsSave = True
+
+ def _createchunks(self, command):
+ """
+ createChunks <box>
+
+ Creates any chunks not present in the specified region.
+ New chunks are filled with only air. New chunks are written
+ to disk immediately.
+ """
+ if len(command) == 0:
+ self.printUsage("createchunks")
+ return
+
+ box = self.readBox(command)
+
+ chunksCreated = self.level.createChunksInBox(box)
+
+ print "Created {0} chunks." .format(len(chunksCreated))
+
+ self.needsSave = True
+
+ def _deletechunks(self, command):
+ """
+ deleteChunks <box>
+
+ Removes all chunks contained in the specified region.
+ Chunks are deleted from disk immediately.
+ """
+ if len(command) == 0:
+ self.printUsage("deletechunks")
+ return
+
+ box = self.readBox(command)
+
+ deletedChunks = self.level.deleteChunksInBox(box)
+
+ print "Deleted {0} chunks." .format(len(deletedChunks))
+
+ def _prune(self, command):
+ """
+ prune <box>
+
+ Removes all chunks not contained in the specified region. Useful for enforcing a finite map size.
+ Chunks are deleted from disk immediately.
+ """
+ if len(command) == 0:
+ self.printUsage("prune")
+ return
+
+ box = self.readBox(command)
+
+ i = 0
+ for cx, cz in list(self.level.allChunks):
+ if cx < box.mincx or cx >= box.maxcx or cz < box.mincz or cz >= box.maxcz:
+ self.level.deleteChunk(cx, cz)
+ i += 1
+
+ print "Pruned {0} chunks." .format(i)
+
+ def _relight(self, command):
+ """
+ relight [ <box> ]
+
+ Recalculates lights in the region specified. If omitted,
+ recalculates the entire world.
+ """
+ if len(command):
+ box = self.readBox(command)
+ chunks = itertools.product(range(box.mincx, box.maxcx), range(box.mincz, box.maxcz))
+
+ else:
+ chunks = self.level.allChunks
+
+ self.level.generateLights(chunks)
+
+ print "Relit 0 chunks."
+ self.needsSave = True
+
+ def _create(self, command):
+ """
+ create [ <filename> ]
+
+ Create and load a new Minecraft Alpha world. This world will have no
+ chunks and a random terrain seed. If run from the shell, filename is not
+ needed because you already specified a filename earlier in the command.
+ For example:
+
+ mce.py MyWorld create
+
+ """
+ if len(command) < 1:
+ raise UsageError("Expected a filename")
+
+ filename = command[0]
+ if not os.path.exists(filename):
+ os.mkdir(filename)
+
+ if not os.path.isdir(filename):
+ raise IOError("{0} already exists".format(filename))
+
+ if mclevel.MCInfdevOldLevel.isLevel(filename):
+ raise IOError("{0} is already a Minecraft Alpha world".format(filename))
+
+ level = mclevel.MCInfdevOldLevel(filename, create=True)
+
+ self.level = level
+
+ def _degrief(self, command):
+ """
+ degrief [ <height> ]
+
+ Reverse a few forms of griefing by removing
+ Adminium, Obsidian, Fire, and Lava wherever
+ they occur above the specified height.
+ Without a height, uses height level 32.
+
+ Removes natural surface lava.
+
+ Also see removeEntities
+ """
+ box = self.level.bounds
+ box = BoundingBox(box.origin + (0, 32, 0), box.size - (0, 32, 0))
+ if len(command):
+ try:
+ box.miny = int(command[0])
+ except ValueError:
+ pass
+
+ print "Removing grief matter and surface lava above height {0}...".format(box.miny)
+
+ self.level.fillBlocks(box,
+ self.level.materials.Air,
+ blocksToReplace=[self.level.materials.Bedrock,
+ self.level.materials.Obsidian,
+ self.level.materials.Fire,
+ self.level.materials.LavaActive,
+ self.level.materials.Lava,
+ ]
+ )
+ self.needsSave = True
+
+ def _time(self, command):
+ """
+ time [time of day]
+
+ Set or display the time of day. Acceptable values are "morning", "noon",
+ "evening", "midnight", or a time of day such as 8:02, 12:30 PM, or 16:45.
+ """
+ ticks = self.level.Time
+ timeOfDay = ticks % 24000
+ ageInTicks = ticks - timeOfDay
+ if len(command) == 0:
+
+ days = ageInTicks / 24000
+ hours = timeOfDay / 1000
+ clockHours = (hours + 6) % 24
+
+ ampm = ("AM", "PM")[clockHours > 11]
+
+ minutes = (timeOfDay % 1000) / 60
+
+ print "It is {0}:{1:02} {2} on Day {3}".format(clockHours % 12 or 12, minutes, ampm, days)
+ else:
+ times = {"morning": 6, "noon": 12, "evening": 18, "midnight": 24}
+ word = command[0]
+ minutes = 0
+
+ if word in times:
+ hours = times[word]
+ else:
+ try:
+ if ":" in word:
+ h, m = word.split(":")
+ hours = int(h)
+ minutes = int(m)
+ else:
+ hours = int(word)
+ except Exception, e:
+ raise UsageError(("Cannot interpret time, ", e))
+
+ if len(command) > 1:
+ if command[1].lower() == "pm":
+ hours += 12
+
+ ticks = ageInTicks + hours * 1000 + minutes * 1000 / 60 - 6000
+ if ticks < 0:
+ ticks += 18000
+
+ ampm = ("AM", "PM")[hours > 11 and hours < 24]
+ print "Changed time to {0}:{1:02} {2}".format(hours % 12 or 12, minutes, ampm)
+ self.level.Time = ticks
+ self.needsSave = True
+
+ def _randomseed(self, command):
+ """
+ randomseed [ <seed> ]
+
+ Set or display the world's random seed, a 64-bit integer that uniquely
+ defines the world's terrain.
+ """
+ if len(command):
+ try:
+ seed = long(command[0])
+ except ValueError:
+ raise UsageError("Expected a long integer.")
+
+ self.level.RandomSeed = seed
+ self.needsSave = True
+ else:
+ print "Random Seed: ", self.level.RandomSeed
+
+ def _gametype(self, command):
+ """
+ gametype [ <player> [ <gametype> ] ]
+
+ Set or display the player's game type, an integer that identifies whether
+ their game is survival (0) or creative (1). On single-player worlds, the
+ player is just 'Player'.
+ """
+ if len(command) == 0:
+ print "Players: "
+ for player in self.level.players:
+ print " {0}: {1}".format(player, self.level.getPlayerGameType(player))
+ return
+
+ player = command.pop(0)
+ if len(command) == 0:
+ print "Player {0}: {1}".format(player, self.level.getPlayerGameType(player))
+ return
+
+ try:
+ gametype = int(command[0])
+ except ValueError:
+ raise UsageError("Expected an integer.")
+
+ self.level.setPlayerGameType(gametype, player)
+ self.needsSave = True
+
+ def _worldsize(self, command):
+ """
+ worldsize
+
+ Computes and prints the dimensions of the world. For infinite worlds,
+ also prints the most negative corner.
+ """
+ bounds = self.level.bounds
+ if isinstance(self.level, mclevel.MCInfdevOldLevel):
+ print "\nWorld size: \n {0[0]:7} north to south\n {0[2]:7} east to west\n".format(bounds.size)
+ print "Smallest and largest points: ({0[0]},{0[2]}), ({1[0]},{1[2]})".format(bounds.origin, bounds.maximum)
+
+ else:
+ print "\nWorld size: \n {0[0]:7} wide\n {0[1]:7} tall\n {0[2]:7} long\n".format(bounds.size)
+
+ def _heightmap(self, command):
+ """
+ heightmap <filename>
+
+ Takes a png and imports it as the terrain starting at chunk 0,0.
+ Data is internally converted to greyscale and scaled to the maximum height.
+ The game will fill the terrain with trees and mineral deposits the next
+ time you play the level.
+
+ Please please please try out a small test image before using a big source.
+ Using the levels tool to get a good heightmap is an art, not a science.
+ A smaller map lets you experiment and get it right before having to blow
+ all night generating the really big map.
+
+ Requires the PIL library.
+ """
+ if len(command) == 0:
+ self.printUsage("heightmap")
+ return
+
+ if not sys.stdin.isatty() or raw_input(
+ "This will destroy a large portion of the map and may take a long time. Did you really want to do this?"
+ ).lower() in ("yes", "y", "1", "true"):
+
+ from PIL import Image
+ import datetime
+
+ filename = command.pop(0)
+
+ imgobj = Image.open(filename)
+
+ greyimg = imgobj.convert("L") # luminance
+ del imgobj
+
+ width, height = greyimg.size
+
+ water_level = 64
+
+ xchunks = (height + 15) / 16
+ zchunks = (width + 15) / 16
+
+ start = datetime.datetime.now()
+ for cx in range(xchunks):
+ for cz in range(zchunks):
+ try:
+ self.level.createChunk(cx, cz)
+ except:
+ pass
+ c = self.level.getChunk(cx, cz)
+
+ imgarray = numpy.asarray(greyimg.crop((cz * 16, cx * 16, cz * 16 + 16, cx * 16 + 16)))
+ imgarray = imgarray / 2 # scale to 0-127
+
+ for x in range(16):
+ for z in range(16):
+ if z + (cz * 16) < width - 1 and x + (cx * 16) < height - 1:
+ # world dimension X goes north-south
+ # first array axis goes up-down
+
+ h = imgarray[x, z]
+
+ c.Blocks[x, z, h + 1:] = 0 # air
+ c.Blocks[x, z, h:h + 1] = 2 # grass
+ c.Blocks[x, z, h - 4:h] = 3 # dirt
+ c.Blocks[x, z, :h - 4] = 1 # rock
+
+ if h < water_level:
+ c.Blocks[x, z, h + 1:water_level] = 9 # water
+ if h < water_level + 2:
+ c.Blocks[x, z, h - 2:h + 1] = 12 # sand if it's near water level
+
+ c.Blocks[x, z, 0] = 7 # bedrock
+
+ c.chunkChanged()
+ c.TerrainPopulated = False
+ # the quick lighting from chunkChanged has already lit this simple terrain completely
+ c.needsLighting = False
+
+ logging.info("%s Just did chunk %d,%d" % (datetime.datetime.now().strftime("[%H:%M:%S]"), cx, cz))
+
+ logging.info("Done with mapping!")
+ self.needsSave = True
+ stop = datetime.datetime.now()
+ logging.info("Took %s." % str(stop - start))
+
+ spawnz = width / 2
+ spawnx = height / 2
+ spawny = greyimg.getpixel((spawnx, spawnz))
+ logging.info("You probably want to change your spawn point. I suggest {0}".format((spawnx, spawny, spawnz)))
+
+ def _execute(self, command):
+ """
+ execute <filename>
+ Execute all commands in a file and save.
+ """
+ if len(command) == 0:
+ print "You must give the file with commands to execute"
+ else:
+ commandFile = open(command[0], "r")
+ commandsFromFile = commandFile.readlines()
+ for commandFromFile in commandsFromFile:
+ print commandFromFile
+ self.processCommand(commandFromFile)
+ self._save("")
+
+ def _quit(self, command):
+ """
+ quit [ yes | no ]
+
+ Quits the program.
+ Without 'yes' or 'no', prompts to save before quitting.
+
+ In batch mode, an end of file automatically saves the level.
+ """
+ if len(command) == 0 or not (command[0].lower() in ("yes", "no")):
+ if raw_input("Save before exit? ").lower() in ("yes", "y", "1", "true"):
+ self._save(command)
+ raise SystemExit
+ if len(command) and command[0].lower == "yes":
+ self._save(command)
+
+ raise SystemExit
+
+ def _exit(self, command):
+ self._quit(command)
+
+ def _save(self, command):
+ if self.needsSave:
+ self.level.generateLights()
+ self.level.saveInPlace()
+ self.needsSave = False
+
+ def _load(self, command):
+ """
+ load [ <filename> | <world number> ]
+
+ Loads another world, discarding all changes to this world.
+ """
+ if len(command) == 0:
+ self.printUsage("load")
+ self.loadWorld(command[0])
+
+ def _reload(self, command):
+ self.level = mclevel.fromFile(self.level.filename)
+
+ def _dimension(self, command):
+ """
+ dimension [ <dim> ]
+
+ Load another dimension, a sub-world of this level. Without options, lists
+ all of the dimensions found in this world. <dim> can be a number or one of
+ these keywords:
+ nether, hell, slip: DIM-1
+ earth, overworld, parent: parent world
+ end: DIM1
+ """
+
+ if len(command):
+ if command[0].lower() in ("earth", "overworld", "parent"):
+ if self.level.parentWorld:
+ self.level = self.level.parentWorld
+ return
+ else:
+ print "You are already on earth."
+ return
+
+ elif command[0].lower() in ("hell", "nether", "slip"):
+ dimNo = -1
+ elif command[0].lower() == "end":
+ dimNo = 1
+ else:
+ dimNo = self.readInt(command)
+
+ if dimNo in self.level.dimensions:
+ self.level = self.level.dimensions[dimNo]
+ return
+
+ if self.level.parentWorld:
+ print u"Parent world: {0} ('dimension parent' to return)".format(self.level.parentWorld.displayName)
+
+ if len(self.level.dimensions):
+ print u"Dimensions in {0}:".format(self.level.displayName)
+ for k in self.level.dimensions:
+ print "{0}: {1}".format(k, infiniteworld.MCAlphaDimension.dimensionNames.get(k, "Unknown"))
+
+ def _help(self, command):
+ if len(command):
+ self.printUsage(command[0])
+ else:
+ self.printUsage()
+
+ def _blocks(self, command):
+ """
+ blocks [ <block name> | <block ID> ]
+
+ Prints block IDs matching the name, or the name matching the ID.
+ With nothing, prints a list of all blocks.
+ """
+
+ searchName = None
+ if len(command):
+ searchName = " ".join(command)
+ try:
+ searchNumber = int(searchName)
+ except ValueError:
+ searchNumber = None
+ matches = self.level.materials.blocksMatching(searchName)
+ else:
+ matches = [b for b in self.level.materials.allBlocks if b.ID == searchNumber]
+# print "{0:3}: {1}".format(searchNumber, self.level.materials.names[searchNumber])
+ # return
+
+ else:
+ matches = self.level.materials.allBlocks
+
+ print "{id:9} : {name} {aka}".format(id="(ID:data)", name="Block name", aka="[Other names]")
+ for b in sorted(matches):
+ idstring = "({ID}:{data})".format(ID=b.ID, data=b.blockData)
+ aka = b.aka and " [{aka}]".format(aka=b.aka) or ""
+
+ print "{idstring:9} : {name} {aka}".format(idstring=idstring, name=b.name, aka=aka)
+
+ def printUsage(self, command=""):
+ if command.lower() in self.commands:
+ print "Usage: ", self.commandUsage(command.lower())
+ else:
+ print self.__doc__.format(commandPrefix=("", "mce.py <world> ")[not self.batchMode])
+
+ def printUsageAndQuit(self):
+ self.printUsage()
+ raise SystemExit
+
+ def loadWorld(self, world):
+
+ worldpath = os.path.expanduser(world)
+ if os.path.exists(worldpath):
+ self.level = mclevel.fromFile(worldpath)
+ else:
+ self.level = mclevel.loadWorld(world)
+
+ level = None
+
+ batchMode = False
+
+ def run(self):
+ logging.basicConfig(format=u'%(levelname)s:%(message)s')
+ logging.getLogger().level = logging.INFO
+
+ sys.argv.pop(0)
+
+ if len(sys.argv):
+ world = sys.argv.pop(0)
+
+ if world.lower() in ("-h", "--help"):
+ self.printUsageAndQuit()
+
+ if len(sys.argv) and sys.argv[0].lower() == "create":
+ # accept the syntax, "mce world3 create"
+ self._create([world])
+ print "Created world {0}".format(world)
+
+ sys.exit(0)
+ else:
+ self.loadWorld(world)
+ else:
+ self.batchMode = True
+ self.printUsage()
+
+ while True:
+ try:
+ world = raw_input("Please enter world name or path to world folder: ")
+ self.loadWorld(world)
+ except EOFError, e:
+ print "End of input."
+ raise SystemExit
+ except Exception, e:
+ print "Cannot open {0}: {1}".format(world, e)
+ else:
+ break
+
+ if len(sys.argv):
+ # process one command from command line
+ try:
+ self.processCommand(" ".join(sys.argv))
+ except UsageError:
+ self.printUsageAndQuit()
+ self._save([])
+
+ else:
+ # process many commands on standard input, maybe interactively
+ command = [""]
+ self.batchMode = True
+ while True:
+ try:
+ command = raw_input(u"{0}> ".format(self.level.displayName))
+ print
+ self.processCommand(command)
+
+ except EOFError, e:
+ print "End of file. Saving automatically."
+ self._save([])
+ raise SystemExit
+ except Exception, e:
+ if self.debug:
+ traceback.print_exc()
+ print 'Exception during command: {0!r}'.format(e)
+ print "Use 'debug' to enable tracebacks."
+
+ # self.printUsage()
+
+ def processCommand(self, command):
+ command = command.strip()
+
+ if len(command) == 0:
+ return
+
+ if command[0] == "#":
+ return
+
+ commandWords = command.split()
+
+ keyword = commandWords.pop(0).lower()
+ if not keyword in self.commands:
+ matches = filter(lambda x: x.startswith(keyword), self.commands)
+ if len(matches) == 1:
+ keyword = matches[0]
+ elif len(matches):
+ print "Ambiguous command. Matches: "
+ for k in matches:
+ print " ", k
+ return
+ else:
+ raise UsageError("Command {0} not recognized.".format(keyword))
+
+ func = getattr(self, "_" + keyword)
+
+ try:
+ func(commandWords)
+ except PlayerNotFound, e:
+ print "Cannot find player {0}".format(e.args[0])
+ self._player([])
+
+ except UsageError, e:
+ print e
+ if self.debug:
+ traceback.print_exc()
+ self.printUsage(keyword)
+
+
+def main(argv):
+ profile = os.getenv("MCE_PROFILE", None)
+ editor = mce()
+ if profile:
+ print "Profiling enabled"
+ import cProfile
+ cProfile.runctx('editor.run()', locals(), globals(), profile)
+ else:
+ editor.run()
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+MCLevel interfaces
+
+Sample usage:
+
+import mclevel
+
+# Call mclevel.fromFile to identify and open any of these four file formats:
+#
+# Classic levels - gzipped serialized java objects. Returns an instance of MCJavalevel
+# Indev levels - gzipped NBT data in a single file. Returns an MCIndevLevel
+# Schematics - gzipped NBT data in a single file. Returns an MCSchematic.
+# MCSchematics have the special method rotateLeft which will reorient torches, stairs, and other tiles appropriately.
+# Alpha levels - world folder structure containing level.dat and chunk folders. Single or Multiplayer.
+# Can accept a path to the world folder or a path to the level.dat. Returns an MCInfdevOldLevel
+
+# Load a Classic level.
+level = mclevel.fromFile("server_level.dat");
+
+# fromFile identified the file type and returned a MCJavaLevel. MCJavaLevel doesn't actually know any java. It guessed the
+# location of the Blocks array by starting at the end of the file and moving backwards until it only finds valid blocks.
+# It also doesn't know the dimensions of the level. This is why you have to tell them to MCEdit via the filename.
+# This works here too: If the file were 512 wide, 512 long, and 128 high, I'd have to name it "server_level_512_512_128.dat"
+#
+# This is one area for improvement.
+
+# Classic and Indev levels have all of their blocks in one place.
+blocks = level.Blocks
+
+# Sand to glass.
+blocks[blocks == level.materials.Sand.ID] = level.materials.Glass.ID
+
+# Save the file with another name. This only works for non-Alpha levels.
+level.saveToFile("server_level_glassy.dat");
+
+# Load an Alpha world
+# Loading an Alpha world immediately scans the folder for chunk files. This takes longer for large worlds.
+ourworld = mclevel.fromFile("C:\\Minecraft\\OurWorld");
+
+# Convenience method to load a numbered world from the saves folder.
+world1 = mclevel.loadWorldNumber(1);
+
+# Find out which chunks are present. Doing this will scan the chunk folders the
+# first time it is used. If you already know where you want to be, skip to
+# world1.getChunk(xPos, zPos)
+
+chunkPositions = list(world1.allChunks)
+
+# allChunks returns an iterator that yields a (xPos, zPos) tuple for each chunk
+xPos, zPos = chunkPositions[0];
+
+# retrieve an AnvilChunk object. this object will load and decompress
+# the chunk as needed, and remember whether it needs to be saved or relighted
+
+chunk = world1.getChunk(xPos, zPos)
+
+### Access the data arrays of the chunk like so:
+# Take note that the array is indexed x, z, y. The last index corresponds to
+# height or altitude.
+
+blockType = chunk.Blocks[0,0,64]
+chunk.Blocks[0,0,64] = 1
+
+# Access the chunk's Entities and TileEntities as arrays of TAG_Compound as
+# they appear in the save format.
+
+# Entities usually have Pos, Health, and id
+# TileEntities usually have tileX, tileY, tileZ, and id
+# For more information, google "Chunk File Format"
+
+for entity in chunk.Entities:
+ if entity["id"].value == "Spider":
+ entity["Health"].value = 50
+
+
+# Accessing one byte at a time from the Blocks array is very slow in Python.
+# To get around this, we have methods to access multiple bytes at once.
+# The first technique is slicing. You can use slicing to restrict your access
+# to certain depth levels, or to extract a column or a larger section from the
+# array. Standard python slice notation is used.
+
+# Set the top half of the array to 0. The : says to use the entire array along
+# that dimension. The syntax []= indicates we are overwriting part of the array
+chunk.Blocks[:,:,64:] = 0
+
+# Using [] without = creates a 'view' on part of the array. This is not a
+# copy, it is a reference to a portion of the original array.
+midBlocks = chunk.Blocks[:,:,32:64]
+
+# Here's a gotcha: You can't just write 'midBlocks = 0' since it will replace
+# the 'midBlocks' reference itself instead of accessing the array. Instead, do
+# this to access and overwrite the array using []= syntax.
+midBlocks[:] = 0
+
+
+# The second is masking. Using a comparison operator ( <, >, ==, etc )
+# against the Blocks array will return a 'mask' that we can use to specify
+# positions in the array.
+
+# Create the mask from the result of the equality test.
+fireBlocks = ( chunk.Blocks==world.materials.Fire.ID )
+
+# Access Blocks using the mask to set elements. The syntax is the same as
+# using []= with slices
+chunk.Blocks[fireBlocks] = world.materials.Leaves.ID
+
+# You can also combine mask arrays using logical operations (&, |, ^) and use
+# the mask to access any other array of the same shape.
+# Here we turn all trees into birch trees.
+
+# Extract a mask from the Blocks array to find the locations of tree trunks.
+# Or | it with another mask to find the locations of leaves.
+# Use the combined mask to access the Data array and set those locations to birch
+
+# Note that the Data, BlockLight, and SkyLight arrays have been
+# unpacked from 4-bit arrays to numpy uint8 arrays. This makes them much easier
+# to work with.
+
+treeBlocks = ( chunk.Blocks == world.materials.Wood.ID )
+treeBlocks |= ( chunk.Blocks == world.materials.Leaves.ID )
+chunk.Data[treeBlocks] = 2 # birch
+
+
+# The chunk doesn't know you've changed any of that data. Call chunkChanged()
+# to let it know. This will mark the chunk for lighting calculation,
+# recompression, and writing to disk. It will also immediately recalculate the
+# chunk's HeightMap and fill the SkyLight only with light falling straight down.
+# These are relatively fast and were added here to aid MCEdit.
+
+chunk.chunkChanged();
+
+# To recalculate all of the dirty lights in the world, call generateLights
+world.generateLights();
+
+
+# Move the player and his spawn
+world.setPlayerPosition( (0, 67, 0) ) # add 3 to make sure his head isn't in the ground.
+world.setPlayerSpawnPosition( (0, 64, 0) )
+
+
+# Save the level.dat and any chunks that have been marked for writing to disk
+# This also compresses any chunks marked for recompression.
+world.saveInPlace();
+
+
+# Advanced use:
+# The getChunkSlices method returns an iterator that returns slices of chunks within the specified range.
+# the slices are returned as tuples of (chunk, slices, point)
+
+# chunk: The AnvilChunk object we're interested in.
+# slices: A 3-tuple of slice objects that can be used to index chunk's data arrays
+# point: A 3-tuple of floats representing the relative position of this subslice within the larger slice.
+#
+# Take caution:
+# the point tuple is ordered (x,y,z) in accordance with the tuples used to initialize a bounding box
+# however, the slices tuple is ordered (x,z,y) for easy indexing into the arrays.
+
+# Here is an old version of MCInfdevOldLevel.fillBlocks in its entirety:
+
+def fillBlocks(self, box, blockType, blockData = 0):
+ chunkIterator = self.getChunkSlices(box)
+
+ for (chunk, slices, point) in chunkIterator:
+ chunk.Blocks[slices] = blockType
+ chunk.Data[slices] = blockData
+ chunk.chunkChanged();
+
+
+Copyright 2010 David Rio Vierra
+"""
+
+from indev import MCIndevLevel
+from infiniteworld import MCInfdevOldLevel
+from java import MCJavaLevel
+from logging import getLogger
+from mclevelbase import saveFileDir
+import nbt
+from numpy import fromstring
+import os
+from pocket import PocketWorld
+from schematic import INVEditChest, MCSchematic, ZipSchematic
+import sys
+import traceback
+
+log = getLogger(__name__)
+
+class LoadingError(RuntimeError):
+ pass
+
+
+def fromFile(filename, loadInfinite=True, readonly=True):
+ ''' The preferred method for loading Minecraft levels of any type.
+ pass False to loadInfinite if you'd rather not load infdev levels.
+ '''
+ log.info(u"Identifying " + filename)
+
+ if not filename:
+ raise IOError("File not found: " + filename)
+ if not os.path.exists(filename):
+ raise IOError("File not found: " + filename)
+
+ if ZipSchematic._isLevel(filename):
+ log.info("Zipfile found, attempting zipped infinite level")
+ lev = ZipSchematic(filename)
+ log.info("Detected zipped Infdev level")
+ return lev
+
+ if PocketWorld._isLevel(filename):
+ return PocketWorld(filename)
+
+ if MCInfdevOldLevel._isLevel(filename):
+ log.info(u"Detected Infdev level.dat")
+ if loadInfinite:
+ return MCInfdevOldLevel(filename=filename, readonly=readonly)
+ else:
+ raise ValueError("Asked to load {0} which is an infinite level, loadInfinite was False".format(os.path.basename(filename)))
+
+ if os.path.isdir(filename):
+ raise ValueError("Folder {0} was not identified as a Minecraft level.".format(os.path.basename(filename)))
+
+ f = file(filename, 'rb')
+ rawdata = f.read()
+ f.close()
+ if len(rawdata) < 4:
+ raise ValueError("{0} is too small! ({1}) ".format(filename, len(rawdata)))
+
+ data = fromstring(rawdata, dtype='uint8')
+ if not data.any():
+ raise ValueError("{0} contains only zeroes. This file is damaged beyond repair.")
+
+ if MCJavaLevel._isDataLevel(data):
+ log.info(u"Detected Java-style level")
+ lev = MCJavaLevel(filename, data)
+ lev.compressed = False
+ return lev
+
+ #ungzdata = None
+ compressed = True
+ unzippedData = None
+ try:
+ unzippedData = nbt.gunzip(rawdata)
+ except Exception, e:
+ log.info(u"Exception during Gzip operation, assuming {0} uncompressed: {1!r}".format(filename, e))
+ if unzippedData is None:
+ compressed = False
+ unzippedData = rawdata
+
+ #data =
+ data = unzippedData
+ if MCJavaLevel._isDataLevel(data):
+ log.info(u"Detected compressed Java-style level")
+ lev = MCJavaLevel(filename, data)
+ lev.compressed = compressed
+ return lev
+
+ try:
+ root_tag = nbt.load(buf=data)
+
+ except Exception, e:
+ log.info(u"Error during NBT load: {0!r}".format(e))
+ log.info(traceback.format_exc())
+ log.info(u"Fallback: Detected compressed flat block array, yzx ordered ")
+ try:
+ lev = MCJavaLevel(filename, data)
+ lev.compressed = compressed
+ return lev
+ except Exception, e2:
+ raise LoadingError(("Multiple errors encountered", e, e2), sys.exc_info()[2])
+
+ else:
+ if MCIndevLevel._isTagLevel(root_tag):
+ log.info(u"Detected Indev .mclevel")
+ return MCIndevLevel(root_tag, filename)
+ if MCSchematic._isTagLevel(root_tag):
+ log.info(u"Detected Schematic.")
+ return MCSchematic(root_tag=root_tag, filename=filename)
+
+ if INVEditChest._isTagLevel(root_tag):
+ log.info(u"Detected INVEdit inventory file")
+ return INVEditChest(root_tag=root_tag, filename=filename)
+
+ raise IOError("Cannot detect file type.")
+
+
+def loadWorld(name):
+ filename = os.path.join(saveFileDir, name)
+ return fromFile(filename, True)
+
+
+def loadWorldNumber(i):
+ #deprecated
+ filename = u"{0}{1}{2}{3}{1}".format(saveFileDir, os.sep, u"World", i)
+ return fromFile(filename, True)
--- /dev/null
+'''
+Created on Jul 22, 2011
+
+@author: Rio
+'''
+
+from contextlib import contextmanager
+from logging import getLogger
+import sys
+import os
+
+log = getLogger(__name__)
+
+@contextmanager
+def notclosing(f):
+ yield f
+
+
+class PlayerNotFound(Exception):
+ pass
+
+
+class ChunkNotPresent(Exception):
+ pass
+
+
+class RegionMalformed(Exception):
+ pass
+
+
+class ChunkMalformed(ChunkNotPresent):
+ pass
+
+
+def exhaust(_iter):
+ """Functions named ending in "Iter" return an iterable object that does
+ long-running work and yields progress information on each call. exhaust()
+ is used to implement the non-Iter equivalents"""
+ i = None
+ for i in _iter:
+ pass
+ return i
+
+
+
+def win32_appdata():
+ # try to use win32 api to get the AppData folder since python doesn't populate os.environ with unicode strings.
+
+ try:
+ import win32com.client
+ objShell = win32com.client.Dispatch("WScript.Shell")
+ return objShell.SpecialFolders("AppData")
+ except Exception, e:
+ #print "Error while getting AppData folder using WScript.Shell.SpecialFolders: {0!r}".format(e)
+ try:
+ from win32com.shell import shell, shellcon
+ return shell.SHGetPathFromIDListEx(
+ shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA)
+ )
+ except Exception, e:
+ #print "Error while getting AppData folder using SHGetSpecialFolderLocation: {0!r}".format(e)
+ return os.environ['APPDATA'].decode(sys.getfilesystemencoding())
+
+if sys.platform == "win32":
+ appDataDir = win32_appdata()
+ minecraftDir = os.path.join(appDataDir, u".minecraft")
+ appSupportDir = os.path.join(appDataDir, u"pymclevel")
+
+elif sys.platform == "darwin":
+ appDataDir = os.path.expanduser(u"~/Library/Application Support")
+ minecraftDir = os.path.join(appDataDir, u"minecraft")
+ appSupportDir = os.path.expanduser(u"~/Library/Application Support/pymclevel/")
+
+else:
+ appDataDir = os.path.expanduser(u"~")
+ minecraftDir = os.path.expanduser(u"~/.minecraft")
+ appSupportDir = os.path.expanduser(u"~/.pymclevel")
+
+saveFileDir = os.path.join(minecraftDir, u"saves")
+
+
--- /dev/null
+#
+# YAML Definition for the base Minecraft block types
+#
+# The library we're using apparently only supports YAML 1.1
+# See http://en.wikipedia.org/wiki/YAML or http://yaml.org/spec/1.1/
+#
+###
+###
+#
+# Fields marked with * were added when updating this file for MCEdit + pymclevel
+# The field "tex_data" is not used by MCEdit. To describe a block's alternate
+# textures when the block's data value is different, use the "data" field to
+# define block variants.
+#
+# Fields required by MCEdit: id, name, mapcolor, tex
+#
+# Fields also respected by MCEdit: data, tex_direction, tex_direction_data
+#
+###
+###
+#
+# The basic structure of the YAML file consists of a name (which describes
+# the set of blocks), and then a big list of the blocks themselves, either
+# defined by "blocks" or "mlblocks."
+#
+# When using "blocks", X-Ray requires you to specify textures in terms
+# of their (X, Y) coordinates within minecraft's terrain.png file, with
+# (0, 0) being the upper left. Valid ranges for each coordinate is zero
+# through fifteen. For example, the bookshelf texture is (3, 2), and the
+# birch sapling is (15, 4).
+#
+# Additionally, X-Ray will allow you to specify a "texfile" attribute, if
+# the texture sheet to be used isn't Minecraft's default terrain.png file.
+# An example would be the Better Than Wolves mod, with which you'd specify
+# "texfile: btwmodtex/btwterrain01.png"
+#
+# Using "mlblocks" instead of "blocks" will allow you to specify texture
+# information in a way useful for mods that use a ModLoader-style texture
+# definition, where each block's texture is defined in a separate graphic
+# file. For this kind of block, wherever a coordinate is required, specify
+# the filename instead. Note that using "mlblocks" will require you to
+# additionally specify a "texpath" entry at the top level (alongside "name")
+# which specifies the root directory under which the textures can be found.
+# For example, a definition file for Aethermod blocks would have a texpath
+# of "aether/blocks", and its block 170 (Icestone) would have a "tex" value
+# of "Icestone.png"
+#
+# Required fields for each block: id, idStr, name, mapcolor, tex
+#
+#
+# Brief descriptions of the individual block fields:
+#
+# id: The numeric ID of the block
+#
+# idStr: A String ID of the block. This is what you will use to
+# identify the block types in xray.properties, for instance,
+# to tell X-Ray which resources you're highlighting. Most
+# of these are arbitrary and could be changed at will, but
+# a few are looked for specifically in the X-Ray code, and
+# so shouldn't be changed. A complete list of the ones that
+# shouldn't be changed are:
+#
+# IRON_BARS (the "solid pane" renderer uses it)
+# PISTON_HEAD (the piston renderers use it)
+# PISTON_STICKY_BODY (the piston renderers use it)
+# BEDROCK (the "bedrock" toggle uses it)
+# GRASS (the "grass rendering" toggle uses it)
+# SILVERFISH (the "silverfish highlighting" toggle uses it)
+# SAPLING (some special data handling is needed)
+# PORTAL (we generate the texture to use for this one)
+# ENDER_PORTAL (we generate the texture to use for this one)
+# FIRE (we generate the texture to use for this one)
+# WATER (we do extra processing on the texture)
+# STATIONARY_WATER (we do extra processing on the texture)
+#
+# * data: Defines variants of a block type, depending on the blockData value
+# for that block. Any field available for a block is also available
+# for a data variant. Replaces tex_data.
+# See the entries "Sapling" or "Slab" for examples.
+#
+# name: The name that will show up in X-Ray if you're using it
+# as one of your highlights (unimportant otherwise)
+#
+# * aka: Also known as. Alternate names or keywords to be used when searching
+# for a block by name. This field is also used as a hint when converting
+# blocks between level formats: The names of the source level's blocks
+# are matched against both the name and aka of the destination level's.
+#
+# * brightness: Light level produced by the block. Default is 0 (no light)
+#
+# * opacity: Light levels subtracted when light passes through the block.
+# Default is 15 (fully opaque)
+#
+# mapcolor: RGB values for the minimap, 0-255
+#
+# explored: If set to "true" this block will be checked for in X-Ray's
+# "explored" highlighting mode. This field is not required, and
+# will default to "false."
+#
+# tex: The "main" (and possibly only) texture to use for this block type.
+# In "block" lists, this must be (X, Y) coordinates, and in "mlblock"
+# lists, this must be a filename. There are four blocks which don't
+# require this element: FIRE, PORTAL, WATER, and STATIONARY_WATER.
+# Our textures for those blocks are specially generated by X-Ray.
+#
+#
+# tex_direction: Sometimes blocks have different textures on
+# different faces. If some faces aren't specified, the
+# default "tex" will be used. Note that "tex" is always
+# required, even if all directions are overridden. Valid
+# values are:
+# FORWARD
+# BACKWARD
+# SIDES
+# TOP
+# BOTTOM
+#
+# tex_direction_data: By default, X-Ray will render "FORWARD"
+# as if it's facing North. Sometimes the data value of
+# a block will determine which way it faces, though, and
+# this is where you can specify that. For example, the
+# Dispenser's definition is:
+# {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+#
+# Note that currently only NORTH, SOUTH, WEST, and EAST are
+# supported. Presumably I should add in UP and DOWN as
+# well, but that's a feature for another day.
+#
+# type: This determines how X-Ray will render the block. The
+# default type is "NORMAL" which doesn't need to be specified.
+# Valid types are currently:
+#
+# BED
+# BUTTON
+# CAKE
+# CROPS
+# DECORATION_CROSS
+# DOOR
+# FENCE
+# FENCE_GATE
+# FLOOR
+# HALFHEIGHT
+# LADDER
+# LEVER
+# MINECART_TRACKS
+# NORMAL
+# PISTON_BODY
+# PISTON_HEAD
+# PORTAL
+# PRESSURE_PLATE
+# GLASS
+# SEMISOLID
+# SIGNPOST
+# SIMPLE_RAIL
+# STAIRS
+# THINSLICE
+# TORCH
+# TRAPDOOR
+# WALLSIGN
+# WATER
+# VINE
+# HUGE_MUSHROOM
+# SOLID_PANE
+# CHEST
+# NETHER_WART
+# STEM
+# ENDER_PORTAL
+# ENDER_PORTAL_FRAME
+# CAULDRON
+# ENCHANTMENT_TABLE
+# BREWING_STAND
+#
+# "SEMISOLID" refers to blocks which are cube-shaped but shouldn't
+# be considered fully solid by X-Ray's rendering engine. Examples
+# are lava, fire, and mob spawners.
+#
+# "FLOOR" is currently used for redstone wire and Water Lilies
+#
+# "THINSLICE" is currently only used for snow, and I may eventually
+# change that to "SNOW" instead, and clean up that rendering.
+#
+# tex_extra: This is a section to provide extra texture information for
+# block types which require it. A list of the required tex_extra
+# attributes per block type are:
+#
+# DOOR
+# The main texture specified should be the top part of the door.
+# "bottom" should be specified as the texture for the bottom.
+#
+# LEVER
+# The main texture should be the lever itself
+# "base" defines the texture of the lever base
+#
+# MINECART_TRACKS
+# The main texture should be the straight track.
+# "curve" should be the curved texture.
+#
+# SIMPLE_RAIL
+# "powered" specifies the texture to use if the rail is powered.
+# Note that while this attribute only makes sense for the actual
+# "POWERED_RAIL" block, it's still required for any block type which
+# is of type SIMPLE_RAIL
+#
+# CAKE
+# The main texture should be the top of the cake.
+# "side_uncut" should be the uncut sides of the cake
+# "side_cut" shoudl be the cut side of the cake
+# "bottom" should be the bottom
+#
+# BED
+# The main texture should be the top "head" block (with the pillow)
+# "foot_top" is the top of the lower half of the bed
+# "foot" and "head" specify the textures at the very end of the bed
+# "foot_side" and "head_side" specify the sidesm for both head and foot
+#
+# PISTON_BODY
+# The main texture should be the compressed side-view texture.
+# "head" specifies the face of the piston head
+# "back" specifies the back texture of the body
+# "front" specifies the front of the body, drawn if the piston is extended
+#
+# PISTON_HEAD
+# The main texture should be the regular (non-sticky) face of the head
+# "head_sticky" should be the sticky texture
+# "body" should be the main PISTON_BODY texture (used to get the sides of the head)
+#
+# CHEST
+# The main texture should be the front face of the small chest. The others
+# should be self-explanatory:
+# side_small, top, front_big_left, front_big_right, back_big_left, back_big_right
+#
+# CROPS
+# The main texture should be the fully-grown crop texture.
+# "smaller_1" through "smaller_7" should be the various stages of growth, with
+# "smaller_7" being the youngest.
+#
+# NETHER_WART
+# The main texture should be the fully-grown nether wart texture.
+# "smaller_1" through "smaller_2" should be the various stages of growth, with
+# "smaller_2" being the youngest.
+#
+# HUGE_MUSHROOM
+# The main texture should be the outward "skin" texture of the mushroom.
+# "stem" specifies the stem texture
+# "pores" specifies the texture for pores
+#
+# STEM
+# The main texture should be the upright stem.
+# "curve" specifies the texture to use when curved towards its fruit
+#
+# CAULDRON
+# The main texture should be the cauldron sides.
+# "inside" should be the inside faces
+# "top" should be the lip of the cauldron
+#
+# ENCHANTMENT_TABLE
+# The main texture should be the top
+# "sides" should be the side textures
+# "bottom" should be the bottom face
+#
+# BREWING_STAND
+# The main texture should be the main texture
+# "base" should be the base that potions reside on
+#
+# ENDER_PORTAL_FRAME
+# The main texture should be the top texture
+# "sides" should be the side texture
+# "bottom" should be the bottom texture
+# "eye" should be the texture for an inserted Eye of Ender
+#
+name: Vanilla Minecraft Block Types
+
+blocks:
+
+ - id: 1
+ idStr: STONE
+ name: Stone
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+
+ - id: 2
+ idStr: GRASS
+ name: Grass
+ mapcolor: [117, 176, 73]
+ tex: [0, 0]
+ tex_direction:
+ FORWARD: [3, 0]
+ SIDES: [3, 0]
+ BACKWARD: [3, 0]
+ BOTTOM: [2, 0]
+
+ - id: 3
+ idStr: DIRT
+ name: Dirt
+ mapcolor: [134, 96, 67]
+ tex: [2, 0]
+
+ - id: 4
+ idStr: COBBLESTONE
+ name: Cobblestone
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+
+ - id: 5
+ idStr: PLANK
+ name: Wood Planks
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ data:
+ 0:
+ name: Wood Planks
+ tex: [4, 0]
+ 1:
+ name: Wood Planks
+ tex: [6, 12]
+ 2:
+ name: Wood Planks
+ tex: [6, 13]
+ 3:
+ name: Wood Planks
+ tex: [7, 12]
+
+ - id: 6
+ idStr: SAPLING
+ name: Sapling
+ mapcolor: [120, 120, 120]
+ tex: [15, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+ data:
+ 0:
+ name: Sapling
+ tex: [15, 0]
+ 1:
+ name: Spruce Sapling
+ tex: [15, 3]
+ 2:
+ name: Birch Sapling
+ tex: [15, 4]
+ 3:
+ name: Jungle Sapling
+ tex: [14, 1]
+
+ - id: 7
+ idStr: BEDROCK
+ name: Bedrock
+ aka: Adminium
+ mapcolor: [84, 84, 84]
+ tex: [1, 1]
+
+ - id: 8
+ idStr: WATER
+ name: Water (active)
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 9
+ idStr: STATIONARY_WATER
+ name: Water
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 10
+ idStr: LAVA
+ name: Lava (active)
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+ brightness: 15
+
+ - id: 11
+ idStr: STATIONARY_LAVA
+ name: Lava
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+ brightness: 15
+
+ - id: 12
+ idStr: SAND
+ name: Sand
+ mapcolor: [218, 210, 158]
+ tex: [2, 1]
+
+ - id: 13
+ idStr: GRAVEL
+ name: Gravel
+ mapcolor: [136, 126, 126]
+ tex: [3, 1]
+
+ - id: 14
+ idStr: GOLD_ORE
+ name: Gold Ore
+ mapcolor: [143, 140, 125]
+ tex: [0, 2]
+
+ - id: 15
+ idStr: IRON_ORE
+ name: Iron Ore
+ mapcolor: [136, 130, 127]
+ tex: [1, 2]
+
+ - id: 16
+ idStr: COAL_ORE
+ name: Coal Ore
+ mapcolor: [115, 115, 115]
+ tex: [2, 2]
+
+ - id: 17
+ idStr: WOOD
+ name: Wood
+ mapcolor: [102, 81, 51]
+ tex: [4, 1]
+ tex_direction:
+ TOP: [5, 1]
+ BOTTOM: [5, 1]
+ data:
+ 0:
+ name: Wood
+ tex: [4, 1]
+ 1:
+ name: Pine Wood
+ aka: Redwood, Spruce Wood
+ tex: [4, 7]
+ 2:
+ name: Birch Wood
+ tex: [5, 7]
+ 3:
+ name: Jungle Wood
+ tex: [9, 9]
+
+ - id: 18
+ idStr: LEAVES
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3] # The "correct" one is actually [4, 3] but with the current
+ # transparency rendering issues, this one looks better.
+ opacity: 1
+ data:
+ 0:
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+ 1:
+ name: Pine Leaves
+ aka: Spruce Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 8]
+ 2:
+ name: Birch Leaves
+ mapcolor: [89, 151, 76]
+ tex: [5, 3]
+ 3:
+ name: Jungle Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 12]
+
+ 4: # bit 0x4 - "Permanent"
+ name: Leaves (Permanent)
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+ 5:
+ name: Pine Leaves (Permanent)
+ aka: Spruce Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 8]
+ 6:
+ name: Birch Leaves (Permanent)
+ mapcolor: [89, 151, 76]
+ tex: [5, 3]
+ 7:
+ name: Jungle Leaves (Permanent)
+ mapcolor: [74, 131, 66]
+ tex: [5, 12]
+
+ 8: # bit 0x8 - "Decaying"
+ name: Leaves (Decaying)
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+ 9:
+ name: Pine Leaves (Decaying)
+ aka: Spruce Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 8]
+ 10:
+ name: Birch Leaves (Decaying)
+ mapcolor: [89, 151, 76]
+ tex: [5, 3]
+ 11:
+ name: Jungle Leaves (Decaying)
+ mapcolor: [74, 131, 66]
+ tex: [5, 12]
+
+
+ - id: 19
+ idStr: SPONGE
+ name: Sponge
+ mapcolor: [193, 193, 57]
+ tex: [0, 3]
+
+ - id: 20
+ idStr: GLASS
+ name: Glass
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: GLASS
+ opacity: 0
+
+ - id: 21
+ idStr: LAPIS_LAZULI_ORE
+ name: Lapis Lazuli Ore
+ mapcolor: [27, 70, 161]
+ tex: [0, 10]
+
+ - id: 22
+ idStr: LAPIS_LAZULI_BLOCK
+ name: Lapis Lazuli Block
+ mapcolor: [0, 0, 0]
+ tex: [0, 9]
+
+ - id: 23
+ idStr: DISPENSER
+ name: Dispenser
+ mapcolor: [96, 96, 96]
+ tex: [14, 2]
+ tex_direction:
+ FORWARD: [14, 2]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [14, 3]
+ BOTTOM: [14, 3]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+
+ - id: 24
+ idStr: SANDSTONE
+ name: Sandstone
+ mapcolor: [218, 210, 158]
+ tex: [0, 12]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 13]
+ data:
+ 0:
+ name: Sandstone
+ mapcolor: [218, 210, 158]
+ tex: [0, 12]
+ 1:
+ name: Sandstone
+ aka: Decorative Sandstone
+ mapcolor: [218, 210, 158]
+ tex: [5, 14]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 11]
+ 2:
+ name: Sandstone
+ aka: Smooth Sandstone
+ mapcolor: [218, 210, 158]
+ tex: [6, 14]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 11]
+
+ - id: 25
+ idStr: NOTE_BLOCK
+ name: Note Block
+ mapcolor: [114, 88, 56]
+ tex: [10, 4]
+
+ - id: 26
+ idStr: BED
+ name: Bed
+ mapcolor: [255, 0, 0]
+ tex: [7, 8]
+ type: BED
+ tex_extra:
+ foot_top: [6, 8]
+ head_side: [7, 9]
+ foot_side: [6, 9]
+ foot: [5, 9]
+ head: [8, 9]
+ opacity: 0
+
+ - id: 27
+ idStr: POWERED_RAIL
+ name: Powered Rail
+ mapcolor: [120, 53, 28]
+ tex: [3, 10]
+ type: SIMPLE_RAIL
+ tex_extra:
+ powered: [3, 11]
+ opacity: 0
+
+ - id: 28
+ idStr: DETECTOR_RAIL
+ name: Detector Rail
+ mapcolor: [200, 189, 189]
+ tex: [3, 12]
+ type: SIMPLE_RAIL
+ tex_extra:
+ powered: [3, 12] # Only because the SIMPLE_RAIL renderer requires it; will
+ # go unused here.
+ opacity: 0
+
+ - id: 29
+ idStr: PISTON_STICKY_BODY
+ name: Sticky Piston
+ mapcolor: [132, 132, 132]
+ tex: [12, 6]
+ type: PISTON_BODY
+ tex_extra:
+ head: [11, 6]
+ back: [13, 6]
+ front: [14, 6]
+
+ - id: 30
+ idStr: WEB
+ name: Web
+ mapcolor: [255, 255, 255]
+ tex: [11, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 31
+ idStr: TALL_GRASS
+ name: Tall Grass
+ mapcolor: [104, 156, 53]
+ tex: [7, 2]
+ data:
+ 0:
+ tex: [7, 3]
+ name: (Unused Shrub)
+ 1:
+ tex: [7, 2]
+ name: Tall Grass
+ 2:
+ tex: [8, 3]
+ name: Fern
+ type: DECORATION_CROSS
+ opacity: 0
+
+
+ - id: 32
+ idStr: DEAD_SHRUB
+ name: Dead Shrub
+ mapcolor: [148, 100, 40]
+ tex: [7, 3]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 33
+ idStr: PISTON_BODY
+ name: Piston
+ mapcolor: [132, 132, 132]
+ tex: [12, 6]
+ type: PISTON_BODY
+ tex_extra:
+ head: [11, 6]
+ back: [13, 6]
+ front: [14, 6]
+
+ - id: 34
+ idStr: PISTON_HEAD
+ name: Piston Head
+ mapcolor: [119, 92, 53]
+ tex: [11, 6]
+ type: PISTON_HEAD
+ tex_extra:
+ head_sticky: [10, 6]
+ body: [12, 6]
+ opacity: 0
+
+ - id: 35
+ idStr: WOOL
+ name: White Wool
+ mapcolor: [222, 222, 222]
+ tex: [0, 4]
+ data:
+ 0:
+ tex: [0, 4]
+ name: White Wool
+ mapcolor: [222, 222, 222]
+ 1:
+ tex: [2, 13]
+ name: Orange Wool
+ mapcolor: [234, 127, 55]
+ 2:
+ tex: [2, 12]
+ name: Magenta Wool
+ mapcolor: [191, 75, 201]
+ 3:
+ tex: [2, 11]
+ name: Light Blue Wool
+ aka: Aqua Wool
+ mapcolor: [104, 139, 212]
+ 4:
+ tex: [2, 10]
+ name: Yellow Wool
+ mapcolor: [104, 139, 212]
+ 5:
+ tex: [2, 9]
+ name: Lime Wool
+ mapcolor: [59, 189, 48]
+ 6:
+ tex: [2, 8]
+ name: Pink Wool
+ mapcolor: [217, 131, 155]
+ 7:
+ tex: [2, 7]
+ name: Gray Wool
+ mapcolor: [66, 66, 66]
+ 8:
+ tex: [1, 14]
+ name: Light Gray Wool
+ mapcolor: [166, 166, 166]
+ 9:
+ tex: [1, 13]
+ name: Cyan Wool
+ mapcolor: [39, 117, 149]
+ 10:
+ tex: [1, 12]
+ name: Purple Wool
+ aka: Indigo Wool, Violet Wool
+ mapcolor: [129, 54, 196]
+ 11:
+ tex: [1, 11]
+ name: Blue Wool
+ mapcolor: [39, 51, 161]
+ 12:
+ tex: [1, 10]
+ name: Brown Wool
+ mapcolor: [86, 51, 28]
+ 13:
+ tex: [1, 9]
+ name: Green Wool
+ mapcolor: [56, 77, 24]
+ 14:
+ tex: [1, 8]
+ name: Red Wool
+ mapcolor: [164, 45, 41]
+ 15:
+ tex: [1, 7]
+ name: Black Wool
+ mapcolor: [0, 0, 0]
+
+ - id: 37
+ idStr: YELLOW_FLOWER
+ name: Flower
+ mapcolor: [255, 255, 0]
+ tex: [13, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 38
+ idStr: RED_ROSE
+ name: Rose
+ mapcolor: [255, 0, 0]
+ tex: [12, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 39
+ idStr: BROWN_MUSHROOM
+ name: Brown Mushroom
+ mapcolor: [145, 109, 85]
+ tex: [13, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 40
+ idStr: RED_MUSHROOM
+ name: Red Mushroom
+ mapcolor: [226, 18, 18]
+ tex: [12, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 41
+ idStr: GOLD_BLOCK
+ name: Block of Gold
+ mapcolor: [231, 165, 45]
+ tex: [7, 1]
+
+ - id: 42
+ idStr: IRON_BLOCK
+ name: Block of Iron
+ mapcolor: [191, 191, 191]
+ tex: [6, 1]
+
+ - id: 43
+ idStr: DOUBLE_SLAB
+ name: Double Slab
+ aka: Step
+ mapcolor: [200, 200, 200]
+ tex: [6, 0]
+ data:
+ 0:
+ tex: [5, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+ name: Double Stone Slab
+ 1:
+ tex: [0, 12]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 13]
+ name: Double Sandstone Slab
+ 2:
+ tex: [4, 0]
+ name: Double Wooden Slab
+ 3:
+ tex: [0, 1]
+ name: Double Cobblestone Slab
+ 4:
+ tex: [7, 0]
+ name: Double Brick Slab
+ 5:
+ tex: [6, 3]
+ name: Double Stone Brick Slab
+ 6:
+ tex: [0, 14]
+ name: Double Nether Brick Slab
+
+ - id: 44
+ idStr: SLAB
+ name: Slab
+ aka: Half Step
+ mapcolor: [200, 200, 200]
+ tex: [6, 0]
+ data:
+ 0:
+ tex: [6, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+ name: Stone Slab
+ 1:
+ tex: [0, 12]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 13]
+ name: Sandstone Slab
+ 2:
+ tex: [4, 0]
+ name: Wooden Slab
+ 3:
+ tex: [0, 1]
+ name: Cobblestone Slab
+ 4:
+ tex: [7, 0]
+ name: Brick Slab
+ 5:
+ tex: [6, 3]
+ name: Stone Brick Slab
+ 6:
+ tex: [0, 14]
+ name: Nether Brick Slab
+ type: HALFHEIGHT
+
+ - id: 45
+ idStr: BRICK
+ name: Brick
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+
+ - id: 46
+ idStr: TNT
+ name: TNT
+ mapcolor: [160, 83, 65]
+ tex: [8, 0]
+ tex_direction:
+ TOP: [9, 0]
+ BOTTOM: [10, 0]
+
+ - id: 47
+ idStr: BOOKSHELF
+ name: Bookshelf
+ mapcolor: [188, 152, 98]
+ tex: [3, 2]
+ tex_direction:
+ TOP: [4, 0]
+ BOTTOM: [4, 0]
+
+ - id: 48
+ idStr: MOSSY_COBBLESTONE
+ name: Moss Stone
+ aka: Mossy Cobblestone
+ mapcolor: [115, 169, 115]
+ tex: [4, 2]
+
+ - id: 49
+ idStr: OBSIDIAN
+ name: Obsidian
+ mapcolor: [26, 11, 43]
+ tex: [5, 2]
+
+ - id: 50
+ idStr: TORCH
+ name: Torch
+ mapcolor: [245, 220, 50]
+ tex: [0, 5]
+ type: TORCH
+ explored: true
+ opacity: 0
+ brightness: 14
+
+ - id: 51
+ idStr: FIRE
+ name: Fire
+ mapcolor: [255, 170, 30]
+ type: SEMISOLID
+ tex: [15, 1]
+ opacity: 0
+ brightness: 15
+
+ - id: 52
+ idStr: MOB_SPAWNER
+ name: Monster Spawner
+ aka: Mob Cage
+ mapcolor: [25, 82, 122]
+ tex: [1, 4]
+ type: SEMISOLID
+ opacity: 0
+
+ - id: 53
+ idStr: WOODEN_STAIRS
+ name: Wooden Stairs
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: STAIRS
+
+ - id: 54
+ texfile: item/largechest.png
+ idStr: CHEST
+ name: Chest
+ mapcolor: [125, 91, 38]
+ tex: [1, 0]
+ type: CHEST
+ tex_extra:
+ side_small: [1, 0]
+ top: [3, 0]
+ front_big_left: [1, 0]
+ front_big_right: [1, 0]
+ back_big_left: [1, 0]
+ back_big_right: [1, 0]
+ opacity: 0
+
+ - id: 55
+ idStr: REDSTONE_WIRE
+ name: Redstone Wire
+ mapcolor: [245, 50, 50]
+ tex: [4, 10]
+ type: FLOOR
+ opacity: 0
+
+ - id: 56
+ idStr: DIAMOND_ORE
+ name: Diamond Ore
+ mapcolor: [129, 140, 143]
+ tex: [2, 3]
+
+ - id: 57
+ idStr: DIAMOND_BLOCK
+ name: Block of Diamond
+ mapcolor: [45, 166, 152]
+ tex: [8, 1]
+
+ - id: 58
+ idStr: WORKBENCH
+ name: Crafting Table
+ aka: Workbench
+ mapcolor: [114, 88, 56]
+ tex: [12, 3]
+ tex_direction:
+ SIDES: [11, 3]
+ TOP: [11, 2]
+ BOTTOM: [4, 0]
+
+ - id: 59
+ idStr: CROPS
+ name: Crops
+ aka: Wheat
+ mapcolor: [146, 192, 0]
+ tex: [15, 5]
+ type: CROPS
+ data:
+ 0: {tex: [8, 5]}
+ 1: {tex: [9, 5]}
+ 2: {tex: [10, 5]}
+ 3: {tex: [11, 5]}
+ 4: {tex: [12, 5]}
+ 5: {tex: [13, 5]}
+ 6: {tex: [14, 5]}
+ 7: {tex: [15, 5]}
+ opacity: 0
+
+ - id: 60
+ idStr: FARMLAND
+ name: Farmland
+ aka: Soil
+ mapcolor: [95, 58, 30]
+ tex: [6, 5]
+ tex_direction:
+ FORWARD: [2, 0]
+ SIDES: [2, 0]
+ BACKWARD: [2, 0]
+ BOTTOM: [2, 0]
+
+ - id: 61
+ idStr: FURNACE
+ name: Furnace
+ mapcolor: [96, 96, 96]
+ tex: [12, 2]
+ tex_direction:
+ FORWARD: [12, 2]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [14, 3]
+ BOTTOM: [14, 3]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+
+ - id: 62
+ idStr: BURNING_FURNACE
+ name: Lit Furnace
+ mapcolor: [96, 96, 96]
+ tex: [13, 3]
+ tex_direction:
+ FORWARD: [13, 3]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [14, 3]
+ BOTTOM: [14, 3]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+ brightness: 14
+
+ - id: 63
+ idStr: SIGNPOST
+ name: Sign
+ mapcolor: [111, 91, 54]
+ tex: [4, 0]
+ type: SIGNPOST
+ opacity: 0
+
+ - id: 64
+ idStr: WOODEN_DOOR
+ name: Wooden Door
+ mapcolor: [136, 109, 67]
+ tex: [1, 5]
+ type: DOOR
+ tex_extra:
+ bottom: [1, 6]
+ opacity: 0
+
+ - id: 65
+ idStr: LADDER
+ name: Ladder
+ mapcolor: [181, 140, 64]
+ tex: [3, 5]
+ type: LADDER
+ opacity: 0
+
+ - id: 66
+ idStr: MINECART_TRACKS
+ name: Rail
+ aka: Minecart Track
+ mapcolor: [150, 134, 102]
+ tex: [0, 8]
+ type: MINECART_TRACKS
+ tex_extra:
+ curve: [0, 7]
+ opacity: 0
+
+ - id: 67
+ idStr: COBBLESTONE_STAIRS
+ name: Stone Stairs
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+ type: STAIRS
+
+ - id: 68
+ idStr: WALL_SIGN
+ name: Wall Sign
+ mapcolor: [111, 91, 54]
+ tex: [4, 0]
+ type: WALLSIGN
+ opacity: 0
+
+ - id: 69
+ idStr: LEVER
+ name: Lever
+ aka: Switch
+ mapcolor: [124, 98, 62]
+ tex: [0, 6]
+ type: LEVER
+ tex_extra:
+ base: [0, 1]
+ opacity: 0
+
+ - id: 70
+ idStr: STONE_PRESSURE_PLATE
+ name: Stone Pressure Plate
+ aka: Stone Floor Plate
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+ type: PRESSURE_PLATE
+ opacity: 0
+
+ - id: 71
+ idStr: IRON_DOOR
+ name: Iron Door
+ mapcolor: [191, 191, 191]
+ tex: [2, 5]
+ type: DOOR
+ tex_extra:
+ bottom: [2, 6]
+ opacity: 0
+
+ - id: 72
+ idStr: WOODEN_PRESSURE_PLATE
+ name: Wooden Pressure Plate
+ aka: Wooden Floor Plate
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: PRESSURE_PLATE
+ opacity: 0
+
+ - id: 73
+ idStr: REDSTONE_ORE
+ name: Redstone Ore
+ mapcolor: [131, 107, 107]
+ tex: [3, 3]
+
+ - id: 74
+ idStr: GLOWING_REDSTONE_ORE
+ name: Redstone Ore (glowing)
+ mapcolor: [131, 107, 107]
+ tex: [3, 3]
+ brightness: 9
+
+ - id: 75
+ idStr: REDSTONE_TORCH_OFF
+ name: Redstone Torch (off)
+ mapcolor: [181, 140, 64]
+ tex: [3, 7]
+ type: TORCH
+ explored: true
+ opacity: 0
+
+
+ - id: 76
+ idStr: REDSTONE_TORCH_ON
+ name: Redstone Torch (on)
+ mapcolor: [255, 0, 0]
+ tex: [3, 6]
+ type: TORCH
+ explored: true
+ opacity: 0
+ brightness: 7
+
+ - id: 77
+ idStr: STONE_BUTTON
+ name: Button
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+ type: BUTTON
+ opacity: 0
+
+ - id: 78
+ idStr: SNOW
+ name: Snow Layer
+ mapcolor: [255, 255, 255]
+ tex: [2, 4]
+ type: THINSLICE
+ opacity: 0
+
+ - id: 79
+ idStr: ICE
+ name: Ice
+ mapcolor: [83, 113, 163]
+ tex: [3, 4]
+ type: WATER
+ opacity: 3
+
+ - id: 80
+ idStr: SNOW_BLOCK
+ name: Snow
+ mapcolor: [250, 250, 250]
+ tex: [2, 4]
+
+ - id: 81
+ idStr: CACTUS
+ name: Cactus
+ mapcolor: [25, 120, 25]
+ tex: [6, 4]
+ tex_direction:
+ TOP: [5, 4]
+ BOTTOM: [5, 4]
+ opacity: 0
+
+ - id: 82
+ idStr: CLAY
+ name: Clay
+ mapcolor: [151, 157, 169]
+ tex: [8, 4]
+
+ - id: 83
+ idStr: SUGARCANE
+ name: Sugar Cane
+ aka: Reeds, Papyrus
+ mapcolor: [100, 67, 50]
+ tex: [9, 4]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 84
+ idStr: JUKEBOX
+ name: Jukebox
+ mapcolor: [114, 88, 56]
+ tex: [10, 4]
+ tex_direction:
+ TOP: [11, 4]
+
+ - id: 85
+ idStr: FENCE
+ name: Fence
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: FENCE
+ opacity: 0
+
+ - id: 86
+ idStr: PUMPKIN
+ name: Pumpkin
+ mapcolor: [227, 144, 29]
+ tex: [7, 7]
+ tex_direction:
+ FORWARD: [7, 7]
+ SIDES: [6, 7]
+ BACKWARD: [6, 7]
+ TOP: [6, 6]
+ BOTTOM: [6, 6]
+ tex_direction_data: {0: SOUTH, 1: WEST, 2: NORTH, 3: EAST}
+
+ - id: 87
+ idStr: NETHERRACK
+ name: Netherrack
+ mapcolor: [104, 8, 8]
+ tex: [7, 6]
+
+ - id: 88
+ idStr: SOUL_SAND
+ name: Soul Sand
+ aka: Slow Sand
+ mapcolor: [106, 82, 68]
+ tex: [8, 6]
+
+ - id: 89
+ idStr: GLOWSTONE
+ name: Glowstone
+ mapcolor: [249, 212, 156]
+ tex: [9, 6]
+ explored: true
+ brightness: 15
+
+ - id: 90
+ idStr: PORTAL
+ name: Nether Portal
+ mapcolor: [214, 127, 255]
+ type: PORTAL
+ brightness: 11
+ opacity: 0
+
+ - id: 91
+ idStr: JACK_O_LANTERN
+ name: Jack-o'-Lantern
+ mapcolor: [249, 255, 58]
+ explored: true
+ tex: [8, 7]
+ tex_direction:
+ FORWARD: [8, 7]
+ SIDES: [6, 7]
+ BACKWARD: [6, 7]
+ TOP: [6, 6]
+ BOTTOM: [6, 6]
+ tex_direction_data: {0: SOUTH, 1: WEST, 2: NORTH, 3: EAST}
+ brightness: 15
+
+ - id: 92
+ idStr: CAKE
+ name: Cake
+ mapcolor: [234, 233, 235]
+ tex: [9, 7]
+ type: CAKE
+ tex_extra:
+ side_uncut: [10, 7]
+ side_cut: [11, 7]
+ bottom: [12, 7]
+ opacity: 0
+
+ - id: 93
+ idStr: REDSTONE_REPEATER_OFF
+ name: Redstone Repeater (off)
+ mapcolor: [245, 50, 50]
+ tex: [3, 8]
+ type: PRESSURE_PLATE
+ opacity: 0
+
+ - id: 94
+ idStr: REDSTONE_REPEATER_ON
+ name: Redstone Repeater (on)
+ mapcolor: [245, 50, 50]
+ tex: [3, 9]
+ type: PRESSURE_PLATE
+ opacity: 0
+ brightness: 9
+
+ - id: 96
+ idStr: TRAPDOOR
+ name: Trapdoor
+ mapcolor: [143, 107, 53]
+ tex: [4, 5]
+ type: TRAPDOOR
+ opacity: 0
+
+ - id: 97
+ idStr: SILVERFISH
+ name: Hidden Silverfish
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+ data:
+ 0:
+ tex: [1, 0]
+ name: Hidden Silverfish (Smooth Stone)
+ 1:
+ tex: [0, 1]
+ name: Hidden Silverfish (Cobblestone)
+ 2:
+ tex: [6, 3]
+ name: Hidden Silverfish (Stone Brick)
+
+ - id: 98
+ idStr: STONE_BRICK
+ name: Stone Bricks
+ mapcolor: [120, 120, 120]
+ tex: [6, 3]
+ data:
+ 0:
+ tex: [6, 3]
+ name: Stone Bricks
+ 1:
+ tex: [4, 6]
+ name: Mossy Stone Bricks
+ 2:
+ tex: [5, 6]
+ name: Cracked Stone Bricks
+ 3:
+ tex: [5, 13]
+ name: Circle Stone Bricks
+
+ - id: 99
+ idStr: HUGE_BROWN_MUSHROOM
+ name: Huge Brown Mushroom
+ mapcolor: [145, 109, 85]
+ tex: [14, 7]
+ type: HUGE_MUSHROOM
+ tex_extra:
+ stem: [13, 8]
+ pores: [14, 8]
+
+ - id: 100
+ idStr: HUGE_RED_MUSHROOM
+ name: Huge Red Mushroom
+ mapcolor: [181, 29, 27]
+ tex: [13, 7]
+ type: HUGE_MUSHROOM
+ tex_extra:
+ stem: [13, 8]
+ pores: [14, 8]
+
+ - id: 101
+ idStr: IRON_BARS
+ name: Iron Bars
+ mapcolor: [77, 77, 78]
+ tex: [5, 5]
+ type: SOLID_PANE
+ opacity: 0
+
+ - id: 102
+ idStr: GLASS_PANE
+ name: Glass Pane
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: SOLID_PANE
+ opacity: 0
+
+ - id: 103
+ idStr: MELON
+ name: Watermelon
+ mapcolor: [190, 184, 41]
+ tex: [9, 8]
+ tex_direction:
+ FORWARD: [8, 8]
+ SIDES: [8, 8]
+ BACKWARD: [8, 8]
+
+ - id: 104
+ idStr: PUMPKIN_STEM
+ name: Pumpkin Stem
+ mapcolor: [227, 144, 29]
+ tex: [15, 6]
+ type: STEM
+ tex_extra:
+ curve: [15, 7]
+ opacity: 0
+
+ - id: 105
+ idStr: MELON_STEM
+ name: Melon Stem
+ mapcolor: [190, 184, 41]
+ tex: [15, 6]
+ type: STEM
+ tex_extra:
+ curve: [15, 7]
+ opacity: 0
+
+ - id: 106
+ idStr: VINE
+ name: Vines
+ mapcolor: [117, 176, 73]
+ tex: [15, 8]
+ type: VINE
+ opacity: 0
+
+ - id: 107
+ idStr: FENCE_GATE
+ name: Fence Gate
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: FENCE_GATE
+ opacity: 0
+
+ - id: 108
+ idStr: BRICK_STAIRS
+ name: Brick Stairs
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+ type: STAIRS
+
+ - id: 109
+ idStr: STONE_BRICK_STAIRS
+ name: Stone Brick Stairs
+ mapcolor: [120, 120, 120]
+ tex: [6, 3]
+ type: STAIRS
+
+ - id: 110
+ idStr: MYCELIUM
+ name: Mycelium
+ mapcolor: [140, 115, 119]
+ tex: [14, 4]
+ tex_direction:
+ BOTTOM: [2, 0]
+ SIDES: [13, 4]
+ FORWARD: [13, 4]
+ BACKWARD: [13, 4]
+
+ - id: 111
+ idStr: LILY_PAD
+ name: Lilypad
+ mapcolor: [117, 176, 73]
+ tex: [12, 4]
+ type: FLOOR
+ opacity: 0
+
+ - id: 112
+ idStr: NETHER_BRICK
+ name: Nether Brick
+ mapcolor: [54, 24, 30]
+ tex: [0, 14]
+
+ - id: 113
+ idStr: NETHER_FENCE
+ name: Nether Fence
+ mapcolor: [54, 24, 30]
+ tex: [0, 14]
+ type: FENCE
+ opacity: 0
+
+ - id: 114
+ idStr: NETHER_STAIRS
+ name: Nether Stairs
+ mapcolor: [54, 24, 30]
+ tex: [0, 14]
+ type: STAIRS
+
+ - id: 115
+ idStr: NETHER_WART
+ name: Nether Wart
+ mapcolor: [112, 8, 28]
+ tex: [4, 14]
+ type: NETHER_WART
+ tex_extra:
+ smaller_1: [3, 14]
+ smaller_2: [2, 14]
+ opacity: 0
+
+ - id: 116
+ idStr: ENCHANTMENT_TABLE
+ name: Enchantment Table
+ mapcolor: [88, 23, 22]
+ tex: [6, 10]
+ type: ENCHANTMENT_TABLE
+ tex_extra:
+ sides: [6, 11]
+ bottom: [7, 11]
+
+ - id: 117
+ idStr: BREWING_STAND
+ name: Brewing Stand
+ mapcolor: [189, 163, 82]
+ tex: [13, 9]
+ type: BREWING_STAND
+ tex_extra:
+ base: [12, 9]
+ opacity: 0
+
+ - id: 118
+ idStr: CAULDRON
+ name: Cauldron
+ mapcolor: [79, 79, 79]
+ tex: [10, 9]
+ type: CAULDRON
+ tex_extra:
+ inside: [11, 8]
+ top: [10, 8]
+
+ - id: 119
+ idStr: ENDER_PORTAL
+ name: Ender Portal
+ mapcolor: [0, 0, 0]
+ type: ENDER_PORTAL
+
+ - id: 120
+ idStr: ENDER_PORTAL_FRAME
+ name: Portal Frame
+ mapcolor: [55, 142, 215]
+ tex: [14, 9]
+ type: ENDER_PORTAL_FRAME
+ tex_extra:
+ sides: [15, 9]
+ bottom: [15, 10]
+ eye: [14, 10]
+
+ - id: 121
+ idStr: ENDER_STONE
+ name: End Stone
+ mapcolor: [235, 248, 182]
+ tex: [15, 10]
+
+ - id: 122
+ idStr: DRAGON_EGG
+ name: Dragon Egg
+ aka: Ender Dragon Egg
+ mapcolor: [45, 1, 51]
+ tex: [7, 10]
+
+ - id: 123
+ idStr: REDSTONE_LAMP_OFF
+ name: Redstone Lamp (off)
+ mapcolor: [156, 104, 54]
+ tex: [3, 13]
+
+ - id: 124
+ idStr: REDSTONE_LAMP_ON
+ name: Redstone Lamp (on)
+ mapcolor: [208, 152, 70]
+ tex: [4, 13]
+ brightness: 15
+
+ - id: 125
+ idStr: WOODEN_DOUBLE_SLAB
+ name: Wooden Double Slab
+ aka: Wood Double Step
+ mapcolor: [200, 200, 200]
+ tex: [4, 0]
+ data:
+ 0:
+ tex: [4, 0]
+ name: Oak-Wood Double Slab
+ 1:
+ tex: [6, 12]
+ name: Spruce-Wood Double Slab
+ 2:
+ tex: [6, 13]
+ name: Birch-Wood Double Slab
+ 3:
+ tex: [7, 12]
+ name: Jungle-Wood Double Slab
+
+ - id: 126
+ idStr: WOODEN_SLAB
+ name: Wooden Slab
+ aka: Wood Half Step
+ mapcolor: [200, 200, 200]
+ tex: [4, 0]
+ data:
+ 0:
+ tex: [4, 0]
+ name: Oak-Wood Slab
+ 1:
+ tex: [6, 12]
+ name: Spruce-Wood Slab
+ 2:
+ tex: [6, 13]
+ name: Birch-Wood Slab
+ 3:
+ tex: [7, 12]
+ name: Jungle-Wood Slab
+ type: HALFHEIGHT
+
+ - id: 127
+ idStr: COCOA_PLANT
+ name: Cocoa Plant
+ mapcolor: [181, 140, 64]
+ tex: [10, 10]
+ type: CROPS
+ opacity: 0
+
+ - id: 128
+ idStr: SANDSTONE_STAIRS
+ name: Sandstone Stairs
+ mapcolor: [157, 128, 79]
+ tex: [0, 12]
+ type: STAIRS
+
+ - id: 129
+ idStr: EMERALD_ORE
+ name: Emerald Ore
+ mapcolor: [200, 200, 200]
+ tex: [11, 10]
+
+ - id: 130
+ idStr: ENDER_CHEST
+ name: Ender Chest
+ mapcolor: [200, 200, 200]
+ tex: [11, 11]
+ data:
+ 0: {tex: [11, 11]}
+ 1: {tex: [11, 11]}
+ 2: {tex: [11, 11]}
+ 3: {tex: [11, 11]}
+ 4: {tex: [11, 11]}
+ opacity: 0
+
+ - id: 131
+ idStr: TRIPWIRE_HOOK
+ name: Tripwire Hook
+ mapcolor: [200, 200, 200]
+ tex: [12, 10]
+ type: CROPS
+ opacity: 0
+
+ - id: 132
+ idStr: TRIPWIRE
+ name: Tripwire
+ mapcolor: [200, 200, 200]
+ tex: [13, 10]
+ type: CROPS
+ opacity: 0
+
+ - id: 133
+ idStr: EMERALD_BLOCK
+ name: Block of Emerald
+ mapcolor: [200, 200, 200]
+ tex: [9, 1]
+
+ - id: 134
+ idStr: SPRUCE_WOOD_STAIRS
+ name: Spruce-Wood Stairs
+ mapcolor: [157, 128, 79]
+ tex: [6, 12]
+ type: STAIRS
+
+ - id: 135
+ idStr: BIRCH_WOOD_STAIRS
+ name: Birch-Wood Stairs
+ mapcolor: [157, 128, 79]
+ tex: [6, 13]
+ type: STAIRS
+
+ - id: 136
+ idStr: JUNGLE_WOOD_STAIRS
+ name: Jungle-Wood Stairs
+ mapcolor: [157, 128, 79]
+ tex: [7, 12]
+ type: STAIRS
+
+ - id: 137
+ idStr: COMMAND_BLOCK
+ name: Command Block
+ mapcolor: [200, 200, 200]
+ tex: [8, 11]
+
+ - id: 138
+ idStr: BEACON_BLOCK
+ name: Beacon Block
+ mapcolor: [200, 200, 200]
+ tex: [9, 2]
+ opacity: 0
+
+ - id: 139
+ idStr: COBBLE_WALL
+ name: Cobblestone Wall
+ mapcolor: [128, 128, 128]
+ tex: [0, 1]
+ type: FENCE
+ data:
+ 0:
+ tex: [0, 1]
+ name: Cobblestone Wall
+ 1:
+ tex: [4, 2]
+ name: Mossy Cobblestone Wall
+ opacity: 0
+
+ - id: 140
+ idStr: FLOWER_POT
+ name: Flower Pot
+ mapcolor: [200, 200, 200]
+ tex: [10, 11]
+ opacity: 0
+
+ - id: 141
+ idStr: CARROTS
+ name: Carrots
+ mapcolor: [200, 100, 100]
+ tex: [11, 12]
+ type: CROPS
+ data:
+ 0: {tex: [8, 12]}
+ 1: {tex: [9, 12]}
+ 2: {tex: [10, 12]}
+ 3: {tex: [11, 12]}
+ opacity: 0
+
+ - id: 142
+ idStr: POTATOES
+ name: Potatoes
+ mapcolor: [200, 100, 100]
+ tex: [12, 12]
+ type: CROPS
+ data:
+ 0: {tex: [8, 12]}
+ 1: {tex: [9, 12]}
+ 2: {tex: [10, 12]}
+ 3: {tex: [12, 12]}
+ opacity: 0
+
+ - id: 143
+ idStr: WOODEN_BUTTON
+ name: Wooden Button
+ mapcolor: [200, 100, 100]
+ tex: [4, 0]
+ type: BUTTON
+ opacity: 0
+
+ - id: 144
+ idStr: MOB_HEAD
+ name: Mob Head
+ mapcolor: [200, 100, 100]
+ tex: [0, 0]
+ opacity: 0
+
+ - id: 145
+ idStr: ANVIL
+ name: Anvil
+ mapcolor: [100, 100, 100]
+ tex: [0, 0]
+ opacity: 0
+
+
+...
--- /dev/null
+import itertools
+import logging
+import os
+from os.path import dirname, join, basename
+import random
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+import urllib
+
+import infiniteworld
+from mclevelbase import appSupportDir, exhaust, ChunkNotPresent
+
+log = logging.getLogger(__name__)
+
+__author__ = 'Rio'
+
+# Thank you, Stackoverflow
+# http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
+def which(program):
+ def is_exe(f):
+ return os.path.exists(f) and os.access(f, os.X_OK)
+
+ fpath, _fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ if sys.platform == "win32":
+ if "SYSTEMROOT" in os.environ:
+ root = os.environ["SYSTEMROOT"]
+ exe_file = os.path.join(root, program)
+ if is_exe(exe_file):
+ return exe_file
+ if "PATH" in os.environ:
+ for path in os.environ["PATH"].split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+
+ return None
+
+
+convert = lambda text: int(text) if text.isdigit() else text
+alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
+
+
+def sort_nicely(l):
+ """ Sort the given list in the way that humans expect.
+ """
+ l.sort(key=alphanum_key)
+
+
+class ServerJarStorage(object):
+ defaultCacheDir = os.path.join(appSupportDir, u"ServerJarStorage")
+
+ def __init__(self, cacheDir=None):
+ if cacheDir is None:
+ cacheDir = self.defaultCacheDir
+
+ self.cacheDir = cacheDir
+
+ if not os.path.exists(self.cacheDir):
+ os.makedirs(self.cacheDir)
+ readme = os.path.join(self.cacheDir, "README.TXT")
+ if not os.path.exists(readme):
+ with file(readme, "w") as f:
+ f.write("""
+About this folder:
+
+This folder is used by MCEdit and pymclevel to store different versions of the
+Minecraft Server to use for terrain generation. It should have one or more
+subfolders, one for each version of the server. Each subfolder must hold at
+least one file named minecraft_server.jar, and the subfolder's name should
+have the server's version plus the names of any installed mods.
+
+There may already be a subfolder here (for example, "Beta 1.7.3") if you have
+used the Chunk Create feature in MCEdit to create chunks using the server.
+
+Version numbers can be automatically detected. If you place one or more
+minecraft_server.jar files in this folder, they will be placed automatically
+into well-named subfolders the next time you run MCEdit. If a file's name
+begins with "minecraft_server" and ends with ".jar", it will be detected in
+this way.
+""")
+
+ self.reloadVersions()
+
+ def reloadVersions(self):
+ cacheDirList = os.listdir(self.cacheDir)
+ self.versions = list(reversed(sorted([v for v in cacheDirList if os.path.exists(self.jarfileForVersion(v))], key=alphanum_key)))
+
+ if MCServerChunkGenerator.javaExe:
+ for f in cacheDirList:
+ p = os.path.join(self.cacheDir, f)
+ if f.startswith("minecraft_server") and f.endswith(".jar") and os.path.isfile(p):
+ print "Unclassified minecraft_server.jar found in cache dir. Discovering version number..."
+ self.cacheNewVersion(p)
+ os.remove(p)
+
+ print "Minecraft_Server.jar storage initialized."
+ print u"Each server is stored in a subdirectory of {0} named with the server's version number".format(self.cacheDir)
+
+ print "Cached servers: ", self.versions
+
+ def downloadCurrentServer(self):
+ print "Downloading the latest Minecraft Server..."
+ try:
+ (filename, headers) = urllib.urlretrieve("http://www.minecraft.net/download/minecraft_server.jar")
+ except Exception, e:
+ print "Error downloading server: {0!r}".format(e)
+ return
+
+ self.cacheNewVersion(filename, allowDuplicate=False)
+
+ def cacheNewVersion(self, filename, allowDuplicate=True):
+ """ Finds the version number from the server jar at filename and copies
+ it into the proper subfolder of the server jar cache folder"""
+
+ version = MCServerChunkGenerator._serverVersionFromJarFile(filename)
+ print "Found version ", version
+ versionDir = os.path.join(self.cacheDir, version)
+
+ i = 1
+ newVersionDir = versionDir
+ while os.path.exists(newVersionDir):
+ if not allowDuplicate:
+ return
+
+ newVersionDir = versionDir + " (" + str(i) + ")"
+ i += 1
+
+ os.mkdir(newVersionDir)
+
+ shutil.copy2(filename, os.path.join(newVersionDir, "minecraft_server.jar"))
+
+ if version not in self.versions:
+ self.versions.append(version)
+
+ def jarfileForVersion(self, v):
+ return os.path.join(self.cacheDir, v, "minecraft_server.jar").encode(sys.getfilesystemencoding())
+
+ def checksumForVersion(self, v):
+ jf = self.jarfileForVersion(v)
+ with file(jf, "rb") as f:
+ import hashlib
+ return hashlib.md5(f.read()).hexdigest()
+
+ broken_versions = ["Beta 1.9 Prerelease {0}".format(i) for i in (1, 2, 3)]
+
+ @property
+ def latestVersion(self):
+ if len(self.versions) == 0:
+ return None
+ return max((v for v in self.versions if v not in self.broken_versions), key=alphanum_key)
+
+ def getJarfile(self, version=None):
+ if len(self.versions) == 0:
+ print "No servers found in cache."
+ self.downloadCurrentServer()
+
+ version = version or self.latestVersion
+ if version not in self.versions:
+ return None
+ return self.jarfileForVersion(version)
+
+
+class JavaNotFound(RuntimeError):
+ pass
+
+
+class VersionNotFound(RuntimeError):
+ pass
+
+
+def readProperties(filename):
+ if not os.path.exists(filename):
+ return {}
+
+ with file(filename) as f:
+ properties = dict((line.split("=", 2) for line in (l.strip() for l in f) if not line.startswith("#")))
+
+ return properties
+
+
+def saveProperties(filename, properties):
+ with file(filename, "w") as f:
+ for k, v in properties.iteritems():
+ f.write("{0}={1}\n".format(k, v))
+
+
+def findJava():
+ if sys.platform == "win32":
+ javaExe = which("java.exe")
+ if javaExe is None:
+ KEY_NAME = "HKLM\SOFTWARE\JavaSoft\Java Runtime Environment"
+ try:
+ p = subprocess.Popen(["REG", "QUERY", KEY_NAME, "/v", "CurrentVersion"], stdout=subprocess.PIPE, universal_newlines=True)
+ o, e = p.communicate()
+ lines = o.split("\n")
+ for l in lines:
+ l = l.strip()
+ if l.startswith("CurrentVersion"):
+ words = l.split(None, 2)
+ version = words[-1]
+ p = subprocess.Popen(["REG", "QUERY", KEY_NAME + "\\" + version, "/v", "JavaHome"], stdout=subprocess.PIPE, universal_newlines=True)
+ o, e = p.communicate()
+ lines = o.split("\n")
+ for l in lines:
+ l = l.strip()
+ if l.startswith("JavaHome"):
+ w = l.split(None, 2)
+ javaHome = w[-1]
+ javaExe = os.path.join(javaHome, "bin", "java.exe")
+ print "RegQuery: java.exe found at ", javaExe
+ break
+
+ except Exception, e:
+ print "Error while locating java.exe using the Registry: ", repr(e)
+ else:
+ javaExe = which("java")
+
+ return javaExe
+
+
+class MCServerChunkGenerator(object):
+ """Generates chunks using minecraft_server.jar. Uses a ServerJarStorage to
+ store different versions of minecraft_server.jar in an application support
+ folder.
+
+ from pymclevel import *
+
+ Example usage:
+
+ gen = MCServerChunkGenerator() # with no arguments, use the newest
+ # server version in the cache, or download
+ # the newest one automatically
+ level = loadWorldNamed("MyWorld")
+
+ gen.generateChunkInLevel(level, 12, 24)
+
+
+ Using an older version:
+
+ gen = MCServerChunkGenerator("Beta 1.6.5")
+
+ """
+ defaultJarStorage = None
+
+ javaExe = findJava()
+ jarStorage = None
+ tempWorldCache = {}
+
+ def __init__(self, version=None, jarfile=None, jarStorage=None):
+
+ self.jarStorage = jarStorage or self.getDefaultJarStorage()
+
+ if self.javaExe is None:
+ raise JavaNotFound("Could not find java. Please check that java is installed correctly. (Could not find java in your PATH environment variable.)")
+ if jarfile is None:
+ jarfile = self.jarStorage.getJarfile(version)
+ if jarfile is None:
+ raise VersionNotFound("Could not find minecraft_server.jar for version {0}. Please make sure that a minecraft_server.jar is placed under {1} in a subfolder named after the server's version number.".format(version or "(latest)", self.jarStorage.cacheDir))
+ self.serverJarFile = jarfile
+ self.serverVersion = version or self._serverVersion()
+
+ @classmethod
+ def getDefaultJarStorage(cls):
+ if cls.defaultJarStorage is None:
+ cls.defaultJarStorage = ServerJarStorage()
+ return cls.defaultJarStorage
+
+ @classmethod
+ def clearWorldCache(cls):
+ cls.tempWorldCache = {}
+
+ for tempDir in os.listdir(cls.worldCacheDir):
+ t = os.path.join(cls.worldCacheDir, tempDir)
+ if os.path.isdir(t):
+ shutil.rmtree(t)
+
+ def createReadme(self):
+ readme = os.path.join(self.worldCacheDir, "README.TXT")
+
+ if not os.path.exists(readme):
+ with file(readme, "w") as f:
+ f.write("""
+ About this folder:
+
+ This folder is used by MCEdit and pymclevel to cache levels during terrain
+ generation. Feel free to delete it for any reason.
+ """)
+
+ worldCacheDir = os.path.join(tempfile.gettempdir(), "pymclevel_MCServerChunkGenerator")
+
+ def tempWorldForLevel(self, level):
+
+ # tempDir = tempfile.mkdtemp("mclevel_servergen")
+ tempDir = os.path.join(self.worldCacheDir, self.jarStorage.checksumForVersion(self.serverVersion), str(level.RandomSeed))
+ propsFile = os.path.join(tempDir, "server.properties")
+ properties = readProperties(propsFile)
+
+ tempWorld = self.tempWorldCache.get((self.serverVersion, level.RandomSeed))
+
+ if tempWorld is None:
+ if not os.path.exists(tempDir):
+ os.makedirs(tempDir)
+ self.createReadme()
+
+ worldName = "world"
+ worldName = properties.setdefault("level-name", worldName)
+
+ tempWorldDir = os.path.join(tempDir, worldName)
+ tempWorld = infiniteworld.MCInfdevOldLevel(tempWorldDir, create=True, random_seed=level.RandomSeed)
+ tempWorld.close()
+
+ tempWorldRO = infiniteworld.MCInfdevOldLevel(tempWorldDir, readonly=True)
+
+ self.tempWorldCache[self.serverVersion, level.RandomSeed] = tempWorldRO
+
+ if level.dimNo == 0:
+ properties["allow-nether"] = "false"
+ else:
+ tempWorld = tempWorld.getDimension(level.dimNo)
+
+ properties["allow-nether"] = "true"
+
+ properties["server-port"] = int(32767 + random.random() * 32700)
+ saveProperties(propsFile, properties)
+
+ return tempWorld, tempDir
+
+ def generateAtPosition(self, tempWorld, tempDir, cx, cz):
+ return exhaust(self.generateAtPositionIter(tempWorld, tempDir, cx, cz))
+
+ def generateAtPositionIter(self, tempWorld, tempDir, cx, cz, simulate=False):
+ tempWorldRW = infiniteworld.MCInfdevOldLevel(tempWorld.filename)
+ tempWorldRW.setPlayerSpawnPosition((cx * 16, 64, cz * 16))
+ tempWorldRW.saveInPlace()
+ tempWorldRW.close()
+ del tempWorldRW
+
+ tempWorld.unload()
+
+ startTime = time.time()
+ proc = self.runServer(tempDir)
+ while proc.poll() is None:
+ line = proc.stderr.readline().strip()
+ log.info(line)
+ yield line
+
+# Forge and FML change stderr output, causing MCServerChunkGenerator to wait endlessly.
+#
+# Vanilla:
+# 2012-11-13 11:29:19 [INFO] Done (9.962s)!
+#
+# Forge/FML:
+# 2012-11-13 11:47:13 [INFO] [Minecraft] Done (8.020s)!
+
+ if "[INFO]" in line and "Done" in line:
+ if simulate:
+ duration = time.time() - startTime
+
+ simSeconds = max(8, int(duration) + 1)
+
+ for i in range(simSeconds):
+ # process tile ticks
+ yield "%2d/%2d: Simulating the world for a little bit..." % (i, simSeconds)
+ time.sleep(1)
+
+ proc.stdin.write("stop\n")
+ proc.wait()
+ break
+ if "FAILED TO BIND" in line:
+ proc.kill()
+ proc.wait()
+ raise RuntimeError("Server failed to bind to port!")
+
+ stdout, _ = proc.communicate()
+
+ if "Could not reserve enough space" in stdout and not MCServerChunkGenerator.lowMemory:
+ MCServerChunkGenerator.lowMemory = True
+ for i in self.generateAtPositionIter(tempWorld, tempDir, cx, cz):
+ yield i
+
+ (tempWorld.parentWorld or tempWorld).loadLevelDat() # reload version number
+
+ def copyChunkAtPosition(self, tempWorld, level, cx, cz):
+ if level.containsChunk(cx, cz):
+ return
+ try:
+ tempChunkBytes = tempWorld._getChunkBytes(cx, cz)
+ except ChunkNotPresent, e:
+ raise ChunkNotPresent, "While generating a world in {0} using server {1} ({2!r})".format(tempWorld, self.serverJarFile, e), sys.exc_info()[2]
+
+ level.worldFolder.saveChunk(cx, cz, tempChunkBytes)
+ level._allChunks = None
+
+ def generateChunkInLevel(self, level, cx, cz):
+ assert isinstance(level, infiniteworld.MCInfdevOldLevel)
+
+ tempWorld, tempDir = self.tempWorldForLevel(level)
+ self.generateAtPosition(tempWorld, tempDir, cx, cz)
+ self.copyChunkAtPosition(tempWorld, level, cx, cz)
+
+ minRadius = 5
+ maxRadius = 20
+
+ def createLevel(self, level, box, simulate=False, **kw):
+ return exhaust(self.createLevelIter(level, box, simulate, **kw))
+
+ def createLevelIter(self, level, box, simulate=False, **kw):
+ if isinstance(level, basestring):
+ filename = level
+ level = infiniteworld.MCInfdevOldLevel(filename, create=True, **kw)
+
+ assert isinstance(level, infiniteworld.MCInfdevOldLevel)
+ minRadius = self.minRadius
+
+ genPositions = list(itertools.product(
+ xrange(box.mincx, box.maxcx, minRadius * 2),
+ xrange(box.mincz, box.maxcz, minRadius * 2)))
+
+ for i, (cx, cz) in enumerate(genPositions):
+ log.info("Generating at %s" % ((cx, cz),))
+ parentDir = dirname(os.path.abspath(level.worldFolder.filename))
+ propsFile = join(parentDir, "server.properties")
+ props = readProperties(join(dirname(self.serverJarFile), "server.properties"))
+ props["level-name"] = basename(level.worldFolder.filename)
+ props["server-port"] = int(32767 + random.random() * 32700)
+ saveProperties(propsFile, props)
+
+ for p in self.generateAtPositionIter(level, parentDir, cx, cz, simulate):
+ yield i, len(genPositions), p
+
+ level.close()
+
+ def generateChunksInLevel(self, level, chunks):
+ return exhaust(self.generateChunksInLevelIter(level, chunks))
+
+ def generateChunksInLevelIter(self, level, chunks, simulate=False):
+ tempWorld, tempDir = self.tempWorldForLevel(level)
+
+ startLength = len(chunks)
+ minRadius = self.minRadius
+ maxRadius = self.maxRadius
+ chunks = set(chunks)
+
+ while len(chunks):
+ length = len(chunks)
+ centercx, centercz = chunks.pop()
+ chunks.add((centercx, centercz))
+ # assume the generator always generates at least an 11x11 chunk square.
+ centercx += minRadius
+ centercz += minRadius
+
+ # boxedChunks = [cPos for cPos in chunks if inBox(cPos)]
+
+ print "Generating {0} chunks out of {1} starting from {2}".format("XXX", len(chunks), (centercx, centercz))
+ yield startLength - len(chunks), startLength
+
+ # chunks = [c for c in chunks if not inBox(c)]
+
+ for p in self.generateAtPositionIter(tempWorld, tempDir, centercx, centercz, simulate):
+ yield startLength - len(chunks), startLength, p
+
+ i = 0
+ for cx, cz in itertools.product(
+ xrange(centercx - maxRadius, centercx + maxRadius),
+ xrange(centercz - maxRadius, centercz + maxRadius)):
+ if level.containsChunk(cx, cz):
+ chunks.discard((cx, cz))
+ elif ((cx, cz) in chunks
+ and all(tempWorld.containsChunk(ncx, ncz) for ncx, ncz in itertools.product(xrange(cx-1, cx+2), xrange(cz-1, cz+2)))
+ ):
+ self.copyChunkAtPosition(tempWorld, level, cx, cz)
+ i += 1
+ chunks.discard((cx, cz))
+ yield startLength - len(chunks), startLength
+
+ if length == len(chunks):
+ print "No chunks were generated. Aborting."
+ break
+
+ level.saveInPlace()
+
+ def runServer(self, startingDir):
+ if isinstance(startingDir, unicode):
+ startingDir = startingDir.encode(sys.getfilesystemencoding())
+
+ return self._runServer(startingDir, self.serverJarFile)
+
+ lowMemory = False
+
+ @classmethod
+ def _runServer(cls, startingDir, jarfile):
+ log.info("Starting server %s in %s", jarfile, startingDir)
+ if cls.lowMemory:
+ memflags = []
+ else:
+ memflags = ["-Xmx1024M", "-Xms1024M", ]
+
+ proc = subprocess.Popen([cls.javaExe, "-Djava.awt.headless=true"] + memflags + ["-jar", jarfile],
+ executable=cls.javaExe,
+ cwd=startingDir,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ return proc
+
+ def _serverVersion(self):
+ return self._serverVersionFromJarFile(self.serverJarFile)
+
+ @classmethod
+ def _serverVersionFromJarFile(cls, jarfile):
+ tempdir = tempfile.mkdtemp("mclevel_servergen")
+ proc = cls._runServer(tempdir, jarfile)
+
+ version = "Unknown"
+ # out, err = proc.communicate()
+ # for line in err.split("\n"):
+
+ while proc.poll() is None:
+ line = proc.stderr.readline()
+ if "Preparing start region" in line:
+ break
+ if "Starting minecraft server version" in line:
+ version = line.split("Starting minecraft server version")[1].strip()
+ break
+
+ if proc.returncode is None:
+ try:
+ proc.kill()
+ except WindowsError:
+ pass # access denied, process already terminated
+
+ proc.wait()
+ shutil.rmtree(tempdir)
+ if ";)" in version:
+ version = version.replace(";)", "") # Damnit, Jeb!
+ # Versions like "0.2.1" are alphas, and versions like "1.0.0" without "Beta" are releases
+ if version[0] == "0":
+ version = "Alpha " + version
+ try:
+ if int(version[0]) > 0:
+ version = "Release " + version
+ except ValueError:
+ pass
+
+ return version
--- /dev/null
+
+# vim:set sw=2 sts=2 ts=2:
+
+"""
+Named Binary Tag library. Serializes and deserializes TAG_* objects
+to and from binary data. Load a Minecraft level by calling nbt.load().
+Create your own TAG_* objects and set their values.
+Save a TAG_* object to a file or StringIO object.
+
+Read the test functions at the end of the file to get started.
+
+This library requires Numpy. Get it here:
+http://new.scipy.org/download.html
+
+Official NBT documentation is here:
+http://www.minecraft.net/docs/NBT.txt
+
+
+Copyright 2010 David Rio Vierra
+"""
+import collections
+import gzip
+import itertools
+import logging
+import struct
+import zlib
+from cStringIO import StringIO
+
+import numpy
+from numpy import array, zeros, fromstring
+
+
+log = logging.getLogger(__name__)
+
+
+class NBTFormatError(RuntimeError):
+ pass
+
+
+TAG_BYTE = 1
+TAG_SHORT = 2
+TAG_INT = 3
+TAG_LONG = 4
+TAG_FLOAT = 5
+TAG_DOUBLE = 6
+TAG_BYTE_ARRAY = 7
+TAG_STRING = 8
+TAG_LIST = 9
+TAG_COMPOUND = 10
+TAG_INT_ARRAY = 11
+TAG_SHORT_ARRAY = 12
+
+
+class TAG_Value(object):
+ """Simple values. Subclasses override fmt to change the type and size.
+ Subclasses may set data_type instead of overriding setValue for automatic data type coercion"""
+ __slots__ = ('_name', '_value')
+
+ def __init__(self, value=0, name=""):
+ self.value = value
+ self.name = name
+
+ fmt = struct.Struct("b")
+ tagID = NotImplemented
+ data_type = NotImplemented
+
+ _name = None
+ _value = None
+
+ @property
+ def value(self):
+ return self._value
+
+ @value.setter
+ def value(self, newVal):
+ """Change the TAG's value. Data types are checked and coerced if needed."""
+ self._value = self.data_type(newVal)
+
+ @property
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, newVal):
+ """Change the TAG's name. Coerced to a unicode."""
+ self._name = unicode(newVal)
+
+ @classmethod
+ def load_from(cls, ctx):
+ data = ctx.data[ctx.offset:]
+ (value,) = cls.fmt.unpack_from(data)
+ self = cls(value=value)
+ ctx.offset += self.fmt.size
+ return self
+
+ def __repr__(self):
+ return "<%s name=\"%s\" value=%r>" % (str(self.__class__.__name__), self.name, self.value)
+
+ def write_tag(self, buf):
+ buf.write(chr(self.tagID))
+
+ def write_name(self, buf):
+ if self.name is not None:
+ write_string(self.name, buf)
+
+ def write_value(self, buf):
+ buf.write(self.fmt.pack(self.value))
+
+
+class TAG_Byte(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_BYTE
+ fmt = struct.Struct(">b")
+ data_type = int
+
+
+class TAG_Short(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_SHORT
+ fmt = struct.Struct(">h")
+ data_type = int
+
+
+class TAG_Int(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_INT
+ fmt = struct.Struct(">i")
+ data_type = int
+
+
+class TAG_Long(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_LONG
+ fmt = struct.Struct(">q")
+ data_type = long
+
+
+class TAG_Float(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_FLOAT
+ fmt = struct.Struct(">f")
+ data_type = float
+
+
+class TAG_Double(TAG_Value):
+ __slots__ = ('_name', '_value')
+ tagID = TAG_DOUBLE
+ fmt = struct.Struct(">d")
+ data_type = float
+
+
+class TAG_Byte_Array(TAG_Value):
+ """Like a string, but for binary data. Four length bytes instead of
+ two. Value is a numpy array, and you can change its elements"""
+
+ tagID = TAG_BYTE_ARRAY
+
+ def __init__(self, value=None, name=""):
+ if value is None:
+ value = zeros(0, self.dtype)
+ self.name = name
+ self.value = value
+
+ def __repr__(self):
+ return "<%s name=%s length=%d>" % (self.__class__, self.name, len(self.value))
+
+ __slots__ = ('_name', '_value')
+
+ def data_type(self, value):
+ return array(value, self.dtype)
+
+ dtype = numpy.dtype('uint8')
+
+ @classmethod
+ def load_from(cls, ctx):
+ data = ctx.data[ctx.offset:]
+ (string_len,) = TAG_Int.fmt.unpack_from(data)
+ value = fromstring(data[4:string_len * cls.dtype.itemsize + 4], cls.dtype)
+ self = cls(value)
+ ctx.offset += string_len * cls.dtype.itemsize + 4
+ return self
+
+ def write_value(self, buf):
+ value_str = self.value.tostring()
+ buf.write(struct.pack(">I%ds" % (len(value_str),), self.value.size, value_str))
+
+
+class TAG_Int_Array(TAG_Byte_Array):
+ """An array of big-endian 32-bit integers"""
+ tagID = TAG_INT_ARRAY
+ __slots__ = ('_name', '_value')
+ dtype = numpy.dtype('>u4')
+
+
+
+class TAG_Short_Array(TAG_Int_Array):
+ """An array of big-endian 16-bit integers. Not official, but used by some mods."""
+ tagID = TAG_SHORT_ARRAY
+ __slots__ = ('_name', '_value')
+ dtype = numpy.dtype('>u2')
+
+
+class TAG_String(TAG_Value):
+ """String in UTF-8
+ The value parameter must be a 'unicode' or a UTF-8 encoded 'str'
+ """
+
+ tagID = TAG_STRING
+
+ def __init__(self, value="", name=""):
+ if name:
+ self.name = name
+ self.value = value
+
+ _decodeCache = {}
+
+ __slots__ = ('_name', '_value')
+
+ def data_type(self, value):
+ if isinstance(value, unicode):
+ return value
+ else:
+ decoded = self._decodeCache.get(value)
+ if decoded is None:
+ decoded = value.decode('utf-8')
+ self._decodeCache[value] = decoded
+
+ return decoded
+
+
+ @classmethod
+ def load_from(cls, ctx):
+ value = load_string(ctx)
+ return cls(value)
+
+ def write_value(self, buf):
+ write_string(self._value, buf)
+
+string_len_fmt = struct.Struct(">H")
+
+
+def load_string(ctx):
+ data = ctx.data[ctx.offset:]
+ (string_len,) = string_len_fmt.unpack_from(data)
+
+ value = data[2:string_len + 2].tostring()
+ ctx.offset += string_len + 2
+ return value
+
+
+def write_string(string, buf):
+ encoded = string.encode('utf-8')
+ buf.write(struct.pack(">h%ds" % (len(encoded),), len(encoded), encoded))
+
+#noinspection PyMissingConstructor
+
+
+class TAG_Compound(TAG_Value, collections.MutableMapping):
+ """A heterogenous list of named tags. Names must be unique within
+ the TAG_Compound. Add tags to the compound using the subscript
+ operator []. This will automatically name the tags."""
+
+ tagID = TAG_COMPOUND
+
+ ALLOW_DUPLICATE_KEYS = False
+
+ __slots__ = ('_name', '_value')
+
+ def __init__(self, value=None, name=""):
+ self.value = value or []
+ self.name = name
+
+ def __repr__(self):
+ return "<%s name='%s' keys=%r>" % (str(self.__class__.__name__), self.name, self.keys())
+
+ def data_type(self, val):
+ for i in val:
+ self.check_value(i)
+ return list(val)
+
+ def check_value(self, val):
+ if not isinstance(val, TAG_Value):
+ raise TypeError("Invalid type for TAG_Compound element: %s" % val.__class__.__name__)
+ if not val.name:
+ raise ValueError("Tag needs a name to be inserted into TAG_Compound: %s" % val)
+
+ @classmethod
+ def load_from(cls, ctx):
+ self = cls()
+ while ctx.offset < len(ctx.data):
+ tag_type = ctx.data[ctx.offset]
+ ctx.offset += 1
+
+ if tag_type == 0:
+ break
+
+ tag_name = load_string(ctx)
+ tag = tag_classes[tag_type].load_from(ctx)
+ tag.name = tag_name
+
+ self._value.append(tag)
+
+ return self
+
+ def save(self, filename_or_buf=None, compressed=True):
+ """
+ Save the TAG_Compound element to a file. Since this element is the root tag, it can be named.
+
+ Pass a filename to save the data to a file. Pass a file-like object (with a read() method)
+ to write the data to that object. Pass nothing to return the data as a string.
+ """
+ if self.name is None:
+ self.name = ""
+
+ buf = StringIO()
+ self.write_tag(buf)
+ self.write_name(buf)
+ self.write_value(buf)
+ data = buf.getvalue()
+
+ if compressed:
+ gzio = StringIO()
+ gz = gzip.GzipFile(fileobj=gzio, mode='wb')
+ gz.write(data)
+ gz.close()
+ data = gzio.getvalue()
+
+ if filename_or_buf is None:
+ return data
+
+ if isinstance(filename_or_buf, basestring):
+ f = file(filename_or_buf, "wb")
+ f.write(data)
+ else:
+ filename_or_buf.write(data)
+
+ def write_value(self, buf):
+ for tag in self.value:
+ tag.write_tag(buf)
+ tag.write_name(buf)
+ tag.write_value(buf)
+
+ buf.write("\x00")
+
+ # --- collection functions ---
+
+ def __getitem__(self, key):
+ # hits=filter(lambda x: x.name==key, self.value)
+ # if(len(hits)): return hits[0]
+ for tag in self.value:
+ if tag.name == key:
+ return tag
+ raise KeyError("Key {0} not found in tag {1}".format(key, self))
+
+ def __iter__(self):
+ return itertools.imap(lambda x: x.name, self.value)
+
+ def __contains__(self, key):
+ return key in map(lambda x: x.name, self.value)
+
+ def __len__(self):
+ return self.value.__len__()
+
+ def __setitem__(self, key, item):
+ """Automatically wraps lists and tuples in a TAG_List, and wraps strings
+ and unicodes in a TAG_String."""
+ if isinstance(item, (list, tuple)):
+ item = TAG_List(item)
+ elif isinstance(item, basestring):
+ item = TAG_String(item)
+
+ item.name = key
+ self.check_value(item)
+
+ # remove any items already named "key".
+ if not self.ALLOW_DUPLICATE_KEYS:
+ self._value = filter(lambda x: x.name != key, self._value)
+
+ self._value.append(item)
+
+ def __delitem__(self, key):
+ self.value.__delitem__(self.value.index(self[key]))
+
+ def add(self, value):
+ if value.name is None:
+ raise ValueError("Tag %r must have a name." % value)
+
+ self[value.name] = value
+
+ def get_all(self, key):
+ return [v for v in self._value if v.name == key]
+
+class TAG_List(TAG_Value, collections.MutableSequence):
+ """A homogenous list of unnamed data of a single TAG_* type.
+ Once created, the type can only be changed by emptying the list
+ and adding an element of the new type. If created with no arguments,
+ returns a list of TAG_Compound
+
+ Empty lists in the wild have been seen with type TAG_Byte"""
+
+ tagID = 9
+
+ def __init__(self, value=None, name="", list_type=TAG_BYTE):
+ # can be created from a list of tags in value, with an optional
+ # name, or created from raw tag data, or created with list_type
+ # taken from a TAG class or instance
+
+ self.name = name
+ self.list_type = list_type
+ self.value = value or []
+
+ __slots__ = ('_name', '_value')
+
+
+ def __repr__(self):
+ return "<%s name='%s' list_type=%r length=%d>" % (self.__class__.__name__, self.name,
+ tag_classes[self.list_type],
+ len(self))
+
+ def data_type(self, val):
+ if val:
+ self.list_type = val[0].tagID
+ assert all([x.tagID == self.list_type for x in val])
+ return list(val)
+
+
+
+ @classmethod
+ def load_from(cls, ctx):
+ self = cls()
+ self.list_type = ctx.data[ctx.offset]
+ ctx.offset += 1
+
+ (list_length,) = TAG_Int.fmt.unpack_from(ctx.data, ctx.offset)
+ ctx.offset += TAG_Int.fmt.size
+
+ for i in range(list_length):
+ tag = tag_classes[self.list_type].load_from(ctx)
+ self.append(tag)
+
+ return self
+
+
+ def write_value(self, buf):
+ buf.write(chr(self.list_type))
+ buf.write(TAG_Int.fmt.pack(len(self.value)))
+ for i in self.value:
+ i.write_value(buf)
+
+ def check_tag(self, value):
+ if value.tagID != self.list_type:
+ raise TypeError("Invalid type %s for TAG_List(%s)" % (value.__class__, tag_classes[self.list_type]))
+
+ # --- collection methods ---
+
+ def __iter__(self):
+ return iter(self.value)
+
+ def __contains__(self, tag):
+ return tag in self.value
+
+ def __getitem__(self, index):
+ return self.value[index]
+
+ def __len__(self):
+ return len(self.value)
+
+ def __setitem__(self, index, value):
+ if isinstance(index, slice):
+ for tag in value:
+ self.check_tag(tag)
+ else:
+ self.check_tag(value)
+
+ self.value[index] = value
+
+ def __delitem__(self, index):
+ del self.value[index]
+
+ def insert(self, index, value):
+ if len(self) == 0:
+ self.list_type = value.tagID
+ else:
+ self.check_tag(value)
+
+ value.name = ""
+ self.value.insert(index, value)
+
+
+tag_classes = { c.tagID: c for c in (TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_String,
+ TAG_Byte_Array, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Short_Array) }
+
+
+
+def gunzip(data):
+ return gzip.GzipFile(fileobj=StringIO(data)).read()
+
+
+def try_gunzip(data):
+ try:
+ data = gunzip(data)
+ except IOError, zlib.error:
+ pass
+ return data
+
+
+def load(filename="", buf=None):
+ """
+ Unserialize data from an NBT file and return the root TAG_Compound object. If filename is passed,
+ reads from the file, otherwise uses data from buf. Buf can be a buffer object with a read() method or a string
+ containing NBT data.
+ """
+ if filename:
+ buf = file(filename, "rb")
+
+ if hasattr(buf, "read"):
+ buf = buf.read()
+
+ return _load_buffer(try_gunzip(buf))
+
+class load_ctx(object):
+ pass
+
+def _load_buffer(buf):
+ if isinstance(buf, str):
+ buf = fromstring(buf, 'uint8')
+ data = buf
+
+ if not len(data):
+ raise NBTFormatError("Asked to load root tag of zero length")
+
+ tag_type = data[0]
+ if tag_type != 10:
+ magic = data[:4]
+ raise NBTFormatError('Not an NBT file with a root TAG_Compound '
+ '(file starts with "%s" (0x%08x)' % (magic.tostring(), magic.view(dtype='uint32')))
+
+ ctx = load_ctx()
+ ctx.offset = 1
+ ctx.data = data
+
+ tag_name = load_string(ctx)
+ tag = TAG_Compound.load_from(ctx)
+ tag.name = tag_name
+
+ return tag
+
+
+__all__ = [a.__name__ for a in tag_classes.itervalues()] + ["load", "gunzip"]
+
+import nbt_util
+
+TAG_Value.__str__ = nbt_util.nested_string
+
+try:
+ #noinspection PyUnresolvedReferences
+ from _nbt import (load, TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_String,
+ TAG_Byte_Array, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Short_Array, NBTFormatError)
+except ImportError:
+ pass
+
--- /dev/null
+import nbt
+
+def nested_string(tag, indent_string=" ", indent=0):
+ result = ""
+
+ if tag.tagID == nbt.TAG_COMPOUND:
+ result += 'TAG_Compound({\n'
+ indent += 1
+ for key, value in tag.iteritems():
+ result += indent_string * indent + '"%s": %s,\n' % (key, nested_string(value, indent_string, indent))
+ indent -= 1
+ result += indent_string * indent + '})'
+
+ elif tag.tagID == nbt.TAG_LIST:
+ result += 'TAG_List([\n'
+ indent += 1
+ for index, value in enumerate(tag):
+ result += indent_string * indent + nested_string(value, indent_string, indent) + ",\n"
+ indent -= 1
+ result += indent_string * indent + '])'
+
+ else:
+ result += "%s(%r)" % (tag.__class__.__name__, tag.value)
+
+ return result
+
+
--- /dev/null
+from level import FakeChunk
+import logging
+from materials import pocketMaterials
+from mclevelbase import ChunkNotPresent, notclosing
+from nbt import TAG_List
+from numpy import array, fromstring, zeros
+import os
+import struct
+
+# values are usually little-endian, unlike Minecraft PC
+
+logger = logging.getLogger(__file__)
+
+
+class PocketChunksFile(object):
+ holdFileOpen = False # if False, reopens and recloses the file on each access
+ SECTOR_BYTES = 4096
+ CHUNK_HEADER_SIZE = 4
+
+ @property
+ def file(self):
+ openfile = lambda: file(self.path, "rb+")
+ if PocketChunksFile.holdFileOpen:
+ if self._file is None:
+ self._file = openfile()
+ return notclosing(self._file)
+ else:
+ return openfile()
+
+ def close(self):
+ if PocketChunksFile.holdFileOpen:
+ self._file.close()
+ self._file = None
+
+ def __init__(self, path):
+ self.path = path
+ self._file = None
+ if not os.path.exists(path):
+ file(path, "w").close()
+
+ with self.file as f:
+
+ filesize = os.path.getsize(path)
+ if filesize & 0xfff:
+ filesize = (filesize | 0xfff) + 1
+ f.truncate(filesize)
+
+ if filesize == 0:
+ filesize = self.SECTOR_BYTES
+ f.truncate(filesize)
+
+ f.seek(0)
+ offsetsData = f.read(self.SECTOR_BYTES)
+
+ self.freeSectors = [True] * (filesize / self.SECTOR_BYTES)
+ self.freeSectors[0] = False
+
+ self.offsets = fromstring(offsetsData, dtype='<u4')
+
+ needsRepair = False
+
+ for index, offset in enumerate(self.offsets):
+ sector = offset >> 8
+ count = offset & 0xff
+
+ for i in xrange(sector, sector + count):
+ if i >= len(self.freeSectors):
+ # raise RegionMalformed("Region file offset table points to sector {0} (past the end of the file)".format(i))
+ print "Region file offset table points to sector {0} (past the end of the file)".format(i)
+ needsRepair = True
+ break
+ if self.freeSectors[i] is False:
+ logger.debug("Double-allocated sector number %s (offset %s @ %s)", i, offset, index)
+ needsRepair = True
+ self.freeSectors[i] = False
+
+ if needsRepair:
+ self.repair()
+
+ logger.info("Found region file {file} with {used}/{total} sectors used and {chunks} chunks present".format(
+ file=os.path.basename(path), used=self.usedSectors, total=self.sectorCount, chunks=self.chunkCount))
+
+ @property
+ def usedSectors(self):
+ return len(self.freeSectors) - sum(self.freeSectors)
+
+ @property
+ def sectorCount(self):
+ return len(self.freeSectors)
+
+ @property
+ def chunkCount(self):
+ return sum(self.offsets > 0)
+
+ def repair(self):
+ pass
+# lostAndFound = {}
+# _freeSectors = [True] * len(self.freeSectors)
+# _freeSectors[0] = _freeSectors[1] = False
+# deleted = 0
+# recovered = 0
+# logger.info("Beginning repairs on {file} ({chunks} chunks)".format(file=os.path.basename(self.path), chunks=sum(self.offsets > 0)))
+# rx, rz = self.regionCoords
+# for index, offset in enumerate(self.offsets):
+# if offset:
+# cx = index & 0x1f
+# cz = index >> 5
+# cx += rx << 5
+# cz += rz << 5
+# sectorStart = offset >> 8
+# sectorCount = offset & 0xff
+# try:
+#
+# if sectorStart + sectorCount > len(self.freeSectors):
+# raise RegionMalformed("Offset {start}:{end} ({offset}) at index {index} pointed outside of the file".format()
+# start=sectorStart, end=sectorStart + sectorCount, index=index, offset=offset)
+#
+# compressedData = self._readChunk(cx, cz)
+# if compressedData is None:
+# raise RegionMalformed("Failed to read chunk data for {0}".format((cx, cz)))
+#
+# format, data = self.decompressSectors(compressedData)
+# chunkTag = nbt.load(buf=data)
+# lev = chunkTag["Level"]
+# xPos = lev["xPos"].value
+# zPos = lev["zPos"].value
+# overlaps = False
+#
+# for i in xrange(sectorStart, sectorStart + sectorCount):
+# if _freeSectors[i] is False:
+# overlaps = True
+# _freeSectors[i] = False
+#
+#
+# if xPos != cx or zPos != cz or overlaps:
+# lostAndFound[xPos, zPos] = (format, compressedData)
+#
+# if (xPos, zPos) != (cx, cz):
+# raise RegionMalformed("Chunk {found} was found in the slot reserved for {expected}".format(found=(xPos, zPos), expected=(cx, cz)))
+# else:
+# raise RegionMalformed("Chunk {found} (in slot {expected}) has overlapping sectors with another chunk!".format(found=(xPos, zPos), expected=(cx, cz)))
+#
+#
+#
+# except Exception, e:
+# logger.info("Unexpected chunk data at sector {sector} ({exc})".format(sector=sectorStart, exc=e))
+# self.setOffset(cx, cz, 0)
+# deleted += 1
+#
+# for cPos, (format, foundData) in lostAndFound.iteritems():
+# cx, cz = cPos
+# if self.getOffset(cx, cz) == 0:
+# logger.info("Found chunk {found} and its slot is empty, recovering it".format(found=cPos))
+# self._saveChunk(cx, cz, foundData[5:], format)
+# recovered += 1
+#
+# logger.info("Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}".format(deleted, recovered, recovered - deleted))
+#
+
+
+ def _readChunk(self, cx, cz):
+ cx &= 0x1f
+ cz &= 0x1f
+ offset = self.getOffset(cx, cz)
+ if offset == 0:
+ return None
+
+ sectorStart = offset >> 8
+ numSectors = offset & 0xff
+ if numSectors == 0:
+ return None
+
+ if sectorStart + numSectors > len(self.freeSectors):
+ return None
+
+ with self.file as f:
+ f.seek(sectorStart * self.SECTOR_BYTES)
+ data = f.read(numSectors * self.SECTOR_BYTES)
+ assert(len(data) > 0)
+ logger.debug("REGION LOAD %s,%s sector %s", cx, cz, sectorStart)
+ return data
+
+ def loadChunk(self, cx, cz, world):
+ data = self._readChunk(cx, cz)
+ if data is None:
+ raise ChunkNotPresent((cx, cz, self))
+
+ chunk = PocketChunk(cx, cz, data[4:], world)
+ return chunk
+
+ def saveChunk(self, chunk):
+ cx, cz = chunk.chunkPosition
+
+ cx &= 0x1f
+ cz &= 0x1f
+ offset = self.getOffset(cx, cz)
+ sectorNumber = offset >> 8
+ sectorsAllocated = offset & 0xff
+
+ data = chunk._savedData()
+
+ sectorsNeeded = (len(data) + self.CHUNK_HEADER_SIZE) / self.SECTOR_BYTES + 1
+ if sectorsNeeded >= 256:
+ return
+
+ if sectorNumber != 0 and sectorsAllocated >= sectorsNeeded:
+ logger.debug("REGION SAVE {0},{1} rewriting {2}b".format(cx, cz, len(data)))
+ self.writeSector(sectorNumber, data, format)
+ else:
+ # we need to allocate new sectors
+
+ # mark the sectors previously used for this chunk as free
+ for i in xrange(sectorNumber, sectorNumber + sectorsAllocated):
+ self.freeSectors[i] = True
+
+ runLength = 0
+ try:
+ runStart = self.freeSectors.index(True)
+
+ for i in range(runStart, len(self.freeSectors)):
+ if runLength:
+ if self.freeSectors[i]:
+ runLength += 1
+ else:
+ runLength = 0
+ elif self.freeSectors[i]:
+ runStart = i
+ runLength = 1
+
+ if runLength >= sectorsNeeded:
+ break
+ except ValueError:
+ pass
+
+ # we found a free space large enough
+ if runLength >= sectorsNeeded:
+ logger.debug("REGION SAVE {0},{1}, reusing {2}b".format(cx, cz, len(data)))
+ sectorNumber = runStart
+ self.setOffset(cx, cz, sectorNumber << 8 | sectorsNeeded)
+ self.writeSector(sectorNumber, data, format)
+ self.freeSectors[sectorNumber:sectorNumber + sectorsNeeded] = [False] * sectorsNeeded
+
+ else:
+ # no free space large enough found -- we need to grow the
+ # file
+
+ logger.debug("REGION SAVE {0},{1}, growing by {2}b".format(cx, cz, len(data)))
+
+ with self.file as f:
+ f.seek(0, 2)
+ filesize = f.tell()
+
+ sectorNumber = len(self.freeSectors)
+
+ assert sectorNumber * self.SECTOR_BYTES == filesize
+
+ filesize += sectorsNeeded * self.SECTOR_BYTES
+ f.truncate(filesize)
+
+ self.freeSectors += [False] * sectorsNeeded
+
+ self.setOffset(cx, cz, sectorNumber << 8 | sectorsNeeded)
+ self.writeSector(sectorNumber, data, format)
+
+ def writeSector(self, sectorNumber, data, format):
+ with self.file as f:
+ logger.debug("REGION: Writing sector {0}".format(sectorNumber))
+
+ f.seek(sectorNumber * self.SECTOR_BYTES)
+ f.write(struct.pack("<I", len(data) + self.CHUNK_HEADER_SIZE)) # // chunk length
+ f.write(data) # // chunk data
+ # f.flush()
+
+ def containsChunk(self, cx, cz):
+ return self.getOffset(cx, cz) != 0
+
+ def getOffset(self, cx, cz):
+ cx &= 0x1f
+ cz &= 0x1f
+ return self.offsets[cx + cz * 32]
+
+ def setOffset(self, cx, cz, offset):
+ cx &= 0x1f
+ cz &= 0x1f
+ self.offsets[cx + cz * 32] = offset
+ with self.file as f:
+ f.seek(0)
+ f.write(self.offsets.tostring())
+
+ def chunkCoords(self):
+ indexes = (i for (i, offset) in enumerate(self.offsets) if offset)
+ coords = ((i % 32, i // 32) for i in indexes)
+ return coords
+
+from infiniteworld import ChunkedLevelMixin
+from level import MCLevel, LightedChunk
+
+
+class PocketWorld(ChunkedLevelMixin, MCLevel):
+ Height = 128
+ Length = 512
+ Width = 512
+
+ isInfinite = True # Wrong. isInfinite actually means 'isChunked' and should be changed
+ materials = pocketMaterials
+
+ @property
+ def allChunks(self):
+ return list(self.chunkFile.chunkCoords())
+
+ def __init__(self, filename):
+ if not os.path.isdir(filename):
+ filename = os.path.dirname(filename)
+ self.filename = filename
+ self.dimensions = {}
+
+ self.chunkFile = PocketChunksFile(os.path.join(filename, "chunks.dat"))
+ self._loadedChunks = {}
+
+ def getChunk(self, cx, cz):
+ for p in cx, cz:
+ if not 0 <= p <= 31:
+ raise ChunkNotPresent((cx, cz, self))
+
+ c = self._loadedChunks.get((cx, cz))
+ if c is None:
+ c = self.chunkFile.loadChunk(cx, cz, self)
+ self._loadedChunks[cx, cz] = c
+ return c
+
+ @classmethod
+ def _isLevel(cls, filename):
+ clp = ("chunks.dat", "level.dat")
+
+ if not os.path.isdir(filename):
+ f = os.path.basename(filename)
+ if f not in clp:
+ return False
+ filename = os.path.dirname(filename)
+
+ return all([os.path.exists(os.path.join(filename, f)) for f in clp])
+
+ def saveInPlace(self):
+ for chunk in self._loadedChunks.itervalues():
+ if chunk.dirty:
+ self.chunkFile.saveChunk(chunk)
+ chunk.dirty = False
+
+ def containsChunk(self, cx, cz):
+ if cx > 31 or cz > 31 or cx < 0 or cz < 0:
+ return False
+ return self.chunkFile.getOffset(cx, cz) != 0
+
+ @property
+ def chunksNeedingLighting(self):
+ for chunk in self._loadedChunks.itervalues():
+ if chunk.needsLighting:
+ yield chunk.chunkPosition
+
+class PocketChunk(LightedChunk):
+ HeightMap = FakeChunk.HeightMap
+
+ Entities = TileEntities = property(lambda self: TAG_List())
+
+ dirty = False
+ filename = "chunks.dat"
+
+ def __init__(self, cx, cz, data, world):
+ self.chunkPosition = (cx, cz)
+ self.world = world
+ data = fromstring(data, dtype='uint8')
+
+ self.Blocks, data = data[:32768], data[32768:]
+ self.Data, data = data[:16384], data[16384:]
+ self.SkyLight, data = data[:16384], data[16384:]
+ self.BlockLight, data = data[:16384], data[16384:]
+ self.DirtyColumns = data[:256]
+
+ self.unpackChunkData()
+ self.shapeChunkData()
+
+
+ def unpackChunkData(self):
+ for key in ('SkyLight', 'BlockLight', 'Data'):
+ dataArray = getattr(self, key)
+ dataArray.shape = (16, 16, 64)
+ s = dataArray.shape
+ # assert s[2] == self.world.Height / 2
+ # unpackedData = insert(dataArray[...,newaxis], 0, 0, 3)
+
+ unpackedData = zeros((s[0], s[1], s[2] * 2), dtype='uint8')
+
+ unpackedData[:, :, ::2] = dataArray
+ unpackedData[:, :, ::2] &= 0xf
+ unpackedData[:, :, 1::2] = dataArray
+ unpackedData[:, :, 1::2] >>= 4
+ setattr(self, key, unpackedData)
+
+ def shapeChunkData(self):
+ chunkSize = 16
+ self.Blocks.shape = (chunkSize, chunkSize, self.world.Height)
+ self.SkyLight.shape = (chunkSize, chunkSize, self.world.Height)
+ self.BlockLight.shape = (chunkSize, chunkSize, self.world.Height)
+ self.Data.shape = (chunkSize, chunkSize, self.world.Height)
+ self.DirtyColumns.shape = chunkSize, chunkSize
+
+ def _savedData(self):
+ def packData(dataArray):
+ assert dataArray.shape[2] == self.world.Height
+
+ data = array(dataArray).reshape(16, 16, self.world.Height / 2, 2)
+ data[..., 1] <<= 4
+ data[..., 1] |= data[..., 0]
+ return array(data[:, :, :, 1])
+
+ if self.dirty:
+ # elements of DirtyColumns are bitfields. Each bit corresponds to a
+ # 16-block segment of the column. We set all of the bits because
+ # we only track modifications at the chunk level.
+ self.DirtyColumns[:] = 255
+
+ return "".join([self.Blocks.tostring(),
+ packData(self.Data).tostring(),
+ packData(self.SkyLight).tostring(),
+ packData(self.BlockLight).tostring(),
+ self.DirtyColumns.tostring(),
+ ])
--- /dev/null
+#
+# YAML Definition for Minecraft Pocket Edition. See minecraft.yaml for details.
+#
+
+name: Minecraft Pocket Edition
+
+defaults:
+ name: Future Block!
+ mapcolor: [201, 119, 240]
+ tex: [11, 8]
+ brightness: 0
+ opacity: 15
+
+
+blocks:
+
+ - id: 1
+ idStr: STONE
+ name: Stone
+ mapcolor: [120, 120, 120]
+ tex: [1, 0]
+
+ - id: 2
+ idStr: GRASS
+ name: Grass
+ mapcolor: [117, 176, 73]
+ tex: [0, 0]
+ tex_direction:
+ FORWARD: [3, 0]
+ SIDES: [3, 0]
+ BACKWARD: [3, 0]
+ BOTTOM: [2, 0]
+
+ - id: 3
+ idStr: DIRT
+ name: Dirt
+ mapcolor: [134, 96, 67]
+ tex: [2, 0]
+
+ - id: 4
+ idStr: COBBLESTONE
+ name: Cobblestone
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+
+ - id: 5
+ idStr: PLANK
+ name: Wood Planks
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+
+ - id: 6
+ idStr: SAPLING
+ name: Sapling
+ mapcolor: [120, 120, 120]
+ tex: [15, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+ data:
+ 0:
+ name: Sapling
+ tex: [15, 0]
+ 1:
+ name: Spruce Sapling
+ tex: [15, 3]
+ 2:
+ name: Birch Sapling
+ tex: [15, 4]
+
+ - id: 7
+ idStr: BEDROCK
+ name: Bedrock
+ aka: Adminium
+ mapcolor: [84, 84, 84]
+ tex: [1, 1]
+
+ - id: 8
+ idStr: WATER
+ name: Water (active)
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 9
+ idStr: STATIONARY_WATER
+ name: Water
+ mapcolor: [38, 92, 255]
+ type: WATER
+ tex: [15, 12]
+ opacity: 3
+
+ - id: 10
+ idStr: LAVA
+ name: Lava (active)
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+ brightness: 15
+
+ - id: 11
+ idStr: STATIONARY_LAVA
+ name: Lava
+ mapcolor: [255, 90, 0]
+ tex: [15, 14]
+ type: SEMISOLID
+ brightness: 15
+
+ - id: 12
+ idStr: SAND
+ name: Sand
+ mapcolor: [218, 210, 158]
+ tex: [2, 1]
+
+ - id: 13
+ idStr: GRAVEL
+ name: Gravel
+ mapcolor: [136, 126, 126]
+ tex: [3, 1]
+
+ - id: 14
+ idStr: GOLD_ORE
+ name: Gold Ore
+ mapcolor: [143, 140, 125]
+ tex: [0, 2]
+
+ - id: 15
+ idStr: IRON_ORE
+ name: Iron Ore
+ mapcolor: [136, 130, 127]
+ tex: [1, 2]
+
+ - id: 16
+ idStr: COAL_ORE
+ name: Coal Ore
+ mapcolor: [115, 115, 115]
+ tex: [2, 2]
+
+ - id: 17
+ idStr: WOOD
+ name: Wood
+ mapcolor: [102, 81, 51]
+ tex: [4, 1]
+ tex_direction:
+ TOP: [5, 1]
+ BOTTOM: [5, 1]
+ data:
+ 0:
+ name: Wood
+ tex: [4, 1]
+ 1:
+ name: Pine Wood
+ aka: Spruce Wood, Redwood
+ tex: [4, 7]
+ 2:
+ name: Birch Wood
+ tex: [5, 7]
+
+ - id: 18
+ idStr: LEAVES
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3] # The "correct" one is actually [4, 3] but with the current
+ # transparency rendering issues, this one looks better.
+ opacity: 1
+ data:
+ 0:
+ name: Leaves
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+ 1:
+ name: Pine Leaves
+ aka: Spruce Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 8]
+ 2:
+ name: Birch Leaves
+ mapcolor: [89, 151, 76]
+ tex: [5, 3]
+
+ 4: # bit 0x4 - "Decaying"
+ name: Leaves (Decaying)
+ mapcolor: [60, 192, 41]
+ tex: [5, 3]
+ 5:
+ name: Pine Leaves (Decaying)
+ aka: Spruce Leaves
+ mapcolor: [74, 131, 66]
+ tex: [5, 8]
+ 6:
+ name: Birch Leaves (Decaying)
+ mapcolor: [89, 151, 76]
+ tex: [5, 3]
+
+ - id: 20
+ idStr: GLASS
+ name: Glass
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: GLASS
+ opacity: 0
+
+ - id: 21
+ idStr: LAPIS_LAZULI_ORE
+ name: Lapis Lazuli Ore
+ mapcolor: [27, 70, 161]
+ tex: [0, 10]
+
+ - id: 22
+ idStr: LAPIS_LAZULI_BLOCK
+ name: Lapis Lazuli Block
+ mapcolor: [0, 0, 0]
+ tex: [0, 9]
+
+ - id: 24
+ idStr: SANDSTONE
+ name: Sandstone
+ mapcolor: [218, 210, 158]
+ tex: [0, 12]
+ tex_direction:
+ TOP: [0, 11]
+ BOTTOM: [0, 13]
+
+ - id: 26
+ idStr: BED
+ name: Bed
+ mapcolor: [255, 0, 0]
+ tex: [7, 8]
+ type: BED
+ tex_extra:
+ foot_top: [6, 8]
+ head_side: [7, 9]
+ foot_side: [6, 9]
+ foot: [5, 9]
+ head: [8, 9]
+ opacity: 0
+
+ - id: 30
+ idStr: WEB
+ name: Web
+ mapcolor: [255, 255, 255]
+ tex: [11, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 31
+ idStr: TALL_GRASS
+ name: Tall Grass
+ mapcolor: [104, 156, 53]
+ tex: [7, 2]
+ data:
+ 0:
+ tex: [7, 3]
+ name: (Unused Shrub)
+ 1:
+ tex: [7, 2]
+ name: Tall Grass
+ 2:
+ tex: [8, 3]
+ name: Fern
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 35
+ idStr: WOOL
+ name: White Wool
+ mapcolor: [222, 222, 222]
+ tex: [0, 4]
+ data:
+ 0:
+ tex: [0, 4]
+ name: White Wool
+ mapcolor: [222, 222, 222]
+ 1:
+ tex: [2, 13]
+ name: Orange Wool
+ mapcolor: [234, 127, 55]
+ 2:
+ tex: [2, 12]
+ name: Magenta Wool
+ mapcolor: [191, 75, 201]
+ 3:
+ tex: [2, 11]
+ name: Light Blue Wool
+ aka: Aqua Wool
+ mapcolor: [104, 139, 212]
+ 4:
+ tex: [2, 10]
+ name: Yellow Wool
+ mapcolor: [104, 139, 212]
+ 5:
+ tex: [2, 9]
+ name: Lime Wool
+ mapcolor: [59, 189, 48]
+ 6:
+ tex: [2, 8]
+ name: Pink Wool
+ mapcolor: [217, 131, 155]
+ 7:
+ tex: [2, 7]
+ name: Gray Wool
+ mapcolor: [66, 66, 66]
+ 8:
+ tex: [1, 14]
+ name: Light Gray Wool
+ mapcolor: [166, 166, 166]
+ 9:
+ tex: [1, 13]
+ name: Cyan Wool
+ mapcolor: [39, 117, 149]
+ 10:
+ tex: [1, 12]
+ name: Purple Wool
+ aka: Indigo Wool, Violet Wool
+ mapcolor: [129, 54, 196]
+ 11:
+ tex: [1, 11]
+ name: Blue Wool
+ mapcolor: [39, 51, 161]
+ 12:
+ tex: [1, 10]
+ name: Brown Wool
+ mapcolor: [86, 51, 28]
+ 13:
+ tex: [1, 9]
+ name: Green Wool
+ mapcolor: [56, 77, 24]
+ 14:
+ tex: [1, 8]
+ name: Red Wool
+ mapcolor: [164, 45, 41]
+ 15:
+ tex: [1, 7]
+ name: Black Wool
+ mapcolor: [0, 0, 0]
+
+ - id: 37
+ idStr: YELLOW_FLOWER
+ name: Flower
+ mapcolor: [255, 255, 0]
+ tex: [13, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 38
+ name: Cyan Flower
+ mapcolor: [255, 0, 0]
+ tex: [12, 0]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 39
+ idStr: BROWN_MUSHROOM
+ name: Brown Mushroom
+ mapcolor: [145, 109, 85]
+ tex: [13, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 40
+ idStr: RED_MUSHROOM
+ name: Red Mushroom
+ mapcolor: [226, 18, 18]
+ tex: [12, 1]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 41
+ idStr: GOLD_BLOCK
+ name: Block of Gold
+ mapcolor: [231, 165, 45]
+ tex: [7, 1]
+
+ - id: 42
+ idStr: IRON_BLOCK
+ name: Block of Iron
+ mapcolor: [191, 191, 191]
+ tex: [6, 1]
+
+ - id: 43
+ idStr: DOUBLE_SLAB
+ name: Double Stone Slab
+ aka: Step
+ mapcolor: [200, 200, 200]
+ tex: [5, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+ data:
+ 0:
+ tex: [5, 0]
+ name: Double Stone Slab
+ 1:
+ tex: [0, 12]
+ name: Double Sandstone Slab
+ 2:
+ tex: [4, 0]
+ name: Double Wooden Slab
+ 3:
+ tex: [0, 1]
+ name: Double Cobblestone Slab
+ 4:
+ tex: [7, 0]
+ name: Double Brick Slab
+
+ - id: 44
+ idStr: SLAB
+ name: Stone Slab
+ aka: Half Step
+ mapcolor: [200, 200, 200]
+ tex: [6, 0]
+ tex_direction:
+ TOP: [6, 0]
+ BOTTOM: [6, 0]
+ FORWARD: [5, 0]
+ BACKWARD: [5, 0]
+ SIDES: [5, 0]
+
+ type: HALFHEIGHT
+ data:
+ 0:
+ tex: [6, 0]
+ name: Stone Slab
+ 1:
+ tex: [0, 12]
+ name: Sandstone Slab
+ 2:
+ tex: [4, 0]
+ name: Wooden Slab
+ 3:
+ tex: [0, 1]
+ name: Cobblestone Slab
+ 4:
+ tex: [7, 0]
+ name: Brick Slab
+
+ - id: 45
+ idStr: BRICK
+ name: Brick
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+
+ - id: 46
+ idStr: TNT
+ name: TNT
+ mapcolor: [160, 83, 65]
+ tex: [8, 0]
+ tex_direction:
+ TOP: [9, 0]
+ BOTTOM: [10, 0]
+
+ - id: 47
+ idStr: BOOKSHELF
+ name: Bookshelf
+ mapcolor: [188, 152, 98]
+ tex: [3, 2]
+ tex_direction:
+ TOP: [4, 0]
+ BOTTOM: [4, 0]
+
+ - id: 48
+ idStr: MOSSY_COBBLESTONE
+ name: Moss Stone
+ aka: Mossy Cobblestone
+ mapcolor: [115, 169, 115]
+ tex: [4, 2]
+
+ - id: 49
+ idStr: OBSIDIAN
+ name: Obsidian
+ mapcolor: [26, 11, 43]
+ tex: [5, 2]
+
+ - id: 50
+ idStr: TORCH
+ name: Torch
+ mapcolor: [245, 220, 50]
+ tex: [0, 5]
+ type: TORCH
+ explored: true
+ opacity: 0
+ brightness: 14
+
+ - id: 51
+ idStr: FIRE
+ name: Fire
+ mapcolor: [255, 170, 30]
+ type: SEMISOLID
+ tex: [15, 1]
+ opacity: 0
+ brightness: 15
+
+ - id: 53
+ idStr: WOODEN_STAIRS
+ name: Wooden Stairs
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: STAIRS
+
+ - id: 54
+ idStr: CHEST
+ name: Chest
+ mapcolor: [125, 91, 38]
+ tex: [11, 1]
+ type: CHEST
+ tex_extra:
+ side_small: [10, 1]
+ top: [9, 1]
+ front_big_left: [9, 2]
+ front_big_right: [10, 2]
+ back_big_left: [9, 3]
+ back_big_right: [10, 3]
+ opacity: 0
+
+ - id: 56
+ idStr: DIAMOND_ORE
+ name: Diamond Ore
+ mapcolor: [129, 140, 143]
+ tex: [2, 3]
+
+ - id: 57
+ idStr: DIAMOND_BLOCK
+ name: Block of Diamond
+ mapcolor: [45, 166, 152]
+ tex: [8, 1]
+
+ - id: 58
+ idStr: WORKBENCH
+ name: Bench
+ mapcolor: [114, 88, 56]
+ tex: [12, 3]
+ tex_direction:
+ SIDES: [11, 3]
+ TOP: [11, 2]
+ BOTTOM: [4, 0]
+
+ - id: 59
+ idStr: CROPS
+ name: Crops
+ aka: Wheat
+ mapcolor: [146, 192, 0]
+ tex: [15, 5]
+ type: CROPS
+ data:
+ 0: {tex: [8, 5]}
+ 1: {tex: [9, 5]}
+ 2: {tex: [10, 5]}
+ 3: {tex: [11, 5]}
+ 4: {tex: [12, 5]}
+ 5: {tex: [13, 5]}
+ 6: {tex: [14, 5]}
+ 7: {tex: [15, 5]}
+ opacity: 0
+
+ - id: 60
+ idStr: FARMLAND
+ name: Farmland
+ mapcolor: [95, 58, 30]
+ tex: [6, 5]
+ tex_direction:
+ FORWARD: [2, 0]
+ SIDES: [2, 0]
+ BACKWARD: [2, 0]
+ BOTTOM: [2, 0]
+
+ - id: 61
+ idStr: FURNACE
+ name: Furnace
+ mapcolor: [96, 96, 96]
+ tex: [12, 2]
+ tex_direction:
+ FORWARD: [12, 2]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [14, 3]
+ BOTTOM: [14, 3]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+
+ - id: 62
+ idStr: BURNING_FURNACE
+ name: Burning Furnace
+ mapcolor: [96, 96, 96]
+ tex: [13, 3]
+ tex_direction:
+ FORWARD: [13, 3]
+ SIDES: [13, 2]
+ BACKWARD: [13, 2]
+ TOP: [14, 3]
+ BOTTOM: [14, 3]
+ tex_direction_data: {2: NORTH, 3: SOUTH, 4: WEST, 5: EAST}
+ brightness: 14
+
+ - id: 64
+ idStr: WOODEN_DOOR
+ name: Wooden Door
+ mapcolor: [136, 109, 67]
+ tex: [1, 5]
+ type: DOOR
+ tex_extra:
+ bottom: [1, 6]
+ opacity: 0
+
+ - id: 65
+ idStr: LADDER
+ name: Ladder
+ mapcolor: [181, 140, 64]
+ tex: [3, 5]
+ type: LADDER
+ opacity: 0
+
+ - id: 67
+ idStr: COBBLESTONE_STAIRS
+ name: Stone Stairs
+ mapcolor: [115, 115, 115]
+ tex: [0, 1]
+ type: STAIRS
+
+ - id: 71
+ idStr: IRON_DOOR
+ name: Iron Door
+ mapcolor: [191, 191, 191]
+ tex: [2, 5]
+ type: DOOR
+ tex_extra:
+ bottom: [2, 6]
+ opacity: 0
+
+ - id: 73
+ idStr: REDSTONE_ORE
+ name: Redstone Ore
+ mapcolor: [131, 107, 107]
+ tex: [3, 3]
+
+ - id: 74
+ idStr: GLOWING_REDSTONE_ORE
+ name: Redstone Ore (Glowing)
+ mapcolor: [131, 107, 107]
+ tex: [3, 3]
+ brightness: 9
+
+ - id: 78
+ idStr: SNOW
+ name: Snow Layer
+ mapcolor: [255, 255, 255]
+ tex: [2, 4]
+ type: THINSLICE
+ opacity: 0
+
+ - id: 79
+ idStr: ICE
+ name: Ice
+ mapcolor: [83, 113, 163]
+ tex: [3, 4]
+ type: WATER
+ opacity: 3
+
+ - id: 80
+ idStr: SNOW_BLOCK
+ name: Snow
+ mapcolor: [250, 250, 250]
+ tex: [2, 4]
+
+ - id: 81
+ idStr: CACTUS
+ name: Cactus
+ mapcolor: [25, 120, 25]
+ tex: [6, 4]
+ tex_direction:
+ TOP: [5, 4]
+ BOTTOM: [5, 4]
+ opacity: 0
+
+ - id: 82
+ idStr: CLAY
+ name: Clay
+ mapcolor: [151, 157, 169]
+ tex: [8, 4]
+
+ - id: 83
+ idStr: SUGARCANE
+ name: Sugar Cane
+ mapcolor: [100, 67, 50]
+ tex: [9, 4]
+ type: DECORATION_CROSS
+ opacity: 0
+
+ - id: 85
+ idStr: FENCE
+ name: Fence
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: FENCE
+ opacity: 0
+
+ - id: 89
+ idStr: GLOWSTONE
+ name: Glowstone
+ mapcolor: [249, 212, 156]
+ tex: [9, 6]
+ explored: true
+ brightness: 15
+
+ - id: 95
+ idStr: INVISIBLE_BEDROCK
+ name: Invisible Bedrock
+
+ - id: 96
+ idStr: TRAPDOOR
+ name: Trapdoor
+ mapcolor: [143, 107, 53]
+ tex: [4, 5]
+ type: TRAPDOOR
+ opacity: 0
+
+ - id: 98
+ idStr: STONE_BRICK
+ name: Stone Bricks
+ mapcolor: [120, 120, 120]
+ tex: [6, 3]
+
+ - id: 102
+ idStr: GLASS_PANE
+ name: Glass Pane
+ mapcolor: [255, 255, 255]
+ tex: [1, 3]
+ type: SOLID_PANE
+ opacity: 0
+
+ - id: 103
+ idStr: MELON
+ name: Watermelon
+ mapcolor: [190, 184, 41]
+ tex: [9, 8]
+ tex_direction:
+ FORWARD: [8, 8]
+ SIDES: [8, 8]
+ BACKWARD: [8, 8]
+
+ - id: 105
+ idStr: MELON_STEM
+ name: Melon Stem
+ mapcolor: [190, 184, 41]
+ tex: [15, 6]
+ type: STEM
+ tex_extra:
+ curve: [15, 7]
+ opacity: 0
+
+ - id: 107
+ idStr: FENCE_GATE
+ name: Fence Gate
+ mapcolor: [157, 128, 79]
+ tex: [4, 0]
+ type: FENCE_GATE
+ opacity: 0
+
+ - id: 108
+ idStr: BRICK_STAIRS
+ name: Brick Stairs
+ mapcolor: [170, 86, 62]
+ tex: [7, 0]
+ type: STAIRS
+
+ - id: 246
+ idStr: GLOWING_OBSIDIAN
+ name: Glowing Obsidian
+ mapcolor: [155, 0, 0]
+ tex: [10, 13]
+ brightness: 12
+
+ - id: 247
+ idStr: NETHER_REACTOR_CORE
+ name: Nether Reactor Core
+ mapcolor: [125, 210, 255]
+ tex: [10, 14]
+ data:
+ 0:
+ tex: [10, 14]
+ name: Nether Reactor Core
+ 1:
+ tex: [9, 14]
+ name: Nether Reactor Core (Used)
+
+...
--- /dev/null
+import logging
+import os
+import struct
+import zlib
+
+from numpy import fromstring
+from mclevelbase import notclosing, RegionMalformed, ChunkNotPresent
+import nbt
+
+log = logging.getLogger(__name__)
+
+__author__ = 'Rio'
+
+def deflate(data):
+ return zlib.compress(data, 2)
+
+def inflate(data):
+ return zlib.decompress(data)
+
+
+class MCRegionFile(object):
+ holdFileOpen = False # if False, reopens and recloses the file on each access
+
+ @property
+ def file(self):
+ openfile = lambda: file(self.path, "rb+")
+ if MCRegionFile.holdFileOpen:
+ if self._file is None:
+ self._file = openfile()
+ return notclosing(self._file)
+ else:
+ return openfile()
+
+ def close(self):
+ if MCRegionFile.holdFileOpen:
+ self._file.close()
+ self._file = None
+
+ def __del__(self):
+ self.close()
+
+ def __init__(self, path, regionCoords):
+ self.path = path
+ self.regionCoords = regionCoords
+ self._file = None
+ if not os.path.exists(path):
+ file(path, "w").close()
+
+ with self.file as f:
+
+ filesize = os.path.getsize(path)
+ if filesize & 0xfff:
+ filesize = (filesize | 0xfff) + 1
+ f.truncate(filesize)
+
+ if filesize == 0:
+ filesize = self.SECTOR_BYTES * 2
+ f.truncate(filesize)
+
+ f.seek(0)
+ offsetsData = f.read(self.SECTOR_BYTES)
+ modTimesData = f.read(self.SECTOR_BYTES)
+
+ self.freeSectors = [True] * (filesize / self.SECTOR_BYTES)
+ self.freeSectors[0:2] = False, False
+
+ self.offsets = fromstring(offsetsData, dtype='>u4')
+ self.modTimes = fromstring(modTimesData, dtype='>u4')
+
+ needsRepair = False
+
+ for offset in self.offsets:
+ sector = offset >> 8
+ count = offset & 0xff
+
+ for i in xrange(sector, sector + count):
+ if i >= len(self.freeSectors):
+ # raise RegionMalformed("Region file offset table points to sector {0} (past the end of the file)".format(i))
+ print "Region file offset table points to sector {0} (past the end of the file)".format(i)
+ needsRepair = True
+ break
+ if self.freeSectors[i] is False:
+ needsRepair = True
+ self.freeSectors[i] = False
+
+ if needsRepair:
+ self.repair()
+
+ log.info("Found region file {file} with {used}/{total} sectors used and {chunks} chunks present".format(
+ file=os.path.basename(path), used=self.usedSectors, total=self.sectorCount, chunks=self.chunkCount))
+
+ def __repr__(self):
+ return "%s(\"%s\")" % (self.__class__.__name__, self.path)
+ @property
+ def usedSectors(self):
+ return len(self.freeSectors) - sum(self.freeSectors)
+
+ @property
+ def sectorCount(self):
+ return len(self.freeSectors)
+
+ @property
+ def chunkCount(self):
+ return sum(self.offsets > 0)
+
+ def repair(self):
+ lostAndFound = {}
+ _freeSectors = [True] * len(self.freeSectors)
+ _freeSectors[0] = _freeSectors[1] = False
+ deleted = 0
+ recovered = 0
+ log.info("Beginning repairs on {file} ({chunks} chunks)".format(file=os.path.basename(self.path), chunks=sum(self.offsets > 0)))
+ rx, rz = self.regionCoords
+ for index, offset in enumerate(self.offsets):
+ if offset:
+ cx = index & 0x1f
+ cz = index >> 5
+ cx += rx << 5
+ cz += rz << 5
+ sectorStart = offset >> 8
+ sectorCount = offset & 0xff
+ try:
+
+ if sectorStart + sectorCount > len(self.freeSectors):
+ raise RegionMalformed("Offset {start}:{end} ({offset}) at index {index} pointed outside of the file".format(
+ start=sectorStart, end=sectorStart + sectorCount, index=index, offset=offset))
+
+ data = self.readChunk(cx, cz)
+ if data is None:
+ raise RegionMalformed("Failed to read chunk data for {0}".format((cx, cz)))
+
+ chunkTag = nbt.load(buf=data)
+ lev = chunkTag["Level"]
+ xPos = lev["xPos"].value
+ zPos = lev["zPos"].value
+ overlaps = False
+
+ for i in xrange(sectorStart, sectorStart + sectorCount):
+ if _freeSectors[i] is False:
+ overlaps = True
+ _freeSectors[i] = False
+
+ if xPos != cx or zPos != cz or overlaps:
+ lostAndFound[xPos, zPos] = data
+
+ if (xPos, zPos) != (cx, cz):
+ raise RegionMalformed("Chunk {found} was found in the slot reserved for {expected}".format(found=(xPos, zPos), expected=(cx, cz)))
+ else:
+ raise RegionMalformed("Chunk {found} (in slot {expected}) has overlapping sectors with another chunk!".format(found=(xPos, zPos), expected=(cx, cz)))
+
+ except Exception, e:
+ log.info("Unexpected chunk data at sector {sector} ({exc})".format(sector=sectorStart, exc=e))
+ self.setOffset(cx, cz, 0)
+ deleted += 1
+
+ for cPos, (format, foundData) in lostAndFound.iteritems():
+ cx, cz = cPos
+ if self.getOffset(cx, cz) == 0:
+ log.info("Found chunk {found} and its slot is empty, recovering it".format(found=cPos))
+ self.saveChunk(cx, cz, foundData[5:])
+ recovered += 1
+
+ log.info("Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}".format(deleted, recovered, recovered - deleted))
+
+
+ def _readChunk(self, cx, cz):
+ cx &= 0x1f
+ cz &= 0x1f
+ offset = self.getOffset(cx, cz)
+ if offset == 0:
+ raise ChunkNotPresent((cx, cz))
+
+ sectorStart = offset >> 8
+ numSectors = offset & 0xff
+ if numSectors == 0:
+ raise ChunkNotPresent((cx, cz))
+
+ if sectorStart + numSectors > len(self.freeSectors):
+ raise ChunkNotPresent((cx, cz))
+
+ with self.file as f:
+ f.seek(sectorStart * self.SECTOR_BYTES)
+ data = f.read(numSectors * self.SECTOR_BYTES)
+ if len(data) < 5:
+ raise RegionMalformed, "Chunk data is only %d bytes long (expected 5)" % len(data)
+
+ # log.debug("REGION LOAD {0},{1} sector {2}".format(cx, cz, sectorStart))
+
+ length = struct.unpack_from(">I", data)[0]
+ format = struct.unpack_from("B", data, 4)[0]
+ data = data[5:length + 5]
+ return data, format
+
+ def readChunk(self, cx, cz):
+ data, format = self._readChunk(cx, cz)
+ if format == self.VERSION_GZIP:
+ return nbt.gunzip(data)
+ if format == self.VERSION_DEFLATE:
+ return inflate(data)
+
+ raise IOError("Unknown compress format: {0}".format(format))
+
+ def copyChunkFrom(self, regionFile, cx, cz):
+ """
+ Silently fails if regionFile does not contain the requested chunk.
+ """
+ try:
+ data, format = regionFile._readChunk(cx, cz)
+ self._saveChunk(cx, cz, data, format)
+ except ChunkNotPresent:
+ pass
+
+ def saveChunk(self, cx, cz, uncompressedData):
+ data = deflate(uncompressedData)
+ self._saveChunk(cx, cz, data, self.VERSION_DEFLATE)
+
+ def _saveChunk(self, cx, cz, data, format):
+ cx &= 0x1f
+ cz &= 0x1f
+ offset = self.getOffset(cx, cz)
+ sectorNumber = offset >> 8
+ sectorsAllocated = offset & 0xff
+
+
+
+ sectorsNeeded = (len(data) + self.CHUNK_HEADER_SIZE) / self.SECTOR_BYTES + 1
+ if sectorsNeeded >= 256:
+ return
+
+ if sectorNumber != 0 and sectorsAllocated >= sectorsNeeded:
+ log.debug("REGION SAVE {0},{1} rewriting {2}b".format(cx, cz, len(data)))
+ self.writeSector(sectorNumber, data, format)
+ else:
+ # we need to allocate new sectors
+
+ # mark the sectors previously used for this chunk as free
+ for i in xrange(sectorNumber, sectorNumber + sectorsAllocated):
+ self.freeSectors[i] = True
+
+ runLength = 0
+ runStart = 0
+ try:
+ runStart = self.freeSectors.index(True)
+
+ for i in range(runStart, len(self.freeSectors)):
+ if runLength:
+ if self.freeSectors[i]:
+ runLength += 1
+ else:
+ runLength = 0
+ elif self.freeSectors[i]:
+ runStart = i
+ runLength = 1
+
+ if runLength >= sectorsNeeded:
+ break
+ except ValueError:
+ pass
+
+ # we found a free space large enough
+ if runLength >= sectorsNeeded:
+ log.debug("REGION SAVE {0},{1}, reusing {2}b".format(cx, cz, len(data)))
+ sectorNumber = runStart
+ self.setOffset(cx, cz, sectorNumber << 8 | sectorsNeeded)
+ self.writeSector(sectorNumber, data, format)
+ self.freeSectors[sectorNumber:sectorNumber + sectorsNeeded] = [False] * sectorsNeeded
+
+ else:
+ # no free space large enough found -- we need to grow the
+ # file
+
+ log.debug("REGION SAVE {0},{1}, growing by {2}b".format(cx, cz, len(data)))
+
+ with self.file as f:
+ f.seek(0, 2)
+ filesize = f.tell()
+
+ sectorNumber = len(self.freeSectors)
+
+ assert sectorNumber * self.SECTOR_BYTES == filesize
+
+ filesize += sectorsNeeded * self.SECTOR_BYTES
+ f.truncate(filesize)
+
+ self.freeSectors += [False] * sectorsNeeded
+
+ self.setOffset(cx, cz, sectorNumber << 8 | sectorsNeeded)
+ self.writeSector(sectorNumber, data, format)
+
+ def writeSector(self, sectorNumber, data, format):
+ with self.file as f:
+ log.debug("REGION: Writing sector {0}".format(sectorNumber))
+
+ f.seek(sectorNumber * self.SECTOR_BYTES)
+ f.write(struct.pack(">I", len(data) + 1)) # // chunk length
+ f.write(struct.pack("B", format)) # // chunk version number
+ f.write(data) # // chunk data
+ # f.flush()
+
+ def containsChunk(self, cx, cz):
+ return self.getOffset(cx, cz) != 0
+
+ def getOffset(self, cx, cz):
+ cx &= 0x1f
+ cz &= 0x1f
+ return self.offsets[cx + cz * 32]
+
+ def setOffset(self, cx, cz, offset):
+ cx &= 0x1f
+ cz &= 0x1f
+ self.offsets[cx + cz * 32] = offset
+ with self.file as f:
+ f.seek(0)
+ f.write(self.offsets.tostring())
+
+ SECTOR_BYTES = 4096
+ SECTOR_INTS = SECTOR_BYTES / 4
+ CHUNK_HEADER_SIZE = 5
+ VERSION_GZIP = 1
+ VERSION_DEFLATE = 2
+
+ compressMode = VERSION_DEFLATE
--- /dev/null
+'''
+Created on Jul 22, 2011
+
+@author: Rio
+'''
+import atexit
+from contextlib import closing
+import os
+import shutil
+import zipfile
+from logging import getLogger
+
+import blockrotation
+from box import BoundingBox
+import infiniteworld
+from level import MCLevel, EntityLevel
+from materials import alphaMaterials, MCMaterials, namedMaterials
+from mclevelbase import exhaust
+import nbt
+from numpy import array, swapaxes, uint8, zeros
+
+log = getLogger(__name__)
+
+__all__ = ['MCSchematic', 'INVEditChest']
+
+
+class MCSchematic (EntityLevel):
+ materials = alphaMaterials
+
+ def __init__(self, shape=None, root_tag=None, filename=None, mats='Alpha'):
+ """ shape is (x,y,z) for a new level's shape. if none, takes
+ root_tag as a TAG_Compound for an existing schematic file. if
+ none, tries to read the tag from filename. if none, results
+ are undefined. materials can be a MCMaterials instance, or one of
+ "Classic", "Alpha", "Pocket" to indicate allowable blocks. The default
+ is Alpha.
+
+ block coordinate order in the file is y,z,x to use the same code as classic/indev levels.
+ in hindsight, this was a completely arbitrary decision.
+
+ the Entities and TileEntities are nbt.TAG_List objects containing TAG_Compounds.
+ this makes it easy to copy entities without knowing about their insides.
+
+ rotateLeft swaps the axes of the different arrays. because of this, the Width, Height, and Length
+ reflect the current dimensions of the schematic rather than the ones specified in the NBT structure.
+ I'm not sure what happens when I try to re-save a rotated schematic.
+ """
+
+ # if(shape != None):
+ # self.setShape(shape)
+
+ if filename:
+ self.filename = filename
+ if None is root_tag and os.path.exists(filename):
+ root_tag = nbt.load(filename)
+ else:
+ self.filename = None
+
+ if mats in namedMaterials:
+ self.materials = namedMaterials[mats]
+ else:
+ assert(isinstance(mats, MCMaterials))
+ self.materials = mats
+
+ if root_tag:
+ self.root_tag = root_tag
+ if "Materials" in root_tag:
+ self.materials = namedMaterials[self.Materials]
+ else:
+ root_tag["Materials"] = nbt.TAG_String(self.materials.name)
+ self.shapeChunkData()
+
+ else:
+ assert shape is not None
+ root_tag = nbt.TAG_Compound(name="Schematic")
+ root_tag["Height"] = nbt.TAG_Short(shape[1])
+ root_tag["Length"] = nbt.TAG_Short(shape[2])
+ root_tag["Width"] = nbt.TAG_Short(shape[0])
+
+ root_tag["Entities"] = nbt.TAG_List()
+ root_tag["TileEntities"] = nbt.TAG_List()
+ root_tag["Materials"] = nbt.TAG_String(self.materials.name)
+
+ root_tag["Blocks"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8))
+ root_tag["Data"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8))
+
+ self.root_tag = root_tag
+
+ self.packUnpack()
+ self.root_tag["Data"].value &= 0xF # discard high bits
+
+
+ def saveToFile(self, filename=None):
+ """ save to file named filename, or use self.filename. XXX NOT THREAD SAFE AT ALL. """
+ if filename is None:
+ filename = self.filename
+ if filename is None:
+ raise IOError, u"Attempted to save an unnamed schematic in place"
+
+ self.Materials = self.materials.name
+
+ self.packUnpack()
+ with open(filename, 'wb') as chunkfh:
+ self.root_tag.save(chunkfh)
+
+ self.packUnpack()
+
+ def __str__(self):
+ return u"MCSchematic(shape={0}, materials={2}, filename=\"{1}\")".format(self.size, self.filename or u"", self.Materials)
+
+ # these refer to the blocks array instead of the file's height because rotation swaps the axes
+ # this will have an impact later on when editing schematics instead of just importing/exporting
+ @property
+ def Length(self):
+ return self.Blocks.shape[1]
+
+ @property
+ def Width(self):
+ return self.Blocks.shape[0]
+
+ @property
+ def Height(self):
+ return self.Blocks.shape[2]
+
+ @property
+ def Blocks(self):
+ return self.root_tag["Blocks"].value
+
+ @property
+ def Data(self):
+ return self.root_tag["Data"].value
+
+ @property
+ def Entities(self):
+ return self.root_tag["Entities"]
+
+ @property
+ def TileEntities(self):
+ return self.root_tag["TileEntities"]
+
+ @property
+ def Materials(self):
+ return self.root_tag["Materials"].value
+
+ @Materials.setter
+ def Materials(self, val):
+ if "Materials" not in self.root_tag:
+ self.root_tag["Materials"] = nbt.TAG_String()
+ self.root_tag["Materials"].value = val
+
+ @classmethod
+ def _isTagLevel(cls, root_tag):
+ return "Schematic" == root_tag.name
+
+ def shapeChunkData(self):
+ w = self.root_tag["Width"].value
+ l = self.root_tag["Length"].value
+ h = self.root_tag["Height"].value
+
+ self.root_tag["Blocks"].value.shape = (h, l, w)
+ self.root_tag["Data"].value.shape = (h, l, w)
+
+ def packUnpack(self):
+ self.root_tag["Blocks"].value = swapaxes(self.root_tag["Blocks"].value, 0, 2) # yzx to xzy
+ self.root_tag["Data"].value = swapaxes(self.root_tag["Data"].value, 0, 2) # yzx to xzy
+
+
+ def _update_shape(self):
+ root_tag = self.root_tag
+ shape = self.Blocks.shape
+ root_tag["Height"] = nbt.TAG_Short(shape[2])
+ root_tag["Length"] = nbt.TAG_Short(shape[1])
+ root_tag["Width"] = nbt.TAG_Short(shape[0])
+
+ def rotateLeft(self):
+
+ self.root_tag["Blocks"].value = swapaxes(self.Blocks, 1, 0)[:, ::-1, :] # x=z; z=-x
+ self.root_tag["Data"].value = swapaxes(self.Data, 1, 0)[:, ::-1, :] # x=z; z=-x
+ self._update_shape()
+
+ blockrotation.RotateLeft(self.Blocks, self.Data)
+
+ log.info(u"Relocating entities...")
+ for entity in self.Entities:
+ for p in "Pos", "Motion":
+ if p == "Pos":
+ zBase = self.Length
+ else:
+ zBase = 0.0
+ newX = entity[p][2].value
+ newZ = zBase - entity[p][0].value
+
+ entity[p][0].value = newX
+ entity[p][2].value = newZ
+ entity["Rotation"][0].value -= 90.0
+ if entity["id"].value in ("Painting", "ItemFrame"):
+ x, z = entity["TileX"].value, entity["TileZ"].value
+ newx = z
+ newz = self.Length - x - 1
+
+ entity["TileX"].value, entity["TileZ"].value = newx, newz
+ entity["Dir"].value = (entity["Dir"].value + 1) % 4
+
+ for tileEntity in self.TileEntities:
+ if not 'x' in tileEntity:
+ continue
+
+ newX = tileEntity["z"].value
+ newZ = self.Length - tileEntity["x"].value - 1
+
+ tileEntity["x"].value = newX
+ tileEntity["z"].value = newZ
+
+ def roll(self):
+ " xxx rotate stuff "
+ self.root_tag["Blocks"].value = swapaxes(self.Blocks, 2, 0)[:, :, ::-1] # x=z; z=-x
+ self.root_tag["Data"].value = swapaxes(self.Data, 2, 0)[:, :, ::-1]
+ self._update_shape()
+
+ def flipVertical(self):
+ " xxx delete stuff "
+ blockrotation.FlipVertical(self.Blocks, self.Data)
+ self.root_tag["Blocks"].value = self.Blocks[:, :, ::-1] # y=-y
+ self.root_tag["Data"].value = self.Data[:, :, ::-1]
+
+ def flipNorthSouth(self):
+ blockrotation.FlipNorthSouth(self.Blocks, self.Data)
+ self.root_tag["Blocks"].value = self.Blocks[::-1, :, :] # x=-x
+ self.root_tag["Data"].value = self.Data[::-1, :, :]
+
+ northSouthPaintingMap = [0, 3, 2, 1]
+
+ log.info(u"N/S Flip: Relocating entities...")
+ for entity in self.Entities:
+
+ entity["Pos"][0].value = self.Width - entity["Pos"][0].value
+ entity["Motion"][0].value = -entity["Motion"][0].value
+
+ entity["Rotation"][0].value -= 180.0
+
+ if entity["id"].value in ("Painting", "ItemFrame"):
+ entity["TileX"].value = self.Width - entity["TileX"].value
+ entity["Dir"].value = northSouthPaintingMap[entity["Dir"].value]
+
+ for tileEntity in self.TileEntities:
+ if not 'x' in tileEntity:
+ continue
+
+ tileEntity["x"].value = self.Width - tileEntity["x"].value - 1
+
+ def flipEastWest(self):
+ " xxx flip entities "
+ blockrotation.FlipEastWest(self.Blocks, self.Data)
+ self.root_tag["Blocks"].value = self.Blocks[:, ::-1, :] # z=-z
+ self.root_tag["Data"].value = self.Data[:, ::-1, :]
+
+ eastWestPaintingMap = [2, 1, 0, 3]
+
+ log.info(u"E/W Flip: Relocating entities...")
+ for entity in self.Entities:
+
+ entity["Pos"][2].value = self.Length - entity["Pos"][2].value
+ entity["Motion"][2].value = -entity["Motion"][2].value
+
+ entity["Rotation"][0].value -= 180.0
+
+ if entity["id"].value in ("Painting", "ItemFrame"):
+ entity["TileZ"].value = self.Length - entity["TileZ"].value
+ entity["Dir"].value = eastWestPaintingMap[entity["Dir"].value]
+
+ for tileEntity in self.TileEntities:
+ tileEntity["z"].value = self.Length - tileEntity["z"].value - 1
+
+ def setShape(self, shape):
+ """shape is a tuple of (width, height, length). sets the
+ schematic's properties and clears the block and data arrays"""
+
+ x, y, z = shape
+ shape = (x, z, y)
+
+ self.root_tag["Blocks"].value = zeros(dtype='uint8', shape=shape)
+ self.root_tag["Data"].value = zeros(dtype='uint8', shape=shape)
+ self.shapeChunkData()
+
+
+ def setBlockDataAt(self, x, y, z, newdata):
+ if x < 0 or y < 0 or z < 0:
+ return 0
+ if x >= self.Width or y >= self.Height or z >= self.Length:
+ return 0
+ self.Data[x, z, y] = (newdata & 0xf)
+
+ def blockDataAt(self, x, y, z):
+ if x < 0 or y < 0 or z < 0:
+ return 0
+ if x >= self.Width or y >= self.Height or z >= self.Length:
+ return 0
+ return self.Data[x, z, y]
+
+ @classmethod
+ def chestWithItemID(cls, itemID, count=64, damage=0):
+ """ Creates a chest with a stack of 'itemID' in each slot.
+ Optionally specify the count of items in each stack. Pass a negative
+ value for damage to create unnaturally sturdy tools. """
+ root_tag = nbt.TAG_Compound()
+ invTag = nbt.TAG_List()
+ root_tag["Inventory"] = invTag
+ for slot in range(9, 36):
+ itemTag = nbt.TAG_Compound()
+ itemTag["Slot"] = nbt.TAG_Byte(slot)
+ itemTag["Count"] = nbt.TAG_Byte(count)
+ itemTag["id"] = nbt.TAG_Short(itemID)
+ itemTag["Damage"] = nbt.TAG_Short(damage)
+ invTag.append(itemTag)
+
+ chest = INVEditChest(root_tag, "")
+
+ return chest
+
+
+class INVEditChest(MCSchematic):
+ Width = 1
+ Height = 1
+ Length = 1
+ Blocks = array([[[alphaMaterials.Chest.ID]]], 'uint8')
+ Data = array([[[0]]], 'uint8')
+ Entities = nbt.TAG_List()
+ Materials = alphaMaterials
+
+ @classmethod
+ def _isTagLevel(cls, root_tag):
+ return "Inventory" in root_tag
+
+ def __init__(self, root_tag, filename):
+
+ if filename:
+ self.filename = filename
+ if None is root_tag:
+ try:
+ root_tag = nbt.load(filename)
+ except IOError, e:
+ log.info(u"Failed to load file {0}".format(e))
+ raise
+ else:
+ assert root_tag, "Must have either root_tag or filename"
+ self.filename = None
+
+ for item in list(root_tag["Inventory"]):
+ slot = item["Slot"].value
+ if slot < 9 or slot >= 36:
+ root_tag["Inventory"].remove(item)
+ else:
+ item["Slot"].value -= 9 # adjust for different chest slot indexes
+
+ self.root_tag = root_tag
+
+ @property
+ def TileEntities(self):
+ chestTag = nbt.TAG_Compound()
+ chestTag["id"] = nbt.TAG_String("Chest")
+ chestTag["Items"] = nbt.TAG_List(self.root_tag["Inventory"])
+ chestTag["x"] = nbt.TAG_Int(0)
+ chestTag["y"] = nbt.TAG_Int(0)
+ chestTag["z"] = nbt.TAG_Int(0)
+
+ return nbt.TAG_List([chestTag], name="TileEntities")
+
+
+class ZipSchematic (infiniteworld.MCInfdevOldLevel):
+ def __init__(self, filename, create=False):
+ self.zipfilename = filename
+
+ tempdir = tempfile.mktemp("schematic")
+ if create is False:
+ zf = zipfile.ZipFile(filename)
+ zf.extractall(tempdir)
+ zf.close()
+
+ super(ZipSchematic, self).__init__(tempdir, create)
+ atexit.register(shutil.rmtree, self.worldFolder.filename, True)
+
+
+ try:
+ schematicDat = nbt.load(self.worldFolder.getFilePath("schematic.dat"))
+
+ self.Width = schematicDat['Width'].value
+ self.Height = schematicDat['Height'].value
+ self.Length = schematicDat['Length'].value
+
+ if "Materials" in schematicDat:
+ self.materials = namedMaterials[schematicDat["Materials"].value]
+
+ except Exception, e:
+ print "Exception reading schematic.dat, skipping: {0!r}".format(e)
+ self.Width = 0
+ self.Length = 0
+
+ def __del__(self):
+ shutil.rmtree(self.worldFolder.filename, True)
+
+ def saveInPlace(self):
+ self.saveToFile(self.zipfilename)
+
+ def saveToFile(self, filename):
+ super(ZipSchematic, self).saveInPlace()
+ schematicDat = nbt.TAG_Compound()
+ schematicDat.name = "Mega Schematic"
+
+ schematicDat["Width"] = nbt.TAG_Int(self.size[0])
+ schematicDat["Height"] = nbt.TAG_Int(self.size[1])
+ schematicDat["Length"] = nbt.TAG_Int(self.size[2])
+ schematicDat["Materials"] = nbt.TAG_String(self.materials.name)
+
+ schematicDat.save(self.worldFolder.getFilePath("schematic.dat"))
+
+ basedir = self.worldFolder.filename
+ assert os.path.isdir(basedir)
+ with closing(zipfile.ZipFile(filename, "w", zipfile.ZIP_STORED)) as z:
+ for root, dirs, files in os.walk(basedir):
+ # NOTE: ignore empty directories
+ for fn in files:
+ absfn = os.path.join(root, fn)
+ zfn = absfn[len(basedir) + len(os.sep):] # XXX: relative path
+ z.write(absfn, zfn)
+
+ def getWorldBounds(self):
+ return BoundingBox((0, 0, 0), (self.Width, self.Height, self.Length))
+
+ @classmethod
+ def _isLevel(cls, filename):
+ return zipfile.is_zipfile(filename)
+
+
+
+
+def adjustExtractionParameters(self, box):
+ x, y, z = box.origin
+ w, h, l = box.size
+ destX = destY = destZ = 0
+
+ if y < 0:
+ destY -= y
+ h += y
+ y = 0
+
+ if y >= self.Height:
+ return
+
+ if y + h >= self.Height:
+ h -= y + h - self.Height
+ y = self.Height - h
+
+ if h <= 0:
+ return
+
+ if self.Width:
+ if x < 0:
+ w += x
+ destX -= x
+ x = 0
+ if x >= self.Width:
+ return
+
+ if x + w >= self.Width:
+ w = self.Width - x
+
+ if w <= 0:
+ return
+
+ if z < 0:
+ l += z
+ destZ -= z
+ z = 0
+
+ if z >= self.Length:
+ return
+
+ if z + l >= self.Length:
+ l = self.Length - z
+
+ if l <= 0:
+ return
+
+ box = BoundingBox((x, y, z), (w, h, l))
+
+ return box, (destX, destY, destZ)
+
+
+def extractSchematicFrom(sourceLevel, box, entities=True):
+ return exhaust(extractSchematicFromIter(sourceLevel, box, entities))
+
+
+def extractSchematicFromIter(sourceLevel, box, entities=True):
+ p = sourceLevel.adjustExtractionParameters(box)
+ if p is None:
+ yield None
+ return
+ newbox, destPoint = p
+
+ tempSchematic = MCSchematic(shape=box.size, mats=sourceLevel.materials)
+ for i in tempSchematic.copyBlocksFromIter(sourceLevel, newbox, destPoint, entities=entities):
+ yield i
+
+ yield tempSchematic
+
+MCLevel.extractSchematic = extractSchematicFrom
+MCLevel.extractSchematicIter = extractSchematicFromIter
+MCLevel.adjustExtractionParameters = adjustExtractionParameters
+
+import tempfile
+
+
+def extractZipSchematicFrom(sourceLevel, box, zipfilename=None, entities=True):
+ return exhaust(extractZipSchematicFromIter(sourceLevel, box, zipfilename, entities))
+
+
+def extractZipSchematicFromIter(sourceLevel, box, zipfilename=None, entities=True):
+ # converts classic blocks to alpha
+ # probably should only apply to alpha levels
+
+ if zipfilename is None:
+ zipfilename = tempfile.mktemp("zipschematic.zip")
+ atexit.register(shutil.rmtree, zipfilename, True)
+
+ p = sourceLevel.adjustExtractionParameters(box)
+ if p is None:
+ return
+ sourceBox, destPoint = p
+
+ destPoint = (0, 0, 0)
+
+ tempSchematic = ZipSchematic(zipfilename, create=True)
+ tempSchematic.materials = sourceLevel.materials
+
+ for i in tempSchematic.copyBlocksFromIter(sourceLevel, sourceBox, destPoint, entities=entities, create=True):
+ yield i
+
+ tempSchematic.Width, tempSchematic.Height, tempSchematic.Length = sourceBox.size
+ tempSchematic.saveInPlace() # lights not needed for this format - crashes minecraft though
+ yield tempSchematic
+
+MCLevel.extractZipSchematic = extractZipSchematicFrom
+MCLevel.extractZipSchematicIter = extractZipSchematicFromIter
+
+
+def extractAnySchematic(level, box):
+ return exhaust(level.extractAnySchematicIter(box))
+
+
+def extractAnySchematicIter(level, box):
+ if box.chunkCount < infiniteworld.MCInfdevOldLevel.loadedChunkLimit:
+ for i in level.extractSchematicIter(box):
+ yield i
+ else:
+ for i in level.extractZipSchematicIter(box):
+ yield i
+
+MCLevel.extractAnySchematic = extractAnySchematic
+MCLevel.extractAnySchematicIter = extractAnySchematicIter
+