Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if __name__ == "__main__":
- print("_parse_scene.py should be used as a module only!")
- exit(0)
- _DEBUG = False
- _PRINT_LAYERS = False #_DEBUG must be True as well
- from os.path import isfile, exists
- from pprint import pprint
- import xml.etree.ElementTree as ET
- obj_data_types = {
- "u8", "s8",
- "u16", "s16",
- "u24", "s24",
- "u32", "s32",
- "u64", "s64",
- "f32", "f64",
- "rgb", "argb"
- }
- def printError(s, fatal=True):
- if _DEBUG or fatal: print("Error: {}".format(s))
- if fatal: exit(-1)
- else : return False
- def sceneIdToPath(scene_id):
- return "../scene_{}.tmx".format(scene_id)
- #poor man's regex lol
- def extractTilesetName(path):
- #will turn something like "../foo/tileset_collision.tsx" into "collision"
- name = path.split("/")[-1]
- return (("_").join(name.split("."))).split("_")[1]
- def tryInt(string):
- try:
- return int(string)
- except ValueError:
- return None
- except TypeError:
- return None
- def tryDict(dict, key):
- try:
- return dict[key]
- except KeyError:
- return None
- def keyWarn(properties, name, fatal=False, cat="property"):
- if tryDict(properties, name) == None:
- #intentionally indented twice
- if fatal: print(" Error: {} \"{}\" doesn't exist!".format(cat, name)); exit(-1)
- elif _DEBUG: print(" Warning: {} \"{}\" doesn't exist!".format(cat, name))
- return True #key does not exist
- else:
- return False #key exists
- #value is a hex quad string of "#AARRGGBB"
- def toRGBA(value):
- alpha = int(value[1:3], 16)
- red = int(value[3:5], 16)
- blue = int(value[5:7], 16)
- green = int(value[7:9], 16)
- return (red, green, blue, alpha)
- #dataValue should always be of type str
- def convertType(dataType, dataValue):
- #(i wish python had switch statements...)
- if dataType == "int" : return int(float(dataValue))
- elif dataType == "bool" : return eval(dataValue.capitalize())
- elif dataType == "float" : return float(dataValue)
- elif dataType == "color" : return toRGBA(dataValue)
- elif dataType == "object" : return int(float(dataValue))
- #elif dataType == 'file' : return dataValue #(redundant)
- else : return dataValue
- def tryTypeWarn(type_str):
- type_num = tryInt(type_str.split("_")[0])
- if type_num != None:
- return type_num
- else:
- if _DEBUG: print("Warning: type \"{}\" is invalid".format(type_str))
- return 0
- #(mostly) yoinked from stackoverflow lol
- def checkForDuplicateIndexes(thelist):
- seen = set()
- for x in thelist:
- if x[0][0] in seen:
- printError("found duplicate obj data index \"{}\"".format(x[0][0]))
- seen.add(x[0][0])
- def parseObject(obj_in):
- obj_out = { "data":[] }
- props_a = obj_in.attrib
- op = "object property"
- if _DEBUG: print(" parsing object \"{}\"...".format(tryDict(props_a, "name") or "NAMELESS"))
- if keyWarn(props_a, "name" , cat=op): obj_out["name" ] = "NAMELESS"
- else : obj_out["name" ] = props_a["name"]
- if keyWarn(props_a, "x" , cat=op): obj_out["x" ] = 0
- else : obj_out["x" ] = convertType("int", props_a["x"])
- if keyWarn(props_a, "y" , cat=op): obj_out["y" ] = 0
- else : obj_out["y" ] = convertType("int", props_a["y"])
- if keyWarn(props_a, "width" , cat=op): obj_out["width" ] = 0
- else : obj_out["width" ] = convertType("int", props_a["width"])
- if keyWarn(props_a, "height", cat=op): obj_out["height"] = 0
- else : obj_out["height"] = convertType("int", props_a["height"])
- if keyWarn(props_a, "type" , cat=op): obj_out["type" ] = 0
- else : obj_out["type" ] = tryTypeWarn(props_a["type"])
- #this should occur whether or not _DEBUG is True
- if obj_out["type"] == 0: print(" Warning: object \"{}\"'s type is equal to 0".format(obj_out["name"]))
- #hitbox offset is not an intrinsic object property,
- #so they need to be specified with a custom property
- obj_out["hb_offset_x"] = 0
- obj_out["hb_offset_y"] = 0
- _props_b = obj_in.find("properties")
- props_b = []
- if _props_b != None:
- for prop in _props_b.findall("property"):
- pName = prop.attrib["name" ].split("_")
- pType = prop.attrib["type" ]
- pValue = prop.attrib["value"]
- if len(pName) < 2: printError("\"{}\" isn't a valid obj data property name".format(pName[0]))
- pIndex = tryInt(pName[0])
- if pIndex == None: printError("\"{}\" has an invalid obj data property index".format(pName[0]))
- pName[0] = pIndex
- props_b.append( (pName, convertType(pType, pValue)) )
- checkForDuplicateIndexes(props_b) #properties can't share the same index
- props_b = sorted(props_b, key = lambda x: x[0][0] ) #sort by their indexes
- else:
- return obj_out
- data = []
- for prop in props_b:
- pType = prop[0][1].lower()
- pName = ("_").join(prop[0][2:])
- pValue = prop[1]
- if pType not in obj_data_types:
- printError("\"{}\" is not a valid obj data type".format(pType))
- if pName[0:10] == "hb_offset_":
- if pType != "u8":
- printError("hb_offset_<x/y> is not of type u8")
- if pName != "hb_offset_x" and pName != "hb_offset_y":
- printError("malformed offset name \"{}\"".format(pName))
- obj_out[pName] = pValue
- else:
- data.append( (pType, pValue, pName) )
- obj_out["data"] = data
- return obj_out
- def printLayer(layers, name):
- if tryDict(layers, name) == None: return
- count = 0
- print(" {}: [".format(name), end="")
- for tile in layers[name]:
- if count%32 == 0: print("\n ", end="")
- print(str(tile).rjust(3), end=",")
- count += 1
- print("\n ],")
- def printScene(scene, printLayers=_PRINT_LAYERS):
- print(" --PROPERTIES--:")
- pprint(scene["properties"], indent=4)
- if printLayers:
- print(" --LAYERS--")
- printLayer(scene["layers"], "collision")
- printLayer(scene["layers"], "fg" )
- printLayer(scene["layers"], "mg" )
- print(" --OBJECTS--")
- pprint(scene["objs"])
- def parseSceneMap(scene_id, announce=True):
- filename = sceneIdToPath(scene_id)
- if not exists(filename): printError("scene \"{}\" does not exist".format(filename))
- if not isfile(filename): printError("scene \"{}\" is not a file".format(filename))
- if _DEBUG or announce: print("PARSING \"{}\":".format(filename))
- tree = ET.parse(filename)
- map = tree.getroot()
- #get map's intrinsic properties
- width = int(map.attrib["width" ]) #should be 32
- height = int(map.attrib["height" ]) #should be 18
- tileWidth = int(map.attrib["tilewidth" ]) #should be 24 (unused)
- tileHeight = int(map.attrib["tileheight"]) #should be 24 (unused)
- mapLength = width*height
- if width != 32: printError("map width is not 32")
- if height != 18: printError("map height is not 18")
- if tileWidth != 24: printError("tile width is not 24")
- if tileHeight != 24: printError("tile height is not 24")
- #get map's custom properties
- props = {}
- for prop in map.find("properties"):
- pName = tryDict(prop.attrib, "name" ) or "NAMELESS"
- pType = tryDict(prop.attrib, "type" )
- pValue = tryDict(prop.attrib, "value") or "NOVALUE"
- props[ pName ] = convertType(pType, pValue)
- if keyWarn(props, "bmp_bg" ): props["bmp_bg" ] = 0
- if keyWarn(props, "repeat_bg" ): props["repeat_bg" ] = False #lol
- #if keyWarn(props, "objs_len" ): props["objs_len" ] = 0 #(calculated later)
- #if keyWarn(props, "tileset_a" ): props["tileset_a" ] = 0 #(calculated later)
- #if keyWarn(props, "tileset_b" ): props["tileset_b" ] = 0 #(calculated later)
- if keyWarn(props, "edge_n" ): props["edge_n" ] = 0
- if keyWarn(props, "edge_s" ): props["edge_s" ] = 0
- if keyWarn(props, "edge_w" ): props["edge_w" ] = 0
- if keyWarn(props, "edge_e" ): props["edge_e" ] = 0
- #if keyWarn(props, "scene" ): props["scene" ] = 0 #(calculated later)
- if keyWarn(props, "music" ): props["music" ] = 0
- if keyWarn(props, "ambience_a"): props["ambience_a"] = 0
- if keyWarn(props, "ambience_b"): props["ambience_b"] = 0
- #calculate tileset boundaries
- _tsets = []
- for tset in map.findall("tileset"):
- tFirstGID = int(tryDict(tset.attrib, "firstgid")) - 1
- tName = extractTilesetName(tryDict(tset.attrib, "source"))
- _tsets.append( ( tFirstGID, tryInt(tName) or 0 ) )
- tsets = sorted(_tsets, key = lambda x: x[0] ) #sorted by first element
- if len(tsets) < 3: printError("map cannot have less than 3 tilesets (including collision)")
- elif len(tsets) > 3: printError("map cannot have more than 3 tilesets (including collision)")
- #there should only be 1 tileset that registers as null (the collision's tileset)
- tset_offsets = [0,] * 3
- tset_nulls, tset_valids = 0, 0
- tset_a, tset_b = 0, 0
- for i in range(len(tsets)):
- #collision tileset
- if tsets[i][1] == 0: tset_offsets[i] = -1; tset_nulls += 1
- #tileset_a
- elif tset_valids == 0: tset_a = tsets[i][1]; tset_valids += 1
- #tileset_b
- else : tset_b = tsets[i][1]; tset_offsets[i] = 128
- if tset_nulls != 1: printError("map must have exactly 1 null tileset, reserved for collider map")
- #get map's layer data
- layers = {}
- for layer in map.findall("layer"):
- lName = tryDict(layer.attrib,"name" ) or "NAMELESS"
- lWidth = int( tryDict(layer.attrib,"width" ) or 0 )
- lHeight = int( tryDict(layer.attrib,"height") or 0 )
- lLength = lWidth*lHeight
- # n-1 to make sure both tile 0 and 1 are treated as transparent
- lData = [ max(int(n)-1, 0) for n in layer.find("data").text.split(",") ] #csv to list
- if lLength != mapLength : valid = printError("layer dims inconsistent with map dimensions")
- if lLength != len(lData): valid = printError("layer dims inconsistent with its attributes")
- layers[ lName ] = lData
- valid = True
- if "collision" not in layers: valid = printError("layer \"collision\" doesn't exist", quit=False)
- if "fg" not in layers: valid = printError("layer \"fg\" doesn't exist", quit=False)
- if "mg" not in layers: valid = printError("layer \"mg\" doesn't exist", quit=False)
- if not valid: exit(-1)
- for i in range(len(layers["collision"])):
- #collider info is 6 bits (2^6 = 64),
- #which is a factor of a normal tileset's 7 (2^7 = 128) anyway
- layers["collision"][i] %= 64
- for i in range(len(layers["fg"])): #(fg & mg should be the same length)
- #fg
- tile = layers["fg"][i]
- offset = tset_offsets[tile//128] #tile//128 should be between 0 -> 2
- if offset == -1 and (tile%128) != 0:
- printError("fg cannot contain collision map tiles")
- layers["fg"][i] = (tile%128) + max(offset, 0)
- if layers["fg"][i] == 128: layers["fg"][i] = 0 #128 is also transparent
- #mg
- tile = layers["mg"][i]
- offset = tset_offsets[tile//128]
- if offset == -1 and (tile%128) != 0:
- printError("mg cannot contain collision map tiles")
- layers["mg"][i] = (tile%128) + max(offset, 0)
- if layers["mg"][i] == 128: layers["mg"][i] = 0 #128 is also transparent
- #check if a given layer's data should be omitted from scene descriptor file
- fg_nonzero = False
- mg_nonzero = False
- for i in range(len(layers["fg"])): #(fg & mg should be the same length)
- if (layers["fg"][i]%128) != 0: fg_nonzero = True
- if (layers["mg"][i]%128) != 0: mg_nonzero = True
- if not fg_nonzero and not fg_nonzero:
- printError("fg and mg cannot both be empty")
- props["objs_len" ] = 0
- props["tileset_a" ] = tset_a
- props["tileset_b" ] = tset_b
- props["scene" ] = scene_id
- props["fg_nonzero"] = fg_nonzero
- props["mg_nonzero"] = mg_nonzero
- #get scene objects
- obj_groups = map.findall("objectgroup")
- objs = []
- if len(obj_groups) > 1: printError("scene cannot have more than 1 object group")
- if len(obj_groups) == 1:
- #(this loop will not run at all if there are no objects)
- for obj in obj_groups[0].findall("object"):
- props["objs_len" ] += 1
- objs.append(parseObject(obj))
- #automatically mark edge as a loop if that edge is null
- if props["edge_n"] == 0: props["edge_n"] = scene_id
- if props["edge_s"] == 0: props["edge_s"] = scene_id
- if props["edge_w"] == 0: props["edge_w"] = scene_id
- if props["edge_e"] == 0: props["edge_e"] = scene_id
- #error if all 4 edges make the scene loop
- edges = (scene_id, props["edge_n"], props["edge_s"],
- props["edge_w"], props["edge_e"])
- if all(i==edges[0] for i in edges):
- printError("all 4 edges make scene loop")
- scene = {}
- scene["properties"] = props
- scene["layers" ] = layers
- scene["objs" ] = objs
- if _DEBUG: printScene(scene)
- return scene
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement