lol its in c
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 lines
4.3 KiB

  1. import argparse
  2. import os
  3. import pathlib
  4. import re
  5. import subprocess
  6. import sys
  7. from typing import NoReturn, Tuple
  8. from PIL import Image
  9. GREEN = (0, 255, 0)
  10. RED = (255, 0, 0)
  11. BLUE = (0, 0, 255)
  12. YELLOW = (255, 255, 0)
  13. def abort(msg: str) -> NoReturn:
  14. print(msg, file=sys.stderr)
  15. sys.exit(1)
  16. Point = Tuple[int, int]
  17. def generate_coll_map(
  18. in_path: pathlib.Path, width: int, height: int, compress: bool = False
  19. ) -> Tuple[bytes, Point, Point]:
  20. png = Image.open(in_path).convert("RGB")
  21. if png.width % 8 != 0 or png.height % 8 != 0:
  22. abort(f"file '{in_path}' has invalid dimensions (should be multiple of 8)")
  23. if png.width // 8 != width or png.height // 8 != height:
  24. abort(f"file '{in_path}' has different size from map")
  25. out_bytes = []
  26. bits = []
  27. spawn = None
  28. camera = (0, 0)
  29. for y in range(png.height // 8):
  30. for x in range(png.width // 8):
  31. pixel = png.getpixel((x * 8, y * 8))
  32. bit = None
  33. if pixel == RED:
  34. bit = 0
  35. elif pixel == GREEN:
  36. bit = 1
  37. elif pixel == BLUE:
  38. bit = 1
  39. spawn = (x, y)
  40. elif pixel == YELLOW:
  41. bit = 1
  42. camera = (x, y)
  43. else:
  44. abort(f"unsupported pixel in collision map: {pixel}")
  45. if compress:
  46. bits.append(bit)
  47. if len(bits) == 8:
  48. byte = sum([bit << i for i, bit in enumerate(bits)])
  49. out_bytes.append(byte)
  50. bits = []
  51. else:
  52. out_bytes.append(bit)
  53. png.close()
  54. if spawn is None:
  55. abort(f"no spawn point located")
  56. return bytes(out_bytes), spawn, camera
  57. def format_bytes(data: bytes, width: int = 16) -> str:
  58. lines = []
  59. for line_no in range(0, len(data), width):
  60. line = data[line_no : line_no + width]
  61. lines.append(" " + ", ".join(["0x%02X" % b for b in line]))
  62. return ",\n".join(lines)
  63. def generate_map(pngfile: str, compress: bool = False) -> None:
  64. pngpath = pathlib.Path(pngfile).resolve()
  65. section = re.sub(r"[^a-z]", "_", pngpath.name.replace(".png", "").lower())
  66. tilepath = pngpath.parent / f"{section}.tiles"
  67. mappath = pngpath.parent / f"{section}.tilemap"
  68. incpath = pngpath.parent / pngpath.name.replace(".png", ".h")
  69. spath = pngpath.parent / pngpath.name.replace(".png", ".c")
  70. png = Image.open(pngpath)
  71. if png.width % 8 != 0 or png.height % 8 != 0:
  72. abort(f"file '{pngfile}' has invalid dimensions (should be multiple of 8)")
  73. width = png.width // 8
  74. height = png.height // 8
  75. if width > 127 or height > 127:
  76. abort(
  77. f"file '{pngfile}' has invalid dimensions (width/height greater than 127 tiles)"
  78. )
  79. png.close()
  80. subprocess.run(
  81. [
  82. "rgbgfx",
  83. "-u",
  84. "-t",
  85. tilepath,
  86. "-o",
  87. mappath,
  88. pngfile,
  89. ]
  90. )
  91. collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png")
  92. collpath_out = pngpath.parent / f"{section}.collision"
  93. coll_map, spawn, camera = generate_coll_map(
  94. collpath, width, height, compress=compress
  95. )
  96. with open(collpath_out, "wb") as outf:
  97. outf.write(coll_map)
  98. tiles_size = os.path.getsize(tilepath)
  99. with open(incpath, "w") as outf:
  100. outf.write(
  101. f"""
  102. #ifndef IS_MAP_{section}_H_
  103. #define IS_MAP_{section}_H_
  104. extern map_t map_{section};
  105. #endif
  106. """
  107. )
  108. with open(spath, "w") as outf:
  109. outf.write(
  110. f"""
  111. #include "map.h"
  112. MAP_ASSET({section}_tiles, "{section}.tiles");
  113. MAP_ASSET({section}_map, "{section}.tilemap");
  114. MAP_ASSET({section}_collision, "{section}.collision");
  115. const map_t map_{section} = {{
  116. (uint16_t)&{section}_tiles[0],
  117. {tiles_size} / 16,
  118. (uint16_t)&{section}_map[0],
  119. (uint16_t)&{section}_collision[0],
  120. {width},
  121. {height},
  122. {spawn[0]},
  123. {spawn[1]},
  124. {camera[0]},
  125. {camera[1]}
  126. }};
  127. """
  128. )
  129. def main() -> None:
  130. parser = argparse.ArgumentParser("generate_map")
  131. parser.add_argument("-c", "--compress", default=False)
  132. parser.add_argument("png")
  133. args = parser.parse_args()
  134. generate_map(args.png, compress=(not not args.compress))
  135. if __name__ == "__main__":
  136. main()