-
Notifications
You must be signed in to change notification settings - Fork 18
/
mapstore.py
336 lines (306 loc) · 12.2 KB
/
mapstore.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
333
334
335
336
# Generates map items from MCDungeon block arrays
# NBT Format for maps:
#
# TAG_Compound
# TAG_Compound "data"
# TAG_Byte "scale" (set to 0 for 1:1)
# TAG_Int "xCenter" (center of map in world coords)
# TAG_Int "zCenter" (center of map in world coords)
# TAG_Short "height" (must be 128)
# TAG_Short "width" (must be 128)
# TAG_Byte "dimension" (0 == overworld)
# TAG_Byte_Array "colors" (xoffset + zoffset * width) 0 == upper left
import os
import sys
import cPickle
import shutil
import hashlib
from numpy import uint8, zeros
from pymclevel import nbt
from utils import Vec
import materials
import items
class new:
def __init__(self, mapstore, dir_paintings='paintings', mapcolor=11141120,
paintingcolor=14079638):
self.mapstore = os.path.join(mapstore, 'data')
self.mapcolor = mapcolor
self.paintingcolor = paintingcolor
# Load the idcounts.dat NBT if it exists, otherwise make
# a new one.
try:
self.idcounts = nbt.load(
os.path.join(
self.mapstore,
'idcounts.dat'))
except:
print 'No idcounts.dat file found. Creating a new one...'
self.idcounts = nbt.TAG_Compound()
# Load the mcdungeon map ID usage cache
if (os.path.isfile(os.path.join(self.mapstore, 'mcdungeon_maps'))):
try:
with open(os.path.join(self.mapstore, 'mcdungeon_maps'), 'rb') as FILE:
self.mapcache = cPickle.load(FILE)
except Exception as e:
print e
print "Failed to read the mcdungeon maps cache file."
print "The file tracking MCDungeon map usage may be corrupt."
print "You can try deleting or moving this file to recover:"
print os.path.join(self.mapstore, 'mcdungeon_maps')
sys.exit(1)
else:
print 'Mapstore cache not found. Creating new one...'
self.mapcache = {'used': {}, 'available': set([])}
# Generate map hash table
self.maphash = {}
for file in os.listdir(self.mapstore):
if (str(file.lower()).endswith(".dat") and
str(file.lower()).startswith("map_")):
# Gen hash and extract map ID
hash = hashlib.md5(
open(
os.path.join(
self.mapstore,
file),
'r').read()).digest()
self.maphash[hash] = int(file[4:-4])
# Store paintings path
if os.path.isdir(os.path.join(sys.path[0], dir_paintings)):
self.painting_path = os.path.join(sys.path[0], dir_paintings)
elif os.path.isdir(dir_paintings):
self.painting_path = dir_paintings
else:
sys.exit("Error: Could not find the paintings folder!")
def update_mapstore(self):
'''Flushes the idcounts.dat and mcdungeon_maps cache to disk'''
try:
with open(os.path.join(self.mapstore, 'mcdungeon_maps'), 'wb') as FILE:
cPickle.dump(self.mapcache, FILE, -1)
except Exception as e:
print e
sys.exit('Failed to write mcdungeon_maps.')
# Minecraft expects this one to be uncompressed. Compressed versions
# cause java exceptions in Minecraft.
try:
self.idcounts.save(os.path.join(self.mapstore, 'idcounts.dat'),
compressed=False)
except Exception as e:
print e
sys.exit('Failed to write idcounts.dat.')
def delete_maps(self, loc):
'''Delete maps for the given dungeon key and return those map IDs to the
avilable pool.'''
if loc in self.mapcache['used']:
self.mapcache['available'].update(self.mapcache['used'][loc])
for m in self.mapcache['used'][loc]:
f = os.path.join(self.mapstore, 'map_%d.dat' % (m))
try:
os.remove(f)
except:
pass
del(self.mapcache['used'][loc])
self.update_mapstore()
else:
print 'No maps found for dungeon at ', loc
def add_painting(self, painting_file):
src = os.path.join(self.painting_path, painting_file + '.dat')
painting_file_hash = hashlib.md5(open(src, 'r').read()).digest()
# Look up file in hashtable
if painting_file_hash in self.maphash:
mapid = self.maphash[painting_file_hash]
else:
# Initialize the map count if it doesn't exist.
if 'map' not in self.idcounts:
self.idcounts['map'] = nbt.TAG_Short(-1)
# Increment and return id
self.idcounts['map'].value += 1
mapid = self.idcounts['map'].value
# Copy the map to the data dir
dest = os.path.join(self.mapstore, 'map_%d.dat' % (mapid))
try:
shutil.copy(src, dest)
except:
sys.exit('Error when placing painting in map directory.')
self.maphash[painting_file_hash] = mapid # Update hashtable
self.update_mapstore()
# Create map item tag
item = nbt.TAG_Compound()
item['id'] = nbt.TAG_String(items.byName('map').id)
item['Damage'] = nbt.TAG_Short(mapid)
item['Count'] = nbt.TAG_Byte(1)
# Fetch the lore text for this map
lorefile = open(
os.path.join(
self.painting_path,
painting_file +
'.txt'))
loredata = lorefile.read().splitlines()
lorefile.close()
# Create display tags
valid_characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ "
item['tag'] = nbt.TAG_Compound()
item['tag']['display'] = nbt.TAG_Compound()
item['tag']['display']['Name'] = nbt.TAG_String(
filter(
lambda x: x in valid_characters,
loredata.pop(0)))
item['tag']['display']['Lore'] = nbt.TAG_List()
# Slice at 5 lines of 50 chars each
for p in loredata[:5]:
line = filter(lambda x: x in valid_characters, p)
item['tag']['display']['Lore'].append(nbt.TAG_String(line[:50]))
item['tag']['display']['MapColor'] = nbt.TAG_Int(self.paintingcolor)
return item
def generate_map(self, dungeon, level):
'''Generate a new map, save it to disk, flush the cache, and return a
map item NBT with the appropriate map ID.'''
dungeon_key = '%s,%s' % (dungeon.position.x, dungeon.position.z)
if dungeon_key not in self.mapcache['used']:
self.mapcache['used'][dungeon_key] = set([])
# Find a map id. Look in the available list for old mcdungeon maps
# that can be reused. If not, bump up the idcount and use that.
if len(self.mapcache['available']) == 0:
# Initialize the map count if it doesn't exist.
if 'map' not in self.idcounts:
self.idcounts['map'] = nbt.TAG_Short(-1)
self.idcounts['map'].value += 1
mapid = self.idcounts['map'].value
self.mapcache['used'][dungeon_key].add(mapid)
else:
mapid = self.mapcache['available'].pop()
self.mapcache['used'][dungeon_key].add(mapid)
filename = os.path.join(self.mapstore, 'map_%d.dat' % (mapid))
# Setup the defaults.
# Offset will be way off somewhere were players are unlikely to go
# to avoid the maps from being overwritten. Nothing else really
# matters.
tags = nbt.TAG_Compound()
tags['data'] = nbt.TAG_Compound()
tags['data']['scale'] = nbt.TAG_Byte(0)
tags['data']['xCenter'] = nbt.TAG_Int(-12500000)
tags['data']['zCenter'] = nbt.TAG_Int(-12500000)
tags['data']['height'] = nbt.TAG_Short(128)
tags['data']['width'] = nbt.TAG_Short(128)
tags['data']['dimension'] = nbt.TAG_Byte(0)
tags['data']['colors'] = nbt.TAG_Byte_Array(zeros(16384, uint8))
# Generate the map.
blocks = dungeon.blocks
colors = tags['data']['colors'].value
y = level * dungeon.room_height - 3
# Scale the map. We only scale up, not down since scaling
# looks terrible.
max_dungeon = max(dungeon.xsize * dungeon.room_size,
dungeon.zsize * dungeon.room_size)
max_dungeon = max(128, max_dungeon)
# If the size is less than 8, try to center it.
xoffset = 0
zoffset = 0
if dungeon.xsize * dungeon.room_size < 128:
xoffset = (128 - dungeon.xsize * dungeon.room_size) / 2
if dungeon.zsize * dungeon.room_size < 128:
zoffset = (128 - dungeon.zsize * dungeon.room_size) / 2
# Draw pixels on the map corresponding to blocks just above
# floor level. Color chests and spawners. Hide things that should be
# hidden.
for x in xrange(128):
for z in xrange(128):
block = Vec(x * max_dungeon / 128 - xoffset,
y,
z * max_dungeon / 128 - zoffset)
if block in blocks:
mat = blocks[block].material
if mat == materials.StonePressurePlate:
colors[x + z * 128] = 10
elif blocks[block].hide is True:
colors[x + z * 128] = 0
elif blocks[block].blank is True:
colors[x + z * 128] = 0
elif mat == materials.Air:
colors[x + z * 128] = 10
elif mat == materials.Spawner:
colors[x + z * 128] = 48
elif (mat == materials.Chest or
mat == materials.TrappedChest):
colors[x + z * 128] = 42
else:
colors[x + z * 128] = 54
else:
colors[x + z * 128] = 0
# Draw the level number in the corner
digits = [
[0, 1, 0,
1, 0, 1,
1, 0, 1,
1, 0, 1,
0, 1, 0],
[0, 1, 0,
1, 1, 0,
0, 1, 0,
0, 1, 0,
1, 1, 1],
[1, 1, 1,
0, 0, 1,
1, 1, 1,
1, 0, 0,
1, 1, 1],
[1, 1, 0,
0, 0, 1,
0, 1, 0,
0, 0, 1,
1, 1, 0],
[1, 0, 1,
1, 0, 1,
1, 1, 1,
0, 0, 1,
0, 0, 1],
[1, 1, 1,
1, 0, 0,
1, 1, 1,
0, 0, 1,
1, 1, 1],
[0, 1, 1,
1, 0, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1],
[1, 1, 1,
0, 0, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0],
[1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
1, 1, 1],
[1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 1,
1, 1, 1]
]
sx = 120
if level < 10:
sx = 124
sz = 123
for d in str(level):
for x in xrange(3):
for z in xrange(5):
if digits[int(d)][x + z * 3] == 1:
colors[x + sx + (z + sz) * 128] = 16
sx += 4
# Save the map file, cache, and idcount.dat
tags.save(filename)
self.update_mapstore()
# Return a map item
item = nbt.TAG_Compound()
item['id'] = nbt.TAG_String(items.byName('map').id)
item['Damage'] = nbt.TAG_Short(mapid)
item['Count'] = nbt.TAG_Byte(1)
item['tag'] = nbt.TAG_Compound()
item['tag']['display'] = nbt.TAG_Compound()
name = dungeon.dungeon_name + ' Lv {l}'
item['tag']['display']['Name'] = nbt.TAG_String(name.format(l=level))
item['tag']['display']['MapColor'] = nbt.TAG_Int(self.mapcolor)
print item['tag']['display']['Name'].value
return item