220 lines
7.9 KiB
Python
Executable file
220 lines
7.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
from argparse import ArgumentParser
|
|
from lxml import etree
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
import sys
|
|
|
|
|
|
def main():
|
|
parser = ArgumentParser(description="Cleanup rc3.world maps")
|
|
parser.add_argument("map", help="map in .tmx format")
|
|
parser.add_argument("-o", "--output", help="where to write modified map")
|
|
parser.add_argument(
|
|
"-i",
|
|
"--in-place",
|
|
help="replace map with modified version",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"--fix-copyright",
|
|
help="add mapCopyright/tilesetCopyright properties",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"--separate-collisions",
|
|
help="move collisions into a separate 'collisions' layer",
|
|
action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"--split-tilesets",
|
|
help="split tilesets bigger than 4096x4096",
|
|
action="store_true",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
output = args.output
|
|
if args.in_place:
|
|
output = args.map
|
|
if not output:
|
|
print("Output unspecified.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
tree = etree.parse(args.map)
|
|
map = tree.getroot()
|
|
if args.fix_copyright:
|
|
fix_copyright(map)
|
|
if args.separate_collisions:
|
|
separate_collisions(map)
|
|
if args.split_tilesets:
|
|
split_tilesets(map)
|
|
tree.write(output)
|
|
|
|
|
|
def fix_copyright(map):
|
|
if map.find("properties/property[@name='mapCopyright']") is None:
|
|
license = input("Map license? [CC0] ") or "CC0"
|
|
properties = map.find("properties")
|
|
if properties is None:
|
|
properties = etree.SubElement(map, "properties")
|
|
etree.SubElement(properties, "property", name="mapCopyright", value=license)
|
|
|
|
for tileset in map.findall("tileset"):
|
|
if tileset.find("properties/property[@name='tilesetCopyright']") is None:
|
|
license = (
|
|
input("Tileset '%s' license? [CC0] " % tileset.attrib["name"]) or "CC0"
|
|
)
|
|
properties = tileset.find("properties")
|
|
if properties is None:
|
|
properties = etree.SubElement(tileset, "properties")
|
|
etree.SubElement(
|
|
properties, "property", name="tilesetCopyright", value=license
|
|
)
|
|
|
|
|
|
def separate_collisions(map):
|
|
# find tiles with collides property
|
|
colliding_tiles = set()
|
|
firstgid = 1
|
|
tilecount = 0
|
|
for tileset in map.findall("tileset"):
|
|
if tileset.attrib["name"] == "collisions":
|
|
continue
|
|
firstgid = int(tileset.attrib["firstgid"])
|
|
tilecount = int(tileset.attrib["tilecount"])
|
|
for tile in tileset.findall("tile"):
|
|
properties = tile.find("properties")
|
|
if properties is None:
|
|
continue
|
|
for collision_property in properties.findall("property[@name='collides']"):
|
|
properties.remove(collision_property)
|
|
if collision_property.attrib["value"] != "true":
|
|
continue
|
|
colliding_tiles.add(firstgid + int(tile.attrib["id"]))
|
|
|
|
# create/find collisions tileset (single white tile)
|
|
collisions_tileset = map.find("tileset[@name='collisions']")
|
|
if collisions_tileset is None:
|
|
collisions_tileset = etree.Element(
|
|
"tileset",
|
|
firstgid=str(firstgid + tilecount),
|
|
name="collisions",
|
|
tilewidth="32",
|
|
tileheight="32",
|
|
tilecount="1",
|
|
columns="1",
|
|
)
|
|
tileset.addnext(collisions_tileset)
|
|
etree.SubElement(
|
|
etree.SubElement(collisions_tileset, "properties"),
|
|
"property",
|
|
name="tilesetCopyright",
|
|
value="not copyrightable",
|
|
)
|
|
image_source = Path("imgs") / "collisions.png"
|
|
image_source.parent.mkdir(parents=True, exist_ok=True)
|
|
Image.new("1", (32, 32), (1)).save(image_source)
|
|
etree.SubElement(
|
|
collisions_tileset,
|
|
"image",
|
|
source=str(image_source),
|
|
width="32",
|
|
height="32",
|
|
)
|
|
etree.SubElement(
|
|
etree.SubElement(
|
|
etree.SubElement(collisions_tileset, "tile", id="0"), "properties"
|
|
),
|
|
"property",
|
|
name="collides",
|
|
type="bool",
|
|
value="true",
|
|
)
|
|
collisions_gid = int(collisions_tileset.attrib["firstgid"])
|
|
|
|
# merge collisions
|
|
collisions = [0] * int(map.attrib["width"]) * int(map.attrib["height"])
|
|
for layer in map.findall("layer"):
|
|
if layer.attrib["name"] == "collisions":
|
|
continue
|
|
data = [int(x) for x in layer.find("data[@encoding='csv']").text.split(",")]
|
|
for i, gid in enumerate(data):
|
|
if gid in colliding_tiles:
|
|
collisions[i] = collisions_gid
|
|
|
|
# create/find collisions layer and save collision data
|
|
collisions_data = map.find("layer[@name='collisions']/data")
|
|
if collisions_data is None:
|
|
collisions_layer = etree.Element(
|
|
"layer",
|
|
id=map.attrib["nextlayerid"],
|
|
name="collisions",
|
|
width=map.attrib["width"],
|
|
height=map.attrib["height"],
|
|
)
|
|
map.find("layer").addprevious(collisions_layer)
|
|
map.attrib["nextlayerid"] = str(int(map.attrib["nextlayerid"]) + 1)
|
|
collisions_data = etree.SubElement(
|
|
collisions_layer,
|
|
"data",
|
|
encoding="csv",
|
|
)
|
|
collisions_data.text = ",".join([str(x) for x in collisions])
|
|
|
|
|
|
def split_tilesets(map):
|
|
# we need the offset of the tileset inside the map element to insert new tilesets correctly
|
|
for offset, tileset in enumerate(map):
|
|
if tileset.tag != "tileset":
|
|
continue
|
|
image = tileset.find("image")
|
|
if int(image.attrib["width"]) <= 4096 and int(image.attrib["height"]) <= 4096:
|
|
continue
|
|
dosplit = input("Split tileset '%s'? [yN] " % tileset.attrib["name"])
|
|
if dosplit.lower() != "y":
|
|
continue
|
|
properties = tileset.find("properties")
|
|
firstgid = int(tileset.attrib["firstgid"])
|
|
tilewidth = int(tileset.attrib["tilewidth"])
|
|
tileheight = int(tileset.attrib["tileheight"])
|
|
image_source = Path(image.attrib["source"])
|
|
with Image.open(image_source) as im:
|
|
for i, x in enumerate(range(0, im.width, 4096)):
|
|
for j, y in enumerate(range(0, im.height, 4096)):
|
|
croped_source = image_source.with_stem(
|
|
"%s_%02d%02d" % (image_source.stem, i, j)
|
|
)
|
|
im.crop(
|
|
(x, y, min(x + 4096, im.width), min(y + 4096, im.height))
|
|
).save(croped_source)
|
|
width = min(4096, im.width - x)
|
|
height = min(4096, im.height - y)
|
|
columns = width // tilewidth
|
|
tilecount = columns * (height // tileheight)
|
|
tileset_croped = etree.Element(
|
|
"tileset",
|
|
name="%s (%d, %d)" % (tileset.attrib["name"], i, j),
|
|
firstgid=str(firstgid),
|
|
tilewidth=str(tilewidth),
|
|
tileheight=str(tileheight),
|
|
tilecount=str(tilecount),
|
|
columns=str(columns),
|
|
)
|
|
map.insert(offset + i + j, tileset_croped)
|
|
firstgid += tilecount
|
|
if properties is not None:
|
|
tileset_croped.append(properties)
|
|
etree.SubElement(
|
|
tileset_croped,
|
|
"image",
|
|
source=str(croped_source),
|
|
width=str(width),
|
|
height=str(height),
|
|
)
|
|
map.remove(tileset)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|