SMW: v2.1 Feature Update (#4652)

### Features:
- Trap Link
  - When you receive a trap, you send a copy of it to every other player with Trap Link enabled
- Ring Link
    - Any coin amounts gained and lost by a linked player will be instantly shared with all other active linked players

Co-authored-by: TheLX5 <luisyuregi@gmail.com>
This commit is contained in:
PoryGone
2025-04-30 10:24:10 -04:00
committed by GitHub
parent 5f974b7457
commit 611e1c2b19
7 changed files with 320 additions and 20 deletions

View File

@@ -1,6 +1,15 @@
# Super Mario World - Changelog # Super Mario World - Changelog
## v2.1
### Features:
- Trap Link
- When you receive a trap, you send a copy of it to every other player with Trap Link enabled
- Ring Link
- Any coin amounts gained and lost by a linked player will be instantly shared with all other active linked players
## v2.0 ## v2.0
### Features: ### Features:

View File

@@ -1,9 +1,11 @@
import logging import logging
import time import time
from typing import Any
from NetUtils import ClientStatus, color from NetUtils import ClientStatus, NetworkItem, color
from worlds.AutoSNIClient import SNIClient from worlds.AutoSNIClient import SNIClient
from .Names.TextBox import generate_received_text from .Names.TextBox import generate_received_text, generate_received_trap_link_text
from .Items import trap_value_to_name, trap_name_to_value
snes_logger = logging.getLogger("SNES") snes_logger = logging.getLogger("SNES")
@@ -42,10 +44,13 @@ SMW_MOON_ACTIVE_ADDR = ROM_START + 0x01BFA8
SMW_HIDDEN_1UP_ACTIVE_ADDR = ROM_START + 0x01BFA9 SMW_HIDDEN_1UP_ACTIVE_ADDR = ROM_START + 0x01BFA9
SMW_BONUS_BLOCK_ACTIVE_ADDR = ROM_START + 0x01BFAA SMW_BONUS_BLOCK_ACTIVE_ADDR = ROM_START + 0x01BFAA
SMW_BLOCKSANITY_ACTIVE_ADDR = ROM_START + 0x01BFAB SMW_BLOCKSANITY_ACTIVE_ADDR = ROM_START + 0x01BFAB
SMW_TRAP_LINK_ACTIVE_ADDR = ROM_START + 0x01BFB7
SMW_RING_LINK_ACTIVE_ADDR = ROM_START + 0x01BFB8
SMW_GAME_STATE_ADDR = WRAM_START + 0x100 SMW_GAME_STATE_ADDR = WRAM_START + 0x100
SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 SMW_MARIO_STATE_ADDR = WRAM_START + 0x71
SMW_COIN_COUNT_ADDR = WRAM_START + 0xDBF
SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B
SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC
SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF
@@ -76,6 +81,7 @@ SMW_UNCOLLECTABLE_DRAGON_COINS = [0x24]
class SMWSNIClient(SNIClient): class SMWSNIClient(SNIClient):
game = "Super Mario World" game = "Super Mario World"
patch_suffix = ".apsmw" patch_suffix = ".apsmw"
slot_data: dict[str, Any] | None
async def deathlink_kill_player(self, ctx): async def deathlink_kill_player(self, ctx):
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
@@ -111,6 +117,84 @@ class SMWSNIClient(SNIClient):
ctx.last_death_link = time.time() ctx.last_death_link = time.time()
def on_package(self, ctx: SNIClient, cmd: str, args: dict[str, Any]) -> None:
super().on_package(ctx, cmd, args)
if cmd == "Connected":
self.slot_data = args.get("slot_data", None)
if cmd != "Bounced":
return
if "tags" not in args:
return
if not hasattr(self, "instance_id"):
self.instance_id = time.time()
source_name = args["data"]["source"]
if "TrapLink" in ctx.tags and "TrapLink" in args["tags"] and source_name != ctx.slot_info[ctx.slot].name:
trap_name: str = args["data"]["trap_name"]
if trap_name not in trap_name_to_value:
# We don't know how to handle this trap, ignore it
return
trap_id: int = trap_name_to_value[trap_name]
if "trap_weights" not in self.slot_data:
return
if f"{trap_id}" not in self.slot_data["trap_weights"]:
return
if self.slot_data["trap_weights"][f"{trap_id}"] == 0:
# The player disabled this trap type
return
self.priority_trap = NetworkItem(trap_id, None, None)
self.priority_trap_message = generate_received_trap_link_text(trap_name, source_name)
self.priority_trap_message_str = f"Received linked {trap_name} from {source_name}"
elif "RingLink" in ctx.tags and "RingLink" in args["tags"] and source_name != self.instance_id:
if not hasattr(self, "pending_ring_link"):
self.pending_ring_link = 0
self.pending_ring_link += args["data"]["amount"]
async def send_trap_link(self, ctx: SNIClient, trap_name: str):
if "TrapLink" not in ctx.tags or ctx.slot == None:
return
await ctx.send_msgs([{
"cmd": "Bounce", "tags": ["TrapLink"],
"data": {
"time": time.time(),
"source": ctx.player_names[ctx.slot],
"trap_name": trap_name
}
}])
snes_logger.info(f"Sent linked {trap_name}")
async def send_ring_link(self, ctx: SNIClient, amount: int):
from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read
if "RingLink" not in ctx.tags or ctx.slot == None:
return
game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1)
if game_state[0] != 0x14:
return
if not hasattr(self, "instance_id"):
self.instance_id = time.time()
await ctx.send_msgs([{
"cmd": "Bounce", "tags": ["RingLink"],
"data": {
"time": time.time(),
"source": self.instance_id,
"amount": amount
}
}])
async def validate_rom(self, ctx): async def validate_rom(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
@@ -123,9 +207,11 @@ class SMWSNIClient(SNIClient):
receive_option = await snes_read(ctx, SMW_RECEIVE_MSG_DATA, 0x1) receive_option = await snes_read(ctx, SMW_RECEIVE_MSG_DATA, 0x1)
send_option = await snes_read(ctx, SMW_SEND_MSG_DATA, 0x1) send_option = await snes_read(ctx, SMW_SEND_MSG_DATA, 0x1)
trap_link = await snes_read(ctx, SMW_TRAP_LINK_ACTIVE_ADDR, 0x1)
ctx.receive_option = receive_option[0] ctx.receive_option = receive_option[0]
ctx.send_option = send_option[0] ctx.send_option = send_option[0]
ctx.trap_link = trap_link[0]
ctx.allow_collect = True ctx.allow_collect = True
@@ -133,6 +219,15 @@ class SMWSNIClient(SNIClient):
if death_link: if death_link:
await ctx.update_death_link(bool(death_link[0] & 0b1)) await ctx.update_death_link(bool(death_link[0] & 0b1))
if trap_link and bool(trap_link[0] & 0b1) and "TrapLink" not in ctx.tags:
ctx.tags.add("TrapLink")
await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}])
ring_link = await snes_read(ctx, SMW_RING_LINK_ACTIVE_ADDR, 1)
if ring_link and bool(ring_link[0] & 0b1) and "RingLink" not in ctx.tags:
ctx.tags.add("RingLink")
await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}])
if ctx.rom != rom_name: if ctx.rom != rom_name:
ctx.current_sublevel_value = 0 ctx.current_sublevel_value = 0
@@ -142,12 +237,17 @@ class SMWSNIClient(SNIClient):
def add_message_to_queue(self, new_message): def add_message_to_queue(self, new_message):
if not hasattr(self, "message_queue"): if not hasattr(self, "message_queue"):
self.message_queue = [] self.message_queue = []
self.message_queue.append(new_message) self.message_queue.append(new_message)
def add_message_to_queue_front(self, new_message):
if not hasattr(self, "message_queue"):
self.message_queue = []
self.message_queue.insert(0, new_message)
async def handle_message_queue(self, ctx): async def handle_message_queue(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
@@ -206,7 +306,8 @@ class SMWSNIClient(SNIClient):
async def handle_trap_queue(self, ctx): async def handle_trap_queue(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
if not hasattr(self, "trap_queue") or len(self.trap_queue) == 0: if (not hasattr(self, "trap_queue") or len(self.trap_queue) == 0) and\
(not hasattr(self, "priority_trap") or self.priority_trap == 0):
return return
game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1)
@@ -221,7 +322,24 @@ class SMWSNIClient(SNIClient):
if pause_state[0] != 0x00: if pause_state[0] != 0x00:
return return
next_trap, message = self.trap_queue.pop(0)
next_trap = None
message = bytearray()
message_str = ""
from_queue = False
if getattr(self, "priority_trap", None) and self.priority_trap.item != 0:
next_trap = self.priority_trap
message = self.priority_trap_message
message_str = self.priority_trap_message_str
self.priority_trap = None
self.priority_trap_message = bytearray()
self.priority_trap_message_str = ""
elif hasattr(self, "trap_queue") and len(self.trap_queue) > 0:
from_queue = True
next_trap, message = self.trap_queue.pop(0)
else:
return
from .Rom import trap_rom_data from .Rom import trap_rom_data
if next_trap.item in trap_rom_data: if next_trap.item in trap_rom_data:
@@ -231,16 +349,22 @@ class SMWSNIClient(SNIClient):
# Timer Trap # Timer Trap
if trap_active[0] == 0 or (trap_active[0] == 1 and trap_active[1] == 0 and trap_active[2] == 0): if trap_active[0] == 0 or (trap_active[0] == 1 and trap_active[1] == 0 and trap_active[2] == 0):
# Trap already active # Trap already active
self.add_trap_to_queue(next_trap, message) if from_queue:
self.add_trap_to_queue(next_trap, message)
return return
else: else:
if len(message_str) > 0:
snes_logger.info(message_str)
if "TrapLink" in ctx.tags and from_queue:
await self.send_trap_link(ctx, trap_value_to_name[next_trap.item])
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([0x01])) snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([0x01]))
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 1, bytes([0x00])) snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 1, bytes([0x00]))
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 2, bytes([0x00])) snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 2, bytes([0x00]))
else: else:
if trap_active[0] > 0: if trap_active[0] > 0:
# Trap already active # Trap already active
self.add_trap_to_queue(next_trap, message) if from_queue:
self.add_trap_to_queue(next_trap, message)
return return
else: else:
if next_trap.item == 0xBC001D: if next_trap.item == 0xBC001D:
@@ -248,12 +372,18 @@ class SMWSNIClient(SNIClient):
# Do not fire if the previous thwimp hasn't reached the player's Y pos # Do not fire if the previous thwimp hasn't reached the player's Y pos
active_thwimp = await snes_read(ctx, SMW_ACTIVE_THWIMP_ADDR, 0x1) active_thwimp = await snes_read(ctx, SMW_ACTIVE_THWIMP_ADDR, 0x1)
if active_thwimp[0] != 0xFF: if active_thwimp[0] != 0xFF:
self.add_trap_to_queue(next_trap, message) if from_queue:
self.add_trap_to_queue(next_trap, message)
return return
verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1)
if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2: if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2:
snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]])) snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]]))
if len(message_str) > 0:
snes_logger.info(message_str)
if "TrapLink" in ctx.tags and from_queue:
await self.send_trap_link(ctx, trap_value_to_name[next_trap.item])
new_item_count = trap_rom_data[next_trap.item][1] new_item_count = trap_rom_data[next_trap.item][1]
snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([new_item_count])) snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([new_item_count]))
@@ -270,9 +400,75 @@ class SMWSNIClient(SNIClient):
return return
if self.should_show_message(ctx, next_trap): if self.should_show_message(ctx, next_trap):
self.add_message_to_queue_front(message)
elif next_trap.item == 0xBC0015:
if self.should_show_message(ctx, next_trap):
self.add_message_to_queue_front(message)
if len(message_str) > 0:
snes_logger.info(message_str)
if "TrapLink" in ctx.tags and from_queue:
await self.send_trap_link(ctx, trap_value_to_name[next_trap.item])
# Handle Literature Trap
from .Names.LiteratureTrap import lit_trap_text_list
import random
rand_trap = random.choice(lit_trap_text_list)
for message in rand_trap:
self.add_message_to_queue(message) self.add_message_to_queue(message)
async def handle_ring_link(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
if "RingLink" not in ctx.tags:
return
if not hasattr(self, "prev_coins"):
self.prev_coins = 0
curr_coins_byte = await snes_read(ctx, SMW_COIN_COUNT_ADDR, 0x1)
curr_coins = curr_coins_byte[0]
if curr_coins < self.prev_coins:
# Coins rolled over from 1-Up
curr_coins += 100
coins_diff = curr_coins - self.prev_coins
if coins_diff > 0:
await self.send_ring_link(ctx, coins_diff)
self.prev_coins = curr_coins % 100
new_coins = curr_coins
if not hasattr(self, "pending_ring_link"):
self.pending_ring_link = 0
if self.pending_ring_link != 0:
new_coins += self.pending_ring_link
new_coins = max(new_coins, 0)
new_1_ups = 0
while new_coins >= 100:
new_1_ups += 1
new_coins -= 100
if new_1_ups > 0:
curr_lives_inc_byte = await snes_read(ctx, WRAM_START + 0x18E4, 0x1)
curr_lives_inc = curr_lives_inc_byte[0]
new_lives_inc = curr_lives_inc + new_1_ups
snes_buffered_write(ctx, WRAM_START + 0x18E4, bytes([new_lives_inc]))
snes_buffered_write(ctx, SMW_COIN_COUNT_ADDR, bytes([new_coins]))
if self.pending_ring_link > 0:
snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x01]))
else:
snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x2A]))
self.pending_ring_link = 0
self.prev_coins = new_coins
await snes_flush_writes(ctx)
async def game_watcher(self, ctx): async def game_watcher(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
@@ -333,6 +529,7 @@ class SMWSNIClient(SNIClient):
await self.handle_message_queue(ctx) await self.handle_message_queue(ctx)
await self.handle_trap_queue(ctx) await self.handle_trap_queue(ctx)
await self.handle_ring_link(ctx)
new_checks = [] new_checks = []
event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60) event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60)
@@ -506,7 +703,7 @@ class SMWSNIClient(SNIClient):
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
if self.should_show_message(ctx, item): if self.should_show_message(ctx, item):
if item.item != 0xBC0012 and item.item not in trap_rom_data: if item.item != 0xBC0012 and item.item != 0xBC0015 and item.item not in trap_rom_data:
# Don't send messages for Boss Tokens # Don't send messages for Boss Tokens
item_name = ctx.item_names.lookup_in_game(item.item) item_name = ctx.item_names.lookup_in_game(item.item)
player_name = ctx.player_names[item.player] player_name = ctx.player_names[item.player]
@@ -515,7 +712,7 @@ class SMWSNIClient(SNIClient):
self.add_message_to_queue(receive_message) self.add_message_to_queue(receive_message)
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF])) snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
if item.item in trap_rom_data: if item.item in trap_rom_data or item.item == 0xBC0015:
item_name = ctx.item_names.lookup_in_game(item.item) item_name = ctx.item_names.lookup_in_game(item.item)
player_name = ctx.player_names[item.player] player_name = ctx.player_names[item.player]
@@ -572,14 +769,6 @@ class SMWSNIClient(SNIClient):
else: else:
# Extra Powerup? # Extra Powerup?
pass pass
elif item.item == 0xBC0015:
# Handle Literature Trap
from .Names.LiteratureTrap import lit_trap_text_list
import random
rand_trap = random.choice(lit_trap_text_list)
for message in rand_trap:
self.add_message_to_queue(message)
await snes_flush_writes(ctx) await snes_flush_writes(ctx)

