mirror of
https://github.com/pmret/papermario.git
synced 2024-11-08 20:12:30 +01:00
8837fbdf65
* WIP work on sprites (sprite_stuff.py) * cleanup of various stuff * separate compiler installation into separate script * wipz * more * renames, bugfixes * more * very grood * cleanin * goods and services * oopth * oopth2 * Parse palette data from xml * more work * more wipperz * more * it working * git subrepo pull --force tools/splat subrepo: subdir: "tools/splat" merged: "e72a868f9f" upstream: origin: "https://github.com/ethteck/splat.git" branch: "master" commit: "e72a868f9f" git-subrepo: version: "0.4.5" origin: "https://github.com/ingydotnet/git-subrepo" commit: "aa416e4" * fix symbol_addrs for new splat * upd8s * Use generated header, other versions, fixes * fixes & formatting * wip fusing npc + player extraction & cleanup * remove npc_files * buildin * fix some bugs * Cleanup, yay0s separately * cleen * cleanup * Respect stack during build * jp spritz * dun * fix c files --------- Co-authored-by: pixel-stuck <mathmcclintic@gmail.com>
314 lines
8.2 KiB
Python
314 lines
8.2 KiB
Python
from dataclasses import dataclass
|
|
from itertools import zip_longest
|
|
import struct
|
|
from typing import Dict, List
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
def iter_in_groups(iterable, n, fillvalue=None):
|
|
args = [iter(iterable)] * n
|
|
return zip_longest(*args, fillvalue=fillvalue)
|
|
|
|
|
|
def read_offset_list(data: bytes):
|
|
l = []
|
|
|
|
for offset in struct.iter_unpack(">i", data):
|
|
if offset[0] == -1:
|
|
break
|
|
l.append(offset[0])
|
|
|
|
return l
|
|
|
|
|
|
class Animation:
|
|
@property
|
|
def name(self) -> str:
|
|
return self.__class__.__name__
|
|
|
|
def get_attributes(self) -> Dict[str, str]:
|
|
raise NotImplementedError()
|
|
|
|
|
|
@dataclass
|
|
class Wait(Animation):
|
|
duration: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"duration": str(self.duration),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetRaster(Animation):
|
|
raster: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"raster": f"{self.raster:X}",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetPalette(Animation):
|
|
palette: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"palette": f"{self.palette:X}",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Goto(Animation):
|
|
pos: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"pos": str(self.pos),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Loop(Animation):
|
|
count: int
|
|
pos: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"count": str(self.count),
|
|
"pos": str(self.pos),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetPos(Animation):
|
|
flag: int
|
|
x: int
|
|
y: int
|
|
z: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"flag": str(self.flag),
|
|
"xyz": f"{self.x},{self.y},{self.z}",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetRot(Animation):
|
|
x: int
|
|
y: int
|
|
z: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"xyz": f"{self.x},{self.y},{self.z}",
|
|
}
|
|
|
|
|
|
SCALE_MODE_INT_TO_STR = {
|
|
0: "uniform",
|
|
1: "x",
|
|
2: "y",
|
|
3: "z",
|
|
}
|
|
SCALE_MODE_STR_TO_INT = {v: k for k, v in SCALE_MODE_INT_TO_STR.items()}
|
|
|
|
|
|
@dataclass
|
|
class SetScale(Animation):
|
|
mode: int
|
|
percent: int
|
|
|
|
def get_mode_str(self):
|
|
if self.mode in SCALE_MODE_INT_TO_STR:
|
|
return SCALE_MODE_INT_TO_STR[self.mode]
|
|
else:
|
|
raise ValueError(f"invalid scale mode {self.mode}")
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"mode": self.get_mode_str(),
|
|
"percent": str(self.percent),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Unknown(Animation):
|
|
v: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"v": str(self.v),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetParent(Animation):
|
|
component_index: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"component_index": str(self.component_index),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SetNotify(Animation):
|
|
v: int
|
|
|
|
def get_attributes(self):
|
|
return {
|
|
"v": str(self.v),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Keyframe(Animation):
|
|
pass
|
|
|
|
|
|
@dataclass
|
|
class AnimComponent:
|
|
x: int
|
|
y: int
|
|
z: int
|
|
commands: List[int]
|
|
|
|
@property
|
|
def size(self):
|
|
return len(self.commands)
|
|
|
|
@staticmethod
|
|
def parse_commands(command_list: List[int]) -> List[Animation]:
|
|
ret: List[Animation] = []
|
|
|
|
def to_signed(value):
|
|
return -(value & 0x8000) | (value & 0x7FFF)
|
|
|
|
i = 0
|
|
while i < len(command_list):
|
|
cmd_start = command_list[i]
|
|
|
|
if cmd_start <= 0xFFF:
|
|
ret.append(Wait(cmd_start))
|
|
elif cmd_start <= 0x1FFF:
|
|
raster = cmd_start % 0x1000
|
|
if raster == 0xFFF:
|
|
raster = -1
|
|
ret.append(SetRaster(raster))
|
|
elif cmd_start <= 0x2FFF:
|
|
ret.append(Goto(cmd_start % 0x2000))
|
|
elif cmd_start <= 0x3FFF:
|
|
flag = cmd_start % 0x3000
|
|
x, y, z = command_list[i + 1 : i + 4]
|
|
x = to_signed(x)
|
|
y = to_signed(y)
|
|
z = to_signed(z)
|
|
i += 3
|
|
ret.append(SetPos(flag, x, y, z))
|
|
elif cmd_start <= 0x4FFF:
|
|
x, y, z = command_list[i : i + 3]
|
|
x = ((x % 0x4000) << 20) >> 20
|
|
y = to_signed(y)
|
|
z = to_signed(z)
|
|
i += 2
|
|
ret.append(SetRot(x, y, z))
|
|
elif cmd_start <= 0x5FFF:
|
|
mode = cmd_start % 0x5000
|
|
percent = command_list[i + 1]
|
|
i += 1
|
|
ret.append(SetScale(mode, percent))
|
|
elif cmd_start <= 0x6FFF:
|
|
palette = cmd_start % 0x6000
|
|
if palette == 0xFFF:
|
|
palette = -1
|
|
ret.append(SetPalette(palette))
|
|
elif cmd_start <= 0x7FFF:
|
|
count = cmd_start % 0x7000
|
|
pos = command_list[i + 1]
|
|
i += 1
|
|
ret.append(Loop(count, pos))
|
|
elif cmd_start <= 0x80FF:
|
|
ret.append(Unknown(cmd_start % 0x8000))
|
|
elif cmd_start <= 0x81FF:
|
|
ret.append(SetParent(cmd_start % 0x8100))
|
|
elif cmd_start <= 0x82FF:
|
|
ret.append(SetNotify(cmd_start % 0x8200))
|
|
else:
|
|
raise Exception("Unknown command")
|
|
i += 1
|
|
return ret
|
|
|
|
@staticmethod
|
|
def from_bytes(data: bytes, sprite_data: bytes):
|
|
commands_offset = int.from_bytes(data[0:4], byteorder="big")
|
|
commands_size = int.from_bytes(data[4:6], byteorder="big") # size in bytes
|
|
commands_data = sprite_data[commands_offset : commands_offset + commands_size]
|
|
|
|
x, y, z = struct.unpack(">hhh", data[6:12])
|
|
|
|
commands = [
|
|
int.from_bytes(d[0:2], byteorder="big", signed=False)
|
|
for d in iter_in_groups(commands_data, 2)
|
|
]
|
|
return AnimComponent(x, y, z, commands)
|
|
|
|
@property
|
|
def animations(self) -> List[Animation]:
|
|
return AnimComponent.parse_commands(self.commands)
|
|
|
|
@staticmethod
|
|
def from_xml(xml: ET.Element):
|
|
commands: List[int] = []
|
|
for cmd in xml:
|
|
if cmd.tag == "Wait":
|
|
duration = int(cmd.attrib["duration"])
|
|
commands.append(duration)
|
|
elif cmd.tag == "SetRaster":
|
|
raster = int(cmd.attrib["raster"], 0x10)
|
|
if raster == -1:
|
|
raster = 0xFFF
|
|
commands.append(0x1000 + raster)
|
|
elif cmd.tag == "Goto":
|
|
commands.append(0x2000 + int(cmd.attrib["pos"]))
|
|
elif cmd.tag == "SetPos":
|
|
flag = int(cmd.attrib["flag"])
|
|
x, y, z = cmd.attrib["xyz"].split(",")
|
|
commands.append(0x3000 + flag)
|
|
commands.append(int(x) & 0xFFFF)
|
|
commands.append(int(y) & 0xFFFF)
|
|
commands.append(int(z) & 0xFFFF)
|
|
elif cmd.tag == "SetRot":
|
|
x, y, z = cmd.attrib["xyz"].split(",")
|
|
commands.append(0x4000 + (int(x) & 0xFFFF))
|
|
commands.append(int(y) & 0xFFFF)
|
|
commands.append(int(z) & 0xFFFF)
|
|
elif cmd.tag == "SetScale":
|
|
mode = SCALE_MODE_STR_TO_INT[cmd.attrib["mode"]]
|
|
percent = int(cmd.attrib["percent"])
|
|
commands.append(0x5000 + mode)
|
|
commands.append(percent)
|
|
elif cmd.tag == "SetPalette":
|
|
palette = int(cmd.attrib["palette"], 0x10)
|
|
if palette == -1:
|
|
palette = 0xFFF
|
|
commands.append(0x6000 + palette)
|
|
elif cmd.tag == "Loop":
|
|
count = int(cmd.attrib["count"])
|
|
pos = int(cmd.attrib["pos"])
|
|
commands.append(0x7000 + count)
|
|
commands.append(pos)
|
|
elif cmd.tag == "Unknown":
|
|
commands.append(0x8000 + int(cmd.attrib["v"]))
|
|
elif cmd.tag == "SetParent":
|
|
commands.append(0x8100 + int(cmd.attrib["component_index"]))
|
|
elif cmd.tag == "SetNotify":
|
|
commands.append(0x8200 + int(cmd.attrib["v"]))
|
|
else:
|
|
raise ValueError(f"unknown command {cmd.tag}")
|
|
x, y, z = xml.attrib["xyz"].split(",")
|
|
return AnimComponent(int(x), int(y), int(z), commands)
|