mirror of https://github.com/pmret/papermario.git
sprite fixes (#1230)
This commit is contained in:
parent
93c7e0fe65
commit
da808bbd17
|
@ -3,7 +3,7 @@
|
|||
from math import floor
|
||||
from sys import argv, path
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
from typing import List, Dict, Tuple
|
||||
import xml.etree.ElementTree as ET
|
||||
import png # type: ignore
|
||||
|
||||
|
@ -78,7 +78,9 @@ def from_dir(
|
|||
|
||||
palettes = []
|
||||
palette_names: List[str] = []
|
||||
for Palette in SpriteSheet.findall("./PaletteList/Palette"):
|
||||
palette_map: Dict[str, int] = {}
|
||||
|
||||
for idx, Palette in enumerate(SpriteSheet.findall("./PaletteList/Palette")):
|
||||
if asset_stack is not None and load_images:
|
||||
img_name = Palette.attrib["src"]
|
||||
img_path = resolve_image_path(sprite_dir, "palettes", img_name, asset_stack)
|
||||
|
@ -91,11 +93,15 @@ def from_dir(
|
|||
|
||||
palettes.append(palette)
|
||||
|
||||
palette_names.append(Palette.get("name", Palette.attrib["src"].split(".png")[0]))
|
||||
pal_name = Palette.get("name", Palette.attrib["src"].split(".png")[0])
|
||||
palette_names.append(pal_name)
|
||||
palette_map[pal_name] = idx
|
||||
|
||||
images = []
|
||||
image_names: List[str] = []
|
||||
for Raster in SpriteSheet.findall("./RasterList/Raster"):
|
||||
image_map: Dict[str, int] = {}
|
||||
|
||||
for idx, Raster in enumerate(SpriteSheet.findall("./RasterList/Raster")):
|
||||
if asset_stack is not None and load_images:
|
||||
img_name = Raster.attrib["src"]
|
||||
img_path = resolve_image_path(sprite_dir, "rasters", img_name, asset_stack)
|
||||
|
@ -109,14 +115,19 @@ def from_dir(
|
|||
|
||||
images.append(image)
|
||||
|
||||
image_names.append(Raster.attrib["src"].split(".png")[0])
|
||||
img_name = Raster.attrib["src"].split(".png")[0]
|
||||
image_names.append(img_name)
|
||||
image_map[img_name] = idx
|
||||
|
||||
animations = []
|
||||
animation_names: List[str] = []
|
||||
for Animation in SpriteSheet.findall("./AnimationList/Animation"):
|
||||
# get a mapping of component names -> list indices
|
||||
comp_map = {comp_xml.attrib["name"]: idx for idx, comp_xml in enumerate(Animation)}
|
||||
# read each component
|
||||
comps: List[AnimComponent] = []
|
||||
for comp_xml in Animation:
|
||||
comp: AnimComponent = AnimComponent.from_xml(comp_xml)
|
||||
comp: AnimComponent = AnimComponent.from_xml(comp_xml, comp_map, image_map, palette_map)
|
||||
comps.append(comp)
|
||||
animation_names.append(Animation.attrib["name"])
|
||||
animations.append(comps)
|
||||
|
|
|
@ -141,20 +141,31 @@ def player_raster_from_xml(xml: ET.Element, back: bool = False) -> PlayerRaster:
|
|||
)
|
||||
|
||||
|
||||
def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[bytes]:
|
||||
def player_xml_to_bytes(sprite_xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[bytes]:
|
||||
out_bytes = b""
|
||||
back_out_bytes = b""
|
||||
|
||||
max_components = int(xml.attrib[MAX_COMPONENTS_XML])
|
||||
num_variations = int(xml.attrib[PALETTE_GROUPS_XML])
|
||||
has_back = xml.attrib[HAS_BACK_XML] == "true"
|
||||
max_components = int(sprite_xml.attrib[MAX_COMPONENTS_XML])
|
||||
num_variations = int(sprite_xml.attrib[PALETTE_GROUPS_XML])
|
||||
has_back = sprite_xml.attrib[HAS_BACK_XML] == "true"
|
||||
|
||||
anim_elems: List[ET.Element] = sprite_xml.findall("./AnimationList/Animation")
|
||||
img_elems: List[ET.Element] = sprite_xml.findall("./RasterList/Raster")
|
||||
pal_elems: List[ET.Element] = sprite_xml.findall("./PaletteList/Palette")
|
||||
|
||||
palette_map = {palette_xml.attrib["name"]: idx for idx, palette_xml in enumerate(pal_elems)}
|
||||
|
||||
image_map = {image_xml.attrib["name"]: idx for idx, image_xml in enumerate(img_elems)}
|
||||
|
||||
# Animations
|
||||
animations: List[List[AnimComponent]] = []
|
||||
for anim_xml in xml[2]:
|
||||
for anim_xml in anim_elems:
|
||||
# get a mapping of component names -> list indices
|
||||
comp_map = {comp_xml.attrib["name"]: idx for idx, comp_xml in enumerate(anim_xml)}
|
||||
# read each component
|
||||
comps: List[AnimComponent] = []
|
||||
for comp_xml in anim_xml:
|
||||
comp: AnimComponent = AnimComponent.from_xml(comp_xml)
|
||||
comp: AnimComponent = AnimComponent.from_xml(comp_xml, comp_map, image_map, palette_map)
|
||||
comps.append(comp)
|
||||
animations.append(comps)
|
||||
|
||||
|
@ -216,7 +227,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
|
|||
palette_list_start_back = len(back_out_bytes) + 0x10
|
||||
palette_bytes: bytes = b""
|
||||
palette_bytes_back: bytes = b""
|
||||
for palette_xml in xml[0]:
|
||||
for palette_xml in pal_elems:
|
||||
source = palette_xml.attrib["src"]
|
||||
front_only = bool(palette_xml.get("front_only", False))
|
||||
if source not in PALETTE_CACHE:
|
||||
|
@ -252,7 +263,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
|
|||
raster_bytes: bytes = b""
|
||||
raster_bytes_back: bytes = b""
|
||||
raster_offset = 0
|
||||
for raster_xml in xml[1]:
|
||||
for raster_xml in img_elems:
|
||||
r = player_raster_from_xml(raster_xml, back=False)
|
||||
raster_bytes += struct.pack(">IBBBB", raster_offset, r.width, r.height, r.palette_idx, 0xFF)
|
||||
|
||||
|
@ -260,7 +271,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
|
|||
|
||||
if has_back:
|
||||
raster_offset = 0
|
||||
for raster_xml in xml[1]:
|
||||
for raster_xml in img_elems:
|
||||
is_back = False
|
||||
|
||||
r = player_raster_from_xml(raster_xml, back=is_back)
|
||||
|
@ -290,7 +301,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
|
|||
# Raster file offsets
|
||||
raster_offsets_bytes = b""
|
||||
raster_offsets_bytes_back = b""
|
||||
for i in range(len(xml[1])):
|
||||
for i in range(len(img_elems)):
|
||||
raster_offsets_bytes += int.to_bytes(raster_list_start + i * 8, 4, "big")
|
||||
raster_offsets_bytes_back += int.to_bytes(raster_list_start_back + i * 8, 4, "big")
|
||||
raster_offsets_bytes += LIST_END_BYTES
|
||||
|
@ -304,7 +315,7 @@ def player_xml_to_bytes(xml: ET.Element, asset_stack: Tuple[Path, ...]) -> List[
|
|||
palette_list_offset_back = len(back_out_bytes) + 0x10
|
||||
palette_offsets_bytes = b""
|
||||
palette_offsets_bytes_back = b""
|
||||
for i, palette_xml in enumerate(xml[0]):
|
||||
for i, palette_xml in enumerate(pal_elems):
|
||||
palette_offsets_bytes += int.to_bytes(palette_list_start + i * 0x20, 4, "big")
|
||||
front_only = bool(palette_xml.attrib.get("front_only", False))
|
||||
if not front_only:
|
||||
|
@ -358,17 +369,21 @@ def write_player_sprite_header(
|
|||
sprite_xml = PLAYER_XML_CACHE[sprite_name]
|
||||
has_back = sprite_xml.attrib[HAS_BACK_XML] == "true"
|
||||
|
||||
anim_elems: List[ET.Element] = sprite_xml.findall("./AnimationList/Animation")
|
||||
img_elems: List[ET.Element] = sprite_xml.findall("./RasterList/Raster")
|
||||
pal_elems: List[ET.Element] = sprite_xml.findall("./PaletteList/Palette")
|
||||
|
||||
player_sprites[f"SPR_{sprite_name}"] = sprite_id
|
||||
player_rasters[sprite_name] = {}
|
||||
player_palettes[sprite_name] = {}
|
||||
player_anims[sprite_name] = {}
|
||||
|
||||
for palette_xml in sprite_xml[0]:
|
||||
for palette_xml in pal_elems:
|
||||
palette_id = int(palette_xml.attrib["id"], 0x10)
|
||||
palette_name = palette_xml.attrib["name"]
|
||||
player_palettes[sprite_name][f"SPR_PAL_{sprite_name}_{palette_name}"] = palette_id
|
||||
|
||||
for anim_id, anim_xml in enumerate(sprite_xml[2]):
|
||||
for anim_id, anim_xml in enumerate(anim_elems):
|
||||
anim_name = anim_xml.attrib["name"]
|
||||
if palette_id > 0:
|
||||
anim_name = f"{palette_name}_{anim_name}"
|
||||
|
@ -377,7 +392,7 @@ def write_player_sprite_header(
|
|||
)
|
||||
|
||||
max_size = 0
|
||||
for raster_xml in sprite_xml[1]:
|
||||
for raster_xml in img_elems:
|
||||
raster_id = int(raster_xml.attrib["id"], 0x10)
|
||||
raster_name = raster_xml.attrib["name"]
|
||||
player_rasters[sprite_name][f"SPR_IMG_{sprite_name}_{raster_name}"] = raster_id
|
||||
|
@ -393,7 +408,7 @@ def write_player_sprite_header(
|
|||
player_sprites[f"SPR_{sprite_name}_Back"] = sprite_id
|
||||
|
||||
max_size = 0
|
||||
for raster_xml in sprite_xml[1]:
|
||||
for raster_xml in img_elems:
|
||||
if "back" in raster_xml.attrib:
|
||||
raster = RASTER_CACHE[raster_xml.attrib["back"][:-4]]
|
||||
if max_size < raster.size:
|
||||
|
@ -513,8 +528,10 @@ def build_player_rasters(sprite_order: List[str], raster_order: List[str]) -> by
|
|||
sheet_rtes: List[RasterTableEntry] = []
|
||||
sheet_rtes_back: List[RasterTableEntry] = []
|
||||
|
||||
img_elems: List[ET.Element] = sprite_xml.findall("./RasterList/Raster")
|
||||
|
||||
has_back = False
|
||||
for raster_xml in sprite_xml[1]:
|
||||
for raster_xml in img_elems:
|
||||
if "back" in raster_xml.attrib:
|
||||
has_back = True
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ class SetParent(Animation):
|
|||
|
||||
def get_attributes(self):
|
||||
return {
|
||||
XML_ATTR_INDEX: str(self.index),
|
||||
XML_ATTR_INDEX: f"{self.index:X}",
|
||||
}
|
||||
|
||||
|
||||
|
@ -214,11 +214,6 @@ class SetNotify(Animation):
|
|||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Keyframe(Animation):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnimComponent:
|
||||
x: int
|
||||
|
@ -255,7 +250,7 @@ class AnimComponent:
|
|||
elif cmd_op == CMD.SET_SCALE:
|
||||
i += 1
|
||||
elif cmd_op == CMD.LOOP:
|
||||
dest = command_list[i + 1]
|
||||
dest = cmd_arg
|
||||
if dest in boundaries and dest not in labels:
|
||||
labels[dest] = f"Pos_{dest}"
|
||||
i += 1
|
||||
|
@ -313,8 +308,8 @@ class AnimComponent:
|
|||
palette = -1
|
||||
ret.append(SetPalette(palette))
|
||||
elif cmd_op == CMD.LOOP:
|
||||
count = cmd_arg
|
||||
dest = command_list[i + 1]
|
||||
dest = cmd_arg
|
||||
count = command_list[i + 1]
|
||||
if dest in labels:
|
||||
lbl_name = labels[dest]
|
||||
ret.append(Loop(count, lbl_name, 0))
|
||||
|
@ -349,18 +344,27 @@ class AnimComponent:
|
|||
return AnimComponent.parse_commands(self.commands)
|
||||
|
||||
@staticmethod
|
||||
def from_xml(xml: ET.Element):
|
||||
def from_xml(xml: ET.Element, comp_map: Dict[str, int], image_map: Dict[str, int], palette_map: Dict[str, int]):
|
||||
commands: List[int] = []
|
||||
labels = {}
|
||||
for cmd in xml:
|
||||
if cmd.tag == "Label":
|
||||
idx = len(commands)
|
||||
labels[cmd.attrib[XML_ATTR_NAME]] = idx
|
||||
labels[cmd.attrib[XML_ATTR_NAME]] = len(commands)
|
||||
elif cmd.tag == "Wait":
|
||||
duration = int(cmd.attrib[XML_ATTR_DURATION])
|
||||
commands.append(duration & 0xFFF)
|
||||
elif cmd.tag == "SetRaster":
|
||||
raster = int(cmd.attrib[XML_ATTR_INDEX], 0x10)
|
||||
raster = -1
|
||||
# prioritize selecting rasters by name, falling back to hardcoded IDs if name attribute is missing
|
||||
if XML_ATTR_NAME in cmd.attrib:
|
||||
img_name = cmd.attrib[XML_ATTR_NAME]
|
||||
if img_name != "":
|
||||
raster = image_map.get(img_name)
|
||||
if raster is None:
|
||||
raise Exception("Undefined raster name for SetRaster: " + img_name)
|
||||
else:
|
||||
raster = int(cmd.attrib[XML_ATTR_INDEX], 0x10)
|
||||
# why is this here? necessary?
|
||||
if raster == -1:
|
||||
raster = 0xFFF
|
||||
commands.append(0x1000 + (raster & 0xFFF))
|
||||
|
@ -393,7 +397,17 @@ class AnimComponent:
|
|||
commands.append(0x5000 + mode)
|
||||
commands.append(percent)
|
||||
elif cmd.tag == "SetPalette":
|
||||
palette = int(cmd.attrib[XML_ATTR_INDEX], 0x10)
|
||||
palette = -1
|
||||
# prioritize selecting palettes by name, falling back to hardcoded IDs if name attribute is missing
|
||||
if XML_ATTR_NAME in cmd.attrib:
|
||||
pal_name = cmd.attrib[XML_ATTR_NAME]
|
||||
if pal_name != "":
|
||||
palette = palette_map.get(pal_name)
|
||||
if palette is None:
|
||||
raise Exception("Undefined palette name for SetPalette: " + pal_name)
|
||||
else:
|
||||
palette = int(cmd.attrib[XML_ATTR_INDEX], 0x10)
|
||||
# why is this here? necessary?
|
||||
if palette == -1:
|
||||
palette = 0xFFF
|
||||
commands.append(0x6000 + (palette & 0xFFF))
|
||||
|
@ -408,14 +422,90 @@ class AnimComponent:
|
|||
if not lbl_name in labels:
|
||||
raise Exception("Label missing for Loop dest: " + lbl_name)
|
||||
pos = labels[lbl_name]
|
||||
commands.append(0x7000 + (count & 0xFFF))
|
||||
commands.append(pos)
|
||||
commands.append(0x7000 + (pos & 0xFFF))
|
||||
commands.append(count)
|
||||
elif cmd.tag == "Unknown":
|
||||
commands.append(0x8000 + (int(cmd.attrib[XML_ATTR_VALUE]) & 0xFF))
|
||||
elif cmd.tag == "SetParent":
|
||||
commands.append(0x8100 + (int(cmd.attrib[XML_ATTR_INDEX]) & 0xFF))
|
||||
parent = -1
|
||||
# prioritize selecting palettes by name, falling back to hardcoded IDs if name attribute is missing
|
||||
if XML_ATTR_NAME in cmd.attrib:
|
||||
par_name = cmd.attrib[XML_ATTR_NAME]
|
||||
if par_name != "":
|
||||
parent = comp_map.get(par_name)
|
||||
if parent is None:
|
||||
raise Exception("Undefined component name for SetParent: " + par_name)
|
||||
else:
|
||||
parent = int(cmd.attrib[XML_ATTR_INDEX], 0x10)
|
||||
if parent == -1:
|
||||
raise Exception("Invalid component for SetParent: " + par_name)
|
||||
commands.append(0x8100 + (parent & 0xFF))
|
||||
elif cmd.tag == "SetNotify":
|
||||
commands.append(0x8200 + (int(cmd.attrib[XML_ATTR_VALUE]) & 0xFF))
|
||||
elif cmd.tag == "Keyframe":
|
||||
# treat keyframes as labels
|
||||
labels[cmd.attrib[XML_ATTR_NAME]] = len(commands)
|
||||
# check for non-default transformations
|
||||
duration = int(cmd.attrib[XML_ATTR_DURATION])
|
||||
if duration > 0:
|
||||
if "pos" in cmd.attrib:
|
||||
dx, dy, dz = map(int, cmd.attrib["pos"].split(","))
|
||||
if dx != 0 or dy != 0 or dz != 0:
|
||||
commands.append(0x3000)
|
||||
commands.append(dx & 0xFFFF)
|
||||
commands.append(dy & 0xFFFF)
|
||||
commands.append(dz & 0xFFFF)
|
||||
if "rot" in cmd.attrib:
|
||||
rx, ry, rz = map(int, cmd.attrib["rot"].split(","))
|
||||
if rx != 0 or ry != 0 or rz != 0:
|
||||
commands.append(0x4000 + (rx & 0xFFF))
|
||||
commands.append(ry & 0xFFFF)
|
||||
commands.append(rz & 0xFFFF)
|
||||
if "scale" in cmd.attrib:
|
||||
sx, sy, sz = map(int, cmd.attrib["scale"].split(","))
|
||||
if sx != 100 or sy != 100 or sz != 100:
|
||||
# check for uniform scale before generating a command for each coord
|
||||
if sx == sy == sz:
|
||||
commands.append(0x5000)
|
||||
commands.append(sx)
|
||||
else:
|
||||
if sx != 100:
|
||||
commands.append(0x5001)
|
||||
commands.append(sx)
|
||||
if sy != 100:
|
||||
commands.append(0x5002)
|
||||
commands.append(sy)
|
||||
if sz != 100:
|
||||
commands.append(0x5003)
|
||||
commands.append(sz)
|
||||
# check for img
|
||||
img_name = cmd.attrib.get("img")
|
||||
if img_name is not None:
|
||||
if img_name == "":
|
||||
raster = -1
|
||||
else:
|
||||
raster = image_map.get(img_name)
|
||||
if raster is None:
|
||||
raise Exception("Undefined raster for Keyframe: " + img_name)
|
||||
# why is this here? necessary?
|
||||
if raster == -1:
|
||||
raster = 0xFFF
|
||||
commands.append(0x1000 + (raster & 0xFFF))
|
||||
# check for pal
|
||||
pal_name = cmd.attrib.get("pal")
|
||||
if pal_name is not None:
|
||||
if pal_name == "":
|
||||
palette = -1
|
||||
else:
|
||||
palette = palette_map.get(pal_name)
|
||||
if palette is None:
|
||||
raise Exception("Undefined palette for Keyframe: " + pal_name)
|
||||
# why is this here? necessary?
|
||||
if palette == -1:
|
||||
palette = 0xFFF
|
||||
commands.append(0x6000 + (palette & 0xFFF))
|
||||
# append wait command
|
||||
commands.append(duration & 0xFFF)
|
||||
elif cmd.tag == "Command": # old Star Rod compatibility
|
||||
commands.append(int(cmd.attrib["val"], 16))
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue