319 lines
11 KiB
Python
319 lines
11 KiB
Python
"""
|
|
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 numpy import array, swapaxes
|
|
import nbt
|
|
import os
|
|
|
|
log = getLogger(__name__)
|
|
|
|
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"):
|
|
self.LocalPlayer["Pos"] = nbt.TAG_List([nbt.TAG_Float(p) for p in pos])
|
|
|
|
def getPlayerPosition(self, player="Ignored"):
|
|
return array(map(lambda x: x.value, self.LocalPlayer["Pos"]))
|
|
|
|
def setPlayerOrientation(self, yp, player="Ignored"):
|
|
self.LocalPlayer["Rotation"] = nbt.TAG_List([nbt.TAG_Float(p) for p in yp])
|
|
|
|
def getPlayerOrientation(self, player="Ignored"):
|
|
""" returns (yaw, pitch) """
|
|
return array(map(lambda x: x.value, self.LocalPlayer["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 "Entities" not 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 "TileEntities" not 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))
|
|
|
|
|
|
localPlayerList = [tag for tag in root_tag["Entities"] if tag['id'].value == 'LocalPlayer']
|
|
if len(localPlayerList) == 0: # omen doesn't make a player entity
|
|
playerTag = nbt.TAG_Compound()
|
|
playerTag['id'] = nbt.TAG_String('LocalPlayer')
|
|
playerTag['Pos'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(64.), nbt.TAG_Float(0.)])
|
|
playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0.), nbt.TAG_Float(45.)])
|
|
self.LocalPlayer = playerTag
|
|
|
|
else:
|
|
self.LocalPlayer = localPlayerList[0]
|
|
|
|
else:
|
|
log.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)
|
|
log.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 is None:
|
|
filename = self.filename
|
|
if filename is None:
|
|
log.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()
|
|
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.Entities.append(self.LocalPlayer)
|
|
# 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.save(filename)
|
|
except:
|
|
os.rename(filename + ".old", filename)
|
|
|
|
try:
|
|
os.remove(filename + ".old")
|
|
except Exception:
|
|
pass
|
|
|
|
self.Entities.remove(self.LocalPlayer)
|
|
|
|
self.BlockLight = self.Data & 0xf
|
|
|
|
self.Data >>= 4
|