-
Notifications
You must be signed in to change notification settings - Fork 13
/
generate_map.py
333 lines (284 loc) · 11.1 KB
/
generate_map.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# generate overall world
import os
import sys
from PIL import Image
from pymclevel import mclevel, box, materials, nbt
from pymclevel.materials import alphaMaterials as m
import random
from tree import Tree, treeObjs
minecraft_save_dir = "."
# On Mac OS X:
# minecraft_save_dir = "/Users/[me]/Library/Application Support/minecraft/saves/"
# On Linux:
# minecraft_save_dir = "/home/[me]/.minecraft/saves/"
map_type = None
if len(sys.argv) > 1:
map_type = sys.argv[1]
if map_type == 'game':
game_mode = 0 # Survival
else:
game_mode = 1 # Creative
if map_type not in ('map', 'game'):
print "Usage: %s [game|map] (map prefix)" % sys.argv[0]
print " 'game' turns the map into a playable survival game."
print " 'map' just renders the map."
sys.exit()
if len(sys.argv) > 2:
filename_prefix = sys.argv[1]
else:
filename_prefix = "fort-washington"
# R-values from the texture TIFF are converted to blocks of the given
# blockID, blockData, depth.
block_id_lookup = {
0 : (m.Grass.ID, None, 2),
10 : (m.Dirt.ID, 1, 1), # blockData 1 == grass can't spread
20 : (m.Grass.ID, None, 2),
30 : (m.Cobblestone.ID, None, 1),
40 : (m.StoneBricks.ID, None, 3),
200 : (m.Water.ID, 0, 2), # blockData 0 == normal state of water
210 : (m.WaterActive.ID, 0, 1),
220 : (m.Water.ID, 0, 1),
}
plant_chance = {
m.Watermelon.ID : 0.00001,
m.Pumpkin.ID : 0.00001,
m.SugarCane.ID : 0.01,
"tree" : 0.001,
m.TallGrass.ID : 0.003,
"flower" : 0.0035,
}
def random_material():
"""Materials to be hidden underground to help survival play."""
stone_chance = 0.90
very_common = [m.Sand, m.Cobblestone, m.CoalOre, m.IronOre]
common = [m.Clay, m.Obsidian, m.Gravel, m.MossStone, m.Dirt]
uncommon = [m.RedstoneOre, m.LapisLazuliOre, m.GoldOre, 129]
rare = [ m.Glowstone, m.DiamondOre, m.BlockofIron, m.TNT,
m.BlockofGold, m.LapisLazuliBlock]
very_rare = [ m.BlockofDiamond ]
x = random.random()
choice = None
l = None
if x < stone_chance:
choice = m.Stone
elif x < 0.96:
l = very_common
elif x < 0.985:
l = common
elif x < 0.998:
l = uncommon
elif x < 0.9995:
l = rare
else:
l = very_rare
if l is not None:
choice = random.choice(l)
if not isinstance(choice, int):
choice = choice.ID
return choice
# Set these values to only render part of the map, either by
# offsetting the origin or displaying a smaller size.
x_offset = 0
truncate_size = 0
elevation_min = 255
elevation_max = 0
# Fun fact: the Fort Washington map is 1.27 miles high and 818 pixels
# high, so our scale is 2.49 meters per pixel. Range on the map is
# from 0 feet (sea level) to 180 feet, or 60 blocks.
voxel_min = 0
voxel_max = 60.0
y_min = 12
print "Loading bitmaps for %s" % filename_prefix
data = dict(elevation=[], features=[])
for t in 'elevation', 'features':
filename = filename_prefix + "-" + t + ".tif"
if not os.path.exists(filename):
print "Could not load image file %s!" % filename
sys.exit()
img = Image.open(filename, "r")
width, height = img.size
for i in range(max(width, truncate_size)):
row = []
for j in range(max(height, truncate_size)):
pixel = img.getpixel((i,j))
value = pixel[0]
if t == 'features':
value = (value, pixel[1]) # block ID, block data
if t == 'elevation':
elevation_min = min(value, elevation_min)
elevation_max = max(value, elevation_max)
row.append(value)
data[t].append(row)
elevation= data['elevation']
material = data['features']
if truncate_size:
elevation = elevation[x_offset:x_offset+truncate_size]
material = material[x_offset:x_offset+truncate_size]
# Scale the height map so that it covers a good range of Minecraft's
# available elevation
scale_factor = (voxel_max-voxel_min) / (elevation_max-elevation_min)
print "Bitmap is %s high, %s wide" % (len(elevation), len(elevation[0]))
print "Scale factor: %s" % scale_factor
def setspawnandsave(world, point):
"""Sets the spawn point and player point in the world and saves the world.
Taken from TopoMC and tweaked to set biome.
"""
world.GameType = game_mode
spawn = point
spawn[1] += 2
world.setPlayerPosition(tuple(point))
world.setPlayerSpawnPosition(tuple(spawn))
# In game mode, set the biome to Plains (1) so passive
# mobs will spawn.
# In map mode, set the biome to Ocean (0) so they won't.
if map_type == 'game':
biome = 1
else:
biome = 0
numchunks = 0
biomes = TAG_Byte_Array([biome] * 256, "Biomes")
for i, cPos in enumerate(world.allChunks, 1):
ch = world.getChunk(*cPos)
if ch.root_tag:
ch.root_tag['Level'].add(biomes)
numchunks += 1
world.saveInPlace()
print "Saved %d chunks." % numchunks
# Where does the world file go?
i = 0
worlddir = None
while not worlddir or os.path.exists(worlddir):
i += 1
name = filename_prefix + " " + map_type + " " + str(i)
worlddir = os.path.join(minecraft_save_dir, name)
print "Creating world %s" % worlddir
world = mclevel.MCInfdevOldLevel(worlddir, create=True)
from pymclevel.nbt import TAG_Int, TAG_String, TAG_Byte_Array
tags = [TAG_Int(0, "MapFeatures"),
TAG_String("flat", "generatorName"),
TAG_String("0", "generatorOptions")]
for tag in tags:
world.root_tag['Data'].add(tag)
peak = [10, 255, 10]
print "Creating chunks."
x_extent = len(elevation)
x_min = 0
x_max = len(elevation)
z_min = 0
z_extent = len(elevation[0])
z_max = z_extent
extra_space = 1
bedrock_bottom_left = [-extra_space, 0,-extra_space]
bedrock_upper_right = [x_extent + extra_space + 1, y_min-1, z_extent + extra_space + 1]
glass_bottom_left = list(bedrock_bottom_left)
glass_bottom_left[1] += 1
glass_upper_right = [x_extent + extra_space+1, 255, z_extent + extra_space+1]
air_bottom_left = (0,y_min,0)
air_upper_right = [x_extent, 255, z_extent]
# Glass walls
wall_material = m.Glass
print "Putting up walls: %r %r" % (glass_bottom_left, glass_upper_right)
tilebox = box.BoundingBox(glass_bottom_left, glass_upper_right)
chunks = world.createChunksInBox(tilebox)
world.fillBlocks(tilebox, wall_material)
# Air in the middle.
bottom_left = (0, 1, 0)
upper_right = (len(elevation), 255, len(elevation[0]))
print "Carving out air layer. %r %r" % (bottom_left, upper_right)
tilebox = box.BoundingBox(bottom_left, upper_right)
world.fillBlocks(tilebox, m.Air, [wall_material])
max_height = (world.Height-elevation_min) * scale_factor
print "Populating chunks."
for x, row in enumerate(elevation):
for z, y in enumerate(row):
block_id, ignore = material[x][z]
block_id, block_data, depth = block_id_lookup[block_id]
y = int(y * scale_factor)
actual_y = y + y_min
if actual_y > peak[1] or (peak[1] == 255 and y != 0):
peak = [x,actual_y,z]
# Don't fill up the whole map from bedrock, just draw a shell.
start_at = max(1, actual_y-depth-10)
# If we were going to optimize this code, this is where the
# optimization would go. Lay down the stone in big slabs and
# then sprinkle goodies into it.
stop_at = actual_y-depth
for elev in range(start_at, stop_at):
if map_type == 'map' or elev == stop_at-1:
block = m.Stone.ID
else:
block = random_material()
world.setBlockAt(x,elev,z, block)
start_at = actual_y - depth
stop_at = actual_y + 1
if block_id == m.WaterActive.ID:
# Carve a little channel for active water so it doesn't overflow.
start_at -= 1
stop_at -= 1
for elev in range(start_at, stop_at):
world.setBlockAt(x, elev, z, block_id)
if block_data:
world.setBlockDataAt(x, elev, z, block_data)
# In game mode, sprinkle some semi-realistic outdoor features
# onto the grass.
if map_type == "game" and block_id == m.Grass.ID:
for plant, probability in plant_chance.items():
choice = random.random()
if choice < probability:
if plant == 'tree':
# Plant a TopoMC tree here.
tree_type = random.choice([2,4,5,5])
# print "Planting a tree at (%s,%s,%s)" % (x, elev+1, z)
(blocks, block_data) = treeObjs[tree_type]((x,elev+1,z))
[world.setBlockAt(tx, ty, tz, block.ID) for (tx, ty, tz, block) in blocks if block != m.Air and tx >= x_min and tz >= z_min and tx <= x_max and tz <= z_max]
[world.setBlockDataAt(tx, ty, tz, bdata) for (tx, ty, tz, bdata) in block_data if bdata != 0 and tx >= x_min and tz >= z_min and tx <= x_max and tz <= z_max]
elif plant == 'flower':
# Plant a flower, nothing too fancy.
id, data = random.choice(
[(37,None), (38,None), (38,3), (38,8)])
world.setBlockAt(x, elev+1,z, id)
if data:
world.setBlockDataAt(x, elev+1,z, data)
elif plant == m.SugarCane.ID:
# Must be next to water
for water_x in (x-1, x+1):
for water_z in (z-1, z+1):
data = world.blockAt(water_x, y, water_z)
if data == m.Water.ID:
# Yay, we found water.
world.setBlockAt(x, elev+1, z, plant)
print "Yay, water at %s, %s, %s" % (x, elev+1,z)
else:
world.setBlockAt(x, elev+1,z, plant)
break
# I can't quite get this to work. The chest shows up but the supplies
# don't.
#
# # Add a chest beneath spawn point with some survival supplies.
# chest_x, chest_y, chest_z = list(peak)
# chest_y -= 2
# chunk = world.getChunk(chest_x/16, chest_z/16)
# world.setBlockAt(chest_x,chest_y,chest_z, m.Chest.ID)
# tiles = nbt.TAG_List()
# chunk.root_tag[TileEntities] = tiles
# chestTag = nbt.TAG_Compound()
# chestTag['id'] = nbt.TAG_String(str(m.Chest.ID-1)) # "Chest"
# chestTag['x'] = nbt.TAG_Int(chest_x)
# chestTag['y'] = nbt.TAG_Int(chest_y)
# chestTag['z'] = nbt.TAG_Int(chest_z)
# tiles.append(chestTag)
# print "Chest at %s,%s,%s" % (chest_x, chest_y, chest_z)
# inventory = []
# starting_chest_contents = [(m.SugarCane, 6), (m.Pumpkin, 1),
# (m.Watermelon, 1), (m.Cactus, 3)]
# for i, (block, quantity) in enumerate(starting_chest_contents):
# slot = i
# print "%s %s in slot %s" % (quantity, block, slot)
# itemTag = nbt.TAG_Compound()
# itemTag["Slot"] = nbt.TAG_Byte(slot)
# itemTag["Count"] = nbt.TAG_Byte(quantity)
# itemTag["id"] = nbt.TAG_Short(block.ID)
# inventory.append(itemTag)
# print "%s items in chest." % nbt.TAG_List(inventory)
# chestTag["Items"] = nbt.TAG_List(inventory)
setspawnandsave(world, peak)