Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /******************************************************************************/
- /******************************************************************************/
- //"kw32g\asset_scripts\_copy_all_scenes.py":
- _PRINT = False
- project_folder = '\\kw32g'
- from time import time
- from os import chdir, system as cmd
- from os.path import exists
- def tryCmd(s, ps=True):
- if _PRINT:
- print(s)
- if ps: cmd('powershell -command '+s)
- else : cmd(s)
- else:
- if ps: cmd(f'powershell -command {s} >nul')
- else : cmd(s+' >nul')
- def main(cwd = None):
- #if not None, cwd should be _scene/tools
- if cwd != None: chdir(cwd)
- timeStart = time()
- #happens whether or not _PRINT is True
- print("copying all scenes...")
- #and the game's root folder should be on
- #the same level as the assets folder
- game_bin = f'..\\..\\..\\{project_folder}\\bin'
- deb_scn = game_bin+'\\_Debug\\dat\\scene'
- rel_scn = game_bin+'\\_Release\\dat\\scene'
- #delete save to account for any potential
- #new differences in binary structure
- #(otherwise it'll fail to read properly!)
- tryCmd(f'rm {game_bin}\\_Debug\\save\\*.*')
- tryCmd(f'rm {game_bin}\\_Release\\save\\*.*')
- deb_sys = f'{game_bin}\\_Debug\\dat\\system_states.bin'
- rel_sys = f'{game_bin}\\_Release\\dat\\system_states.bin'
- if exists(deb_sys): tryCmd(f'rm {deb_sys}')
- if exists(rel_sys): tryCmd(f'rm {rel_sys}')
- #removes all scenes from game files
- tryCmd(f'rm {deb_scn}\\*.*')
- tryCmd(f'rm {rel_scn}\\*.*')
- #copies new scenes to game files
- tryCmd(f'XCOPY /E /Y ..\\_ksd {deb_scn}')
- tryCmd(f'XCOPY /E /Y ..\\_ksd {rel_scn}')
- if _PRINT:
- print(f'SCENE COPIED IN {time()-timeStart} SECONDS')
- if __name__ == "__main__":
- main()
- tryCmd('pause')/******************************************************************************/
- /******************************************************************************/
- //"kw32g\asset_scripts\_generate_arrlen.py":
- project_folder = '\\kw32g'
- from os import chdir, system as cmd, listdir
- from os.path import isfile, join
- arrlen_fmt = '\
- #ifndef _ARRLEN_HPP\n\
- #define _ARRLEN_HPP\n\
- \n\
- #define gl_scenes_len {}\n\
- #define gl_backgrounds_len {}\n\
- #define gl_tilesets_len {}\n\
- #define gl_objImg_len {}\n\
- #define gl_ambience_len {}\n\
- \n\
- #endif /* _ARRLEN_HPP */\n'
- def saveArrlen(path, scenes, backgrounds, tilesets, objImgs, ambiences):
- arrlen = arrlen_fmt.format(scenes, backgrounds, tilesets, objImgs, ambiences)
- #print(arrlen)
- file = open(path, 'w')
- file.write(arrlen)
- file.close()
- def filesInDirectory(_dir):
- return len([name for name in listdir(_dir) if isfile(join(_dir, name))])
- #cwd should be root of assets folder
- def main(cwd = None):
- if cwd != None: chdir(cwd)
- scenes_len = filesInDirectory('.\\_scene\\_tmx\\')-2 #-2 to remove scene 0 and py script
- backgrounds_len = filesInDirectory('.\\_background\\_qoi\\')
- tilesets_len = filesInDirectory('.\\_tileset\\_qoi\\')-1 #-1 for tileset_missing
- objImg_len = filesInDirectory('.\\_objimg\\_qoi\\')
- ambience_len = filesInDirectory('.\\_ambience\\_qoa\\')
- print("generating arrlen...")
- saveArrlen(f'..{project_folder}\\include\\arrlen.hpp',
- scenes_len,
- backgrounds_len,
- tilesets_len,
- objImg_len,
- ambience_len)
- if __name__ == '__main__':
- cmd('powershell -command pause')
- exit(0)
- if __name__ == '__main__': main()/******************************************************************************/
- /******************************************************************************/
- //"kw32g\asset_scripts\_generate_arrlen_clone.py":
- project_folder = '\\kw32g'
- from os import chdir, system as cmd, listdir
- from os.path import isfile, join
- arrlen_fmt = '\
- #ifndef _ARRLEN_HPP\n\
- #define _ARRLEN_HPP\n\
- \n\
- #define gl_scenes_len {}\n\
- #define gl_backgrounds_len {}\n\
- #define gl_tilesets_len {}\n\
- #define gl_objImg_len {}\n\
- #define gl_ambience_len {}\n\
- \n\
- #endif /* _ARRLEN_HPP */\n'
- def saveArrlen(path, scenes, backgrounds, tilesets, objImgs, ambiences):
- arrlen = arrlen_fmt.format(scenes, backgrounds, tilesets, objImgs, ambiences)
- #print(arrlen)
- file = open(path, 'w')
- file.write(arrlen)
- file.close()
- def filesInDirectory(_dir):
- return len([name for name in listdir(_dir) if isfile(join(_dir, name))])
- #cwd should be assets/_scene/_tools
- def main(cwd = None):
- if cwd != None: chdir(cwd)
- scenes_len = filesInDirectory('..\\..\\_scene\\_tmx\\')-2 #-2 to remove scene 0 and py script
- backgrounds_len = filesInDirectory('..\\..\\_background\\_qoi\\')
- tilesets_len = filesInDirectory('..\\..\\_tileset\\_qoi\\')-1 #-1 for tileset_missing
- objImg_len = filesInDirectory('..\\..\\_objimg\\_qoi\\')
- ambience_len = filesInDirectory('..\\..\\_ambience\\_qoa\\')
- print("generating arrlen...")
- saveArrlen(f'..\\..\\..{project_folder}\\include\\arrlen.hpp',
- scenes_len,
- backgrounds_len,
- tilesets_len,
- objImg_len,
- ambience_len)
- if __name__ == '__main__':
- cmd('powershell -command pause')
- exit(0)
- if __name__ == '__main__': main()/******************************************************************************/
- /******************************************************************************/
- //"kw32g\asset_scripts\_parse_scene.py":
- 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 "../_tmx/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])
- templates = {} #dictionary of [path]=(a,b,has_gid)
- def handleTemplate(path):
- if not exists(path): printError("template \"{}\" does not exist".format(path))
- if not isfile(path): printError("template \"{}\" is not a file".format(path))
- if path in templates:
- return templates[path][0], templates[path][1], templates[path][2]
- tmplp = 'template property'
- #(a should include width, height type, and name,
- # and b should include its custom properties
- props_a, props_b = {}, []
- has_gid = False
- tree = ET.parse(path)
- tmpl = tree.getroot() #t[e]mpl[ate]
- objs = tmpl.findall('object')
- if len(objs) != 1: printError(f'template "{path}" must have exactly 1 object')
- obj = objs[0]
- props_a = obj.attrib
- has_gid = not keyWarn(props_a, 'gid', cat=tmplp)
- _props_b = obj.find('properties')
- 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)) )
- templates[path] = (props_a, props_b, has_gid)
- return props_a, props_b, has_gid
- def parseObject(obj_in):
- obj_out = { "data":[] }
- props_a = obj_in.attrib
- op = "object property"
- nml = "NAMELESS"
- if _DEBUG: print(" parsing object \"{}\"...".format(tryDict(props_a, "name") or nml))
- if keyWarn(props_a, "name" , cat=op): obj_out["name" ] = nml
- 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"])
- #tile objects for some reason use a bottom-up position,
- #so it needs to be adjusted to make it top-down
- obj_was_shifted = False
- if not keyWarn(props_a, "gid", cat=op):
- obj_out["y"] -= obj_out["height"]
- if obj_out["height"] != 0: obj_was_shifted = True
- #if object uses a template, you need to read it in order to
- #get the object's width, height type, and name (unless they've been manually set)
- template_props_b = []
- if not keyWarn(props_a, "template", cat=op):
- template_props_a, template_props_b, has_gid = handleTemplate(props_a["template"])
- #only set obj_out's stuff if it hasn't been changed from the template
- if obj_out["name" ] == nml: obj_out["name" ] = template_props_a["name"]
- if obj_out["width" ] == 0 : obj_out["width" ] = convertType("int", template_props_a["width" ])
- if obj_out["height"] == 0 : obj_out["height"] = convertType("int", template_props_a["height"])
- if obj_out["type" ] == 0 : obj_out["type" ] = tryTypeWarn(template_props_a["type"])
- #only shift the y position if it hasn't been shifted already
- #(and the object template is a tile type)
- if has_gid and not obj_was_shifted:
- obj_out["y"] -= obj_out["height"]
- #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"]))
- if obj_out["type"] < 0: return None #if < 0, treat object as 'inactive/only-in-editor'
- #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
- #add template_props_b to props_b, only if a given property index is not found in props_p
- if len(template_props_b) > 0:
- to_add = []
- if len(props_b) > 0:
- for tpb in template_props_b:
- found_index = False
- for pb in props_b:
- if tpb[0] == pb[0]:
- found_index = True
- if not found_index:
- to_add.append(tpb)
- else:
- #if props_b is completely empty, use all of the template's default properties
- to_add = template_props_b
- props_b += to_add
- props_b = sorted(props_b, key = lambda x: x[0][0] ) #sort by their indexes (again)
- 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
- #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"))
- if tName != "obj-img":
- _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
- #(this does technically allow non-collision tiles to be
- # included in the collision layer, so don't do that unless you want to, idk)
- 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 mg_nonzero:
- #fixed a bug that necessitated parsing to halt like this in the first place
- #printError("fg and mg cannot both be empty")
- #instead, i could just warn, since if both are empty, there would
- #be no collision data as they are stored on those layers
- print(f" Warning: scene {scene_id} is completely empty (no collision data written!)")
- #(the 2 leading spaces are intentional)
- 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"):
- #current_object will be None if its type is LESS than 0,
- #in which case it simply won't be counted.
- #this is useful for putting markers as to the destination
- #of things like teleporters, for instance
- current_object = parseObject(obj)
- if current_object != None:
- objs.append(current_object)
- props["objs_len" ] += 1
- #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")
- #(i want to have the option to do this, so i'm
- # removing this check for now!)
- #calculate background id from an optional image layer
- #(this takes precedence over props["bmp_bg"], and will overwrite it)
- image_layers = map.findall("imagelayer")
- if len(image_layers) > 1: printError("scene cannot have >1 image layer")
- elif len(image_layers) != 0:
- images = image_layers[0].findall("image")
- if len(images) > 0:
- bg_name = images[0].attrib['source']
- #this can also be used for the background filenames too lol
- bg_id = tryInt(extractTilesetName(bg_name))
- if bg_id == None: printError(f'bg name "{bg_name}" is invalid')
- props["bmp_bg"] = bg_id
- 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