CV64: Use AP Procedure Patch (#3159)

This commit is contained in:
LiquidCat64 2024-04-18 10:37:51 -06:00 committed by GitHub
parent 7fd7d5e492
commit 437843bf53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1102 additions and 1046 deletions

View File

@ -4,7 +4,7 @@ import settings
import base64 import base64
import logging import logging
from BaseClasses import Item, Region, MultiWorld, Tutorial, ItemClassification from BaseClasses import Item, Region, Tutorial, ItemClassification
from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts
from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id
from .entrances import verify_entrances, get_warp_entrances from .entrances import verify_entrances, get_warp_entrances
@ -18,7 +18,7 @@ from ..AutoWorld import WebWorld, World
from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \ from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \
randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \ randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \
get_countdown_numbers get_countdown_numbers
from .rom import LocalRom, patch_rom, get_base_rom_path, CV64DeltaPatch from .rom import RomData, write_patch, get_base_rom_path, CV64ProcedurePatch, CV64_US_10_HASH
from .client import Castlevania64Client from .client import Castlevania64Client
@ -27,7 +27,7 @@ class CV64Settings(settings.Group):
"""File name of the CV64 US 1.0 rom""" """File name of the CV64 US 1.0 rom"""
copy_to = "Castlevania (USA).z64" copy_to = "Castlevania (USA).z64"
description = "CV64 (US 1.0) ROM File" description = "CV64 (US 1.0) ROM File"
md5s = [CV64DeltaPatch.hash] md5s = [CV64_US_10_HASH]
rom_file: RomFile = RomFile(RomFile.copy_to) rom_file: RomFile = RomFile(RomFile.copy_to)
@ -86,12 +86,6 @@ class CV64World(World):
web = CV64Web() web = CV64Web()
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
rom_file = get_base_rom_path()
if not os.path.exists(rom_file):
raise FileNotFoundError(rom_file)
def generate_early(self) -> None: def generate_early(self) -> None:
# Generate the player's unique authentication # Generate the player's unique authentication
self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16)) self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16))
@ -276,18 +270,13 @@ class CV64World(World):
offset_data.update(get_start_inventory_data(self.player, self.options, offset_data.update(get_start_inventory_data(self.player, self.options,
self.multiworld.precollected_items[self.player])) self.multiworld.precollected_items[self.player]))
cv64_rom = LocalRom(get_base_rom_path()) patch = CV64ProcedurePatch()
write_patch(self, patch, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations)
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.z64") rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}"
f"{patch.patch_file_ending}")
patch_rom(self, cv64_rom, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations) patch.write(rom_path)
cv64_rom.write_to_file(rompath)
patch = CV64DeltaPatch(os.path.splitext(rompath)[0] + CV64DeltaPatch.patch_file_ending, player=self.player,
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
patch.write()
os.unlink(rompath)
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
return self.random.choice(filler_item_names) return self.random.choice(filler_item_names)

View File

@ -14,111 +14,111 @@ if TYPE_CHECKING:
from . import CV64World from . import CV64World
rom_sub_weapon_offsets = { rom_sub_weapon_offsets = {
0x10C6EB: (0x10, rname.forest_of_silence), # Forest 0x10C6EB: (b"\x10", rname.forest_of_silence), # Forest
0x10C6F3: (0x0F, rname.forest_of_silence), 0x10C6F3: (b"\x0F", rname.forest_of_silence),
0x10C6FB: (0x0E, rname.forest_of_silence), 0x10C6FB: (b"\x0E", rname.forest_of_silence),
0x10C703: (0x0D, rname.forest_of_silence), 0x10C703: (b"\x0D", rname.forest_of_silence),
0x10C81F: (0x0F, rname.castle_wall), # Castle Wall 0x10C81F: (b"\x0F", rname.castle_wall), # Castle Wall
0x10C827: (0x10, rname.castle_wall), 0x10C827: (b"\x10", rname.castle_wall),
0x10C82F: (0x0E, rname.castle_wall), 0x10C82F: (b"\x0E", rname.castle_wall),
0x7F9A0F: (0x0D, rname.castle_wall), 0x7F9A0F: (b"\x0D", rname.castle_wall),
0x83A5D9: (0x0E, rname.villa), # Villa 0x83A5D9: (b"\x0E", rname.villa), # Villa
0x83A5E5: (0x0D, rname.villa), 0x83A5E5: (b"\x0D", rname.villa),
0x83A5F1: (0x0F, rname.villa), 0x83A5F1: (b"\x0F", rname.villa),
0xBFC903: (0x10, rname.villa), 0xBFC903: (b"\x10", rname.villa),
0x10C987: (0x10, rname.villa), 0x10C987: (b"\x10", rname.villa),
0x10C98F: (0x0D, rname.villa), 0x10C98F: (b"\x0D", rname.villa),
0x10C997: (0x0F, rname.villa), 0x10C997: (b"\x0F", rname.villa),
0x10CF73: (0x10, rname.villa), 0x10CF73: (b"\x10", rname.villa),
0x10CA57: (0x0D, rname.tunnel), # Tunnel 0x10CA57: (b"\x0D", rname.tunnel), # Tunnel
0x10CA5F: (0x0E, rname.tunnel), 0x10CA5F: (b"\x0E", rname.tunnel),
0x10CA67: (0x10, rname.tunnel), 0x10CA67: (b"\x10", rname.tunnel),
0x10CA6F: (0x0D, rname.tunnel), 0x10CA6F: (b"\x0D", rname.tunnel),
0x10CA77: (0x0F, rname.tunnel), 0x10CA77: (b"\x0F", rname.tunnel),
0x10CA7F: (0x0E, rname.tunnel), 0x10CA7F: (b"\x0E", rname.tunnel),
0x10CBC7: (0x0E, rname.castle_center), # Castle Center 0x10CBC7: (b"\x0E", rname.castle_center), # Castle Center
0x10CC0F: (0x0D, rname.castle_center), 0x10CC0F: (b"\x0D", rname.castle_center),
0x10CC5B: (0x0F, rname.castle_center), 0x10CC5B: (b"\x0F", rname.castle_center),
0x10CD3F: (0x0E, rname.tower_of_execution), # Character towers 0x10CD3F: (b"\x0E", rname.tower_of_execution), # Character towers
0x10CD65: (0x0D, rname.tower_of_execution), 0x10CD65: (b"\x0D", rname.tower_of_execution),
0x10CE2B: (0x0E, rname.tower_of_science), 0x10CE2B: (b"\x0E", rname.tower_of_science),
0x10CE83: (0x10, rname.duel_tower), 0x10CE83: (b"\x10", rname.duel_tower),
0x10CF8B: (0x0F, rname.room_of_clocks), # Room of Clocks 0x10CF8B: (b"\x0F", rname.room_of_clocks), # Room of Clocks
0x10CF93: (0x0D, rname.room_of_clocks), 0x10CF93: (b"\x0D", rname.room_of_clocks),
0x99BC5A: (0x0D, rname.clock_tower), # Clock Tower 0x99BC5A: (b"\x0D", rname.clock_tower), # Clock Tower
0x10CECB: (0x10, rname.clock_tower), 0x10CECB: (b"\x10", rname.clock_tower),
0x10CED3: (0x0F, rname.clock_tower), 0x10CED3: (b"\x0F", rname.clock_tower),
0x10CEDB: (0x0E, rname.clock_tower), 0x10CEDB: (b"\x0E", rname.clock_tower),
0x10CEE3: (0x0D, rname.clock_tower), 0x10CEE3: (b"\x0D", rname.clock_tower),
} }
rom_sub_weapon_flags = { rom_sub_weapon_flags = {
0x10C6EC: 0x0200FF04, # Forest of Silence 0x10C6EC: b"\x02\x00\xFF\x04", # Forest of Silence
0x10C6FC: 0x0400FF04, 0x10C6FC: b"\x04\x00\xFF\x04",
0x10C6F4: 0x0800FF04, 0x10C6F4: b"\x08\x00\xFF\x04",
0x10C704: 0x4000FF04, 0x10C704: b"\x40\x00\xFF\x04",
0x10C831: 0x08, # Castle Wall 0x10C831: b"\x08", # Castle Wall
0x10C829: 0x10, 0x10C829: b"\x10",
0x10C821: 0x20, 0x10C821: b"\x20",
0xBFCA97: 0x04, 0xBFCA97: b"\x04",
# Villa # Villa
0xBFC926: 0xFF04, 0xBFC926: b"\xFF\x04",
0xBFC93A: 0x80, 0xBFC93A: b"\x80",
0xBFC93F: 0x01, 0xBFC93F: b"\x01",
0xBFC943: 0x40, 0xBFC943: b"\x40",
0xBFC947: 0x80, 0xBFC947: b"\x80",
0x10C989: 0x10, 0x10C989: b"\x10",
0x10C991: 0x20, 0x10C991: b"\x20",
0x10C999: 0x40, 0x10C999: b"\x40",
0x10CF77: 0x80, 0x10CF77: b"\x80",
0x10CA58: 0x4000FF0E, # Tunnel 0x10CA58: b"\x40\x00\xFF\x0E", # Tunnel
0x10CA6B: 0x80, 0x10CA6B: b"\x80",
0x10CA60: 0x1000FF05, 0x10CA60: b"\x10\x00\xFF\x05",
0x10CA70: 0x2000FF05, 0x10CA70: b"\x20\x00\xFF\x05",
0x10CA78: 0x4000FF05, 0x10CA78: b"\x40\x00\xFF\x05",
0x10CA80: 0x8000FF05, 0x10CA80: b"\x80\x00\xFF\x05",
0x10CBCA: 0x02, # Castle Center 0x10CBCA: b"\x02", # Castle Center
0x10CC10: 0x80, 0x10CC10: b"\x80",
0x10CC5C: 0x40, 0x10CC5C: b"\x40",
0x10CE86: 0x01, # Duel Tower 0x10CE86: b"\x01", # Duel Tower
0x10CD43: 0x02, # Tower of Execution 0x10CD43: b"\x02", # Tower of Execution
0x10CE2E: 0x20, # Tower of Science 0x10CE2E: b"\x20", # Tower of Science
0x10CF8E: 0x04, # Room of Clocks 0x10CF8E: b"\x04", # Room of Clocks
0x10CF96: 0x08, 0x10CF96: b"\x08",
0x10CECE: 0x08, # Clock Tower 0x10CECE: b"\x08", # Clock Tower
0x10CED6: 0x10, 0x10CED6: b"\x10",
0x10CEE6: 0x20, 0x10CEE6: b"\x20",
0x10CEDE: 0x80, 0x10CEDE: b"\x80",
} }
rom_empty_breakables_flags = { rom_empty_breakables_flags = {
0x10C74D: 0x40FF05, # Forest of Silence 0x10C74D: b"\x40\xFF\x05", # Forest of Silence
0x10C765: 0x20FF0E, 0x10C765: b"\x20\xFF\x0E",
0x10C774: 0x0800FF0E, 0x10C774: b"\x08\x00\xFF\x0E",
0x10C755: 0x80FF05, 0x10C755: b"\x80\xFF\x05",
0x10C784: 0x0100FF0E, 0x10C784: b"\x01\x00\xFF\x0E",
0x10C73C: 0x0200FF0E, 0x10C73C: b"\x02\x00\xFF\x0E",
0x10C8D0: 0x0400FF0E, # Villa foyer 0x10C8D0: b"\x04\x00\xFF\x0E", # Villa foyer
0x10CF9F: 0x08, # Room of Clocks flags 0x10CF9F: b"\x08", # Room of Clocks flags
0x10CFA7: 0x01, 0x10CFA7: b"\x01",
0xBFCB6F: 0x04, # Room of Clocks candle property IDs 0xBFCB6F: b"\x04", # Room of Clocks candle property IDs
0xBFCB73: 0x05, 0xBFCB73: b"\x05",
} }
rom_axe_cross_lower_values = { rom_axe_cross_lower_values = {
@ -269,19 +269,18 @@ renon_item_dialogue = {
} }
def randomize_lighting(world: "CV64World") -> Dict[int, int]: def randomize_lighting(world: "CV64World") -> Dict[int, bytes]:
"""Generates randomized data for the map lighting table.""" """Generates randomized data for the map lighting table."""
randomized_lighting = {} randomized_lighting = {}
for entry in range(67): for entry in range(67):
for sub_entry in range(19): for sub_entry in range(19):
if sub_entry not in [3, 7, 11, 15] and entry != 4: if sub_entry not in [3, 7, 11, 15] and entry != 4:
# The fourth entry in the lighting table affects the lighting on some item pickups; skip it # The fourth entry in the lighting table affects the lighting on some item pickups; skip it
randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = \ randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = bytes([world.random.randint(0, 255)])
world.random.randint(0, 255)
return randomized_lighting return randomized_lighting
def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]: def shuffle_sub_weapons(world: "CV64World") -> Dict[int, bytes]:
"""Shuffles the sub-weapons amongst themselves.""" """Shuffles the sub-weapons amongst themselves."""
sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if
rom_sub_weapon_offsets[offset][1] in world.active_stage_exits} rom_sub_weapon_offsets[offset][1] in world.active_stage_exits}
@ -295,7 +294,7 @@ def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]:
return dict(zip(sub_weapon_dict, sub_bytes)) return dict(zip(sub_weapon_dict, sub_bytes))
def randomize_music(world: "CV64World") -> Dict[int, int]: def randomize_music(world: "CV64World") -> Dict[int, bytes]:
"""Generates randomized or disabled data for all the music in the game.""" """Generates randomized or disabled data for all the music in the game."""
music_array = bytearray(0x7A) music_array = bytearray(0x7A)
for number in music_sfx_ids: for number in music_sfx_ids:
@ -340,15 +339,10 @@ def randomize_music(world: "CV64World") -> Dict[int, int]:
music_array[i] = fade_in_songs[i] music_array[i] = fade_in_songs[i]
del (music_array[0x00: 0x10]) del (music_array[0x00: 0x10])
# Convert the music array into a data dict return {0xBFCD30: bytes(music_array)}
music_offsets = {}
for i in range(len(music_array)):
music_offsets[0xBFCD30 + i] = music_array[i]
return music_offsets
def randomize_shop_prices(world: "CV64World") -> Dict[int, int]: def randomize_shop_prices(world: "CV64World") -> Dict[int, bytes]:
"""Randomize the shop prices based on the minimum and maximum values chosen. """Randomize the shop prices based on the minimum and maximum values chosen.
The minimum price will adjust if it's higher than the max.""" The minimum price will adjust if it's higher than the max."""
min_price = world.options.minimum_gold_price.value min_price = world.options.minimum_gold_price.value
@ -363,21 +357,15 @@ def randomize_shop_prices(world: "CV64World") -> Dict[int, int]:
shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)] shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)]
# Convert the price list into a data dict. Which offset it starts from depends on how many bytes it takes up. # Convert the price list into a data dict.
price_dict = {} price_dict = {}
for i in range(len(shop_price_list)): for i in range(len(shop_price_list)):
if shop_price_list[i] <= 0xFF: price_dict[0x103D6C + (i * 12)] = int.to_bytes(shop_price_list[i], 4, "big")
price_dict[0x103D6E + (i*12)] = 0
price_dict[0x103D6F + (i*12)] = shop_price_list[i]
elif shop_price_list[i] <= 0xFFFF:
price_dict[0x103D6E + (i*12)] = shop_price_list[i]
else:
price_dict[0x103D6D + (i*12)] = shop_price_list[i]
return price_dict return price_dict
def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, int]: def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, bytes]:
"""Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should """Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should
increase a number. increase a number.
@ -400,16 +388,11 @@ def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Locat
if countdown_number is not None: if countdown_number is not None:
countdown_list[countdown_number] += 1 countdown_list[countdown_number] += 1
# Convert the Countdown list into a data dict return {0xBFD818: bytes(countdown_list)}
countdown_dict = {}
for i in range(len(countdown_list)):
countdown_dict[0xBFD818 + i] = countdown_list[i]
return countdown_dict
def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \ def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \
-> Tuple[Dict[int, int], List[str], List[bytearray], List[List[Union[int, str, None]]]]: -> Tuple[Dict[int, bytes], List[str], List[bytearray], List[List[Union[int, str, None]]]]:
"""Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of """Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of
the item, the second determines what the item actually is when picked up. All items from other worlds will be AP the item, the second determines what the item actually is when picked up. All items from other worlds will be AP
items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's
@ -449,12 +432,11 @@ def get_location_data(world: "CV64World", active_locations: Iterable[Location])
# Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's # Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's
# very own, or it belongs to an Item Link that the player is a part of. # very own, or it belongs to an Item Link that the player is a part of.
if loc.item.player == world.player or (loc.item.player in world.multiworld.groups and if loc.item.player == world.player:
world.player in world.multiworld.groups[loc.item.player]['players']):
if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None: if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None:
location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id") location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id")
else: else:
location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code") location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code") & 0xFF
else: else:
# Make the item the unused Wooden Stake - our multiworld item. # Make the item the unused Wooden Stake - our multiworld item.
location_bytes[get_location_info(loc.name, "offset")] = 0x11 location_bytes[get_location_info(loc.name, "offset")] = 0x11
@ -534,11 +516,12 @@ def get_location_data(world: "CV64World", active_locations: Iterable[Location])
shop_colors_list.append(get_item_text_color(loc)) shop_colors_list.append(get_item_text_color(loc))
return location_bytes, shop_name_list, shop_colors_list, shop_desc_list return {offset: int.to_bytes(byte, 1, "big") for offset, byte in location_bytes.items()}, shop_name_list,\
shop_colors_list, shop_desc_list
def get_loading_zone_bytes(options: CV64Options, starting_stage: str, def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, int]: active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, bytes]:
"""Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data. """Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data.
The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map
to send the player to, and which spot within the map to spawn the player at.""" to send the player to, and which spot within the map to spawn the player at."""
@ -551,8 +534,8 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
# Start loading zones # Start loading zones
# If the start zone is the start of the line, have it simply refresh the map. # If the start zone is the start of the line, have it simply refresh the map.
if active_stage_exits[stage]["prev"] == "Menu": if active_stage_exits[stage]["prev"] == "Menu":
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = 0xFF loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = b"\xFF"
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = 0x00 loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x00"
elif active_stage_exits[stage]["prev"]: elif active_stage_exits[stage]["prev"]:
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \ loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \
get_stage_info(active_stage_exits[stage]["prev"], "end map id") get_stage_info(active_stage_exits[stage]["prev"], "end map id")
@ -563,7 +546,7 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
if active_stage_exits[stage]["prev"] == rname.castle_center: if active_stage_exits[stage]["prev"] == rname.castle_center:
if options.character_stages == CharacterStages.option_carrie_only or \ if options.character_stages == CharacterStages.option_carrie_only or \
active_stage_exits[rname.castle_center]["alt"] == stage: active_stage_exits[rname.castle_center]["alt"] == stage:
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] += 1 loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x03"
# End loading zones # End loading zones
if active_stage_exits[stage]["next"]: if active_stage_exits[stage]["next"]:
@ -582,16 +565,16 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
return loading_zone_bytes return loading_zone_bytes
def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, int]: def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, bytes]:
"""Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything """Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything
has to be handled appropriately.""" has to be handled appropriately."""
start_inventory_data = {0xBFD867: 0, # Jewels start_inventory_data = {}
0xBFD87B: 0, # PowerUps
0xBFD883: 0, # Sub-weapon
0xBFD88B: 0} # Ice Traps
inventory_items_array = [0 for _ in range(35)] inventory_items_array = [0 for _ in range(35)]
total_money = 0 total_money = 0
total_jewels = 0
total_powerups = 0
total_ice_traps = 0
items_max = 10 items_max = 10
@ -615,42 +598,46 @@ def get_start_inventory_data(player: int, options: CV64Options, precollected_ite
inventory_items_array[inventory_offset] = 2 inventory_items_array[inventory_offset] = 2
# Starting sub-weapon # Starting sub-weapon
elif sub_equip_id is not None: elif sub_equip_id is not None:
start_inventory_data[0xBFD883] = sub_equip_id start_inventory_data[0xBFD883] = bytes(sub_equip_id)
# Starting PowerUps # Starting PowerUps
elif item.name == iname.powerup: elif item.name == iname.powerup:
start_inventory_data[0xBFD87B] += 1 total_powerups += 1
if start_inventory_data[0xBFD87B] > 2: # Can't have more than 2 PowerUps.
start_inventory_data[0xBFD87B] = 2 if total_powerups > 2:
total_powerups = 2
# Starting Gold # Starting Gold
elif "GOLD" in item.name: elif "GOLD" in item.name:
total_money += int(item.name[0:4]) total_money += int(item.name[0:4])
# Money cannot be higher than 99999.
if total_money > 99999: if total_money > 99999:
total_money = 99999 total_money = 99999
# Starting Jewels # Starting Jewels
elif "jewel" in item.name: elif "jewel" in item.name:
if "L" in item.name: if "L" in item.name:
start_inventory_data[0xBFD867] += 10 total_jewels += 10
else: else:
start_inventory_data[0xBFD867] += 5 total_jewels += 5
if start_inventory_data[0xBFD867] > 99: # Jewels cannot be higher than 99.
start_inventory_data[0xBFD867] = 99 if total_jewels > 99:
total_jewels = 99
# Starting Ice Traps # Starting Ice Traps
else: else:
start_inventory_data[0xBFD88B] += 1 total_ice_traps += 1
if start_inventory_data[0xBFD88B] > 0xFF: # Ice Traps cannot be higher than 255.
start_inventory_data[0xBFD88B] = 0xFF if total_ice_traps > 0xFF:
total_ice_traps = 0xFF
# Convert the jewels into data.
start_inventory_data[0xBFD867] = bytes([total_jewels])
# Convert the Ice Traps into data.
start_inventory_data[0xBFD88B] = bytes([total_ice_traps])
# Convert the inventory items into data. # Convert the inventory items into data.
for i in range(len(inventory_items_array)): start_inventory_data[0xBFE518] = bytes(inventory_items_array)
start_inventory_data[0xBFE518 + i] = inventory_items_array[i]
# Convert the starting money into data. Which offset it starts from depends on how many bytes it takes up. # Convert the starting money into data.
if total_money <= 0xFF: start_inventory_data[0xBFE514] = int.to_bytes(total_money, 4, "big")
start_inventory_data[0xBFE517] = total_money
elif total_money <= 0xFFFF:
start_inventory_data[0xBFE516] = total_money
else:
start_inventory_data[0xBFE515] = total_money
return start_inventory_data return start_inventory_data

View File

@ -197,12 +197,15 @@ deathlink_nitro_edition = [
0xA168FFFD, # SB T0, 0xFFFD (T3) 0xA168FFFD, # SB T0, 0xFFFD (T3)
] ]
nitro_fall_killer = [ launch_fall_killer = [
# Custom code to force the instant fall death if at a high enough falling speed after getting killed by the Nitro # Custom code to force the instant fall death if at a high enough falling speed after getting killed by something
# explosion, since the game doesn't run the checks for the fall death after getting hit by said explosion and could # that launches you (whether it be the Nitro explosion or a Big Toss hit). The game doesn't normally run the check
# result in a softlock when getting blown into an abyss. # that would trigger the fall death after you get killed by some other means, which could result in a softlock
# when a killing blow launches you into an abyss.
0x3C0C8035, # LUI T4, 0x8035 0x3C0C8035, # LUI T4, 0x8035
0x918807E2, # LBU T0, 0x07E2 (T4) 0x918807E2, # LBU T0, 0x07E2 (T4)
0x24090008, # ADDIU T1, R0, 0x0008
0x11090002, # BEQ T0, T1, [forward 0x02]
0x2409000C, # ADDIU T1, R0, 0x000C 0x2409000C, # ADDIU T1, R0, 0x000C
0x15090006, # BNE T0, T1, [forward 0x06] 0x15090006, # BNE T0, T1, [forward 0x06]
0x3C098035, # LUI T1, 0x8035 0x3C098035, # LUI T1, 0x8035
@ -2863,3 +2866,13 @@ big_tosser = [
0xAD000814, # SW R0, 0x0814 (T0) 0xAD000814, # SW R0, 0x0814 (T0)
0x03200008 # JR T9 0x03200008 # JR T9
] ]
dog_bite_ice_trap_fix = [
# Sets the freeze timer to 0 when a maze garden dog bites the player to ensure the ice chunk model will break if the
# player gets bitten while frozen via Ice Trap.
0x3C088039, # LUI T0, 0x8039
0xA5009E76, # SH R0, 0x9E76 (T0)
0x3C090F00, # LUI T1, 0x0F00
0x25291CB8, # ADDIU T1, T1, 0x1CB8
0x01200008 # JR T1
]

View File

@ -27,7 +27,7 @@ in vanilla, contains 5 checks in rando.
#### Bat archway rock #### Bat archway rock
After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front
of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new
to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guranteed spot to find a PowerUp at. to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guaranteed spot to find a PowerUp at.

View File

@ -74,7 +74,8 @@ class HardItemPool(Toggle):
class Special1sPerWarp(Range): class Special1sPerWarp(Range):
"""Sets how many Special1 jewels are needed per warp menu option unlock.""" """Sets how many Special1 jewels are needed per warp menu option unlock.
This will decrease until the number x 7 is less than or equal to the Total Specail1s if it isn't already."""
range_start = 1 range_start = 1
range_end = 10 range_end = 10
default = 1 default = 1
@ -82,8 +83,7 @@ class Special1sPerWarp(Range):
class TotalSpecial1s(Range): class TotalSpecial1s(Range):
"""Sets how many Speical1 jewels are in the pool in total. """Sets how many Speical1 jewels are in the pool in total."""
If this is set to be less than Special1s Per Warp x 7, it will decrease by 1 until it isn't."""
range_start = 7 range_start = 7
range_end = 70 range_end = 70
default = 7 default = 7

