import xml.etree.ElementTree as ET import json import os import sys sys.path.append("../assets/") import fxconv from tiled import * VERBOSE = 1 SIGN_TYPES = ["SGN", "INFO"] FACES = ["MALE", "FEMALE", "MILKMAN", "POLICE"] PRECISION = 8 def convert(input, output, params, target): if params["custom-type"] == "tmx": convert_map(input, output, params, target) return 0 elif params["custom-type"] == "dialog": convert_dialog(input, output, params, target) return 0 def convert_map(input, output, params, target): if VERBOSE: print(f"INFO: Converting map {input} -> {output}") input_map = Map(input) dialog_file = "" background_layer = [] foreground_layer = [] walkable_layer = [] map_x = 0 map_y = 0 width = 0 height = 0 outdoor_tileset = None walkable_tileset = None dialog_num = 0 dialog_ids = [] npc_paths = {} npcs = {} signs = {} map_struct = fxconv.Structure() # Get the dialog file try: if VERBOSE: print("INFO: Getting the dialog file") dialog_file = input_map.get_property("dialogFile") if VERBOSE: print(f"INFO: Dialog file: {dialog_file}.") except Exception as e: sys.stderr.write(f"ERROR: Failed to get the dialog file.\n" + f" Error message: {e}\n") sys.exit(1) # Get the map position try: if VERBOSE: print("INFO: Getting the map position") map_x = int(input_map.get_property("mapX")) map_y = int(input_map.get_property("mapY")) if VERBOSE: print(f"INFO: Map position: ({map_x}, {map_y}).") except Exception as e: sys.stderr.write(f"ERROR: Failed to get the map position.\n" + f" Error message: {e}\n") sys.exit(1) # Get informations about dialogs try: if VERBOSE: print("INFO: Getting informations about dialogs") with open(f"{input_map.parent_dir}/{dialog_file}", "r") as file: dialog_data = json.load(file) dialog_num = len(dialog_data["dialogs"]) for i in dialog_data["dialogs"]: dialog_ids.append(i["ID"]) except Exception as e: sys.stderr.write(f"ERROR: Failed to get informations about dialogs.\n" + f" Error message: {e}\n") sys.exit(1) # Get the outdoor tileset try: if VERBOSE: print("INFO: Getting the outdoor tileset") outdoor_tileset = input_map.get_tileset_by_firstgid(1) except Exception as e: sys.stderr.write(f"ERROR: Failed to get the outdoor tileset.\n" + f" Error message: {e}\n") sys.exit(1) # Get the walkable tileset try: if VERBOSE: print("INFO: Getting the walkable tileset") walkable_tileset = input_map.get_tileset_by_firstgid(409) except Exception as e: sys.stderr.write(f"ERROR: Failed to get the walkable tileset.\n" + f" Error message: {e}\n") sys.exit(1) # Get the background try: if VERBOSE: print("INFO: Getting the background layer") bg_layer = input_map.get_layer_by_name("Background") # The bg layer will be used to set the map width and height. width = bg_layer.get_width() height = bg_layer.get_height() if VERBOSE: print(f"INFO: Map size: ({width}, {height}).") # Get the layer data himself background_layer = bg_layer.get_data_with_tileset(outdoor_tileset) # Check if the size of the layer data is correct. if len(background_layer) != width*height: raise Exception("Bad layer size!") if VERBOSE: print("INFO: Layer data has the right size.") except Exception as e: sys.stderr.write(f"ERROR: Failed to get the background layer.\n" + f" Error message: {e}\n") sys.exit(1) # Get the foreground try: if VERBOSE: print("INFO: Getting the foreground layer") fg_layer = input_map.get_layer_by_name("Foreground") # Get the layer data himself foreground_layer = fg_layer.get_data_with_tileset(outdoor_tileset) # Check if the size of the layer data is correct. if len(foreground_layer) != width*height: raise Exception("Bad layer size!") if VERBOSE: print("INFO: Layer data has the right size.") except Exception as e: sys.stderr.write(f"ERROR: Failed to get the foreground layer.\n" + f" Error message: {e}\n") sys.exit(1) # Get the walkable layer try: if VERBOSE: print("INFO: Getting the walkable layer") wk_layer = input_map.get_layer_by_name("Walkable") # Get the layer data himself walkable_layer = wk_layer.get_data_with_tileset(walkable_tileset) # Check if the size of the layer data is correct. if len(walkable_layer) != width*height: raise Exception("Bad layer size!") if VERBOSE: print("INFO: Layer data has the right size.") except Exception as e: sys.stderr.write(f"ERROR: Failed to get the walkable layer.\n" + f" Error message: {e}\n") sys.exit(1) # Get the extra data try: if VERBOSE: print("INFO: Getting the extra data") ed_objgroup = input_map.get_objectgroup_by_name("ExtraData") # Get the paths the NPCs take. for object in ed_objgroup.objects: if object.get_data_type() == "polyline": npc_paths[object.id] = object.get_data() # Get the NPCs for object in ed_objgroup.objects: if object.get_data_type() == "point" and object.type == "NPC": path = [0, 0] if int(object.get_property("hasPath")): if object.get_property("path") in npc_paths: path = npc_paths[object.get_property("path")] else: raise Exception("Path required but not found!") dialog_id = 0 has_dialog = 0 try: dialog_id = int(object.get_property("dialogID")) has_dialog = 1 except: pass data = { "position": object.get_data(), "name": object.name, "needAction": int(object.get_property("needAction")), "dialogID": dialog_id, "hasDialog": has_dialog, "face": FACES.index(object.get_property("face")), "path": path } npcs[object.id] = data # Get the signs for object in ed_objgroup.objects: if object.get_data_type() == "point" and object.type in SIGN_TYPES: data = { "position": object.get_data(), "name": object.name, "needAction": int(object.get_property("needAction")), "dialogID": int(object.get_property("dialogID")), "icon": SIGN_TYPES.index(object.type) } signs[object.id] = data except Exception as e: sys.stderr.write(f"ERROR: Failed to get the extra data.\n" + f" Error message: {e}\n") sys.exit(1) # Generate the structs # Map struct map_struct += fxconv.u32(map_x) map_struct += fxconv.u32(map_y) map_struct += fxconv.u32(width) map_struct += fxconv.u32(height) map_struct += fxconv.u32(3) map_struct += fxconv.u32(outdoor_tileset.columns) tileset_name = os.path.splitext(os.path.basename(outdoor_tileset.source))[0] map_struct += fxconv.ref(f"img_{tileset_name}") walkable_data = bytes() for i in walkable_layer: if i < 0: i = 0 walkable_data += fxconv.u8(i) map_struct += fxconv.ptr(walkable_data) # Load NPCs map_struct += fxconv.u32(len(npcs)) npc_struct = fxconv.Structure() for i in npcs.values(): npc_struct += fxconv.u32((i["position"][0]+i["path"][0])< 2) npc_struct += fxconv.u32(len(i["path"])//2) npc_struct += fxconv.u32(0) xpath = bytes() ypath = bytes() x = True for n in i["path"]: if x: xpath += fxconv.u16(n) else: ypath += fxconv.u16(n) x = not x npc_struct += fxconv.ptr(xpath) npc_struct += fxconv.ptr(ypath) npc_struct += fxconv.u32(0) # TODO: Type npc_struct += fxconv.u8(0) # TODO: Group npc_struct += fxconv.u8(0) # TODO: Hostile to npc_struct += fxconv.u16(0) # TODO: Padding (what is it ?) map_struct += fxconv.ptr(npc_struct) # Load signs map_struct += fxconv.u32(len(signs)) sign_struct = fxconv.Structure() for i in signs.values(): sign_struct += fxconv.u32(i["position"][0]) sign_struct += fxconv.u32(i["position"][1]) sign_struct += fxconv.u32(i["icon"]) sign_struct += fxconv.string(i["name"]) sign_struct += fxconv.u32(i["dialogID"]) sign_struct += fxconv.u32(i["needAction"]) map_struct += fxconv.ptr(sign_struct) # Load portals map_struct += fxconv.u32(0) # TODO: Portal support in-game map_struct += fxconv.ptr(bytes()) map_struct += fxconv.u32(dialog_num) dialog_name = os.path.splitext(os.path.basename(dialog_file))[0] map_struct += fxconv.ref(f"_{dialog_name}") background_data = bytes() for i in background_layer: background_data += fxconv.u16(i) map_struct += fxconv.ptr(background_data) foreground_data = bytes() for i in foreground_layer: foreground_data += fxconv.u16(i) map_struct += fxconv.ptr(foreground_data) # Create the fxconv object name = os.path.splitext(os.path.basename(input))[0] fxconv.elf(map_struct, output, f"_{name}", **target) def convert_dialog(input, output, params, target): if VERBOSE: print(f"INFO: Converting dialog file {input} -> {output}") dialog_data = None try: with open(input, "r") as file: dialog_data = json.load(file) except Exception as e: sys.stderr.write(f"ERROR: Failed parse json.\n" + f" Error message: {e}\n") sys.exit(1) dialog_struct = fxconv.Structure() try: for i in dialog_data["dialogs"]: dialog_id = i["ID"] dialog_struct += fxconv.u32(dialog_id) dialog_struct += fxconv.string(i["dialog"]) dialog_struct += fxconv.u32(i["isQuestion"]) dialog_struct += fxconv.string(i["choice"].replace('$', '\0')) dialog_struct += fxconv.string(i["conclusion1"]) dialog_struct += fxconv.u32(i["next1"]) dialog_struct += fxconv.string(i["conclusion2"]) dialog_struct += fxconv.u32(i["next2"]) dialog_struct += fxconv.u32(i["nextOther"]) # Save this struct name = os.path.splitext(os.path.basename(input))[0] fxconv.elf(dialog_struct, output, f"__{name}", **target) except Exception as e: sys.stderr.write(f"ERROR: Failed convert dialogs.\n" + f" Error message: {e}\n") sys.exit(1)