Advertisement
Kitomas

_compile_scene.py WIP

May 10th, 2024
818
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.55 KB | None | 1 0
  1. if __name__ != "__main__": exit(0) #lol
  2. _DEBUG = True
  3. from time import time
  4. from sys import argv
  5. from os import listdir, system as cmd
  6. from os.path import isfile, join, exists
  7. from pprint import pprint
  8. import xml.etree.ElementTree as ET
  9.  
  10.  
  11.  
  12. def printError(s, fatal=True):
  13.     print("Error: {}".format(s))
  14.     if fatal: exit(-1)
  15.     else    : return False
  16.  
  17. def sceneIdToPath(scene_id):
  18.     return "../scene_{}/scene_{}_pattern.tmx".format(scene_id, scene_id)
  19.    
  20.    
  21.    
  22. #poor man's regex lol
  23. def extractTilesetName(path):
  24.     #will turn something like "../foo/tileset_collision.tsx" into "collision"
  25.     name = path.split("/")[-1]
  26.     return (("_").join(name.split("."))).split("_")[1]
  27.    
  28. def tryInt(string):
  29.     try:
  30.         return int(string)
  31.     except ValueError:
  32.         return None
  33.     except TypeError:
  34.         return None
  35.        
  36. def tryDict(dict, key):
  37.     try:
  38.         return dict[key]
  39.     except KeyError:
  40.         return None
  41.  
  42. def propertyWarning(properties, name, fatal=False):
  43.     if tryDict(properties, name) == None:
  44.         #intentionally indented twice
  45.         if fatal: print("  Error: property \"{}\" doesn't exist!".format(name)); exit(-1)
  46.         else    : print("  Warning: property \"{}\" doesn't exist!".format(name))
  47.         return True #property does not exist
  48.     else:
  49.         return False #property exists
  50.  
  51. #value is a hex quad string of "#AARRGGBB"
  52. def toRGBA(value):
  53.     alpha = int(value[1:3], 16)
  54.     red   = int(value[3:5], 16)
  55.     blue  = int(value[5:7], 16)
  56.     green = int(value[7:9], 16)
  57.     return (red, green, blue, alpha)
  58.  
  59. #dataValue should always be of type str
  60. def convertType(dataType, dataValue):
  61.     #(i wish python had switch statements...)
  62.     if   dataType == "int"    : return int(float(dataValue))
  63.     elif dataType == "bool"   : return bool(dataValue)
  64.     elif dataType == "float"  : return float(dataValue)
  65.     elif dataType == "color"  : return toRGBA(dataValue)
  66.     elif dataType == "object" : return int(float(dataValue))
  67.     #elif dataType == 'file'  : return dataValue #(redundant)
  68.     else                      : return dataValue
  69.  
  70.  
  71.  
  72. def parseObject(obj_in):
  73.     obj_out = {}
  74.     props   = {}
  75.    
  76.     #(tbd)
  77.    
  78.     return { "test" : 2 }
  79.  
  80.  
  81.  
  82. def parseSceneMap(scene_id, announce=False):
  83.     filename = sceneIdToPath(scene_id)
  84.     if not exists(filename): printError("scene \"{}\" does not exist".format(filename))
  85.     if not isfile(filename): printError("scene \"{}\" is not a file".format(filename))
  86.     if _DEBUG or announce: print("PARSING \"{}\":".format(filename))
  87.     tree = ET.parse(filename)
  88.     map  = tree.getroot()
  89.    
  90.     #get map's intrinsic properties
  91.     width       = int(map.attrib["width"     ]) #should be 32
  92.     height      = int(map.attrib["height"    ]) #should be 18
  93.     #tileWidth  = int(map.attrib["tilewidth" ]) #should be 24 (unused)
  94.     #tileHeight = int(map.attrib["tileheight"]) #should be 24 (unused)
  95.     mapLength   = width*height
  96.    
  97.    
  98.    
  99.     #get map's custom properties
  100.     props = {}
  101.     for prop in map.find("properties"):
  102.         pName  = tryDict(prop.attrib, "name" ) or "NAMELESS"
  103.         pType  = tryDict(prop.attrib, "type" )
  104.         pValue = tryDict(prop.attrib, "value") or "NOVALUE"
  105.         props[ pName ] = convertType(pType, pValue)
  106.        
  107.     if  propertyWarning(props, "bmp_bg"    ):  props["bmp_bg"    ] = 0
  108.     if  propertyWarning(props, "repeat_bg" ):  props["repeat_bg" ] = False #lol
  109.     #if propertyWarning(props, "objs_len"  ):  props["objs_len"  ] = 0 #(calculated later)
  110.     #if propertyWarning(props, "tileset_a" ):  props["tileset_a" ] = 0 #(calculated later)
  111.     #if propertyWarning(props, "tileset_b" ):  props["tileset_b" ] = 0 #(calculated later)
  112.     if  propertyWarning(props, "edge_n"    ):  props["edge_n"    ] = 0
  113.     if  propertyWarning(props, "edge_s"    ):  props["edge_s"    ] = 0
  114.     if  propertyWarning(props, "edge_w"    ):  props["edge_w"    ] = 0
  115.     if  propertyWarning(props, "edge_e"    ):  props["edge_e"    ] = 0
  116.     #if propertyWarning(props, "scene"     ):  props["scene"     ] = 0 #(calculated later)
  117.     if  propertyWarning(props, "music"     ):  props["music"     ] = 0
  118.     if  propertyWarning(props, "ambience_a"):  props["ambience_a"] = 0
  119.     if  propertyWarning(props, "ambience_b"):  props["ambience_b"] = 0
  120.    
  121.    
  122.    
  123.     #calculate tileset boundaries
  124.     _tsets = []
  125.     for tset in map.findall("tileset"):
  126.         tFirstGID = int(tryDict(tset.attrib, "firstgid")) - 1
  127.         tName     = extractTilesetName(tryDict(tset.attrib, "source"))
  128.         _tsets.append(  ( tFirstGID, tryInt(tName) or 0 )  )
  129.     tsets = sorted(_tsets,  key = lambda x: x[0]  ) #sorted by first element
  130.    
  131.     if   len(tsets) < 3: printError("map cannot have less than 3 tilesets (including collision)")
  132.     elif len(tsets) > 3: printError("map cannot have more than 3 tilesets (including collision)")
  133.        
  134.      #there should only be 1 tileset that registers as null (the collision's tileset)
  135.     tset_offsets = [0,] * 3
  136.     tset_nulls, tset_valids  =  0, 0
  137.     tset_a,     tset_b       =  0, 0
  138.     for i in range(len(tsets)):
  139.         #collision tileset
  140.         if    tsets[i][1] == 0:  tset_offsets[i] =  -1;  tset_nulls  += 1
  141.         #tileset_a
  142.         elif  tset_valids == 0:  tset_a = tsets[i][1];   tset_valids += 1
  143.         #tileset_b
  144.         else                  :  tset_b = tsets[i][1]#;  tset_valids += 1
  145.    
  146.     if tset_nulls != 1: printError("map must have exactly 1 null tileset, reserved for collider map")
  147.  
  148.    
  149.    
  150.     #get map's layer data
  151.     layers = {}
  152.     for layer in map.findall("layer"):
  153.         lName   =      tryDict(layer.attrib,"name"  ) or "NAMELESS"
  154.         lWidth  = int( tryDict(layer.attrib,"width" ) or -1 )
  155.         lHeight = int( tryDict(layer.attrib,"height") or -1 )
  156.         # n-1 to make sure both tile 0 and 1 are treated as transparent
  157.         lData   = [ max(int(n)-1, 0) for n in layer.find("data").text.split(",") ] #csv to list
  158.         if lWidth*lHeight != mapLength : valid = printError("layer dims inconsistent with map dimensions")
  159.         if lWidth*lHeight != len(lData): valid = printError("layer dims inconsistent with its attributes")
  160.         layers[ lName ] = lData
  161.    
  162.     valid = True
  163.     if "collision" not in layers: valid = printError("layer \"collision\" doesn't exist", quit=False)
  164.     if "fg"        not in layers: valid = printError("layer \"fg\" doesn't exist",        quit=False)
  165.     if "mg"        not in layers: valid = printError("layer \"mg\" doesn't exist",        quit=False)
  166.     if not valid: exit(-1)
  167.    
  168.     for i in range(len(layers["collision"])):
  169.         #collider info is 6 bits (2^6 = 64),
  170.          #which is a factor of a normal tileset's 7 (2^7 = 128) anyway
  171.         layers["collision"][i] %= 64
  172.        
  173.     for i in range(len(layers["fg"])): #(fg & mg should be the same length)
  174.         #fg
  175.         tile = layers["fg"][i]
  176.         offset = tset_offsets[tile//128] #tile//128 should be between 0 -> 2
  177.         if offset == -1: printError("fg cannot contain collision map tiles")
  178.         layers["fg"][i] = (tile%128) + offset
  179.         #mg
  180.         tile = layers["mg"][i]
  181.         offset = tset_offsets[tile//128]
  182.         if offset == -1: printError("mg cannot contain collision map tiles")
  183.         layers["mg"][i] = (tile%128) + offset
  184.  
  185.    
  186.    
  187.     props["objs_len" ] = 0
  188.     props["tileset_a"] = tset_a
  189.     props["tileset_b"] = tset_b
  190.     props["scene"    ] = scene_id
  191.    
  192.     #get scene objects
  193.     obj_groups = map.findall("objectgroup")
  194.     objs = []
  195.     if len(obj_groups) >  1: printError("scene cannot have more than 1 object group")
  196.     if len(obj_groups) == 1:
  197.         #(this loop will not run at all if there are no objects)
  198.         for obj in obj_groups[0].findall("object"):
  199.             props["objs_len" ] += 1
  200.             objs.append(parseObject(obj))
  201.  
  202.    
  203.    
  204.     if _DEBUG:
  205.         print("  --PROPERTIES--:")
  206.         pprint(props, indent=4)
  207.         print("  --TILESETS--:")
  208.         pprint(tsets, indent=4, width=40)
  209.     #    print("  --LAYERS--")
  210.     #    print(layers)
  211.         print("  --OBJECTS--")
  212.         pprint(objs)
  213.    
  214.     scene = {}
  215.     scene["properties"] = props
  216.     scene["layers"    ] = layers
  217.    
  218.     return scene
  219.    
  220.    
  221.  
  222.  
  223.  
  224.  
  225. '''
  226. struct Object { //48B
  227.  kit::u64               _user_0;
  228.  kit::u64               _user_1;
  229.  kit::u64               _user_2;
  230.  Object_TickCallback funcUpdate;
  231.  
  232.  
  233.  struct {
  234.    kit::u16           hb_size_x;
  235.    kit::u16           hb_size_y;
  236.    kit::u8          hb_offset_x;
  237.    kit::u8          hb_offset_y;
  238.  };
  239.  
  240.  kit::s16                     x;
  241.  kit::s16                     y;
  242.  
  243.  kit::u16                  type;
  244. };
  245.  
  246.  
  247. //for storing the contents of a .ksd file (compressed scene data)
  248. #define KSD_MAGIC 0x4644536B //"kSDF"
  249. #define SD_FILELEN(_scene_desc) ((_scene_desc).dataSize+sizeof(SceneDescriptor))
  250. struct SceneDescriptor { //64B
  251. /*0x00*/  kit::u32      magic; //= 0x4644536B = "kSDF" (no null terminator)
  252. /*0x04*/  kit::u32   dataSize; //size of file in bytes, minus the header (which is always 64B)
  253.  
  254.          //offsets to array data in file (if nullptr, data is assumed to not be present!)
  255.          //(also, objs[x].type refers to the index inside gl_objCallbacks used by the object.
  256.           //each element of gl_objCallbacks is of type Object_TickCallback)
  257. /*0x08*/  Object*        objs; //contains the original states of each object in the scene
  258. /*0x10*/  Tile*        pat_mg; //pattern data is compressed using RLE, where the 1st element's .value
  259. /*0x18*/  Tile*        pat_fg;  //member is the run length, with the 2nd being the actual tile data
  260.  
  261.        struct {
  262. /*0x20*/  kit::u16     bmp_bg : 15; //associated background id
  263. /*0x21*/  kit::u16  repeat_bg :  1; //'should bg repeat?' (stretches to screen otherwise)
  264.        };
  265.  
  266.        struct {
  267. /*0x22*/  kit::u16   objs_len : 15; //number of objects in scene
  268. /*0x23*/  kit::u16    visited :  1; //used to help determine if objects should reset on load
  269.        };
  270.  
  271. /*0x24*/  kit::u16  tileset_a; //1st associated tileset
  272. /*0x26*/  kit::u16  tileset_b; //2nd associated tileset
  273.  
  274. /*0x28*/  kit::u16     edge_n; //scene id for north edge
  275. /*0x2A*/  kit::u16     edge_s; //scene id for south edge
  276. /*0x2C*/  kit::u16     edge_w; //scene id for west edge
  277. /*0x2E*/  kit::u16     edge_e; //scene id for east edge
  278. /*0x30*/  kit::u16      scene; //scene id for scene itself
  279.  
  280. /*0x32*/  kit::u16      music; //music id;  0 for no change, -1 (65535) to stop
  281. /*0x34*/  kit::u16 ambience_a; //ambient track id a;  0 for no change, -1 to stop
  282. /*0x36*/  kit::u16 ambience_b; //ambient track id b;  0 for no change, -1 to stop
  283.  
  284. /*0x38*/  kit::BinaryData* fileData = nullptr; //raw file data; appears as nullptr in file
  285.  
  286. /*0x40*/  //... (array data is stored in order of: objs, pat_mg, and pat_fg)
  287.  
  288.          void unload();
  289.  
  290.          ~SceneDescriptor(){ unload(); }
  291.          SceneDescriptor(){ kit::memory::set(this, 0, sizeof(SceneDescriptor)); }
  292.          SceneDescriptor(kit::u16 scene_id);
  293. };
  294. '''
  295.  
  296. magic      = "kSDF" #0x00:  4B
  297. dataSize   = 0      #0x04:  8B
  298.  
  299. objs       = 0      #0x08:  8B
  300. pat_mg     = 0      #0x10:  8B
  301. pat_mg     = 0      #0x18:  8B
  302.  
  303. bmp_bg     = 0      #0x20: 15b (as in [b]its, not [B]ytes)
  304. repeat_bg  = 0      #0x21:  1b
  305.  
  306. objs_len   = 0      #0x22: 15b
  307. visited    = False  #0x23:  1b (always 0 in file)
  308.  
  309. tileset_a  = 0      #0x24:  2B
  310. tileset_b  = 0      #0x26:  2B
  311.  
  312. edge_n     = 0      #0x28:  2B
  313. edge_s     = 0      #0x2A:  2B
  314. edge_w     = 0      #0x2C:  2B
  315. edge_e     = 0      #0x2D:  2B
  316. scene      = 0      #0x30:  2B
  317.  
  318. music      = 0      #0x32:  2B
  319. ambience_a = 0      #0x34:  2B
  320. ambience_b = 0      #0x36:  2B
  321.  
  322. fileData   = 0      #0x38:  8B
  323.  
  324. #(array data)       #0x40: <dataSize>B
  325.  
  326. '''
  327. #iirc byte objects are immutable, but the header is only 72 bytes anyway
  328. le = "little" #[l]ittle [e]ndian (i wish python had macros)
  329. header_data  = bytes(magic,"ascii")        # 0x00: magic
  330. header_data += format                      # 0x04: format
  331. header_data += headerSize.to_bytes(  2,le) # 0x06: headerSize
  332.  
  333.  
  334. fileoutPath = join(folderout,filenames[which])
  335.  
  336. fileout = open(fileoutPath,"wb")
  337.  
  338. fileout.write(header_data)
  339. fileout.write(sample_data)
  340.  
  341. fileout.close()
  342. timeTakenMS = (time()-startTime)*1000
  343.  
  344.  
  345. print("\noutput file info:")
  346. print(("  magic        = \"{}\"").format(magic))
  347.  
  348.  
  349. print("\nsaved to: \"{}\"".format(fileoutPath))
  350. print("time taken: {:.4}ms".format(timeTakenMS))
  351. print("\npress any key to exit")
  352. cmd("pause >nul")
  353. '''
  354.  
  355.  
  356. startTime = time()
  357. parseSceneMap(1)
  358. timeTakenMS = (time()-startTime)*1000
  359. print(timeTakenMS)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement