chaos-drop/converters.py

197 lines
6.5 KiB
Python

import fxconv
import re
def convert(input, output, params, target):
recognized = True
if params["custom-type"] == "level":
o = convert_level(input, params)
else:
recognized = False
if recognized:
fxconv.elf(o, output, "_" + params["name"], **target)
return 0
return 1
def num(value):
return fxconv.u32(value * 65536)
DISTANCE_FACTOR = 24
def advance(position, coordinate):
silent = False
if coordinate.startswith("@"):
silent = True
coordinate = coordinate[1:]
if coordinate.startswith("="):
value = int(coordinate)
else:
value = position + int(coordinate[1:])
if silent:
return position, value
else:
return value, value
def convert_level(input, params):
RE_VALUES = {
"name": re.compile(r'.*'),
}
RE_ELEMENTS = {
"mirror": (True, re.compile(r'')),
"plane": (False, re.compile(r'(\S+)\s+(\S+)')),
"text": (True, re.compile(r'\"([^"]*)\"')),
"vfx": (True, re.compile(r'([a-z]+)')),
}
RE_POSITION = re.compile(r'^\s*(@)?\s*(?:(\d+)/)?\s*(\d+)?')
header = dict()
elements = []
pos = 0
with open(input, "r") as fp:
lines = [l for l in fp.read().splitlines() if l
and not l.startswith("#")]
for i, l in enumerate(lines):
# Key/value metadata
if ":" in l:
key, value = [x.strip() for x in l.split(":", 1)]
if key not in RE_VALUES:
raise fxconv.FxconvError(f"unknown key '{key}'")
if not RE_VALUES[key].fullmatch(value):
raise fxconv.FxconvError(
f"invalid value for '{key}': '{value}'")
header[key] = value
continue
# Elements
m_pos = RE_POSITION.match(l)
if not m_pos:
raise fxconv.FxconvError(f"invalid position: '{l}'")
start = pos + int(m_pos[2]) if m_pos[2] else pos
end = start + int(m_pos[3]) if m_pos[3] else start
if not m_pos[1]:
pos = end
l = l[len(m_pos[0]):]
things = [x.strip() for x in l.strip().split(" ", 1)]
if len(things) == 1:
elname, data = things[0], ""
else:
elname, data = things
if RE_ELEMENTS[elname][0] and end == start:
raise fxconv.FxconvError(f"'{elname}' needs a size")
if not RE_ELEMENTS[elname][0] and end != start:
raise fxconv.FxconvError(f"'{elname}' needs no size")
m = RE_ELEMENTS[elname][1].fullmatch(data)
if not m:
raise fxconv.FxconvError(f"bad data for '{elname}': '{data}'")
elements.append((elname, start, end, m))
mirrors = fxconv.ObjectData()
planes = fxconv.ObjectData()
texts = fxconv.ObjectData()
vfxs = fxconv.ObjectData()
mirror_count = 0
plane_count = 0
text_count = 0
vfx_count = 0
for (key, start, end, m) in elements:
if key == "mirror":
mirrors += num(start * DISTANCE_FACTOR)
mirrors += num(end * DISTANCE_FACTOR)
mirror_count += 1
elif key == "plane":
kind = m[1]
shape_str = m[2]
predef_shapes = {
"VLINES_1_5": 0b1000110001100011000110001,
"VLINES_1_3_5": 0b1010110101101011010110101,
"VLINES_2_4": 0b0101001010010100101001010,
"HLINES_1_5": 0b1111100000000000000011111,
"HLINES_1_3_5": 0b1111100000111110000011111,
"HLINES_2_4": 0b0000011111000001111100000,
"HASH": 0b0101011111010101111101010,
"BOX": 0b1111110001100011000111111,
"BOX_DOT": 0b1111110001101011000111111,
"BIGDOT": 0b0000001110011100111000000,
"DOT": 0b0000000000001000000000000,
"RING": 0b0000001110010100111000000,
"CORNERS": 0b1101111011000001101111011,
"DIAGONAL+": 0b0001100111011101110011000,
"DIAGONAL-": 0b1100011100011100011100011,
"ARROW_+X": 0b0010000010111110001000100,
"ARROW_-X": 0b0010001000111110100000100,
"ARROW_+Z": 0b0010001110101010010000100,
"ARROW_-Z": 0b0010000100101010111000100,
"FULL": 0b1111111111111111111111111,
}
if shape_str in predef_shapes:
shape = predef_shapes[shape_str]
else:
assert shape_str.startswith("0b")
shape = "".join(c for c in shape_str[2:] if c != "'")
assert(len(shape) == 25)
shape = int(shape, 2)
kinds = {
"DAMAGE": 0,
"ARROW": 1,
"INVIS": 2,
"DEATH": 3,
}
assert kind in kinds
data = 0
if kind == "ARROW":
directions = ["ARROW_-Z", "ARROW_+X", "ARROW_+Z", "ARROW_-X"]
assert shape_str in directions
data = directions.index(shape_str)
planes += num(start * DISTANCE_FACTOR)
planes += fxconv.u32(shape)
planes += fxconv.u16(kinds[kind])
planes += fxconv.u16(data)
plane_count += 1
elif key == "text":
string = m[1].replace("\\n", "\n").replace("\\\\", "\\")
texts += num(start * DISTANCE_FACTOR)
texts += num(end * DISTANCE_FACTOR)
texts += fxconv.string(string)
text_count += 1
elif key == "vfx":
effects = {
"darken": 0,
"whiten": 1,
"invert": 2,
}
if m[1] not in effects:
raise fxconv.FxconvError(f"invalid vfx '{m[1]}'")
vfxs += num(start * DISTANCE_FACTOR)
vfxs += num(end * DISTANCE_FACTOR)
vfxs += fxconv.u32(effects[m[1]])
vfx_count += 1
o = fxconv.ObjectData()
o += fxconv.string(header.get("name", "<Untitled>"))
o += num(int(header.get("finish", pos+2)) * DISTANCE_FACTOR)
o += fxconv.u32(mirror_count)
o += fxconv.ptr(mirrors)
o += fxconv.u32(plane_count)
o += fxconv.ptr(planes)
o += fxconv.u32(text_count)
o += fxconv.ptr(texts)
o += fxconv.u32(vfx_count)
o += fxconv.ptr(vfxs)
return o