Skip to content

Commit e320f13

Browse files
authored
Merge pull request #234 from MetroidAdvRandomizerSystem/zm-tilesets
[ZM] Minor location graphics and custom messages
2 parents 84b9019 + 51a2cae commit e320f13

32 files changed

Lines changed: 2555 additions & 79 deletions

src/mars_patcher/constants/game_data.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ def tileset_count(rom: Rom) -> int:
4545
raise ValueError(rom.game)
4646

4747

48+
def anim_tileset_entries(rom: Rom) -> int:
49+
"""Returns the address of the animated tileset entries."""
50+
if rom.game == Game.MF:
51+
raise NotImplementedError()
52+
elif rom.game == Game.ZM:
53+
return rom.read_ptr(ReservedPointersZM.ANIM_TILESET_ENTRIES_PTR.value)
54+
55+
raise ValueError("Rom has unknown game loaded.")
56+
57+
58+
def anim_tileset_count(rom: Rom) -> int:
59+
"""Returns the number of animated tilesets in the game."""
60+
if rom.game == Game.MF:
61+
return 0xE
62+
elif rom.game == Game.ZM:
63+
return 0x8
64+
raise ValueError(rom.game)
65+
66+
4867
def area_doors_ptrs(rom: Rom) -> int:
4968
"""Returns the address of the area doors pointers."""
5069
if rom.game == Game.MF:
@@ -89,6 +108,15 @@ def area_connections_count(rom: Rom) -> int:
89108
raise ValueError("Rom has unknown game loaded.")
90109

91110

111+
def anim_graphics_count(rom: Rom) -> int:
112+
"""Returns the number of animated graphics in the game."""
113+
if rom.game == Game.MF:
114+
return 0x47
115+
elif rom.game == Game.ZM:
116+
return 0x26
117+
raise ValueError(rom.game, rom.region)
118+
119+
92120
def anim_palette_entries(rom: Rom) -> int:
93121
"""Returns the address of the animated palette entries."""
94122
if rom.game == Game.MF:

src/mars_patcher/convert_array.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from mars_patcher.rom import ROM_OFFSET
2+
3+
4+
def u8_to_u16(data: bytes | list[int]) -> list[int]:
5+
"""Converts a bytes object or list of 8-bit integers to a list of 16-bit integers."""
6+
assert len(data) % 2 == 0, "Data length must be a multiple of 2"
7+
return [data[i] | (data[i + 1] << 8) for i in range(0, len(data), 2)]
8+
9+
10+
def u16_to_u8(data: list[int]) -> bytes:
11+
"""Converts a list of 16-bit integers to a bytes object of 8-bit integers."""
12+
output = bytearray()
13+
for val in data:
14+
output.append(val & 0xFF)
15+
output.append(val >> 8)
16+
return output
17+
18+
19+
def ptr_to_u8(val: int) -> bytes:
20+
"""Converts a single pointer to a bytes object of 8-bit integers."""
21+
assert val < ROM_OFFSET, f"Pointer should be less than {ROM_OFFSET:X} but is {val:X}"
22+
val += ROM_OFFSET
23+
return bytes([val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, val >> 24])

src/mars_patcher/mf/item_patcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def write_items(self) -> None:
8484
if not min_loc.hidden:
8585
# Get tilemap
8686
tileset = Tileset(rom, room.tileset())
87-
addr = tileset.rle_tilemap_addr()
87+
addr = tileset.tilemap_addr()
8888
# Find tank in tilemap
8989
addr += 2 + (TANK_BG1_START * 8)
9090
tile = TANK_TILE[tank_slot]

src/mars_patcher/palette.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import random
33

44
from mars_patcher.color_spaces import HsvColor, OklabColor, RgbBitSize, RgbColor
5+
from mars_patcher.convert_array import u16_to_u8
56
from mars_patcher.rom import Rom
67

78
HUE_VARIATION_RANGE = 180.0
@@ -80,12 +81,8 @@ def rows(self) -> int:
8081
return len(self.colors) // 16
8182

8283
def byte_data(self) -> bytes:
83-
arr = bytearray()
84-
for color in self.colors:
85-
val = color.rgb_15()
86-
arr.append(val & 0xFF)
87-
arr.append(val >> 8)
88-
return bytes(arr)
84+
values = [c.rgb_15() for c in self.colors]
85+
return u16_to_u8(values)
8986

9087
def write(self, rom: Rom, addr: int) -> None:
9188
data = self.byte_data()

src/mars_patcher/text.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from functools import cache
44

