Skip to content

Commit dc2aa5f

Browse files
authored
SMW: v1.1 Content Update (ArchipelagoMW#1344)
* Make Bowser unkillable on Egg Hunt * Increment Data Package version Changed a location name. * Baseline for Bowser Rooms shuffling * Add boss shuffle * Remove extra space * Overworld Palette Shuffle * Fix Literature Trap typo * Handle Queuing traps and new Timer Trap * Fix trap name and actually create them * Early Climb and Overworld Speed * Add correct tooltip for Early Climb * Tooltip text edit * Address unconnected regions * Add option to fully exclude Special Zone levels from the seed * Fix Chocolate Island 4 Dragon Coins logic * Update worlds/smw/Client.py to use `getattr`
1 parent 428344b commit dc2aa5f

11 files changed

+423
-56
lines changed

worlds/smw/Aesthetics.py

+15
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@
136136
0xFFF45A: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle
137137
}
138138

139+
valid_ow_palettes = {
140+
0x2D1E: [0x00, 0x01, 0x03], # Main OW
141+
0x2D1F: [0x00, 0x03, 0x04], # Yoshi's Island
142+
0x2D20: [0x00, 0x01, 0x03, 0x04], # Vanilla Dome
143+
0x2D21: [0x00, 0x02, 0x03, 0x04], # Forest of Illusion
144+
0x2D22: [0x00, 0x01, 0x03, 0x04], # Valley of Bowser
145+
0x2D24: [0x00, 0x02, 0x03], # Star Road
146+
}
147+
139148
def generate_shuffled_level_music(world, player):
140149
shuffled_level_music = level_music_value_data.copy()
141150

@@ -158,6 +167,12 @@ def generate_shuffled_ow_music(world, player):
158167

159168
return shuffled_ow_music
160169

170+
def generate_shuffled_ow_palettes(rom, world, player):
171+
if world.overworld_palette_shuffle[player]:
172+
for address, valid_palettes in valid_ow_palettes.items():
173+
chosen_palette = world.random.choice(valid_palettes)
174+
rom.write_byte(address, chosen_palette)
175+
161176
def generate_shuffled_header_data(rom, world, player):
162177
if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]:
163178
return

worlds/smw/Client.py

+78-4
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,74 @@ async def handle_message_queue(self, ctx):
169169

170170
await snes_flush_writes(ctx)
171171

172-
return
172+
173+
def add_trap_to_queue(self, trap_item, trap_msg):
174+
self.trap_queue = getattr(self, "trap_queue", [])
175+
176+
self.trap_queue.append((trap_item, trap_msg))
177+
178+
179+
async def handle_trap_queue(self, ctx):
180+
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
181+
182+
if not hasattr(self, "trap_queue") or len(self.trap_queue) == 0:
183+
return
184+
185+
game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1)
186+
if game_state[0] != 0x14:
187+
return
188+
189+
mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1)
190+
if mario_state[0] != 0x00:
191+
return
192+
193+
pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1)
194+
if pause_state[0] != 0x00:
195+
return
196+
197+
next_trap, message = self.trap_queue.pop(0)
198+
199+
from worlds.smw.Rom import trap_rom_data
200+
if next_trap.item in trap_rom_data:
201+
trap_active = await snes_read(ctx, WRAM_START + trap_rom_data[next_trap.item][0], 0x3)
202+
203+
if next_trap.item == 0xBC0016:
204+
# Timer Trap
205+
if trap_active[0] == 0 or (trap_active[0] == 1 and trap_active[1] == 0 and trap_active[2] == 0):
206+
# Trap already active
207+
self.add_trap_to_queue(next_trap, message)
208+
return
209+
else:
210+
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([0x01]))
211+
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 1, bytes([0x00]))
212+
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 2, bytes([0x00]))
213+
else:
214+
if trap_active[0] > 0:
215+
# Trap already active
216+
self.add_trap_to_queue(next_trap, message)
217+
return
218+
else:
219+
verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1)
220+
if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2:
221+
snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]]))
222+
223+
new_item_count = trap_rom_data[next_trap.item][1]
224+
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([new_item_count]))
225+
226+
current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1)
227+
if current_level[0] in SMW_BAD_TEXT_BOX_LEVELS:
228+
return
229+
230+
boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1)
231+
if boss_state[0] in SMW_BOSS_STATES:
232+
return
233+
234+
active_boss = await snes_read(ctx, SMW_ACTIVE_BOSS_ADDR, 0x1)
235+
if active_boss[0] != 0x00:
236+
return
237+
238+
if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((next_trap.flags & 1) != 0)):
239+
self.add_message_to_queue(message)
173240

174241

175242
async def game_watcher(self, ctx):
@@ -229,13 +296,14 @@ async def game_watcher(self, ctx):
229296
await snes_flush_writes(ctx)
230297

231298
await self.handle_message_queue(ctx)
299+
await self.handle_trap_queue(ctx)
232300

