digitales-heizhaus/mapcleanup.py

221 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
from argparse import ArgumentParser
from copy import copy
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 2048x2048",
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("tiles") / "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):
for tileset in map.findall("tileset"):
image = tileset.find("image")
if int(image.attrib["width"]) <= 2048 and int(image.attrib["height"]) <= 2048:
continue
if int(image.attrib["width"]) > 2048:
print("Can't split tileset '%s'. Spliting of width > 2048 not implemented." % tileset.attrib["name"])
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, 2048)):
for j, y in enumerate(range(0, im.height, 2048)):
croped_source = image_source.with_stem(
"%s_%02d%02d" % (image_source.stem, i, j)
)
im.crop(
(x, y, min(x + 2048, im.width), min(y + 2048, im.height))
).save(croped_source)
width = min(2048, im.width - x)
height = min(2048, 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),
)
tileset.addprevious(tileset_croped)
firstgid += tilecount
if properties is not None:
tileset_croped.append(copy(properties))
etree.SubElement(
tileset_croped,
"image",
source=str(croped_source),
width=str(width),
height=str(height),
)
map.remove(tileset)
if __name__ == "__main__":
main()