55
from mars_patcher.constants.game_data import character_widths
6+
from mars_patcher.convert_array import u16_to_u8
67
from mars_patcher.mf.constants.game_data import file_screen_text_ptrs
7-
from mars_patcher.mf.data import get_data_path
8-
from mars_patcher.rom import Region, Rom
8+
from mars_patcher.mf.data import get_data_path as get_data_path_mf
9+
from mars_patcher.rom import Rom
10+
from mars_patcher.zm.data import get_data_path as get_data_path_zm
911

1012
SPACE_CHAR = 0x40
1113
SPACE_TAG = 0x8000
@@ -56,13 +58,18 @@ class MessageType(Enum):
5658

5759

5860
@cache
59-
def get_char_map(region: Region) -> dict[str, int]:
60-
path = get_data_path("char_map_mf.json")
61+
def get_char_map(rom: Rom) -> dict[str, int]:
62+
if rom.is_mf():
63+
path = get_data_path_mf("char_map_mf.json")
64+
elif rom.is_zm():
65+
path = get_data_path_zm("char_map_zm.json")
66+
else:
67+
raise ValueError(rom.game)
6168
with open(path, encoding="utf-8") as f:
6269
sections = json.load(f)
6370
char_map: dict[str, int] = {}
6471
for section in sections:
65-
if region.name in section["regions"]:
72+
if rom.region.name in section["regions"]:
6673
char_map.update(section["chars"])
6774
char_map["\n"] = NEWLINE
6875
return char_map
@@ -118,7 +125,7 @@ def encode_text(
118125
max_width: int = MAX_LINE_WIDTH,
119126
centered: bool = False,
120127
) -> bytes:
121-
char_map = get_char_map(rom.region)
128+
char_map = get_char_map(rom)
122129
char_widths_addr = character_widths(rom)
123130
text: list[int] = []
124131
line_width = 0
@@ -237,11 +244,7 @@ def encode_text(
237244

238245
text.append(END)
239246

240-
text_bytes = bytearray()
241-
for val in text:
242-
text_bytes.append(val & 0xFF)
243-
text_bytes.append(val >> 8)
244-
return bytes(text_bytes)
247+
return u16_to_u8(text)
245248

246249

247250
def write_seed_hash(rom: Rom, seed_hash: str) -> None:

src/mars_patcher/tilemap.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from mars_patcher.compress import comp_lz77, decomp_lz77
2+
from mars_patcher.convert_array import u8_to_u16, u16_to_u8
3+
from mars_patcher.rom import Rom
4+
5+
6+
class Tilemap:
7+
def __init__(self, rom: Rom, ptr: int, compressed: bool):
8+
self.rom = rom
9+
self.pointer = ptr
10+
self.compressed = compressed
11+
# Get data
12+
addr = rom.read_ptr(ptr)
13+
self.data: list[int] = []
14+
if compressed:
15+
data, self.data_size = decomp_lz77(rom.data, addr)
16+
self.data = u8_to_u16(data)
17+
else:
18+
addr += 2
19+
while True:
20+
val = rom.read_16(addr)
21+
if val == 0:
22+
if len(self.data) % 4 != 0:
23+
raise ValueError("Tilemap length should be a multiple of 4")
24+
break
25+
if len(self.data) >= 1024 * 4:
26+
raise ValueError("Tilemap is too long")
27+
self.data.append(val)
28+
addr += 2
29+
self.data_size = len(self.data) * 2 + 4
30+
31+
def byte_data(self) -> bytes:
32+
if self.compressed:
33+
data = comp_lz77(u16_to_u8(self.data))
34+
return bytes(data)
35+
else:
36+
return bytes([2, 0]) + u16_to_u8(self.data) + bytes([0, 0])
37+
38+
def write(self, copy: bool) -> None:
39+
data = self.byte_data()
40+
if copy:
41+
self.rom.write_data_with_pointers(data, [self.pointer])
42+
else:
43+
addr = self.rom.read_ptr(self.pointer)
44+
self.rom.write_repointable_data(addr, self.data_size, data, [self.pointer])

src/mars_patcher/tileset.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,44 @@
1-
from mars_patcher.constants.game_data import tileset_entries
1+
from mars_patcher.constants.game_data import anim_tileset_entries, tileset_entries
22
from mars_patcher.rom import Rom
33

4+
TILESET_SIZE = 0x14
5+
ANIM_TILESET_SIZE = 0x30
6+
47

58
class Tileset:
69
def __init__(self, rom: Rom, id: int):
710
self.rom = rom
8-
self.addr = tileset_entries(rom) + id * 0x14
11+
self.addr = tileset_entries(rom) + id * TILESET_SIZE
12+
13+
def block_bg_gfx_ptr(self) -> int:
14+
return self.addr
15+
16+
def block_bg_gfx_addr(self) -> int:
17+
return self.rom.read_ptr(self.block_bg_gfx_ptr())
18+
19+
def palette_ptr(self) -> int:
20+
return self.addr + 4
21+
22+
def palette_addr(self) -> int:
23+
return self.rom.read_ptr(self.palette_ptr())
24+
25+
def tiled_bg_gfx_ptr(self) -> int:
26+
return self.addr + 8
27+
28+
def tiled_bg_gfx_addr(self) -> int:
29+
return self.rom.read_ptr(self.tiled_bg_gfx_ptr())
30+
31+
def tilemap_ptr(self) -> int:
32+
return self.addr + 0xC
33+
34+
def tilemap_addr(self) -> int:
35+
return self.rom.read_ptr(self.tilemap_ptr())
36+
37+
def anim_tileset(self) -> int:
38+
return self.rom.read_8(self.addr + 0x10)
39+
40+
def anim_tileset_addr(self) -> int:
41+
return anim_tileset_entries(self.rom) + self.anim_tileset() * ANIM_TILESET_SIZE
942

10-
def rle_tilemap_addr(self) -> int:
11-
return self.rom.read_ptr(self.addr + 0xC)
43+
def anim_palette(self) -> int:
44+
return self.rom.read_8(self.addr + 0x11)

src/mars_patcher/zm/auto_generated_types.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@
137137
'TOURIAN_TO_CRATERIA'
138138
]
139139
ValidLanguages = typ.Literal[
140-
'JAPANESE_KANJI',
141-
'JAPANESE_HIRAGANA',
142-
'ENGLISH',
143-
'GERMAN',
144-
'FRENCH',
145-
'ITALIAN',
146-
'SPANISH'
140+
'JapaneseKanji',
141+
'JapaneseHiragana',
142+
'English',
143+
'German',
144+
'French',
145+
'Italian',
146+
'Spanish'
147147
]
148148
Validmusictracks = typ.Literal[
149149
'BRINSTAR',
@@ -213,15 +213,15 @@
213213
MessageLanguages: typ.TypeAlias = dict[ValidLanguages, str]
214214

215215
class ItemMessages(typ.TypedDict, total=False):
216-
kind: typ.Required[ItemMessagesKind]
217-
languages: MessageLanguages
218-
centered: bool = True
219-
message_id: typ.Annotated[int, '0 <= value <= 56']
216+
Kind: typ.Required[ItemMessagesKind]
217+
Languages: MessageLanguages
218+
Centered: bool = True
219+
MessageID: typ.Annotated[int, '0 <= value <= 56']
220220
"""The Message ID, will display one of the predefined messages in the ROM"""
221221

222222
ItemMessagesKind = typ.Literal[
223-
'CUSTOM_MESSAGE',
224-
'MESSAGE_ID'
223+
'CustomMessage',
224+
'MessageID'
225225
]
226226
Jingle = typ.Literal[
227227
'DEFAULT',
@@ -266,7 +266,7 @@ class MarsschemazmLocationsMajorLocationsItem(typ.TypedDict, total=False):
266266
item_sprite: ValidItemSprites
267267
"""Valid graphics for item tanks/sprites."""
268268

269-
item_messages: ItemMessages
269+
ItemMessages: ItemMessages
270270
jingle: Jingle
271271
"""The sound that plays when an item is collected"""
272272

@@ -293,7 +293,7 @@ class MarsschemazmLocationsMinorLocationsItem(typ.TypedDict):
293293
item_sprite: typ.NotRequired[ValidItemSprites]
294294
"""Valid graphics for item tanks/sprites."""
295295

296-
item_messages: typ.NotRequired[ItemMessages]
296+
ItemMessages: typ.NotRequired[ItemMessages]
297297
jingle: typ.NotRequired[Jingle]
298298
"""The sound that plays when an item is collected"""
299299

src/mars_patcher/zm/constants/game_data.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
from mars_patcher.zm.constants.reserved_space import ReservedPointersZM
33

44

5-
def tileset_tilemap_sizes_addr(rom: Rom) -> int:
6-
return rom.read_ptr(ReservedPointersZM.TILESET_TILEMAP_SIZES_PTR.value)
7-
8-
95
def chozo_statue_targets_addr(rom: Rom) -> int:
106
return rom.read_ptr(ReservedPointersZM.CHOZO_STATUE_TARGETS_PTR.value)
117

0 commit comments

Comments
 (0)