233301
new_checks = []
234302
event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60)
235303
progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F))
236304
dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C))
237305
dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1)
238-
from worlds.smw.Rom import item_rom_data, ability_rom_data
306+
from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data
239307
from worlds.smw.Levels import location_id_to_level_id, level_info_dict
240308
from worlds import AutoWorldRegister
241309
for loc_name, level_data in location_id_to_level_id.items():
@@ -307,7 +375,7 @@ async def game_watcher(self, ctx):
307375
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
308376

309377
if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)):
310-
if item.item != 0xBC0012:
378+
if item.item != 0xBC0012 and item.item not in trap_rom_data:
311379
# Don't send messages for Boss Tokens
312380
item_name = ctx.item_names[item.item]
313381
player_name = ctx.player_names[item.player]
@@ -316,7 +384,13 @@ async def game_watcher(self, ctx):
316384
self.add_message_to_queue(receive_message)
317385

318386
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index]))
319-
if item.item in item_rom_data:
387+
if item.item in trap_rom_data:
388+
item_name = ctx.item_names[item.item]
389+
player_name = ctx.player_names[item.player]
390+
391+
receive_message = generate_received_text(item_name, player_name)
392+
self.add_trap_to_queue(item, receive_message)
393+
elif item.item in item_rom_data:
320394
item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1)
321395
increment = item_rom_data[item.item][1]
322396

worlds/smw/Items.py

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class SMWItem(Item):
4949
ItemName.ice_trap: ItemData(0xBC0013, False, True),
5050
ItemName.stun_trap: ItemData(0xBC0014, False, True),
5151
ItemName.literature_trap: ItemData(0xBC0015, False, True),
52+
ItemName.timer_trap: ItemData(0xBC0016, False, True),
5253
}
5354

5455
event_table = {

worlds/smw/Levels.py

+91-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
11

22
from .Names import LocationName
33

4+
5+
class BowserRoom():
6+
name: str
7+
exitAddress: int
8+
roomID: int
9+
10+
def __init__(self, name: str, exitAddress: int, roomID: int):
11+
self.name = name
12+
self.exitAddress = exitAddress
13+
self.roomID = roomID
14+
15+
full_bowser_rooms = [
16+
BowserRoom("Hallway 1 - Door 1", 0x3A680, 0x0D),
17+
BowserRoom("Hallway 1 - Door 2", 0x3A684, 0x0D),
18+
BowserRoom("Hallway 1 - Door 3", 0x3A688, 0x0D),
19+
BowserRoom("Hallway 1 - Door 4", 0x3A68C, 0x0D),
20+
BowserRoom("Hallway 2 - Door 1", 0x3A8CB, 0xD0),
21+
BowserRoom("Hallway 2 - Door 2", 0x3A8CF, 0xD0),
22+
BowserRoom("Hallway 2 - Door 3", 0x3A8D3, 0xD0),
23+
BowserRoom("Hallway 2 - Door 4", 0x3A8D7, 0xD0),
24+
25+
BowserRoom("Room 1", 0x3A705, 0xD4),
26+
BowserRoom("Room 2", 0x3A763, 0xD3),
27+
BowserRoom("Room 3", 0x3A800, 0xD2),
28+
BowserRoom("Room 4", 0x3A83D, 0xD1),
29+
BowserRoom("Room 5", 0x3A932, 0xCF),
30+
BowserRoom("Room 6", 0x3A9E1, 0xCE),
31+
BowserRoom("Room 7", 0x3AA75, 0xCD),
32+
BowserRoom("Room 8", 0x3AAC7, 0xCC),
33+
]
34+
35+
standard_bowser_rooms = [
36+
BowserRoom("Room 1", 0x3A705, 0xD4),
37+
BowserRoom("Room 2", 0x3A763, 0xD3),
38+
BowserRoom("Room 3", 0x3A800, 0xD2),
39+
BowserRoom("Room 4", 0x3A83D, 0xD1),
40+
BowserRoom("Room 5", 0x3A932, 0xCF),
41+
BowserRoom("Room 6", 0x3A9E1, 0xCE),
42+
BowserRoom("Room 7", 0x3AA75, 0xCD),
43+
BowserRoom("Room 8", 0x3AAC7, 0xCC),
44+
]
45+
46+
47+
class BossRoom():
48+
name: str
49+
exitAddress: int
50+
exitAddressAlt: int
51+
roomID: int
52+
53+
def __init__(self, name: str, exitAddress: int, roomID: int, exitAddressAlt=None):
54+
self.name = name
55+
self.exitAddress = exitAddress
56+
self.roomID = roomID
57+
self.exitAddressAlt = exitAddressAlt
58+
59+
60+
submap_boss_rooms = [
61+
BossRoom("#1 Lemmy Koopa", 0x311E3, 0xF6), # Submap 0x1F6
62+
BossRoom("#3 Lemmy Koopa", 0x33749, 0xF2), # Submap 0x1F2
63+
BossRoom("Valley Reznor", 0x3A132, 0xDE), # Submap 0x1DE
64+
BossRoom("#7 Larry Koopa", 0x3A026, 0xEB), # Submap 0x1EB
65+
]
66+
67+
ow_boss_rooms = [
68+
BossRoom("#2 Morton Koopa Jr.", 0x3209B, 0xE5), # OW 0x0E5
69+
BossRoom("Vanilla Reznor", 0x33EAB, 0xDF), # OW 0x0DF
70+
BossRoom("#4 Ludwig von Koopa", 0x346EA, 0xD9), # OW 0x0D9
71+
BossRoom("Forest Reznor", 0x3643E, 0xD5, 0x36442), # OW 0x0D5
72+
BossRoom("#5 Roy Koopa", 0x35ABC, 0xCC), # OW 0x0CC
73+
BossRoom("Chocolate Reznor", 0x3705B, 0xE2), # OW 0x0E2
74+
BossRoom("#6 Wendy O. Koopa", 0x38BB5, 0xD3), # OW 0x0D3
75+
]
76+
77+
478
class SMWPath():
579
thisEndDirection: int
680
otherLevelID: int
@@ -203,6 +277,9 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1
203277
0x3B,
204278
0x3A,
205279
0x37,
280+
]
281+
282+
special_zone_levels = [
206283
0x4E,
207284
0x4F,
208285
0x50,
@@ -443,6 +520,7 @@ def generate_level_list(world, player):
443520
world.random.shuffle(easy_single_levels_copy)
444521
hard_single_levels_copy = hard_single_levels.copy()
445522
world.random.shuffle(hard_single_levels_copy)
523+
special_zone_levels_copy = special_zone_levels.copy()
446524
easy_double_levels_copy = easy_double_levels.copy()
447525
world.random.shuffle(easy_double_levels_copy)
448526
hard_double_levels_copy = hard_double_levels.copy()
@@ -474,6 +552,8 @@ def generate_level_list(world, player):
474552
shuffled_level_list.append(0x16)
475553

476554
single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy())
555+
if not world.exclude_special_zone[player]:
556+
single_levels_copy.extend(special_zone_levels_copy)
477557
world.random.shuffle(single_levels_copy)
478558