File diff suppressed because it is too large Load Diff

View File

@ -47,9 +47,9 @@ if TYPE_CHECKING:
# corresponding Locations and Entrances will all be created. # corresponding Locations and Entrances will all be created.
stage_info = { stage_info = {
"Forest of Silence": { "Forest of Silence": {
"start region": rname.forest_start, "start map id": 0x00, "start spawn id": 0x00, "start region": rname.forest_start, "start map id": b"\x00", "start spawn id": b"\x00",
"mid region": rname.forest_mid, "mid map id": 0x00, "mid spawn id": 0x04, "mid region": rname.forest_mid, "mid map id": b"\x00", "mid spawn id": b"\x04",
"end region": rname.forest_end, "end map id": 0x00, "end spawn id": 0x01, "end region": rname.forest_end, "end map id": b"\x00", "end spawn id": b"\x01",
"endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B, "endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B,
"save number offsets": [0x1049C5, 0x1049CD, 0x1049D5], "save number offsets": [0x1049C5, 0x1049CD, 0x1049D5],
"regions": [rname.forest_start, "regions": [rname.forest_start,
@ -58,9 +58,9 @@ stage_info = {
}, },
"Castle Wall": { "Castle Wall": {
"start region": rname.cw_start, "start map id": 0x02, "start spawn id": 0x00, "start region": rname.cw_start, "start map id": b"\x02", "start spawn id": b"\x00",
"mid region": rname.cw_start, "mid map id": 0x02, "mid spawn id": 0x07, "mid region": rname.cw_start, "mid map id": b"\x02", "mid spawn id": b"\x07",
"end region": rname.cw_exit, "end map id": 0x02, "end spawn id": 0x10, "end region": rname.cw_exit, "end map id": b"\x02", "end spawn id": b"\x10",
"endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61, "endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61,
"save number offsets": [0x1049DD, 0x1049E5, 0x1049ED], "save number offsets": [0x1049DD, 0x1049E5, 0x1049ED],
"regions": [rname.cw_start, "regions": [rname.cw_start,
@ -69,9 +69,9 @@ stage_info = {
}, },
"Villa": { "Villa": {
"start region": rname.villa_start, "start map id": 0x03, "start spawn id": 0x00, "start region": rname.villa_start, "start map id": b"\x03", "start spawn id": b"\x00",
"mid region": rname.villa_storeroom, "mid map id": 0x05, "mid spawn id": 0x04, "mid region": rname.villa_storeroom, "mid map id": b"\x05", "mid spawn id": b"\x04",
"end region": rname.villa_crypt, "end map id": 0x1A, "end spawn id": 0x03, "end region": rname.villa_crypt, "end map id": b"\x1A", "end spawn id": b"\x03",
"endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81, "endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81,
"altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81, "altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81,
"save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D], "save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D],
@ -85,9 +85,9 @@ stage_info = {
}, },
"Tunnel": { "Tunnel": {
"start region": rname.tunnel_start, "start map id": 0x07, "start spawn id": 0x00, "start region": rname.tunnel_start, "start map id": b"\x07", "start spawn id": b"\x00",
"mid region": rname.tunnel_end, "mid map id": 0x07, "mid spawn id": 0x03, "mid region": rname.tunnel_end, "mid map id": b"\x07", "mid spawn id": b"\x03",
"end region": rname.tunnel_end, "end map id": 0x07, "end spawn id": 0x11, "end region": rname.tunnel_end, "end map id": b"\x07", "end spawn id": b"\x11",
"endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt", "endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt",
"save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D], "save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D],
"regions": [rname.tunnel_start, "regions": [rname.tunnel_start,
@ -95,9 +95,9 @@ stage_info = {
}, },
"Underground Waterway": { "Underground Waterway": {
"start region": rname.uw_main, "start map id": 0x08, "start spawn id": 0x00, "start region": rname.uw_main, "start map id": b"\x08", "start spawn id": b"\x00",
"mid region": rname.uw_main, "mid map id": 0x08, "mid spawn id": 0x03, "mid region": rname.uw_main, "mid map id": b"\x08", "mid spawn id": b"\x03",
"end region": rname.uw_end, "end map id": 0x08, "end spawn id": 0x01, "end region": rname.uw_end, "end map id": b"\x08", "end spawn id": b"\x01",
"endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie", "endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie",
"save number offsets": [0x104A35, 0x104A3D], "save number offsets": [0x104A35, 0x104A3D],
"regions": [rname.uw_main, "regions": [rname.uw_main,
@ -105,9 +105,9 @@ stage_info = {
}, },
"Castle Center": { "Castle Center": {
"start region": rname.cc_main, "start map id": 0x19, "start spawn id": 0x00, "start region": rname.cc_main, "start map id": b"\x19", "start spawn id": b"\x00",
"mid region": rname.cc_main, "mid map id": 0x0E, "mid spawn id": 0x03, "mid region": rname.cc_main, "mid map id": b"\x0E", "mid spawn id": b"\x03",
"end region": rname.cc_elev_top, "end map id": 0x0F, "end spawn id": 0x02, "end region": rname.cc_elev_top, "end map id": b"\x0F", "end spawn id": b"\x02",
"endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9, "endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9,
"altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1, "altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1,
"save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75], "save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75],
@ -119,20 +119,20 @@ stage_info = {
}, },
"Duel Tower": { "Duel Tower": {
"start region": rname.dt_main, "start map id": 0x13, "start spawn id": 0x00, "start region": rname.dt_main, "start map id": b"\x13", "start spawn id": b"\x00",
"startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9, "startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9,
"mid region": rname.dt_main, "mid map id": 0x13, "mid spawn id": 0x15, "mid region": rname.dt_main, "mid map id": b"\x13", "mid spawn id": b"\x15",
"end region": rname.dt_main, "end map id": 0x13, "end spawn id": 0x01, "end region": rname.dt_main, "end map id": b"\x13", "end spawn id": b"\x01",
"endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt", "endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt",
"save number offsets": [0x104ACD], "save number offsets": [0x104ACD],
"regions": [rname.dt_main] "regions": [rname.dt_main]
}, },
"Tower of Execution": { "Tower of Execution": {
"start region": rname.toe_main, "start map id": 0x10, "start spawn id": 0x00, "start region": rname.toe_main, "start map id": b"\x10", "start spawn id": b"\x00",
"startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19, "startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19,
"mid region": rname.toe_main, "mid map id": 0x10, "mid spawn id": 0x02, "mid region": rname.toe_main, "mid map id": b"\x10", "mid spawn id": b"\x02",
"end region": rname.toe_main, "end map id": 0x10, "end spawn id": 0x12, "end region": rname.toe_main, "end map id": b"\x10", "end spawn id": b"\x12",
"endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt", "endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt",
"save number offsets": [0x104A7D, 0x104A85], "save number offsets": [0x104A7D, 0x104A85],
"regions": [rname.toe_main, "regions": [rname.toe_main,
@ -140,10 +140,10 @@ stage_info = {
}, },
"Tower of Science": { "Tower of Science": {
"start region": rname.tosci_start, "start map id": 0x12, "start spawn id": 0x00, "start region": rname.tosci_start, "start map id": b"\x12", "start spawn id": b"\x00",
"startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79, "startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79,
"mid region": rname.tosci_conveyors, "mid map id": 0x12, "mid spawn id": 0x03, "mid region": rname.tosci_conveyors, "mid map id": b"\x12", "mid spawn id": b"\x03",
"end region": rname.tosci_conveyors, "end map id": 0x12, "end spawn id": 0x04, "end region": rname.tosci_conveyors, "end map id": b"\x12", "end spawn id": b"\x04",
"endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie", "endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie",
"save number offsets": [0x104A95, 0x104A9D, 0x104AA5], "save number offsets": [0x104A95, 0x104A9D, 0x104AA5],
"regions": [rname.tosci_start, "regions": [rname.tosci_start,
@ -153,28 +153,28 @@ stage_info = {
}, },
"Tower of Sorcery": { "Tower of Sorcery": {
"start region": rname.tosor_main, "start map id": 0x11, "start spawn id": 0x00, "start region": rname.tosor_main, "start map id": b"\x11", "start spawn id": b"\x00",
"startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49, "startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49,
"mid region": rname.tosor_main, "mid map id": 0x11, "mid spawn id": 0x01, "mid region": rname.tosor_main, "mid map id": b"\x11", "mid spawn id": b"\x01",
"end region": rname.tosor_main, "end map id": 0x11, "end spawn id": 0x13, "end region": rname.tosor_main, "end map id": b"\x11", "end spawn id": b"\x13",
"endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie", "endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie",
"save number offsets": [0x104A8D], "save number offsets": [0x104A8D],
"regions": [rname.tosor_main] "regions": [rname.tosor_main]
}, },
"Room of Clocks": { "Room of Clocks": {
"start region": rname.roc_main, "start map id": 0x1B, "start spawn id": 0x00, "start region": rname.roc_main, "start map id": b"\x1B", "start spawn id": b"\x00",
"mid region": rname.roc_main, "mid map id": 0x1B, "mid spawn id": 0x02, "mid region": rname.roc_main, "mid map id": b"\x1B", "mid spawn id": b"\x02",
"end region": rname.roc_main, "end map id": 0x1B, "end spawn id": 0x14, "end region": rname.roc_main, "end map id": b"\x1B", "end spawn id": b"\x14",
"endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1, "endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1,
"save number offsets": [0x104AC5], "save number offsets": [0x104AC5],
"regions": [rname.roc_main] "regions": [rname.roc_main]
}, },
"Clock Tower": { "Clock Tower": {
"start region": rname.ct_start, "start map id": 0x17, "start spawn id": 0x00, "start region": rname.ct_start, "start map id": b"\x17", "start spawn id": b"\x00",
"mid region": rname.ct_middle, "mid map id": 0x17, "mid spawn id": 0x02, "mid region": rname.ct_middle, "mid map id": b"\x17", "mid spawn id": b"\x02",
"end region": rname.ct_end, "end map id": 0x17, "end spawn id": 0x03, "end region": rname.ct_end, "end map id": b"\x17", "end spawn id": b"\x03",
"endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39, "endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39,
"save number offsets": [0x104AB5, 0x104ABD], "save number offsets": [0x104AB5, 0x104ABD],
"regions": [rname.ct_start, "regions": [rname.ct_start,
@ -183,8 +183,8 @@ stage_info = {
}, },
"Castle Keep": { "Castle Keep": {
"start region": rname.ck_main, "start map id": 0x14, "start spawn id": 0x02, "start region": rname.ck_main, "start map id": b"\x14", "start spawn id": b"\x02",
"mid region": rname.ck_main, "mid map id": 0x14, "mid spawn id": 0x03, "mid region": rname.ck_main, "mid map id": b"\x14", "mid spawn id": b"\x03",
"end region": rname.ck_drac_chamber, "end region": rname.ck_drac_chamber,
"save number offsets": [0x104AAD], "save number offsets": [0x104AAD],
"regions": [rname.ck_main] "regions": [rname.ck_main]