In [ ]:
#!/usr/bin/env python
#import the minecraft.py module from the minecraft directory
import mcpi.minecraft as minecraft
#import minecraft block module
import mcpi.block as block
#import time, so delays can be used
import time
import os
import getopt
import sys
try:
import server
server_address = server.address
except:
server_address = "127.0.0.1"
block_id_to_name = {
'0':'AIR',
'1':'STONE',
'2':'GRASS',
'3':'DIRT',
'4':'COBBLESTONE',
'5':'WOOD_PLANKS',
'6':'SAPLING',
'7':'BEDROCK',
'8':'WATER_FLOWING',
'9':'WATER_STATIONARY',
'10':'LAVA_FLOWING',
'11':'LAVA_STATIONARY',
'12':'SAND',
'13':'GRAVEL',
'14':'GOLD_ORE',
'15':'IRON_ORE',
'16':'COAL_ORE',
'17':'WOOD',
'18':'LEAVES',
'20':'GLASS',
'21':'LAPIS_LAZULI_ORE',
'22':'LAPIS_LAZULI_BLOCK',
'24':'SANDSTONE',
'26':'BED',
'30':'COBWEB',
'31':'GRASS_TALL',
'35':'WOOL',
'37':'FLOWER_YELLOW',
'38':'FLOWER_CYAN',
'39':'MUSHROOM_BROWN',
'40':'MUSHROOM_RED',
'41':'GOLD_BLOCK',
'42':'IRON_BLOCK',
'43':'STONE_SLAB_DOUBLE',
'44':'STONE_SLAB',
'45':'BRICK_BLOCK',
'46':'TNT',
'47':'BOOKSHELF',
'48':'MOSS_STONE',
'49':'OBSIDIAN',
'50':'TORCH',
'51':'FIRE',
'53':'STAIRS_WOOD',
'54':'CHEST',
'56':'DIAMOND_ORE',
'57':'DIAMOND_BLOCK',
'58':'CRAFTING_TABLE',
'60':'FARMLAND',
'61':'FURNACE_INACTIVE',
'62':'FURNACE_ACTIVE',
'64':'DOOR_WOOD',
'65':'LADDER',
'67':'STAIRS_COBBLESTONE',
'71':'DOOR_IRON',
'73':'REDSTONE_ORE',
'78':'SNOW',
'79':'ICE',
'80':'SNOW_BLOCK',
'81':'CACTUS',
'82':'CLAY',
'83':'SUGAR_CANE',
'85':'FENCE',
'89':'GLOWSTONE_BLOCK',
'95':'BEDROCK_INVISIBLE',
'98':'STONE_BRICK',
'102':'GLASS_PANE',
'103':'MELON',
'107':'FENCE_GATE',
'246':'GLOWING_OBSIDIAN',
'247':'NETHER_REACTOR_CORE'
}
def usage(exit_val, output_file):
print """
usage: minescan [-hcevj] [-x num] [-y num] [-z num]
[-X num] [-Y num] [-Z num] [-o outputfile]
MineScan will connect to Minecraft PI Edition server or Bukkit Server using the
RaspberryJuice plugin. It will by default scan a region and generate a python
script which can be used to regenerate the blocks in that region.
There is also an option to create a glass box around the region it will scan.
This can be used to first define an area in which you will later create something
in minecraft, then scan it.
The options are as follows:
-h: this Help message on how to use minescan
-c: Create a glass container around the specified coordinates. Doesn't affect
what's inside the container.
-e: Creates a EMPTY glass container. This will annihilate anything inside the
glass container.
-v: Verbose output on the console, prints information on what's found...
-j: If specified, scripts assumes it's running on RaspberryJuice which doesn't
support the getBlockWithData command. As a result, some detail won't be scanned.
MineScan needs to be told the xyz coordinates of where to scan and/or create
the container and the how far to scan. This is done by specifying:
-x num: The starting value of x. This can be positive or negative
-y num: The starting value of y. This can be positive or negative
-z num: The starting value of z. This can be positive or negative
-X num: The starting value of X. This can be positive or negative
-Y num: The starting value of Y. This can be positive or negative
-Z num: The starting value of Z. This can be positive or negative
The defaults are as follows:
x = 0, y = 0, z = 0, X = 10, Y = 10, Z = 10
This means that the program will scan and/or create a container
from 0,0,0 to 10,10,10.
Note that when scanning the program must scan each block one by one,
so in the above example 10 * 10 * 10 or 1000 blocks must be scanned. This
takes some time. So if you increase these numbers significantly it will take
some time to complete!
Finally...
-o filename: specifies what file to output, such as "scanned.py". If this
file exists, it will be overwritten! The default is to create a filename with
a prefix of "my-minescanned" with the date/time and a ".py".
If not specified, this time the program would have output to %s
-s address: Specifies the IP address/hostname of the Minecraft Server.
Default is 127.0.0.1 (local machine)
""" % (output_file)
sys.exit(exit_val)
# I've considered writing this as a class and sharing data via member variables, however I want this
# script to be easy to understand for anyone that is starting to learn python
# as a result I'm passing all arguments to the function. It's ugly but easier for someone starting to learn.
def generate_container(mc, opt_ispy, opt_x1, opt_y1, opt_z1, opt_x2, opt_y2, opt_z2, verbose, opt_container, opt_container_empty):
if verbose:
print "Creating Container(%s, %s, %s, %s, %s, %s)" % (opt_x1, opt_y1, opt_z1, opt_x2, opt_y2, opt_z2)
if not opt_container_empty:
# this will create an glass container without attempting to keep what's inside.
# it does this by creating a large filled glass container, than hollowing it out with air
# thus removing anything inside.
mc.setBlocks(opt_x1-1,opt_y1-1,opt_z1-1,opt_x2+1,opt_y2+1,opt_z2+1,block.GLASS.id)
mc.setBlocks(opt_x1,opt_y1,opt_z1,opt_x2,opt_y2,opt_z2,block.AIR.id)
else:
# this will create a glass container around whatever is inside.
# as a result this takes additional steps as it has to create each of the 6 sides.
mc.setBlocks(opt_x1-1,opt_y1-1,opt_z1-1,opt_x2+1,opt_y2+1,opt_z1-1, block.GLASS.id)
mc.setBlocks(opt_x1-1,opt_y1-1,opt_z1-1,opt_x1-1,opt_y2+1,opt_z2+1, block.GLASS.id)
mc.setBlocks(opt_x1-1,opt_y1-1,opt_z1-1,opt_x2+1,opt_y1-1,opt_z2+1, block.GLASS.id) # floor
mc.setBlocks(opt_x1-1,opt_y2+1,opt_z1-1,opt_x2+1,opt_y2+1,opt_z2+1, block.GLASS.id) # roof
mc.setBlocks(opt_x2+1,opt_y1-1,opt_z2+1,opt_x2+1,opt_y2+1,opt_z1-1, block.GLASS.id)
mc.setBlocks(opt_x2+1,opt_y1-1,opt_z2+1,opt_x1-1,opt_y2+1,opt_z2+1, block.GLASS.id)
return
def minescan(mc, opt_ispy, opt_x1, opt_y1, opt_z1, opt_x2, opt_y2, opt_z2, verbose, output_file):
try:
py_out = open(output_file, "w")
except:
print "Unable to open file %s for writing, exiting" % (output_file)
sys.exit(1)
py_out.write("""#!/usr/bin/env python
# generated by minescan.py from mcpipy.com
import mcpi.minecraft as minecraft
import mcpi.block as block
import server
mc = minecraft.Minecraft.create(server.address)
mc.postToChat("Re-creating world")
# The following was generated by minescan.py from mcpipy.com
""")
# write the rest of your code here...
# mc.postToChat("Hello MCPIPY World!")
total_cycles = ((opt_x2 - opt_x1)+1) * ((opt_y2 - opt_y1)+1) * ((opt_z2 - opt_z1)+1)
if verbose:
print "Scanning %d blocks" % total_cycles
cycle = 0
block_data = 0
for y in range(0, (opt_y2 - opt_y1)+1):
for x in range(0, (opt_x2 - opt_x1)+1):
for z in range(0, (opt_z2 - opt_z1)+1):
if opt_ispy:
(block_id, block_data) = mc.getBlockWithData(opt_x1 + x,opt_y1 + y,opt_z1 + z)
else:
block_id = mc.getBlock(opt_x1 + x,opt_y1 + y,opt_z1 + z)
# block_id = 0
if block_id:
if str(block_id) in block_id_to_name:
if block_data:
if verbose:
print "mc.getBlockWithData(%s,%s,%s) = %s, %s" % (x,y,z,block_id_to_name[str(block_id)],block_data)
py_out.write("mc.setBlock(%s,%s,%s,block.%s.id,%s)\n" % (x,y,z,block_id_to_name[str(block_id)],block_data))
else:
if verbose:
print "mc.getBlock(%s,%s,%s) = %s" % (x,y,z,block_id_to_name[str(block_id)])
py_out.write("mc.setBlock(%s,%s,%s,block.%s.id)\n" % (x,y,z,block_id_to_name[str(block_id)]))
else: # Block ID not defined in MCPI v0.1.1
if block_data:
if verbose:
print "mc.getBlockWithData(%s,%s,%s) = %s, %s" % (x,y,z,block_id, block_data)
py_out.write("# Note this block is not supported in Minecraft Pi Edition v0.1.1\n")
py_out.write("mc.setBlock(%s,%s,%s,%s,%s)\n" % (x,y,z,block_id, block_data))
else:
if verbose:
print "mc.getBlock(%s,%s,%s) = %s" % (x,y,z,block_id)
py_out.write("# Note this block is not supported in Minecraft Pi Edition v0.1.1\n")
py_out.write("mc.setBlock(%s,%s,%s,%s)\n" % (x,y,z,block_id))
else: # No block id returned
if verbose:
print "mc.getBlock(%s,%s,%s) = %s" % (x,y,z,block_id_to_name[str(block_id)])
cycle += 1
# Print this once every 10 cycles
if not cycle % 10:
percent_complete = (float(cycle) / float(total_cycles)) * 100
print "[%d/%d] %.f%s complete" % (cycle, total_cycles, percent_complete, "%")
py_out.write("mc.postToChat('World Re-created!')\n")
try:
os.fchmod(py_out.fileno(), 0755)
except:
print "Unable to set execute bit"
py_out.close()
def main(argv):
global server_address
output_file = "my-minescanned-%s.py" % (time.strftime("%Y%m%d-%H%M%S"))
opt_x1 = 0
opt_y1 = 0
opt_z1 = 0
opt_x2 = 10
opt_y2 = 10
opt_z2 = 10
opt_ispy = True
# verbose = True
verbose = False
opt_container_empty = False
opt_container = False
try:
# h = help
# o: = set output file
# x: = set opt_x
# y: = set opt_y
# z: = set opt_z
# x: = set opt_x
# y: = set opt_y
# z: = set opt_z
# c = create container, don't scan
# e = create empty container, don't scan
# v = verbose mode
# s: = server ip
opts, args = getopt.getopt(argv,"ho:x:y:z:X:Y:Z:b:u:cevs:j",["ofile="])
except getopt.GetoptError:
usage(2, output_file)
for opt, arg in opts:
if opt == '-h':
usage(0, output_file)
elif opt == '-x':
opt_x1 = int(arg)
elif opt == '-y':
opt_y1 = int(arg)
elif opt == '-z':
opt_z1 = int(arg)
elif opt == '-X':
opt_x2 = int(arg)
elif opt == '-Y':
opt_y2 = int(arg)
elif opt == '-Z':
opt_z2 = int(arg)
elif opt == '-j':
opt_ispy = False
elif opt == '-v':
verbose = True
elif opt == '-e':
opt_container = True
elif opt == '-c':
# opt_container = True
opt_container_empty = True
elif opt == '-s':
server_address = arg
elif opt in ("-o", "--ofile"):
output_file = arg
if (opt_x2 <= opt_x1) or (opt_y2 <= opt_y1) or (opt_z2 <= opt_z1):
print "Error, the second number must always be greater than the first number."
print """
x1 = "%s"
x2 = "%s"
y1 = "%s"
y2 = "%s"
z1 = "%s"
z2 = "%s"
""" % (opt_x1, opt_x2, opt_y1, opt_y2, opt_z1, opt_z2)
print "Exiting"
sys.exit(-1)
if verbose:
print """
Options as follows:
output file = "%s"
x1 = "%s"
x2 = "%s"
y1 = "%s"
y2 = "%s"
z1 = "%s"
z2 = "%s"
Create Container: %s
Create Empty Container: %s
Server: %s
Is Raspberry Pi: %s
""" % (output_file, opt_x1, opt_x2, opt_y1, opt_y2, opt_z1, opt_z2,
opt_container, opt_container_empty, server_address, opt_ispy)
elif not opt_container and not opt_container_empty:
print 'Output file is "%s"' % (output_file)
mc = minecraft.Minecraft.create(server_address)
if opt_container or opt_container_empty:
generate_container(mc, opt_ispy, opt_x1, opt_y1, opt_z1, opt_x2, opt_y2, opt_z2, verbose, opt_container, opt_container_empty)
else:
minescan(mc, opt_ispy, opt_x1, opt_y1, opt_z1, opt_x2, opt_y2, opt_z2, verbose, output_file)
if __name__ == "__main__":
main(sys.argv[1:])