479559
castle_fortress_levels_copy = (easy_castle_fortress_levels_copy.copy() + hard_castle_fortress_levels_copy.copy())
@@ -566,14 +646,17 @@ def generate_level_list(world, player):
566646

567647
# Special Zone
568648
shuffled_level_list.append(0x4D)
569-
shuffled_level_list.append(single_levels_copy.pop(0))
570-
shuffled_level_list.append(single_levels_copy.pop(0))
571-
shuffled_level_list.append(single_levels_copy.pop(0))
572-
shuffled_level_list.append(single_levels_copy.pop(0))
573-
shuffled_level_list.append(single_levels_copy.pop(0))
574-
shuffled_level_list.append(single_levels_copy.pop(0))
575-
shuffled_level_list.append(single_levels_copy.pop(0))
576-
shuffled_level_list.append(single_levels_copy.pop(0))
649+
if not world.exclude_special_zone[player]:
650+
shuffled_level_list.append(single_levels_copy.pop(0))
651+
shuffled_level_list.append(single_levels_copy.pop(0))
652+
shuffled_level_list.append(single_levels_copy.pop(0))
653+
shuffled_level_list.append(single_levels_copy.pop(0))
654+
shuffled_level_list.append(single_levels_copy.pop(0))
655+
shuffled_level_list.append(single_levels_copy.pop(0))
656+
shuffled_level_list.append(single_levels_copy.pop(0))
657+
shuffled_level_list.append(single_levels_copy.pop(0))
658+
else:
659+
shuffled_level_list.extend(special_zone_levels_copy)
577660
shuffled_level_list.append(0x48)
578661

579662
return shuffled_level_list

worlds/smw/Locations.py

+22
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,28 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None
212212
**yoshi_house_location_table,
213213
}
214214

215+
special_zone_level_names = [
216+
LocationName.special_zone_1_exit_1,
217+
LocationName.special_zone_2_exit_1,
218+
LocationName.special_zone_3_exit_1,
219+
LocationName.special_zone_4_exit_1,
220+
LocationName.special_zone_5_exit_1,
221+
LocationName.special_zone_6_exit_1,
222+
LocationName.special_zone_7_exit_1,
223+
LocationName.special_zone_8_exit_1,
224+
]
225+
226+
special_zone_dragon_coin_names = [
227+
LocationName.special_zone_1_dragon,
228+
LocationName.special_zone_2_dragon,
229+
LocationName.special_zone_3_dragon,
230+
LocationName.special_zone_4_dragon,
231+
LocationName.special_zone_5_dragon,
232+
LocationName.special_zone_6_dragon,
233+
LocationName.special_zone_7_dragon,
234+
LocationName.special_zone_8_dragon,
235+
]
236+
215237
location_table = {}
216238

217239

worlds/smw/Names/ItemName.py

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
ice_trap = "Ice Trap"
2727
stun_trap = "Stun Trap"
2828
literature_trap = "Literature Trap"
29+
timer_trap = "Timer Trap"
2930

3031
# Other Definitions
3132
victory = "The Princess"

0 commit comments

Comments
 (0)