View File

@@ -75,3 +75,49 @@ item_table = {
} }
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
trap_value_to_name: typing.Dict[int, str] = {
0xBC0013: ItemName.ice_trap,
0xBC0014: ItemName.stun_trap,
0xBC0015: ItemName.literature_trap,
0xBC0016: ItemName.timer_trap,
0xBC001C: ItemName.reverse_controls_trap,
0xBC001D: ItemName.thwimp_trap,
}
trap_name_to_value: typing.Dict[str, int] = {
# Our native Traps
ItemName.ice_trap: 0xBC0013,
ItemName.stun_trap: 0xBC0014,
ItemName.literature_trap: 0xBC0015,
ItemName.timer_trap: 0xBC0016,
ItemName.reverse_controls_trap: 0xBC001C,
ItemName.thwimp_trap: 0xBC001D,
# Common other trap names
"Chaos Control Trap": 0xBC0014, # Stun Trap
"Confuse Trap": 0xBC001C, # Reverse Trap
"Exposition Trap": 0xBC0015, # Literature Trap
"Cutscene Trap": 0xBC0015, # Literature Trap
"Freeze Trap": 0xBC0014, # Stun Trap
"Frozen Trap": 0xBC0014, # Stun Trap
"Paralyze Trap": 0xBC0014, # Stun Trap
"Reversal Trap": 0xBC001C, # Reverse Trap
"Fuzzy Trap": 0xBC001C, # Reverse Trap
"Confound Trap": 0xBC001C, # Reverse Trap
"Confusion Trap": 0xBC001C, # Reverse Trap
"Police Trap": 0xBC001D, # Thwimp Trap
"Buyon Trap": 0xBC001D, # Thwimp Trap
"Gooey Bag": 0xBC001D, # Thwimp Trap
"TNT Barrel Trap": 0xBC001D, # Thwimp Trap
"Honey Trap": 0xBC0014, # Stun Trap
"Screen Flip Trap": 0xBC001C, # Reverse Trap
"Banana Trap": 0xBC0013, # Ice Trap
"Bomb": 0xBC001D, # Thwimp Trap
"Bonk Trap": 0xBC0014, # Stun Trap
"Ghost": 0xBC001D, # Thwimp Trap
"Fast Trap": 0xBC0016, # Timer Trap
"Nut Trap": 0xBC001D, # Thwimp Trap
"Army Trap": 0xBC001D, # Thwimp Trap
}

