Source code for mce

# !/usr/bin/env python
import pymclevel.mclevelbase
import pymclevel.mclevel
import pymclevel.materials
import pymclevel.infiniteworld
import sys
import os
from pymclevel.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

mclevelbase = pymclevel.mclevelbase
mclevel = pymclevel.mclevel
materials = pymclevel.materials
infiniteworld = pymclevel.infiniteworld

try:
    import readline  # if available, used by raw_input()
except:
    pass


[docs]class UsageError(RuntimeError): pass
[docs]class BlockMatchError(RuntimeError): pass
[docs]class PlayerNotFound(RuntimeError): pass
[docs]class mce(object): """ Block commands: {commandPrefix}clone <sourceBox> <destPoint> [noair] [nowater] {commandPrefix}fill <blockType> [ <box> ] {commandPrefix}replace <blockType> [with] <newBlockType> [ <box> ] {commandPrefix}export <filename> <sourceBox> {commandPrefix}import <filename> <destPoint> [noair] [nowater] {commandPrefix}createChest <point> <item> [ <count> ] {commandPrefix}analyze Player commands: {commandPrefix}player [ <player> [ <point> ] ] {commandPrefix}spawn [ <point> ] Entity commands: {commandPrefix}removeEntities [ <EntityID> ] {commandPrefix}dumpSigns [ <filename> ] {commandPrefix}dumpChests [ <filename> ] Chunk commands: {commandPrefix}createChunks <box> {commandPrefix}deleteChunks <box> {commandPrefix}prune <box> {commandPrefix}relight [ <box> ] World commands: {commandPrefix}create <filename> {commandPrefix}dimension [ <dim> ] {commandPrefix}degrief {commandPrefix}time [ <time> ] {commandPrefix}worldsize {commandPrefix}heightmap <filename> {commandPrefix}randomseed [ <seed> ] {commandPrefix}gametype [ <player> [ <gametype> ] ] Editor commands: {commandPrefix}save {commandPrefix}reload {commandPrefix}load <filename> | <world number> {commandPrefix}execute <filename> {commandPrefix}quit Informational: {commandPrefix}blocks [ <block name> | <block ID> ] {commandPrefix}help [ <command> ] **IMPORTANT** {commandPrefix}box Type 'box' to learn how to specify points and areas. """ random_seed = os.getenv('MCE_RANDOM_SEED', None) last_played = os.getenv("MCE_LAST_PLAYED", None)
[docs] def commandUsage(self, command): " returns usage info for the named command - just give the docstring for the handler func " func = getattr(self, "_" + command) return func.__doc__
commands = [ "clone", "fill", "replace", "export", "execute", "import", "createchest", "player", "spawn", "removeentities", "dumpsigns", "dumpchests", "createchunks", "deletechunks", "prune", "relight", "create", "degrief", "time", "worldsize", "heightmap", "randomseed", "gametype", "save", "load", "reload", "dimension", "repair", "quit", "exit", "help", "blocks", "analyze", "region", "debug", "log", "box", ] debug = False needsSave = False @staticmethod
[docs] def readInt(command): try: val = int(command.pop(0)) except ValueError: raise UsageError("Cannot understand numeric input") return val
@staticmethod
[docs] def prettySplit(command): cmdstring = " ".join(command) lex = shlex.shlex(cmdstring) lex.whitespace_split = True lex.whitespace += "()," command[:] = list(lex)
[docs] def readBox(self, command): self.prettySplit(command) sourcePoint = self.readIntPoint(command) if command[0].lower() == "to": command.pop(0) sourcePoint2 = self.readIntPoint(command) sourceSize = sourcePoint2 - sourcePoint else: sourceSize = self.readIntPoint(command, isPoint=False) if len([p for p in sourceSize if p <= 0]): raise UsageError("Box size cannot be zero or negative") box = BoundingBox(sourcePoint, sourceSize) return box
[docs] def readIntPoint(self, command, isPoint=True): point = self.readPoint(command, isPoint) point = map(int, map(floor, point)) return Vector(*point)
[docs] def readPoint(self, command, isPoint=True): self.prettySplit(command) try: word = command.pop(0) if isPoint and (word in self.level.players): x, y, z = self.level.getPlayerPosition(word) if len(command) and command[0].lower() == "delta": command.pop(0) try: x += int(command.pop(0)) y += int(command.pop(0)) z += int(command.pop(0)) except ValueError: raise UsageError("Error decoding point input (expected a number).") return x, y, z except IndexError: raise UsageError("Error decoding point input (expected more values).") try: try: x = float(word) except ValueError: if isPoint: raise PlayerNotFound(word) raise y = float(command.pop(0)) z = float(command.pop(0)) except ValueError: raise UsageError("Error decoding point input (expected a number).") except IndexError: raise UsageError("Error decoding point input (expected more values).") return x, y, z
[docs] def readBlockInfo(self, command): keyword = command.pop(0) matches = self.level.materials.blocksMatching(keyword) blockInfo = None if len(matches): if len(matches) == 1: blockInfo = matches[0] # eat up more words that possibly specify a block. stop eating when 0 matching blocks. while len(command): newMatches = self.level.materials.blocksMatching(keyword + " " + command[0]) if len(newMatches) == 1: blockInfo = newMatches[0] if len(newMatches) > 0: matches = newMatches keyword = keyword + " " + command.pop(0) else: break else: try: data = 0 if ":" in keyword: blockID, data = map(int, keyword.split(":")) else: blockID = int(keyword) blockInfo = self.level.materials.blockWithID(blockID, data) except ValueError: blockInfo = None if blockInfo is None: print "Ambiguous block specifier: ", keyword if len(matches): print "Matches: " for m in matches: if m == self.level.materials.defaultName: continue print "{0:3}:{1:<2} : {2}".format(m.ID, m.blockData, m.name) else: print "No blocks matched." raise BlockMatchError return blockInfo
@staticmethod
[docs] def readBlocksToCopy(command): blocksToCopy = range(materials.id_limit) while len(command): word = command.pop() if word == "noair": blocksToCopy.remove(0) if word == "nowater": blocksToCopy.remove(8) blocksToCopy.remove(9) return blocksToCopy
@staticmethod def _box(command): """ Boxes: Many commands require a <box> as arguments. A box can be specified with a point and a size: (12, 5, 15), (5, 5, 5) or with two points, making sure to put the keyword "to" between them: (12, 5, 15) to (17, 10, 20) The commas and parentheses are not important. You may add them for improved readability. Points: Points and sizes are triplets of numbers ordered X Y Z. X is position north-south, increasing southward. Y is position up-down, increasing upward. Z is position east-west, increasing westward. Players: A player's name can be used as a point - it will use the position of the player's head. Use the keyword 'delta' after the name to specify a point near the player. Example: codewarrior delta 0 5 0 This refers to a point 5 blocks above codewarrior's head. """ raise UsageError def _debug(self, command): self.debug = not self.debug print "Debug", ("disabled", "enabled")[self.debug] @staticmethod def _log(command): """ log [ <number> ] Get or set the log threshold. 0 logs everything; 50 only logs major errors. """ if len(command): try: logging.getLogger().level = int(command[0]) except ValueError: raise UsageError("Cannot understand numeric input.") else: print "Log level: {0}".format(logging.getLogger().level) def _clone(self, command): """ clone <sourceBox> <destPoint> [noair] [nowater] Clone blocks in a cuboid starting at sourcePoint and extending for sourceSize blocks in each direction. Blocks and entities in the area are cloned at destPoint. """ if len(command) == 0: self.printUsage("clone") return box = self.readBox(command) destPoint = self.readPoint(command) destPoint = map(int, map(floor, destPoint)) blocksToCopy = self.readBlocksToCopy(command) tempSchematic = self.level.extractSchematic(box) self.level.copyBlocksFrom(tempSchematic, BoundingBox((0, 0, 0), box.origin), destPoint, blocksToCopy) self.needsSave = True print "Cloned 0 blocks." def _fill(self, command): """ fill <blockType> [ <box> ] Fill blocks with blockType in a cuboid starting at point and extending for size blocks in each direction. Without a destination, fills the whole world. blockType and may be a number from 0-255 or a name listed by the 'blocks' command. """ if len(command) == 0: self.printUsage("fill") return blockInfo = self.readBlockInfo(command) if len(command): box = self.readBox(command) else: box = None print "Filling with {0}".format(blockInfo.name) self.level.fillBlocks(box, blockInfo) self.needsSave = True print "Filled {0} blocks.".format("all" if box is None else box.volume) def _replace(self, command): """ replace <blockType> [with] <newBlockType> [ <box> ] Replace all blockType blocks with newBlockType in a cuboid starting at point and extending for size blocks in each direction. Without a destination, replaces blocks over the whole world. blockType and newBlockType may be numbers from 0-255 or names listed by the 'blocks' command. """ if len(command) == 0: self.printUsage("replace") return blockInfo = self.readBlockInfo(command) if command[0].lower() == "with": command.pop(0) newBlockInfo = self.readBlockInfo(command) if len(command): box = self.readBox(command) else: box = None print "Replacing {0} with {1}".format(blockInfo.name, newBlockInfo.name) self.level.fillBlocks(box, newBlockInfo, blocksToReplace=[blockInfo]) self.needsSave = True print "Done." def _createchest(self, command): """ createChest <point> <item> [ <count> ] Create a chest filled with the specified item. Stacks are 64 if count is not given. """ point = map(lambda x: int(floor(float(x))), self.readPoint(command)) itemID = self.readInt(command) count = 64 if len(command): count = self.readInt(command) chest = mclevel.MCSchematic.chestWithItemID(itemID, count) self.level.copyBlocksFrom(chest, chest.bounds, point) self.needsSave = True def _analyze(self, command): """ analyze Counts all of the block types in every chunk of the world. """ blockCounts = zeros((65536,), 'uint64') print "Analyzing {0} chunks...".format(self.level.chunkCount) # for input to bincount, create an array of uint16s by # shifting the data left and adding the blocks for i, cPos in enumerate(self.level.allChunks, 1): ch = self.level.getChunk(*cPos) btypes = numpy.array(ch.Data.ravel(), dtype='uint16') btypes <<= 12 btypes += ch.Blocks.ravel() counts = bincount(btypes) blockCounts[:counts.shape[0]] += counts if i % 100 == 0: logging.info("Chunk {0}...".format(i)) for blockID in range(materials.id_limit): for data in range(16): i = (data << 12) + blockID if blockCounts[i]: idstring = "({id}:{data})".format(id=blockID, data=data) print "{idstring:9} {name:30}: {count:<10}".format( idstring=idstring, name=self.level.materials.blockWithID(blockID, data).name, count=blockCounts[i]) self.needsSave = True def _export(self, command): """ export <filename> <sourceBox> Exports blocks in the specified region to a file in schematic format. This file can be imported with mce or MCEdit. """ if len(command) == 0: self.printUsage("export") return filename = command.pop(0) box = self.readBox(command) tempSchematic = self.level.extractSchematic(box) tempSchematic.saveToFile(filename) print "Exported {0} blocks.".format(tempSchematic.bounds.volume) def _import(self, command): """ import <filename> <destPoint> [noair] [nowater] Imports a level or schematic into this world, beginning at destPoint. Supported formats include - Alpha single or multiplayer world folder containing level.dat, - Zipfile containing Alpha world folder, - Classic single-player .mine, - Classic multiplayer server_level.dat, - Indev .mclevel - Schematic from RedstoneSim, MCEdit, mce - .inv from INVEdit (appears as a chest) """ if len(command) == 0: self.printUsage("import") return filename = command.pop(0) destPoint = self.readPoint(command) blocksToCopy = self.readBlocksToCopy(command) importLevel = mclevel.fromFile(filename) self.level.copyBlocksFrom(importLevel, importLevel.bounds, destPoint, blocksToCopy, create=True) self.needsSave = True print "Imported {0} blocks.".format(importLevel.bounds.volume) def _player(self, command): """ player [ <player> [ <point> ] ] Move the named player to the specified point. Without a point, prints the named player's position. Without a player, prints all players and positions. In a single-player world, the player is named Player. """ if len(command) == 0: print "Players: " for player in self.level.players: print " {0}: {1}".format(player, self.level.getPlayerPosition(player)) return player = command.pop(0) if len(command) == 0: print "Player {0}: {1}".format(player, self.level.getPlayerPosition(player)) return point = self.readPoint(command) self.level.setPlayerPosition(point, player) self.needsSave = True print "Moved player {0} to {1}".format(player, point) def _spawn(self, command): """ spawn [ <point> ] Move the world's spawn point. Without a point, prints the world's spawn point. """ if len(command): point = self.readPoint(command) point = map(int, map(floor, point)) self.level.setPlayerSpawnPosition(point) self.needsSave = True print "Moved spawn point to ", point else: print "Spawn point: ", self.level.playerSpawnPosition() def _dumpsigns(self, command): """ dumpSigns [ <filename> ] Saves the text and location of every sign in the world to a text file. With no filename, saves signs to <worldname>.signs Output is newline-delimited. 5 lines per sign. Coordinates are on the first line, followed by four lines of sign text. For example: [229, 118, -15] "To boldly go where no man has gone before." Coordinates are ordered the same as point inputs: [North/South, Down/Up, East/West] """ if len(command): filename = command[0] else: filename = self.level.displayName + ".signs" # We happen to encode the output file in UTF-8 too, although # we could use another UTF encoding. The '-sig' encoding puts # a signature at the start of the output file that tools such # as Microsoft Windows Notepad and Emacs understand to mean # the file has UTF-8 encoding. outFile = codecs.open(filename, "w", encoding='utf-8-sig') print "Dumping signs..." signCount = 0 for i, cPos in enumerate(self.level.allChunks): try: chunk = self.level.getChunk(*cPos) except mclevelbase.ChunkMalformed: continue for tileEntity in chunk.TileEntities: if tileEntity["id"].value == "Sign": signCount += 1 outFile.write(str(map(lambda x: tileEntity[x].value, "xyz")) + "\n") for i in range(4): signText = tileEntity["Text{0}".format(i + 1)].value outFile.write(signText + u"\n") if i % 100 == 0: print "Chunk {0}...".format(i) print "Dumped {0} signs to {1}".format(signCount, filename) outFile.close() def _region(self, command): """ region [rx rz] List region files in this world. """ level = self.level assert (isinstance(level, mclevel.MCInfdevOldLevel)) assert level.version def getFreeSectors(rf): runs = [] start = None count = 0 for i, free in enumerate(rf.freeSectors): if free: if start is None: start = i count = 1 else: count += 1 else: if start is None: pass else: runs.append((start, count)) start = None count = 0 return runs def printFreeSectors(runs): for i, (start, count) in enumerate(runs): if i % 4 == 3: print "" print "{start:>6}+{count:<4}".format(**locals()), print "" if len(command): if len(command) > 1: rx, rz = map(int, command[:2]) print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks) rf = level.regionFiles.get((rx, rz)) if rf is None: print "Region {rx},{rz} not found.".format(**locals()) return print "Region {rx:6}, {rz:6}: {used}/{sectors} sectors".format(used=rf.usedSectors, sectors=rf.sectorCount) print "Offset Table:" for cx in range(32): for cz in range(32): if cz % 4 == 0: print "" print "{0:3}, {1:3}: ".format(cx, cz), off = rf.getOffset(cx, cz) sector, length = off >> 8, off & 0xff print "{sector:>6}+{length:<2} ".format(**locals()), print "" runs = getFreeSectors(rf) if len(runs): print "Free sectors:", printFreeSectors(runs) else: if command[0] == "free": print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks) for (rx, rz), rf in level.regionFiles.iteritems(): runs = getFreeSectors(rf) if len(runs): print "R {0:3}, {1:3}:".format(rx, rz), printFreeSectors(runs) else: print "Calling allChunks to preload region files: %d chunks" % len(level.allChunks) coords = (r for r in level.regionFiles) for i, (rx, rz) in enumerate(coords): print "({rx:6}, {rz:6}): {count}, ".format(count=level.regionFiles[rx, rz].chunkCount), if i % 5 == 4: print "" def _repair(self, command): """ repair Attempt to repair inconsistent region files. MAKE A BACKUP. WILL DELETE YOUR DATA. Scans for and repairs errors in region files: Deletes chunks whose sectors overlap with another chunk Rearranges chunks that are in the wrong slot in the offset table Deletes completely unreadable chunks Only usable with region-format saves. """ if self.level.version: self.level.preloadRegions() for rf in self.level.regionFiles.itervalues(): rf.repair() def _dumpchests(self, command): """ dumpChests [ <filename> ] Saves the content and location of every chest in the world to a text file. With no filename, saves signs to <worldname>.chests Output is delimited by brackets and newlines. A set of coordinates in brackets begins a chest, followed by a line for each inventory slot. For example: [222, 51, 22] 2 String 3 String 3 Iron bar Coordinates are ordered the same as point inputs: [North/South, Down/Up, East/West] """ from pymclevel.items import items if len(command): filename = command[0] else: filename = self.level.displayName + ".chests" outFile = file(filename, "w") print "Dumping chests..." chestCount = 0 for i, cPos in enumerate(self.level.allChunks): try: chunk = self.level.getChunk(*cPos) except mclevelbase.ChunkMalformed: continue for tileEntity in chunk.TileEntities: if tileEntity["id"].value == "Chest": chestCount += 1 outFile.write(str(map(lambda x: tileEntity[x].value, "xyz")) + "\n") itemsTag = tileEntity["Items"] if len(itemsTag): for itemTag in itemsTag: try: id = itemTag["id"].value damage = itemTag["Damage"].value item = items.findItem(id, damage) itemname = item.name except KeyError: itemname = "Unknown Item {0}".format(itemTag) except Exception, e: itemname = repr(e) outFile.write("{0} {1}:{2}\n".format(itemTag["Count"].value, itemname, itemTag["Damage"].value)) else: outFile.write("Empty Chest\n") if i % 100 == 0: print "Chunk {0}...".format(i) print "Dumped {0} chests to {1}".format(chestCount, filename) outFile.close() def _removeentities(self, command): """ removeEntities [ [except] [ <EntityID> [ <EntityID> ... ] ] ] Remove all entities matching one or more entity IDs. With the except keyword, removes all entities not matching one or more entity IDs. Without any IDs, removes all entities in the world, except for Paintings. Known Mob Entity IDs: Mob Monster Creeper Skeleton Spider Giant Zombie Slime Pig Sheep Cow Chicken Known Item Entity IDs: Item Arrow Snowball Painting Known Vehicle Entity IDs: Minecart Boat Known Dynamic Tile Entity IDs: PrimedTnt FallingSand """ ENT_MATCHTYPE_ANY = 0 ENT_MATCHTYPE_EXCEPT = 1 ENT_MATCHTYPE_NONPAINTING = 2 def match(entityID, matchType, matchWords): if ENT_MATCHTYPE_ANY == matchType: return entityID.lower() in matchWords elif ENT_MATCHTYPE_EXCEPT == matchType: return not (entityID.lower() in matchWords) else: # ENT_MATCHTYPE_EXCEPT == matchType return entityID != "Painting" removedEntities = {} match_words = [] if len(command): if command[0].lower() == "except": command.pop(0) print "Removing all entities except ", command match_type = ENT_MATCHTYPE_EXCEPT else: print "Removing {0}...".format(", ".join(command)) match_type = ENT_MATCHTYPE_ANY match_words = map(lambda x: x.lower(), command) else: print "Removing all entities except Painting..." match_type = ENT_MATCHTYPE_NONPAINTING for cx, cz in self.level.allChunks: chunk = self.level.getChunk(cx, cz) entitiesRemoved = 0 for entity in list(chunk.Entities): entityID = entity["id"].value if match(entityID, match_type, match_words): removedEntities[entityID] = removedEntities.get(entityID, 0) + 1 chunk.Entities.remove(entity) entitiesRemoved += 1 if entitiesRemoved: chunk.chunkChanged(False) if len(removedEntities) == 0: print "No entities to remove." else: print "Removed entities:" for entityID in sorted(removedEntities.keys()): print " {0}: {1:6}".format(entityID, removedEntities[entityID]) self.needsSave = True def _createchunks(self, command): """ createChunks <box> Creates any chunks not present in the specified region. New chunks are filled with only air. New chunks are written to disk immediately. """ if len(command) == 0: self.printUsage("createchunks") return box = self.readBox(command) chunksCreated = self.level.createChunksInBox(box) print "Created {0} chunks.".format(len(chunksCreated)) self.needsSave = True def _deletechunks(self, command): """ deleteChunks <box> Removes all chunks contained in the specified region. Chunks are deleted from disk immediately. """ if len(command) == 0: self.printUsage("deletechunks") return box = self.readBox(command) deletedChunks = self.level.deleteChunksInBox(box) print "Deleted {0} chunks.".format(len(deletedChunks)) def _prune(self, command): """ prune <box> Removes all chunks not contained in the specified region. Useful for enforcing a finite map size. Chunks are deleted from disk immediately. """ if len(command) == 0: self.printUsage("prune") return box = self.readBox(command) i = 0 for cx, cz in list(self.level.allChunks): if cx < box.mincx or cx >= box.maxcx or cz < box.mincz or cz >= box.maxcz: self.level.deleteChunk(cx, cz) i += 1 print "Pruned {0} chunks.".format(i) def _relight(self, command): """ relight [ <box> ] Recalculates lights in the region specified. If omitted, recalculates the entire world. """ if len(command): box = self.readBox(command) chunks = itertools.product(range(box.mincx, box.maxcx), range(box.mincz, box.maxcz)) else: chunks = self.level.allChunks self.level.generateLights(chunks) print "Relit 0 chunks." self.needsSave = True def _create(self, command): """ create [ <filename> ] Create and load a new Minecraft Alpha world. This world will have no chunks and a random terrain seed. If run from the shell, filename is not needed because you already specified a filename earlier in the command. For example: mce.py MyWorld create """ if len(command) < 1: raise UsageError("Expected a filename") filename = command[0] if not os.path.exists(filename): os.mkdir(filename) if not os.path.isdir(filename): raise IOError("{0} already exists".format(filename)) if mclevel.MCInfdevOldLevel.isLevel(filename): raise IOError("{0} is already a Minecraft Alpha world".format(filename)) level = mclevel.MCInfdevOldLevel(filename, create=True) self.level = level def _degrief(self, command): """ degrief [ <height> ] Reverse a few forms of griefing by removing Adminium, Obsidian, Fire, and Lava wherever they occur above the specified height. Without a height, uses height level 32. Removes natural surface lava. Also see removeEntities """ box = self.level.bounds box = BoundingBox(box.origin + (0, 32, 0), box.size - (0, 32, 0)) if len(command): try: box.miny = int(command[0]) except ValueError: pass print "Removing grief matter and surface lava above height {0}...".format(box.miny) self.level.fillBlocks(box, self.level.materials.Air, blocksToReplace=[self.level.materials.Bedrock, self.level.materials.Obsidian, self.level.materials.Fire, self.level.materials.LavaActive, self.level.materials.Lava, ] ) self.needsSave = True def _time(self, command): """ time [time of day] Set or display the time of day. Acceptable values are "morning", "noon", "evening", "midnight", or a time of day such as 8:02, 12:30 PM, or 16:45. """ ticks = self.level.Time timeOfDay = ticks % 24000 ageInTicks = ticks - timeOfDay if len(command) == 0: days = ageInTicks / 24000 hours = timeOfDay / 1000 clockHours = (hours + 6) % 24 ampm = ("AM", "PM")[clockHours > 11] minutes = (timeOfDay % 1000) / 60 print "It is {0}:{1:02} {2} on Day {3}".format(clockHours % 12 or 12, minutes, ampm, days) else: times = {"morning": 6, "noon": 12, "evening": 18, "midnight": 24} word = command[0] minutes = 0 if word in times: hours = times[word] else: try: if ":" in word: h, m = word.split(":") hours = int(h) minutes = int(m) else: hours = int(word) except Exception, e: raise UsageError(("Cannot interpret time, ", e)) if len(command) > 1: if command[1].lower() == "pm": hours += 12 ticks = ageInTicks + hours * 1000 + minutes * 1000 / 60 - 6000 if ticks < 0: ticks += 18000 ampm = ("AM", "PM")[11 < hours < 24] print "Changed time to {0}:{1:02} {2}".format(hours % 12 or 12, minutes, ampm) self.level.Time = ticks self.needsSave = True def _randomseed(self, command): """ randomseed [ <seed> ] Set or display the world's random seed, a 64-bit integer that uniquely defines the world's terrain. """ if len(command): try: seed = long(command[0]) except ValueError: raise UsageError("Expected a long integer.") self.level.RandomSeed = seed self.needsSave = True else: print "Random Seed: ", self.level.RandomSeed def _gametype(self, command): """ gametype [ <player> [ <gametype> ] ] Set or display the player's game type, an integer that identifies whether their game is survival (0) or creative (1). On single-player worlds, the player is just 'Player'. """ if len(command) == 0: print "Players: " for player in self.level.players: print " {0}: {1}".format(player, self.level.getPlayerGameType(player)) return player = command.pop(0) if len(command) == 0: print "Player {0}: {1}".format(player, self.level.getPlayerGameType(player)) return try: gametype = int(command[0]) except ValueError: raise UsageError("Expected an integer.") self.level.setPlayerGameType(gametype, player) self.needsSave = True def _worldsize(self, command): """ worldsize Computes and prints the dimensions of the world. For infinite worlds, also prints the most negative corner. """ bounds = self.level.bounds if isinstance(self.level, mclevel.MCInfdevOldLevel): print "\nWorld size: \n {0[0]:7} north to south\n {0[2]:7} east to west\n".format(bounds.size) print "Smallest and largest points: ({0[0]},{0[2]}), ({1[0]},{1[2]})".format(bounds.origin, bounds.maximum) else: print "\nWorld size: \n {0[0]:7} wide\n {0[1]:7} tall\n {0[2]:7} long\n".format(bounds.size) def _heightmap(self, command): """ heightmap <filename> Takes a png and imports it as the terrain starting at chunk 0,0. Data is internally converted to greyscale and scaled to the maximum height. The game will fill the terrain with trees and mineral deposits the next time you play the level. Please please please try out a small test image before using a big source. Using the levels tool to get a good heightmap is an art, not a science. A smaller map lets you experiment and get it right before having to blow all night generating the really big map. Requires the PIL library. """ if len(command) == 0: self.printUsage("heightmap") return if not sys.stdin.isatty() or raw_input( "This will destroy a large portion of the map and may take a long time. Did you really want to do this?" ).lower() in ("yes", "y", "1", "true"): from PIL import Image import datetime filename = command.pop(0) imgobj = Image.open(filename) greyimg = imgobj.convert("L") # luminance del imgobj width, height = greyimg.size water_level = 64 xchunks = (height + 15) / 16 zchunks = (width + 15) / 16 start = datetime.datetime.now() for cx in range(xchunks): for cz in range(zchunks): try: self.level.createChunk(cx, cz) except: pass c = self.level.getChunk(cx, cz) imgarray = numpy.asarray(greyimg.crop((cz * 16, cx * 16, cz * 16 + 16, cx * 16 + 16))) imgarray /= 2 # scale to 0-127 for x in range(16): for z in range(16): if z + (cz * 16) < width - 1 and x + (cx * 16) < height - 1: # world dimension X goes north-south # first array axis goes up-down h = imgarray[x, z] c.Blocks[x, z, h + 1:] = 0 # air c.Blocks[x, z, h:h + 1] = 2 # grass c.Blocks[x, z, h - 4:h] = 3 # dirt c.Blocks[x, z, :h - 4] = 1 # rock if h < water_level: c.Blocks[x, z, h + 1:water_level] = 9 # water if h < water_level + 2: c.Blocks[x, z, h - 2:h + 1] = 12 # sand if it's near water level c.Blocks[x, z, 0] = 7 # bedrock c.chunkChanged() c.TerrainPopulated = False # the quick lighting from chunkChanged has already lit this simple terrain completely c.needsLighting = False logging.info("%s Just did chunk %d,%d" % (datetime.datetime.now().strftime("[%H:%M:%S]"), cx, cz)) logging.info("Done with mapping!") self.needsSave = True stop = datetime.datetime.now() logging.info("Took %s." % str(stop - start)) spawnz = width / 2 spawnx = height / 2 spawny = greyimg.getpixel((spawnx, spawnz)) logging.info("You probably want to change your spawn point. I suggest {0}".format((spawnx, spawny, spawnz))) def _execute(self, command): """ execute <filename> Execute all commands in a file and save. """ if len(command) == 0: print "You must give the file with commands to execute" else: commandFile = open(command[0], "r") commandsFromFile = commandFile.readlines() for commandFromFile in commandsFromFile: print commandFromFile self.processCommand(commandFromFile) self._save("") def _quit(self, command): """ quit [ yes | no ] Quits the program. Without 'yes' or 'no', prompts to save before quitting. In batch mode, an end of file automatically saves the level. """ if len(command) == 0 or not (command[0].lower() in ("yes", "no")): if raw_input("Save before exit? ").lower() in ("yes", "y", "1", "true"): self._save(command) raise SystemExit if len(command) and command[0].lower == "yes": self._save(command) raise SystemExit def _exit(self, command): self._quit(command) def _save(self, command): if self.needsSave: self.level.generateLights() self.level.saveInPlace() self.needsSave = False def _load(self, command): """ load [ <filename> | <world number> ] Loads another world, discarding all changes to this world. """ if len(command) == 0: self.printUsage("load") self.loadWorld(command[0]) def _reload(self, command): self.level = mclevel.fromFile(self.level.filename) def _dimension(self, command): """ dimension [ <dim> ] Load another dimension, a sub-world of this level. Without options, lists all of the dimensions found in this world. <dim> can be a number or one of these keywords: nether, hell, slip: DIM-1 earth, overworld, parent: parent world end: DIM1 """ if len(command): if command[0].lower() in ("earth", "overworld", "parent"): if self.level.parentWorld: self.level = self.level.parentWorld return else: print "You are already on earth." return elif command[0].lower() in ("hell", "nether", "slip"): dimNo = -1 elif command[0].lower() == "end": dimNo = 1 else: dimNo = self.readInt(command) if dimNo in self.level.dimensions: self.level = self.level.dimensions[dimNo] return if self.level.parentWorld: print u"Parent world: {0} ('dimension parent' to return)".format(self.level.parentWorld.displayName) if len(self.level.dimensions): print u"Dimensions in {0}:".format(self.level.displayName) for k in self.level.dimensions: print "{0}: {1}".format(k, infiniteworld.MCAlphaDimension.dimensionNames.get(k, "Unknown")) def _help(self, command): if len(command): self.printUsage(command[0]) else: self.printUsage() def _blocks(self, command): """ blocks [ <block name> | <block ID> ] Prints block IDs matching the name, or the name matching the ID. With nothing, prints a list of all blocks. """ if len(command): searchName = " ".join(command) try: searchNumber = int(searchName) except ValueError: matches = self.level.materials.blocksMatching(searchName) else: matches = [b for b in self.level.materials.allBlocks if b.ID == searchNumber] # print "{0:3}: {1}".format(searchNumber, self.level.materials.names[searchNumber]) # return else: matches = self.level.materials.allBlocks print "{id:9} : {name} {aka}".format(id="(ID:data)", name="Block name", aka="[Other names]") for b in sorted(matches): idstring = "({ID}:{data})".format(ID=b.ID, data=b.blockData) aka = b.aka and " [{aka}]".format(aka=b.aka) or "" print "{idstring:9} : {name} {aka}".format(idstring=idstring, name=b.name, aka=aka)
[docs] def printUsage(self, command=""): if command.lower() in self.commands: print "Usage: ", self.commandUsage(command.lower()) else: print self.__doc__.format(commandPrefix=("", "mce.py <world> ")[not self.batchMode])
[docs] def printUsageAndQuit(self): self.printUsage() raise SystemExit
[docs] def loadWorld(self, world): worldpath = os.path.expanduser(world) if os.path.exists(worldpath): self.level = mclevel.fromFile(worldpath) else: self.level = mclevel.loadWorld(world)
level = None batchMode = False
[docs] def run(self): logging.basicConfig(format=u'%(levelname)s:%(message)s') logging.getLogger().level = logging.INFO sys.argv.pop(0) if len(sys.argv): world = sys.argv.pop(0) if world.lower() in ("-h", "--help"): self.printUsageAndQuit() if len(sys.argv) and sys.argv[0].lower() == "create": # accept the syntax, "mce world3 create" self._create([world]) print "Created world {0}".format(world) sys.exit(0) else: self.loadWorld(world) else: self.batchMode = True self.printUsage() while True: try: world = raw_input("Please enter world name or path to world folder: ") self.loadWorld(world) except EOFError: print "End of input." raise SystemExit except Exception, e: print "Cannot open {0}: {1}".format(world, e) else: break if len(sys.argv): # process one command from command line try: self.processCommand(" ".join(sys.argv)) except UsageError: self.printUsageAndQuit() self._save([]) else: # process many commands on standard input, maybe interactively self.batchMode = True while True: try: command = raw_input(u"{0}> ".format(self.level.displayName)) print self.processCommand(command) except EOFError: print "End of file. Saving automatically." self._save([]) raise SystemExit except Exception, e: if self.debug: traceback.print_exc() print 'Exception during command: {0!r}'.format(e) print "Use 'debug' to enable tracebacks." # self.printUsage()
[docs] def processCommand(self, command): command = command.strip() if len(command) == 0: return if command[0] == "#": return commandWords = command.split() keyword = commandWords.pop(0).lower() if keyword not in self.commands: matches = filter(lambda x: x.startswith(keyword), self.commands) if len(matches) == 1: keyword = matches[0] elif len(matches): print "Ambiguous command. Matches: " for k in matches: print " ", k return else: raise UsageError("Command {0} not recognized.".format(keyword)) func = getattr(self, "_" + keyword) try: func(commandWords) except PlayerNotFound, e: print "Cannot find player {0}".format(e.args[0]) self._player([]) except UsageError, e: print e if self.debug: traceback.print_exc() self.printUsage(keyword)
[docs]def main(argv): profile = os.getenv("MCE_PROFILE", None) editor = mce() if profile: print "Profiling enabled" import cProfile cProfile.runctx('editor.run()', locals(), globals(), profile) else: editor.run() return 0
if __name__ == '__main__': sys.exit(main(sys.argv))