forked from mcedit/pymclevel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
indev.py
325 lines (258 loc) · 11.4 KB
/
indev.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
'''
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 mclevelbase import Blocks, Data, Entities, Height, Length, Map, TileEntities, Width
from numpy import array, swapaxes, uint8
import nbt
import os
log = getLogger(__name__)
warn, error, info, debug = log.warn, log.error, log.info, log.debug
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"):
for x in self.root_tag["Entities"]:
if x["id"].value == "LocalPlayer":
x["Pos"] = nbt.TAG_List([nbt.TAG_Float(p) for p in pos])
def getPlayerPosition(self, player="Ignored"):
for x in self.root_tag["Entities"]:
if x["id"].value == "LocalPlayer":
return array(map(lambda x: x.value, x["Pos"]))
def setPlayerOrientation(self, yp, player="Ignored"):
for x in self.root_tag["Entities"]:
if x["id"].value == "LocalPlayer":
x["Rotation"] = nbt.TAG_List([nbt.TAG_Float(p) for p in yp])
def getPlayerOrientation(self, player="Ignored"):
""" returns (yaw, pitch) """
for x in self.root_tag["Entities"]:
if x["id"].value == "LocalPlayer":
return array(map(lambda x: x.value, x["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 not Entities 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 not TileEntities 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))
if len(filter(lambda x: x['id'].value == 'LocalPlayer', root_tag[Entities])) == 0: # omen doesn't make a player entity
p = nbt.TAG_Compound()
p['id'] = nbt.TAG_String('LocalPlayer')
p['Pos'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(64.), nbt.TAG_Float(0.)])
p['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(45.)])
root_tag[Entities].append(p)
# self.saveInPlace()
else:
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)
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 == None:
filename = self.filename
if filename == None:
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(name=Map)
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.root_tag[Map]
# 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.saveGzipped(filename)
except:
os.rename(filename + ".old", filename)
try:
os.remove(filename + ".old")
except Exception:
pass
self.BlockLight = self.Data & 0xf
self.Data >>= 4