2024-07-30 17:44:12 +02:00
|
|
|
import xml.etree.ElementTree as ET
|
2023-07-05 23:22:15 +02:00
|
|
|
import json
|
2023-08-08 09:46:07 +02:00
|
|
|
import os
|
2024-07-30 17:44:12 +02:00
|
|
|
import sys
|
2024-07-31 12:06:00 +02:00
|
|
|
sys.path.append("../assets/")
|
2024-07-30 20:15:39 +02:00
|
|
|
import fxconv
|
2024-07-31 12:06:00 +02:00
|
|
|
from tiled import *
|
2024-07-30 17:44:12 +02:00
|
|
|
|
|
|
|
VERBOSE = 1
|
2024-07-31 12:54:40 +02:00
|
|
|
SIGN_TYPES = ["SGN", "INFO"]
|
|
|
|
FACES = ["MALE", "FEMALE", "MILKMAN", "POLICE"]
|
2024-07-31 14:38:29 +02:00
|
|
|
PRECISION = 8
|
2024-07-30 17:44:12 +02:00
|
|
|
|
2023-07-05 23:22:15 +02:00
|
|
|
def convert(input, output, params, target):
|
2024-07-30 17:44:12 +02:00
|
|
|
if params["custom-type"] == "tmx":
|
|
|
|
convert_map(input, output, params, target)
|
2024-07-28 12:40:39 +02:00
|
|
|
return 0
|
2024-07-30 22:37:04 +02:00
|
|
|
elif params["custom-type"] == "dialog":
|
2024-07-30 17:44:12 +02:00
|
|
|
convert_dialog(input, output, params, target)
|
2024-07-28 12:40:39 +02:00
|
|
|
return 0
|
|
|
|
|
2024-07-30 17:44:12 +02:00
|
|
|
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 = []
|
2024-07-31 12:06:00 +02:00
|
|
|
map_x = 0
|
|
|
|
map_y = 0
|
2024-07-30 17:44:12 +02:00
|
|
|
width = 0
|
|
|
|
height = 0
|
|
|
|
outdoor_tileset = None
|
|
|
|
walkable_tileset = None
|
2024-07-30 22:37:04 +02:00
|
|
|
dialog_num = 0
|
|
|
|
dialog_ids = []
|
2024-07-30 17:44:12 +02:00
|
|
|
|
|
|
|
npc_paths = {}
|
|
|
|
npcs = {}
|
|
|
|
signs = {}
|
|
|
|
|
2024-07-30 20:15:39 +02:00
|
|
|
map_struct = fxconv.Structure()
|
|
|
|
|
2024-07-30 17:44:12 +02:00
|
|
|
# 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)
|
2024-07-30 22:37:04 +02:00
|
|
|
|
2024-07-31 12:54:40 +02:00
|
|
|
# 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)
|
|
|
|
|
2024-07-30 22:37:04 +02:00
|
|
|
# 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)
|
2024-07-30 17:44:12 +02:00
|
|
|
|
|
|
|
# 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":
|
2024-07-31 14:12:55 +02:00
|
|
|
path = [0, 0]
|
2024-07-30 17:44:12 +02:00
|
|
|
if int(object.get_property("hasPath")):
|
|
|
|
if object.get_property("path") in npc_paths:
|
|
|
|
path = npc_paths[object.get_property("path")]
|
2024-07-28 12:40:39 +02:00
|
|
|
else:
|
2024-07-30 17:44:12 +02:00
|
|
|
raise Exception("Path required but not found!")
|
2024-07-31 13:53:47 +02:00
|
|
|
dialog_id = 0
|
|
|
|
has_dialog = 0
|
|
|
|
try:
|
|
|
|
dialog_id = int(object.get_property("dialogID"))
|
|
|
|
has_dialog = 1
|
|
|
|
except:
|
|
|
|
pass
|
2024-07-30 17:44:12 +02:00
|
|
|
data = {
|
|
|
|
"position": object.get_data(),
|
2024-07-31 12:54:40 +02:00
|
|
|
"name": object.name,
|
|
|
|
"needAction": int(object.get_property("needAction")),
|
2024-07-31 13:53:47 +02:00
|
|
|
"dialogID": dialog_id,
|
|
|
|
"hasDialog": has_dialog,
|
2024-07-31 12:54:40 +02:00
|
|
|
"face": FACES.index(object.get_property("face")),
|
2024-07-30 17:44:12 +02:00
|
|
|
"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 = {
|
2024-07-31 12:54:40 +02:00
|
|
|
"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)
|
2024-07-30 17:44:12 +02:00
|
|
|
}
|
|
|
|
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
|
2024-07-30 22:37:04 +02:00
|
|
|
# Map struct
|
2024-07-31 12:06:00 +02:00
|
|
|
map_struct += fxconv.u32(map_x)
|
|
|
|
map_struct += fxconv.u32(map_y)
|
2024-07-30 20:15:39 +02:00
|
|
|
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)
|
|
|
|
|
2024-07-30 22:37:04 +02:00
|
|
|
# Load NPCs
|
2024-07-31 13:53:47 +02:00
|
|
|
map_struct += fxconv.u32(len(npcs))
|
|
|
|
npc_struct = fxconv.Structure()
|
|
|
|
for i in npcs.values():
|
2024-07-31 14:38:29 +02:00
|
|
|
npc_struct += fxconv.u32((i["position"][0]+i["path"][0])<<PRECISION)
|
|
|
|
npc_struct += fxconv.u32((i["position"][1]+i["path"][1])<<PRECISION)
|
2024-07-31 13:53:47 +02:00
|
|
|
npc_struct += fxconv.u32(i["position"][0])
|
|
|
|
npc_struct += fxconv.u32(i["position"][1])
|
|
|
|
npc_struct += fxconv.u16(i["face"])
|
|
|
|
npc_struct += fxconv.u8(0)
|
|
|
|
npc_struct += fxconv.u8(i["hasDialog"])
|
|
|
|
npc_struct += fxconv.u32(i["dialogID"])
|
|
|
|
npc_struct += fxconv.u32(i["needAction"])
|
|
|
|
npc_struct += fxconv.string(i["name"])
|
|
|
|
npc_struct += fxconv.u32(len(i["path"]) > 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)
|
2024-07-30 22:37:04 +02:00
|
|
|
# Load signs
|
2024-07-31 13:53:47 +02:00
|
|
|
map_struct += fxconv.u32(len(signs))
|
2024-07-31 12:54:40 +02:00
|
|
|
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
|
2024-07-30 20:15:39 +02:00
|
|
|
map_struct += fxconv.u32(0) # TODO: Portal support in-game
|
|
|
|
map_struct += fxconv.ptr(bytes())
|
2024-07-30 22:37:04 +02:00
|
|
|
map_struct += fxconv.u32(dialog_num)
|
|
|
|
|
|
|
|
dialog_name = os.path.splitext(os.path.basename(dialog_file))[0]
|
|
|
|
map_struct += fxconv.ref(f"_{dialog_name}")
|
2024-07-30 20:15:39 +02:00
|
|
|
|
|
|
|
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)
|
2024-07-30 17:44:12 +02:00
|
|
|
|
|
|
|
def convert_dialog(input, output, params, target):
|
|
|
|
if VERBOSE: print(f"INFO: Converting dialog file {input} -> {output}")
|
2024-07-30 22:37:04 +02:00
|
|
|
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"])
|
2024-07-31 12:54:40 +02:00
|
|
|
dialog_struct += fxconv.string(i["choice"].replace('$', '\0'))
|
2024-07-30 22:37:04 +02:00
|
|
|
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)
|