From: daid303 Date: Wed, 2 Jan 2013 16:51:01 +0000 (+0100) Subject: Added minecraft import tool. X-Git-Tag: 13.03~130 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=03a0c149bedc0bf3410ee32bbd1bf8aa4fd7f93d;p=cura.git Added minecraft import tool. --- diff --git a/Cura/gui/mainWindow.py b/Cura/gui/mainWindow.py index 9a6e4364..0baedfd3 100644 --- a/Cura/gui/mainWindow.py +++ b/Cura/gui/mainWindow.py @@ -2,10 +2,7 @@ from __future__ import absolute_import import wx import os -import platform import webbrowser -import shutil -import glob from Cura.gui import configBase from Cura.gui import expertConfig @@ -19,9 +16,10 @@ from Cura.gui import firmwareInstall 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 @@ -86,6 +84,8 @@ class mainWindow(wx.Frame): 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() @@ -398,6 +398,11 @@ class mainWindow(wx.Frame): 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() diff --git a/Cura/gui/tools/__init__.py b/Cura/gui/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Cura/gui/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/Cura/gui/batchRun.py b/Cura/gui/tools/batchRun.py similarity index 100% rename from Cura/gui/batchRun.py rename to Cura/gui/tools/batchRun.py diff --git a/Cura/gui/tools/minecraftImport.py b/Cura/gui/tools/minecraftImport.py new file mode 100644 index 00000000..b64952dc --- /dev/null +++ b/Cura/gui/tools/minecraftImport.py @@ -0,0 +1,273 @@ +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]) diff --git a/Cura/util/pymclevel/LICENSE.txt b/Cura/util/pymclevel/LICENSE.txt new file mode 100644 index 00000000..fdbc94c3 --- /dev/null +++ b/Cura/util/pymclevel/LICENSE.txt @@ -0,0 +1,13 @@ +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 diff --git a/Cura/util/pymclevel/README.txt b/Cura/util/pymclevel/README.txt new file mode 100644 index 00000000..72bc5801 --- /dev/null +++ b/Cura/util/pymclevel/README.txt @@ -0,0 +1,127 @@ +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 + fill [ ] + replace [with] [ ] + + export + import + + analyze + + Player commands: + player [ [ ] ] + spawn [ ] + + Entity commands: + removeEntities [ ] + + Chunk commands: + createChunks + deleteChunks + prune + relight [ ] + + World commands: + degrief + + Editor commands: + save + reload + load | + quit + + Informational: + blocks [ | ] + help [ ] + + 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> diff --git a/Cura/util/pymclevel/__init__.py b/Cura/util/pymclevel/__init__.py new file mode 100644 index 00000000..6f08de9d --- /dev/null +++ b/Cura/util/pymclevel/__init__.py @@ -0,0 +1,14 @@ +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 diff --git a/Cura/util/pymclevel/biome_types.py b/Cura/util/pymclevel/biome_types.py new file mode 100644 index 00000000..71eeb70a --- /dev/null +++ b/Cura/util/pymclevel/biome_types.py @@ -0,0 +1,26 @@ +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", +} diff --git a/Cura/util/pymclevel/block_copy.py b/Cura/util/pymclevel/block_copy.py new file mode 100644 index 00000000..9a1d39e3 --- /dev/null +++ b/Cura/util/pymclevel/block_copy.py @@ -0,0 +1,145 @@ +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)) + + + + + diff --git a/Cura/util/pymclevel/block_fill.py b/Cura/util/pymclevel/block_fill.py new file mode 100644 index 00000000..d322c5a8 --- /dev/null +++ b/Cura/util/pymclevel/block_fill.py @@ -0,0 +1,103 @@ +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)) diff --git a/Cura/util/pymclevel/blockrotation.py b/Cura/util/pymclevel/blockrotation.py new file mode 100644 index 00000000..8a1ba545 --- /dev/null +++ b/Cura/util/pymclevel/blockrotation.py @@ -0,0 +1,525 @@ +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] diff --git a/Cura/util/pymclevel/box.py b/Cura/util/pymclevel/box.py new file mode 100644 index 00000000..caf6f0e9 --- /dev/null +++ b/Cura/util/pymclevel/box.py @@ -0,0 +1,211 @@ +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 diff --git a/Cura/util/pymclevel/cachefunc.py b/Cura/util/pymclevel/cachefunc.py new file mode 100644 index 00000000..765ca47d --- /dev/null +++ b/Cura/util/pymclevel/cachefunc.py @@ -0,0 +1,164 @@ +# 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) diff --git a/Cura/util/pymclevel/classic.yaml b/Cura/util/pymclevel/classic.yaml new file mode 100644 index 00000000..49babe3d --- /dev/null +++ b/Cura/util/pymclevel/classic.yaml @@ -0,0 +1,297 @@ + +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 diff --git a/Cura/util/pymclevel/entity.py b/Cura/util/pymclevel/entity.py new file mode 100644 index 00000000..aa8aa0fe --- /dev/null +++ b/Cura/util/pymclevel/entity.py @@ -0,0 +1,190 @@ +''' +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 diff --git a/Cura/util/pymclevel/faces.py b/Cura/util/pymclevel/faces.py new file mode 100644 index 00000000..dd405c87 --- /dev/null +++ b/Cura/util/pymclevel/faces.py @@ -0,0 +1,17 @@ + +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)) + ) diff --git a/Cura/util/pymclevel/indev.py b/Cura/util/pymclevel/indev.py new file mode 100644 index 00000000..ff915644 --- /dev/null +++ b/Cura/util/pymclevel/indev.py @@ -0,0 +1,318 @@ +""" +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 diff --git a/Cura/util/pymclevel/indev.yaml b/Cura/util/pymclevel/indev.yaml new file mode 100644 index 00000000..8fe876e9 --- /dev/null +++ b/Cura/util/pymclevel/indev.yaml @@ -0,0 +1,415 @@ + +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 diff --git a/Cura/util/pymclevel/infiniteworld.py b/Cura/util/pymclevel/infiniteworld.py new file mode 100644 index 00000000..d44cd767 --- /dev/null +++ b/Cura/util/pymclevel/infiniteworld.py @@ -0,0 +1,1829 @@ +''' +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() + diff --git a/Cura/util/pymclevel/items.py b/Cura/util/pymclevel/items.py new file mode 100644 index 00000000..341905ea --- /dev/null +++ b/Cura/util/pymclevel/items.py @@ -0,0 +1,643 @@ +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() diff --git a/Cura/util/pymclevel/items.txt b/Cura/util/pymclevel/items.txt new file mode 100644 index 00000000..c4dd198d --- /dev/null +++ b/Cura/util/pymclevel/items.txt @@ -0,0 +1,534 @@ +: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 diff --git a/Cura/util/pymclevel/java.py b/Cura/util/pymclevel/java.py new file mode 100644 index 00000000..9ecbc47e --- /dev/null +++ b/Cura/util/pymclevel/java.py @@ -0,0 +1,185 @@ +''' +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;z0, + # 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 diff --git a/Cura/util/pymclevel/materials.py b/Cura/util/pymclevel/materials.py new file mode 100644 index 00000000..bb58c1a0 --- /dev/null +++ b/Cura/util/pymclevel/materials.py @@ -0,0 +1,873 @@ + +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 "".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 "".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(", ") diff --git a/Cura/util/pymclevel/mce.py b/Cura/util/pymclevel/mce.py new file mode 100644 index 00000000..b03316da --- /dev/null +++ b/Cura/util/pymclevel/mce.py @@ -0,0 +1,1500 @@ +#!/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 [noair] [nowater] + {commandPrefix}fill [ ] + {commandPrefix}replace [with] [ ] + + {commandPrefix}export + {commandPrefix}import [noair] [nowater] + + {commandPrefix}createChest [ ] + {commandPrefix}analyze + + Player commands: + {commandPrefix}player [ [ ] ] + {commandPrefix}spawn [ ] + + Entity commands: + {commandPrefix}removeEntities [ ] + {commandPrefix}dumpSigns [ ] + {commandPrefix}dumpChests [ ] + + Chunk commands: + {commandPrefix}createChunks + {commandPrefix}deleteChunks + {commandPrefix}prune + {commandPrefix}relight [ ] + + World commands: + {commandPrefix}create + {commandPrefix}dimension [ ] + {commandPrefix}degrief + {commandPrefix}time [