"""
Slug parser
parses .rwl files into dict and vise versa
you would need it: https://docs.google.com/document/d/1zcxeQGibkZORstwGQUovhQk71k00B69oYwkqFpGyOqs/edit#heading=h.7xts9mnasx5f
Todo: write a better docs about this
"""
import json
import zipfile
from RWESharp.Core.lingoIO import frompoint, tojson, makearr
from RWESharp.Level.RWELevel import RWELevel, defaultlevellines, minimallevellines
from RWESharp.info import PATH_LEVELS
from RWESharp.Core.Exceptions import FileStorageError
import numpy as np
import os
from enum import auto, Flag
[docs]
class StoreRWL(Flag):
Geometry = auto()
Tiles = auto()
Props = auto()
Cameras = auto()
Properties = auto()
Effects = auto()
Custom = auto()
STORE_EVERYTHING = StoreRWL.Geometry | StoreRWL.Tiles | StoreRWL.Props | StoreRWL.Cameras | StoreRWL.Effects | StoreRWL.Properties | StoreRWL.Custom
VER = 1
# ENCODING = "latin-1"
[docs]
class RWLParser:
[docs]
@staticmethod
def parse_rwl(string: str, store=STORE_EVERYTHING) -> tuple[dict, bytes | None]:
proj = {}
version = VER
width, height = 72, 43
borders = [0, 0, 0, 0]
lightimage = None
with zipfile.ZipFile(string) as content:
proj["EX2"] = tojson(defaultlevellines[5])
proj["EX"] = tojson(defaultlevellines[4])
proj["LE"] = tojson(defaultlevellines[3])
proj["TE"] = tojson(minimallevellines[1])
proj["WL"] = tojson(minimallevellines[7])
with content.open("info") as f:
lines = [i.decode() for i in f.readlines()]
version = int(lines[0])
width, height = [int(i) for i in lines[1].split(";")]
proj["EX2"]["size"] = makearr([width, height], "point")
borders = [int(i) for i in lines[2].split(";")]
proj["EX2"]["extraTiles"] = borders
lightprops = lines[3].split(";")
proj["EX2"]["light"] = lightprops[0] == "1"
proj["LE"]["lightAngle"] = int(lightprops[1])
proj["LE"]["flatness"] = int(lightprops[2])
proj["EX2"]["tileSeed"] = int(lines[4])
proj["TE"]["defaultMaterial"] = lines[5].replace("\n", "")
proj["WL"]["waterLevel"] = int(lines[6])
proj["WL"]["waterInFront"] = int(lines[7])
proj["GE"] = []
from BaseMod.LevelParts import GeoLevelPart
with content.open("geo") as geo:
for x in range(width):
proj["GE"].append([])
for y in range(height):
proj["GE"][-1].append([])
for l in range(3):
proj["GE"][-1][-1].append([int.from_bytes(geo.read(1)), GeoLevelPart.byte2stack(np.frombuffer(geo.read(2), dtype=np.uint16))])
unique_tiles = {}
with content.open("tilenames") as tilenames:
for tile in tilenames.read().decode().split("\n"):
s = tile.split(";")
unique_tiles[s[0]] = [int(s[1]), int(s[2])]
unique_materials = []
with content.open("materials") as materials:
unique_materials = materials.read().decode().split("\n")
# print(unique_materials, unique_tiles)
with content.open("tiles") as tiles:
for x in range(width):
proj["TE"]["tlMatrix"].append([])
for y in range(height):
proj["TE"]["tlMatrix"][-1].append([])
for l in range(3):
first = int.from_bytes(tiles.read(1))
if first == 0: # default
proj["TE"]["tlMatrix"][-1][-1].append({"tp": "default", "data": 0})
elif first & 128 == 0: # material
proj["TE"]["tlMatrix"][-1][-1].append({"tp": "material", "data": unique_materials[(first & 127) - 1]})
elif first & 128 == 128: # tile head/tile body
second = int.from_bytes(tiles.read(1))
ishead = second & 1 == 0
if ishead:
tile = first & 127 + ((second & 126) << 7)
tilename = list(unique_tiles.keys())[tile]
proj["TE"]["tlMatrix"][-1][-1].append({"tp": "tileHead", "data": [makearr(unique_tiles[tilename], "point"), tilename]})
continue
third = int.from_bytes(tiles.read(1))
# print(bin((third << 16) + (second << 8) + first), third, second, first)
layer = first & 3 # magic numbers my beloved
ypos = ((first & 124) >> 2) + ((second & 62) << 4) - 512
xpos = ((second & 192) >> 6) + (third << 2) - 512
# print(xpos, ypos, x, y)
xpos = x - xpos
ypos = y - ypos
# print(xpos, ypos, layer)
proj["TE"]["tlMatrix"][-1][-1].append({"tp": "tileBody", "data": [makearr([xpos, ypos], "point"), layer]})
else:
print("the what")
proj["FE"] = tojson(minimallevellines[2])
with content.open("effects") as effects:
amount = int(effects.readline().decode())
for i in range(amount):
effect = {}
stats = effects.readline().decode().rstrip("\n").split(";")
effect["nm"] = stats[0]
effect["tp"] = stats[1]
if stats[2] != "":
effect["repeats"] = int(stats[2])
if stats[3] != "":
effect["affectOpenAreas"] = float(stats[3])
if stats[4] != "":
effect["crossScreen"] = int(stats[4])
optionsamount = int(stats[5])
effect["options"] = []
for i in range(optionsamount):
option = effects.readline().decode().rstrip("\n").split(";")
name = option[0]
value = int(option[-1]) if name == "Seed" else option[-1]
effect["options"].append([name, option[1:-1], value])
effect["mtrx"] = []
for x in range(width):
effect["mtrx"].append([])
for y in range(height):
effect["mtrx"][-1].append(int.from_bytes(effects.read(1)))
proj["FE"]["effects"].append(effect)
proj["PR"] = tojson(minimallevellines[8])
with content.open("props") as props:
amount = int(props.readline().decode().rstrip("\n"))
for p in range(amount):
prop = []
stats = props.readline().decode().rstrip("\n").split(";")
prop.append(int(stats[0]))
prop.append(stats[1])
prop.append(makearr([stats[2], stats[3]], "point"))
xposes = [float(i) for i in props.readline().decode().rstrip("\n").split(";")]
yposes = [float(i) for i in props.readline().decode().rstrip("\n").split(";")]
prop.append([makearr([x, y], "point") for x, y in zip(xposes, yposes)])
settingsamount = int(props.readline().decode().rstrip("\n"))
prop.append({"settings": {}})
for s in range(settingsamount):
setting = props.readline().decode().rstrip("\n").split(";")
prop[4]["settings"][setting[0]] = int(setting[1])
proj["PR"]["props"].append(prop)
proj["CM"] = tojson(minimallevellines[6])
with content.open("cameras") as cameras:
amount = int(cameras.readline().decode().rstrip("\n"))
for c in range(amount):
proj["CM"]["cameras"].append(makearr(cameras.readline().decode().rstrip("\n").split(";"), "point"))
xposes = [float(i) for i in cameras.readline().decode().rstrip("\n").split(";")]
yposes = [float(i) for i in cameras.readline().decode().rstrip("\n").split(";")]
proj["CM"]["quads"].append([[x, y] for x, y in zip(xposes, yposes)])
filenames = [i.filename for i in content.filelist]
if "light.png" in filenames:
with content.open("light.png") as light:
lightimage = light.read()
if "custom" in filenames:
with content.open("custom") as custom:
proj["CLD"] = json.loads(custom.read().decode())
return proj, lightimage
[docs]
@staticmethod
def save_rwl(level: dict, path: str, lightdata: bytes=None):
with zipfile.ZipFile(path, "w") as content:
size = [1, 1]
with content.open("info", "w") as info:
size = [int(i) for i in frompoint(level["EX2"]["size"])]
info.write("\n".join([
"1",
f"{size[0]};{size[1]}",
";".join([str(i) for i in level["EX2"]["extraTiles"]]),
f'{"1" if level["EX2"]["light"] == 1 else "0"};{level["LE"]["lightAngle"]};{level["LE"]["flatness"]}',
str(level["EX2"]["tileSeed"]),
level["TE"]["defaultMaterial"],
str(level["WL"]["waterLevel"]),
str(level["WL"]["waterInFront"])
]).encode())
from BaseMod.LevelParts import GeoLevelPart
with content.open("geo", "w") as geo:
for x in level["GE"]:
for y in x:
for l in y:
geo.write(np.uint8(l[0]))
geo.write(GeoLevelPart.stack2byte(l[1]))
with content.open("tiles", "w") as tiles: # holy whitespace
uniquematerials: list[str] = []
uniquetiles: dict[str, [int, int]] = {}
for ix, x in enumerate(level["TE"]["tlMatrix"]):
for iy, y in enumerate(x):
for l in y:
type = l["tp"].lower()
if type == "default": # 00000000
tiles.write((0).to_bytes(1))
elif type == "material": # 0xxxxxxx > 0
if l["data"] not in uniquematerials:
uniquematerials.append(l["data"])
tiles.write((uniquematerials.index(l["data"]) + 1).to_bytes(1))
elif type == "tilehead": # xxxxxxx0 1xxxxxxx
uniquetiles[l["data"][1]] = frompoint(l["data"][0])
num = list(uniquetiles.keys()).index(l["data"][1])
export = (128 + ((num & (127 << 7)) << 2) + (num & 127))
tiles.write((export & 255).to_bytes(1))
tiles.write(((export & 65280) >> 8).to_bytes(1))
# print(bin(export), (export & 65280) >> 8, export & 255)
elif type == "tilebody": # XXXXXXXX XXYYYYY1 1YYYYYLL
layer = l["data"][1]
headpos = [int(i) for i in frompoint(l["data"][0])]
xpos = ix - headpos[0] + 512
ypos = iy - headpos[1] + 512
export = (384 + layer + (xpos << 14) + ((ypos & 31) << 2) + ((ypos & (31 << 5)) << 4))
tiles.write((export & 255).to_bytes(1))
tiles.write(((export & 65280) >> 8).to_bytes(1))
tiles.write(((export & 16711680) >> 16).to_bytes(1))
# print(xpos, ypos, xpos - 512, ypos - 512, ix, iy)
# print(bin(export), (export & 16711680) >> 16, (export & 65280) >> 8, export & 255)
# 0b100001000010000110110111 132 33 183
# 528 525 16 13 52 211
# 0b100001000010000110110111 132 33 183
# 16 13 13 156
# -3 143 3
else:
print("explode")
if len(uniquematerials) > 127 or len(uniquetiles) > 16383:
raise FileStorageError("File unable to store with rwl file format")
with content.open("materials", "w") as materials:
materials.write("\n".join(uniquematerials).encode())
with content.open("tilenames", "w") as tilenames:
tilenames.write("\n".join([f"{k};{v[0]};{v[1]}" for k, v in uniquetiles.items()]).encode())
with content.open("effects", "w") as effects:
effects.write(str(len(level["FE"]["effects"])).encode())
effects.write("\n".encode())
for i in level["FE"]["effects"]:
effects.write(";".join([i["nm"], i["tp"], str(i.get("repeats", "")), str(i.get("affectOpenAreas", "")), str(i.get("crossScreen", "")), str(len(i["options"]))]).encode())
effects.write("\n".encode())
for o in i["options"]:
effects.write(";".join([o[0], *o[1], str(o[2])]).encode())
effects.write("\n".encode())
# uh some small problem is that values might be less accurate because of rounding but tbh do we care about 0.1234 in 0 to 100?
for x in i["mtrx"]:
for y in x:
effects.write(int(y).to_bytes(1))
with content.open("props", "w") as props:
props.write(str(len(level["PR"]["props"])).encode())
props.write("\n".encode()) # not sure if i should just do b"\n" but it isn't a big deal
for p in level["PR"]["props"]:
catpos = frompoint(p[2])
props.write(";".join([str(p[0]), p[1], str(catpos[0]), str(catpos[1])]).encode())
props.write("\n".encode())
points = [frompoint(i) for i in p[3]]
props.write(";".join([str(i[0]) for i in points]).encode())
props.write("\n".encode())
props.write(";".join([str(i[1]) for i in points]).encode())
props.write("\n".encode())
props.write(str(len(p[4]["settings"])).encode())
props.write("\n".encode())
for k, v in p[4]["settings"].items():
props.write(f"{k};{v}".encode())
props.write("\n".encode())
with content.open("cameras", "w") as cameras:
cameras.write(str(len(level["CM"]["cameras"])).encode())
cameras.write("\n".encode())
for i, c in enumerate(level["CM"]["cameras"]):
cameras.write(";".join([str(d) for d in frompoint(c)]).encode())
cameras.write("\n".encode())
quads = level["CM"]["quads"][i]
cameras.write(";".join([str(q[0]) for q in quads]).encode())
cameras.write("\n".encode())
cameras.write(";".join([str(q[1]) for q in quads]).encode())
cameras.write("\n".encode())
file, _ = os.path.splitext(path)
if os.path.exists(file + ".png") or lightdata is not None:
with content.open("light.png", "w") as light:
light.write(open(file + ".png", "rb").read() if lightdata is None else lightdata)
if level.get("CLD") is None:
return
with content.open("custom", "w") as custom:
custom.write(json.dumps(level["CLD"]).encode())
if __name__ == '__main__':
# benchmark
# lvl = RWELevel(None, "/levelEditorProjects/SU_A25.wep")
# encoded = RWLParser.save_rwl(lvl)
#print(encoded)
# open("/levelEditorProjects/SU_A25.rwl", "wb").write(encoded)
# decoded = RWLParser.parse_rwl(encoded)
lvl = RWELevel.turntoproject(open(os.path.join(PATH_LEVELS, "HE_LEG.txt")).read())
# level = RWLParser.parse_rwl(os.path.join(PATH_LEVELS, "level.rwl"))
level_path = os.path.join(PATH_LEVELS, "HE_LEG.rwl")
RWLParser.save_rwl(lvl.data, level_path)
decoded, light = RWLParser.parse_rwl(level_path)
print(light)
with open(os.path.join(PATH_LEVELS, "decoded.wep"), "w") as f:
f.write(json.dumps(decoded))
# print(decoded)