mirror of
https://git.planet-casio.com/Slyvtt/Collab_RPG.git
synced 2024-12-28 04:23:42 +01:00
392 lines
15 KiB
Python
392 lines
15 KiB
Python
"""
|
|
This is the main converter script. It uses the tiled.py script to handle the
|
|
tiled maps.
|
|
|
|
We're trying to follow the PEP, so please read PEP 8 (if you haven't already):
|
|
https://peps.python.org/pep-0008/, so please write variable names in sneak_case
|
|
and class names in PascalCase.
|
|
|
|
To improve the lisibility of this code, please document your methods, add
|
|
comments (yes, it's hard to add the right amount of comments), and add type
|
|
hints, to avoid bugs and make it easy to understand how to use them.
|
|
|
|
To document your methods, you should read PEP 257:
|
|
https://peps.python.org/pep-0257/.
|
|
|
|
Thanks,
|
|
Mibi88
|
|
"""
|
|
|
|
import xml.etree.ElementTree as ET
|
|
import json
|
|
import os
|
|
import sys
|
|
# Add the assets folder to the path, to be able to import the tiled script.
|
|
sys.path.append("../assets/")
|
|
import fxconv
|
|
from tinytiled import *
|
|
|
|
# If the output of the converter should be verbose.
|
|
VERBOSE = 1
|
|
# The sign types, used to find the sign icon.
|
|
SIGN_TYPES = ["SGN", "INFO"]
|
|
# The NPC faces, used to find the face id.
|
|
FACES = ["MALE", "FEMALE", "MILKMAN", "POLICE"]
|
|
# The precision of the fixed point numbers.
|
|
# WARNING: The PRECISION define in config.h should contain the same value!
|
|
PRECISION = 8
|
|
|
|
def convert(input: str, output: str, params: dict, target):
|
|
"""
|
|
This method gets called by fxconv for each asset to convert.
|
|
"""
|
|
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: str, output: str, params: dict, target):
|
|
"""
|
|
Convert a map.
|
|
"""
|
|
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 = {}
|
|
portals = {}
|
|
|
|
name = os.path.splitext(os.path.basename(input))[0]
|
|
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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:
|
|
# Show a simple error message on failure.
|
|
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
|
|
# Get the portals
|
|
for object in ed_objgroup.objects:
|
|
if (object.get_data_type() == "rectangle"
|
|
and object.type == "PORTAL"):
|
|
data = {
|
|
"rect": object.get_data(),
|
|
"name": object.name,
|
|
"dest": object.get_property("dest"),
|
|
"destPortal": object.get_property("destPortal")
|
|
}
|
|
portals[object.id] = data
|
|
except Exception as e:
|
|
# Show a simple error message on failure.
|
|
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}")
|
|
|
|
# Store the walkable layer
|
|
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():
|
|
# Convert currentpos to a fixed point value.
|
|
npc_struct += fxconv.u32((i["position"][0]+i["path"][0])<<PRECISION)
|
|
npc_struct += fxconv.u32((i["position"][1]+i["path"][1])<<PRECISION)
|
|
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)
|
|
# Load signs
|
|
map_struct += fxconv.u32(len(signs))
|
|
sign_struct = fxconv.Structure()
|
|
for i in signs.values():
|
|
# Create a sign struct for each sign.
|
|
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(len(portals))
|
|
portal_struct = fxconv.Structure()
|
|
for i in portals.values():
|
|
dest_file_name = os.path.splitext(os.path.basename(i["dest"]))[0]
|
|
dest_portal = i["destPortal"]
|
|
portal_name = i["name"]
|
|
if VERBOSE: print(f"INFO: Storing portal {name}_{portal_name}")
|
|
portal_struct += fxconv.sym(f"{name}_{portal_name}")
|
|
# Add the collider
|
|
rect = i["rect"]
|
|
portal_struct += fxconv.u32(rect[0])
|
|
portal_struct += fxconv.u32(rect[1])
|
|
portal_struct += fxconv.u32(rect[2])
|
|
portal_struct += fxconv.u32(rect[3])
|
|
# Add a reference to the destination portal
|
|
portal_struct += fxconv.ref(f"{dest_file_name}_{dest_portal}")
|
|
portal_struct += fxconv.ref(f"{dest_file_name}")
|
|
map_struct += fxconv.ptr(portal_struct)
|
|
map_struct += fxconv.u32(dialog_num)
|
|
|
|
# Get the name of the dialog file and create a reference to it: it is built
|
|
# separately.
|
|
dialog_name = os.path.splitext(os.path.basename(dialog_file))[0]
|
|
map_struct += fxconv.ref(f"_{dialog_name}")
|
|
|
|
# Store the background layer
|
|
background_data = bytes()
|
|
for i in background_layer:
|
|
background_data += fxconv.u16(i)
|
|
map_struct += fxconv.ptr(background_data)
|
|
|
|
# Store the foreground layer
|
|
foreground_data = bytes()
|
|
for i in foreground_layer:
|
|
foreground_data += fxconv.u16(i)
|
|
map_struct += fxconv.ptr(foreground_data)
|
|
|
|
# Create the fxconv object
|
|
fxconv.elf(map_struct, output, f"_{name}", **target)
|
|
|
|
def convert_dialog(input: str, output: str, params: dict, target):
|
|
"""
|
|
Convert a JSON dialog file.
|
|
"""
|
|
if VERBOSE: print(f"INFO: Converting dialog file {input} -> {output}")
|
|
|
|
# Load the JSON dialog file.
|
|
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)
|
|
|
|
# Create the dialog struct
|
|
dialog_struct = fxconv.Structure()
|
|
try:
|
|
for i in dialog_data["dialogs"]:
|
|
# Create a dialog structure for each dialog.
|
|
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:
|
|
# Show an error message if the conversion fails.
|
|
sys.stderr.write(f"ERROR: Failed convert dialogs.\n"
|
|
+ f" Error message: {e}\n")
|
|
sys.exit(1)
|