View File

@@ -117,6 +117,31 @@ def generate_received_text(item_name: str, player_name: str):
return out_array return out_array
def generate_received_trap_link_text(item_name: str, player_name: str):
out_array = bytearray()
item_name = item_name[:18]
player_name = player_name[:18]
item_buffer = max(0, math.floor((18 - len(item_name)) / 2))
player_buffer = max(0, math.floor((18 - len(player_name)) / 2))
out_array += bytearray([0x9F, 0x9F])
out_array += string_to_bytes(" Received linked")
out_array[-1] += 0x80
out_array += bytearray([0x1F] * item_buffer)
out_array += string_to_bytes(item_name)
out_array[-1] += 0x80
out_array += string_to_bytes(" from")
out_array[-1] += 0x80
out_array += bytearray([0x1F] * player_buffer)
out_array += string_to_bytes(player_name)
out_array[-1] += 0x80
out_array += bytearray([0x9F, 0x9F])
return out_array
def generate_sent_text(item_name: str, player_name: str): def generate_sent_text(item_name: str, player_name: str):
out_array = bytearray() out_array = bytearray()

View File

@@ -398,6 +398,20 @@ class StartingLifeCount(Range):
default = 5 default = 5
class RingLink(Toggle):
"""
Whether your in-level coin gain/loss is linked to other players
"""
display_name = "Ring Link"
class TrapLink(Toggle):
"""
Whether your received traps are linked to other players
"""
display_name = "Trap Link"
smw_option_groups = [ smw_option_groups = [
OptionGroup("Goal Options", [ OptionGroup("Goal Options", [
Goal, Goal,
@@ -447,6 +461,8 @@ smw_option_groups = [
@dataclass @dataclass
class SMWOptions(PerGameCommonOptions): class SMWOptions(PerGameCommonOptions):
death_link: DeathLink death_link: DeathLink
ring_link: RingLink
trap_link: TrapLink
goal: Goal goal: Goal
bosses_required: BossesRequired bosses_required: BossesRequired
max_yoshi_egg_cap: NumberOfYoshiEggs max_yoshi_egg_cap: NumberOfYoshiEggs

View File

@@ -719,8 +719,8 @@ def handle_vertical_scroll(rom):
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0D0-0DF 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0D0-0DF
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, # Levels 0E0-0EF 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, # Levels 0E0-0EF
0x02, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0F0-0FF 0x02, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0F0-0FF
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, # Levels 100-10F 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, # Levels 100-10F
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 110-11F 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, # Levels 110-11F
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 120-12F 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 120-12F
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 130-13F 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 130-13F
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 140-14F 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 140-14F
@@ -3160,6 +3160,8 @@ def patch_rom(world: World, rom, player, active_level_dict):
rom.write_byte(0x01BFA9, world.options.hidden_1up_checks.value) rom.write_byte(0x01BFA9, world.options.hidden_1up_checks.value)
rom.write_byte(0x01BFAA, world.options.bonus_block_checks.value) rom.write_byte(0x01BFAA, world.options.bonus_block_checks.value)
rom.write_byte(0x01BFAB, world.options.blocksanity.value) rom.write_byte(0x01BFAB, world.options.blocksanity.value)
rom.write_byte(0x01BFB7, world.options.trap_link.value)
rom.write_byte(0x01BFB8, world.options.ring_link.value)
from Utils import __version__ from Utils import __version__

View File

@@ -90,6 +90,7 @@ class SMWWorld(World):
"blocksanity", "blocksanity",
) )
slot_data["active_levels"] = self.active_level_dict slot_data["active_levels"] = self.active_level_dict
slot_data["trap_weights"] = self.output_trap_weights()
return slot_data return slot_data
@@ -322,3 +323,15 @@ class SMWWorld(World):
def set_rules(self): def set_rules(self):
set_rules(self) set_rules(self)
def output_trap_weights(self) -> dict[int, int]:
trap_data = {}
trap_data[0xBC0013] = self.options.ice_trap_weight.value
trap_data[0xBC0014] = self.options.stun_trap_weight.value
trap_data[0xBC0015] = self.options.literature_trap_weight.value
trap_data[0xBC0016] = self.options.timer_trap_weight.value
trap_data[0xBC001C] = self.options.reverse_trap_weight.value
trap_data[0xBC001D] = self.options.thwimp_trap_weight.value
return trap_data