summaryrefslogtreecommitdiff
path: root/gfx-iso/asset/mkasset.py
blob: 15f79128aeca49df4bba41ff0e5a2e52951ad3ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# Converts tile sets and tile maps to binary formats (.TS, .TM) for the engine.
#
# Currently handles Tiled's .tsx and .tmx file formats.
#
# The output is a binary tile set file (.TS) or a binary tile map file (.TM).
import argparse
import ctypes
from PIL import Image
import sys
from xml.etree import ElementTree

# Maximum length of path strings in .TS and .TM files.
MAX_PATH_LENGTH = 128


def drop_extension(filepath):
    return filepath[:filepath.rfind('.')]


def to_char_array(string, length):
    """Convert a string to a fixed-length ASCII char array.

    The length of str must be at most length-1 so that the resulting string can
    be null-terminated.
    """
    assert (len(string) < length)
    chars = string.encode("ascii")
    nulls = ("\0" * (length - len(string))).encode("ascii")
    return chars + nulls


def convert_tsx(input_filepath, output_filepath):
    """Converts a Tiled .tsx tileset file to a .TS tile set file."""
    xml = ElementTree.parse(input_filepath)
    root = xml.getroot()

    tile_count = int(root.attrib["tilecount"])
    max_tile_width = int(root.attrib["tilewidth"])
    max_tile_height = int(root.attrib["tileheight"])

    print(f"Tile count: {tile_count}")
    print(f"Max width:  {max_tile_width}")
    print(f"Max height: {max_tile_height}")

    with open(output_filepath, 'bw') as output:
        output.write(ctypes.c_uint16(tile_count))
        output.write(ctypes.c_uint16(max_tile_width))
        output.write(ctypes.c_uint16(max_tile_height))

        num_tile = 0
        for tile in root:
            # Skip the "grid" and other non-tile elements.
            if not tile.tag == "tile":
                continue

            # Assuming tiles are numbered 0..N.
            tile_id = int(tile.attrib["id"])
            assert (tile_id == num_tile)
            num_tile += 1

            image = tile[0]
            tile_width = int(image.attrib["width"])
            tile_height = int(image.attrib["height"])
            tile_path = image.attrib["source"]

            output.write(ctypes.c_uint16(tile_width))
            output.write(ctypes.c_uint16(tile_height))

            with Image.open(tile_path) as im:
                bytes = im.convert('RGBA').tobytes()
                output.write(bytes)


def convert_tmx(input_filepath, output_filepath):
    """Converts a Tiled .tmx file to a .TM tile map file."""
    xml = ElementTree.parse(input_filepath)
    root = xml.getroot()

    map_width = int(root.attrib["width"])
    map_height = int(root.attrib["height"])
    base_tile_width = int(root.attrib["tilewidth"])
    base_tile_height = int(root.attrib["tileheight"])
    num_layers = 1

    print(f"Map width:   {map_width}")
    print(f"Map height:  {map_height}")
    print(f"Tile width:  {base_tile_width}")
    print(f"Tile height: {base_tile_height}")

    with open(output_filepath, 'bw') as output:
        output.write(ctypes.c_uint16(map_width))
        output.write(ctypes.c_uint16(map_height))
        output.write(ctypes.c_uint16(base_tile_width))
        output.write(ctypes.c_uint16(base_tile_height))
        output.write(ctypes.c_uint16(num_layers))

        tileset_path = None

        for child in root:
            if child.tag == "tileset":
                tileset = child
                tileset_path = tileset.attrib["source"]

                print(f"Tile set: {tileset_path}")

                tileset_path = tileset_path.replace("tsx", "ts")
            elif child.tag == "layer":
                layer = child
                layer_id = int(layer.attrib["id"])
                layer_width = int(layer.attrib["width"])
                layer_height = int(layer.attrib["height"])

                print(f"Layer:  {layer_id}")
                print(f"Width:  {layer_width}")
                print(f"Height: {layer_height}")

                assert (tileset_path)
                output.write(to_char_array(tileset_path, MAX_PATH_LENGTH))

                # Assume the layer's dimensions matches the map's.
                assert (layer_width == map_width)
                assert (layer_height == map_height)

                data = layer[0]
                # Handle other encodings later.
                assert (data.attrib["encoding"] == "csv")

                csv = data.text.strip()
                rows = csv.split('\n')
                for row in rows:
                    tile_ids = [x.strip() for x in row.split(',') if x]
                    for tile_id in tile_ids:
                        output.write(ctypes.c_uint16(int(tile_id)))


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("input", help="Input file (.tsx, .tmx)")
    args = parser.parse_args()

    output_filepath_no_ext = drop_extension(args.input)
    if ".tsx" in args.input:
        output_filepath = output_filepath_no_ext + ".ts"
        convert_tsx(args.input, output_filepath)
    elif ".tmx" in args.input:
        output_filepath = output_filepath_no_ext + ".tm"
        convert_tmx(args.input, output_filepath)
    else:
        print(f"Unhandled file format: {args.input}")

    return 0


if __name__ == '__main__':
    sys.exit(main())