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 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 .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id
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, \
randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \
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
@ -27,7 +27,7 @@ class CV64Settings(settings.Group):
"""File name of the CV64 US 1.0 rom"""
copy_to = "Castlevania (USA).z64"
description = "CV64 (US 1.0) ROM File"
md5s = [CV64DeltaPatch.hash]
md5s = [CV64_US_10_HASH]
rom_file: RomFile = RomFile(RomFile.copy_to)
@ -86,12 +86,6 @@ class CV64World(World):
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:
# Generate the player's unique authentication
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,
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)
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)
patch.write(rom_path)
def get_filler_item_name(self) -> str:
return self.random.choice(filler_item_names)

View File

@ -14,111 +14,111 @@ if TYPE_CHECKING:
from . import CV64World
rom_sub_weapon_offsets = {
0x10C6EB: (0x10, rname.forest_of_silence), # Forest
0x10C6F3: (0x0F, rname.forest_of_silence),
0x10C6FB: (0x0E, rname.forest_of_silence),
0x10C703: (0x0D, rname.forest_of_silence),
0x10C6EB: (b"\x10", rname.forest_of_silence), # Forest
0x10C6F3: (b"\x0F", rname.forest_of_silence),
0x10C6FB: (b"\x0E", rname.forest_of_silence),
0x10C703: (b"\x0D", rname.forest_of_silence),
0x10C81F: (0x0F, rname.castle_wall), # Castle Wall
0x10C827: (0x10, rname.castle_wall),
0x10C82F: (0x0E, rname.castle_wall),
0x7F9A0F: (0x0D, rname.castle_wall),
0x10C81F: (b"\x0F", rname.castle_wall), # Castle Wall
0x10C827: (b"\x10", rname.castle_wall),
0x10C82F: (b"\x0E", rname.castle_wall),
0x7F9A0F: (b"\x0D", rname.castle_wall),
0x83A5D9: (0x0E, rname.villa), # Villa
0x83A5E5: (0x0D, rname.villa),
0x83A5F1: (0x0F, rname.villa),
0xBFC903: (0x10, rname.villa),
0x10C987: (0x10, rname.villa),
0x10C98F: (0x0D, rname.villa),
0x10C997: (0x0F, rname.villa),
0x10CF73: (0x10, rname.villa),
0x83A5D9: (b"\x0E", rname.villa), # Villa
0x83A5E5: (b"\x0D", rname.villa),
0x83A5F1: (b"\x0F", rname.villa),
0xBFC903: (b"\x10", rname.villa),
0x10C987: (b"\x10", rname.villa),
0x10C98F: (b"\x0D", rname.villa),
0x10C997: (b"\x0F", rname.villa),
0x10CF73: (b"\x10", rname.villa),
0x10CA57: (0x0D, rname.tunnel), # Tunnel
0x10CA5F: (0x0E, rname.tunnel),
0x10CA67: (0x10, rname.tunnel),
0x10CA6F: (0x0D, rname.tunnel),
0x10CA77: (0x0F, rname.tunnel),
0x10CA7F: (0x0E, rname.tunnel),
0x10CA57: (b"\x0D", rname.tunnel), # Tunnel
0x10CA5F: (b"\x0E", rname.tunnel),
0x10CA67: (b"\x10", rname.tunnel),
0x10CA6F: (b"\x0D", rname.tunnel),
0x10CA77: (b"\x0F", rname.tunnel),
0x10CA7F: (b"\x0E", rname.tunnel),
0x10CBC7: (0x0E, rname.castle_center), # Castle Center
0x10CC0F: (0x0D, rname.castle_center),
0x10CC5B: (0x0F, rname.castle_center),
0x10CBC7: (b"\x0E", rname.castle_center), # Castle Center
0x10CC0F: (b"\x0D", rname.castle_center),
0x10CC5B: (b"\x0F", rname.castle_center),
0x10CD3F: (0x0E, rname.tower_of_execution), # Character towers
0x10CD65: (0x0D, rname.tower_of_execution),
0x10CE2B: (0x0E, rname.tower_of_science),
0x10CE83: (0x10, rname.duel_tower),
0x10CD3F: (b"\x0E", rname.tower_of_execution), # Character towers
0x10CD65: (b"\x0D", rname.tower_of_execution),
0x10CE2B: (b"\x0E", rname.tower_of_science),
0x10CE83: (b"\x10", rname.duel_tower),
0x10CF8B: (0x0F, rname.room_of_clocks), # Room of Clocks
0x10CF93: (0x0D, rname.room_of_clocks),
0x10CF8B: (b"\x0F", rname.room_of_clocks), # Room of Clocks
0x10CF93: (b"\x0D", rname.room_of_clocks),
0x99BC5A: (0x0D, rname.clock_tower), # Clock Tower
0x10CECB: (0x10, rname.clock_tower),
0x10CED3: (0x0F, rname.clock_tower),
0x10CEDB: (0x0E, rname.clock_tower),
0x10CEE3: (0x0D, rname.clock_tower),
0x99BC5A: (b"\x0D", rname.clock_tower), # Clock Tower
0x10CECB: (b"\x10", rname.clock_tower),
0x10CED3: (b"\x0F", rname.clock_tower),
0x10CEDB: (b"\x0E", rname.clock_tower),
0x10CEE3: (b"\x0D", rname.clock_tower),
}
rom_sub_weapon_flags = {
0x10C6EC: 0x0200FF04, # Forest of Silence
0x10C6FC: 0x0400FF04,
0x10C6F4: 0x0800FF04,
0x10C704: 0x4000FF04,
0x10C6EC: b"\x02\x00\xFF\x04", # Forest of Silence
0x10C6FC: b"\x04\x00\xFF\x04",
0x10C6F4: b"\x08\x00\xFF\x04",
0x10C704: b"\x40\x00\xFF\x04",
0x10C831: 0x08, # Castle Wall
0x10C829: 0x10,
0x10C821: 0x20,
0xBFCA97: 0x04,
0x10C831: b"\x08", # Castle Wall
0x10C829: b"\x10",
0x10C821: b"\x20",
0xBFCA97: b"\x04",
# Villa
0xBFC926: 0xFF04,
0xBFC93A: 0x80,
0xBFC93F: 0x01,
0xBFC943: 0x40,
0xBFC947: 0x80,
0x10C989: 0x10,
0x10C991: 0x20,
0x10C999: 0x40,
0x10CF77: 0x80,
0xBFC926: b"\xFF\x04",
0xBFC93A: b"\x80",
0xBFC93F: b"\x01",
0xBFC943: b"\x40",
0xBFC947: b"\x80",
0x10C989: b"\x10",
0x10C991: b"\x20",
0x10C999: b"\x40",
0x10CF77: b"\x80",
0x10CA58: 0x4000FF0E, # Tunnel
0x10CA6B: 0x80,
0x10CA60: 0x1000FF05,
0x10CA70: 0x2000FF05,
0x10CA78: 0x4000FF05,
0x10CA80: 0x8000FF05,
0x10CA58: b"\x40\x00\xFF\x0E", # Tunnel
0x10CA6B: b"\x80",
0x10CA60: b"\x10\x00\xFF\x05",
0x10CA70: b"\x20\x00\xFF\x05",
0x10CA78: b"\x40\x00\xFF\x05",
0x10CA80: b"\x80\x00\xFF\x05",
0x10CBCA: 0x02, # Castle Center
0x10CC10: 0x80,
0x10CC5C: 0x40,
0x10CBCA: b"\x02", # Castle Center
0x10CC10: b"\x80",
0x10CC5C: b"\x40",
0x10CE86: 0x01, # Duel Tower
0x10CD43: 0x02, # Tower of Execution
0x10CE2E: 0x20, # Tower of Science
0x10CE86: b"\x01", # Duel Tower
0x10CD43: b"\x02", # Tower of Execution
0x10CE2E: b"\x20", # Tower of Science
0x10CF8E: 0x04, # Room of Clocks
0x10CF96: 0x08,
0x10CF8E: b"\x04", # Room of Clocks
0x10CF96: b"\x08",
0x10CECE: 0x08, # Clock Tower
0x10CED6: 0x10,
0x10CEE6: 0x20,
0x10CEDE: 0x80,
0x10CECE: b"\x08", # Clock Tower
0x10CED6: b"\x10",
0x10CEE6: b"\x20",
0x10CEDE: b"\x80",
}
rom_empty_breakables_flags = {
0x10C74D: 0x40FF05, # Forest of Silence
0x10C765: 0x20FF0E,
0x10C774: 0x0800FF0E,
0x10C755: 0x80FF05,
0x10C784: 0x0100FF0E,
0x10C73C: 0x0200FF0E,
0x10C74D: b"\x40\xFF\x05", # Forest of Silence
0x10C765: b"\x20\xFF\x0E",
0x10C774: b"\x08\x00\xFF\x0E",
0x10C755: b"\x80\xFF\x05",
0x10C784: b"\x01\x00\xFF\x0E",
0x10C73C: b"\x02\x00\xFF\x0E",
0x10C8D0: 0x0400FF0E, # Villa foyer
0x10C8D0: b"\x04\x00\xFF\x0E", # Villa foyer
0x10CF9F: 0x08, # Room of Clocks flags
0x10CFA7: 0x01,
0xBFCB6F: 0x04, # Room of Clocks candle property IDs
0xBFCB73: 0x05,
0x10CF9F: b"\x08", # Room of Clocks flags
0x10CFA7: b"\x01",
0xBFCB6F: b"\x04", # Room of Clocks candle property IDs
0xBFCB73: b"\x05",
}
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."""
randomized_lighting = {}
for entry in range(67):
for sub_entry in range(19):
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
randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = \
world.random.randint(0, 255)
randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = bytes([world.random.randint(0, 255)])
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."""
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}
@ -295,7 +294,7 @@ def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]:
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."""
music_array = bytearray(0x7A)
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]
del (music_array[0x00: 0x10])
# Convert the music array into a data dict
music_offsets = {}
for i in range(len(music_array)):
music_offsets[0xBFCD30 + i] = music_array[i]
return music_offsets
return {0xBFCD30: bytes(music_array)}
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.
The minimum price will adjust if it's higher than the max."""
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)]
# 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 = {}
for i in range(len(shop_price_list)):
if shop_price_list[i] <= 0xFF:
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]
price_dict[0x103D6C + (i * 12)] = int.to_bytes(shop_price_list[i], 4, "big")
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
increase a number.
@ -400,16 +388,11 @@ def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Locat
if countdown_number is not None:
countdown_list[countdown_number] += 1
# Convert the Countdown list into a data dict
countdown_dict = {}
for i in range(len(countdown_list)):
countdown_dict[0xBFD818 + i] = countdown_list[i]
return countdown_dict
return {0xBFD818: bytes(countdown_list)}
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
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
@ -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
# 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
world.player in world.multiworld.groups[loc.item.player]['players']):
if loc.item.player == world.player:
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")
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:
# Make the item the unused Wooden Stake - our multiworld item.
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))
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,
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.
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."""
@ -551,8 +534,8 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
# Start loading zones
# If the start zone is the start of the line, have it simply refresh the map.
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 spawn offset")] = 0x00
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = b"\xFF"
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x00"
elif active_stage_exits[stage]["prev"]:
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \
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 options.character_stages == CharacterStages.option_carrie_only or \
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
if active_stage_exits[stage]["next"]:
@ -582,16 +565,16 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
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
has to be handled appropriately."""
start_inventory_data = {0xBFD867: 0, # Jewels
0xBFD87B: 0, # PowerUps
0xBFD883: 0, # Sub-weapon
0xBFD88B: 0} # Ice Traps
start_inventory_data = {}
inventory_items_array = [0 for _ in range(35)]
total_money = 0
total_jewels = 0
total_powerups = 0
total_ice_traps = 0
items_max = 10
@ -615,42 +598,46 @@ def get_start_inventory_data(player: int, options: CV64Options, precollected_ite
inventory_items_array[inventory_offset] = 2
# Starting sub-weapon
elif sub_equip_id is not None:
start_inventory_data[0xBFD883] = sub_equip_id
start_inventory_data[0xBFD883] = bytes(sub_equip_id)
# Starting PowerUps
elif item.name == iname.powerup:
start_inventory_data[0xBFD87B] += 1
if start_inventory_data[0xBFD87B] > 2:
start_inventory_data[0xBFD87B] = 2
total_powerups += 1
# Can't have more than 2 PowerUps.
if total_powerups > 2:
total_powerups = 2
# Starting Gold
elif "GOLD" in item.name:
total_money += int(item.name[0:4])
# Money cannot be higher than 99999.
if total_money > 99999:
total_money = 99999
# Starting Jewels
elif "jewel" in item.name:
if "L" in item.name:
start_inventory_data[0xBFD867] += 10
total_jewels += 10
else:
start_inventory_data[0xBFD867] += 5
if start_inventory_data[0xBFD867] > 99:
start_inventory_data[0xBFD867] = 99
total_jewels += 5
# Jewels cannot be higher than 99.
if total_jewels > 99:
total_jewels = 99
# Starting Ice Traps
else:
start_inventory_data[0xBFD88B] += 1
if start_inventory_data[0xBFD88B] > 0xFF:
start_inventory_data[0xBFD88B] = 0xFF
total_ice_traps += 1
# Ice Traps cannot be higher than 255.
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.
for i in range(len(inventory_items_array)):
start_inventory_data[0xBFE518 + i] = inventory_items_array[i]
start_inventory_data[0xBFE518] = bytes(inventory_items_array)
# Convert the starting money into data. Which offset it starts from depends on how many bytes it takes up.
if total_money <= 0xFF:
start_inventory_data[0xBFE517] = total_money
elif total_money <= 0xFFFF:
start_inventory_data[0xBFE516] = total_money
else:
start_inventory_data[0xBFE515] = total_money
# Convert the starting money into data.
start_inventory_data[0xBFE514] = int.to_bytes(total_money, 4, "big")
return start_inventory_data

View File

@ -197,12 +197,15 @@ deathlink_nitro_edition = [
0xA168FFFD, # SB T0, 0xFFFD (T3)
]
nitro_fall_killer = [
# Custom code to force the instant fall death if at a high enough falling speed after getting killed by the Nitro
# explosion, since the game doesn't run the checks for the fall death after getting hit by said explosion and could
# result in a softlock when getting blown into an abyss.
launch_fall_killer = [
# Custom code to force the instant fall death if at a high enough falling speed after getting killed by something
# that launches you (whether it be the Nitro explosion or a Big Toss hit). The game doesn't normally run the check
# 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
0x918807E2, # LBU T0, 0x07E2 (T4)
0x24090008, # ADDIU T1, R0, 0x0008
0x11090002, # BEQ T0, T1, [forward 0x02]
0x2409000C, # ADDIU T1, R0, 0x000C
0x15090006, # BNE T0, T1, [forward 0x06]
0x3C098035, # LUI T1, 0x8035
@ -2863,3 +2866,13 @@ big_tosser = [
0xAD000814, # SW R0, 0x0814 (T0)
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
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
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):
"""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_end = 10
default = 1
@ -82,8 +83,7 @@ class Special1sPerWarp(Range):
class TotalSpecial1s(Range):
"""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."""
"""Sets how many Speical1 jewels are in the pool in total."""
range_start = 7
range_end = 70
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.
stage_info = {
"Forest of Silence": {
"start region": rname.forest_start, "start map id": 0x00, "start spawn id": 0x00,
"mid region": rname.forest_mid, "mid map id": 0x00, "mid spawn id": 0x04,
"end region": rname.forest_end, "end map id": 0x00, "end spawn id": 0x01,
"start region": rname.forest_start, "start map id": b"\x00", "start spawn id": b"\x00",
"mid region": rname.forest_mid, "mid map id": b"\x00", "mid spawn id": b"\x04",
"end region": rname.forest_end, "end map id": b"\x00", "end spawn id": b"\x01",
"endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B,
"save number offsets": [0x1049C5, 0x1049CD, 0x1049D5],
"regions": [rname.forest_start,
@ -58,9 +58,9 @@ stage_info = {
},
"Castle Wall": {
"start region": rname.cw_start, "start map id": 0x02, "start spawn id": 0x00,
"mid region": rname.cw_start, "mid map id": 0x02, "mid spawn id": 0x07,
"end region": rname.cw_exit, "end map id": 0x02, "end spawn id": 0x10,
"start region": rname.cw_start, "start map id": b"\x02", "start spawn id": b"\x00",
"mid region": rname.cw_start, "mid map id": b"\x02", "mid spawn id": b"\x07",
"end region": rname.cw_exit, "end map id": b"\x02", "end spawn id": b"\x10",
"endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61,
"save number offsets": [0x1049DD, 0x1049E5, 0x1049ED],
"regions": [rname.cw_start,
@ -69,9 +69,9 @@ stage_info = {
},
"Villa": {
"start region": rname.villa_start, "start map id": 0x03, "start spawn id": 0x00,
"mid region": rname.villa_storeroom, "mid map id": 0x05, "mid spawn id": 0x04,
"end region": rname.villa_crypt, "end map id": 0x1A, "end spawn id": 0x03,
"start region": rname.villa_start, "start map id": b"\x03", "start spawn id": b"\x00",
"mid region": rname.villa_storeroom, "mid map id": b"\x05", "mid spawn id": b"\x04",
"end region": rname.villa_crypt, "end map id": b"\x1A", "end spawn id": b"\x03",
"endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81,
"altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81,
"save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D],
@ -85,9 +85,9 @@ stage_info = {
},
"Tunnel": {
"start region": rname.tunnel_start, "start map id": 0x07, "start spawn id": 0x00,
"mid region": rname.tunnel_end, "mid map id": 0x07, "mid spawn id": 0x03,
"end region": rname.tunnel_end, "end map id": 0x07, "end spawn id": 0x11,
"start region": rname.tunnel_start, "start map id": b"\x07", "start spawn id": b"\x00",
"mid region": rname.tunnel_end, "mid map id": b"\x07", "mid spawn id": b"\x03",
"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",
"save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D],
"regions": [rname.tunnel_start,
@ -95,9 +95,9 @@ stage_info = {
},
"Underground Waterway": {
"start region": rname.uw_main, "start map id": 0x08, "start spawn id": 0x00,
"mid region": rname.uw_main, "mid map id": 0x08, "mid spawn id": 0x03,
"end region": rname.uw_end, "end map id": 0x08, "end spawn id": 0x01,
"start region": rname.uw_main, "start map id": b"\x08", "start spawn id": b"\x00",
"mid region": rname.uw_main, "mid map id": b"\x08", "mid spawn id": b"\x03",
"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",
"save number offsets": [0x104A35, 0x104A3D],
"regions": [rname.uw_main,
@ -105,9 +105,9 @@ stage_info = {
},
"Castle Center": {
"start region": rname.cc_main, "start map id": 0x19, "start spawn id": 0x00,
"mid region": rname.cc_main, "mid map id": 0x0E, "mid spawn id": 0x03,
"end region": rname.cc_elev_top, "end map id": 0x0F, "end spawn id": 0x02,
"start region": rname.cc_main, "start map id": b"\x19", "start spawn id": b"\x00",
"mid region": rname.cc_main, "mid map id": b"\x0E", "mid spawn id": b"\x03",
"end region": rname.cc_elev_top, "end map id": b"\x0F", "end spawn id": b"\x02",
"endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9,
"altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1,
"save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75],
@ -119,20 +119,20 @@ stage_info = {
},
"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,
"mid region": rname.dt_main, "mid map id": 0x13, "mid spawn id": 0x15,
"end region": rname.dt_main, "end map id": 0x13, "end spawn id": 0x01,
"mid region": rname.dt_main, "mid map id": b"\x13", "mid spawn id": b"\x15",
"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",
"save number offsets": [0x104ACD],
"regions": [rname.dt_main]
},
"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,
"mid region": rname.toe_main, "mid map id": 0x10, "mid spawn id": 0x02,
"end region": rname.toe_main, "end map id": 0x10, "end spawn id": 0x12,
"mid region": rname.toe_main, "mid map id": b"\x10", "mid spawn id": b"\x02",
"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",
"save number offsets": [0x104A7D, 0x104A85],
"regions": [rname.toe_main,
@ -140,10 +140,10 @@ stage_info = {
},
"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,
"mid region": rname.tosci_conveyors, "mid map id": 0x12, "mid spawn id": 0x03,
"end region": rname.tosci_conveyors, "end map id": 0x12, "end spawn id": 0x04,
"mid region": rname.tosci_conveyors, "mid map id": b"\x12", "mid spawn id": b"\x03",
"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",
"save number offsets": [0x104A95, 0x104A9D, 0x104AA5],
"regions": [rname.tosci_start,
@ -153,28 +153,28 @@ stage_info = {
},
"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,
"mid region": rname.tosor_main, "mid map id": 0x11, "mid spawn id": 0x01,
"end region": rname.tosor_main, "end map id": 0x11, "end spawn id": 0x13,
"mid region": rname.tosor_main, "mid map id": b"\x11", "mid spawn id": b"\x01",
"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",
"save number offsets": [0x104A8D],
"regions": [rname.tosor_main]
},
"Room of Clocks": {
"start region": rname.roc_main, "start map id": 0x1B, "start spawn id": 0x00,
"mid region": rname.roc_main, "mid map id": 0x1B, "mid spawn id": 0x02,
"end region": rname.roc_main, "end map id": 0x1B, "end spawn id": 0x14,
"start region": rname.roc_main, "start map id": b"\x1B", "start spawn id": b"\x00",
"mid region": rname.roc_main, "mid map id": b"\x1B", "mid spawn id": b"\x02",
"end region": rname.roc_main, "end map id": b"\x1B", "end spawn id": b"\x14",
"endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1,
"save number offsets": [0x104AC5],
"regions": [rname.roc_main]
},
"Clock Tower": {
"start region": rname.ct_start, "start map id": 0x17, "start spawn id": 0x00,
"mid region": rname.ct_middle, "mid map id": 0x17, "mid spawn id": 0x02,
"end region": rname.ct_end, "end map id": 0x17, "end spawn id": 0x03,
"start region": rname.ct_start, "start map id": b"\x17", "start spawn id": b"\x00",
"mid region": rname.ct_middle, "mid map id": b"\x17", "mid spawn id": b"\x02",
"end region": rname.ct_end, "end map id": b"\x17", "end spawn id": b"\x03",
"endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39,
"save number offsets": [0x104AB5, 0x104ABD],
"regions": [rname.ct_start,
@ -183,8 +183,8 @@ stage_info = {
},
"Castle Keep": {
"start region": rname.ck_main, "start map id": 0x14, "start spawn id": 0x02,
"mid region": rname.ck_main, "mid map id": 0x14, "mid spawn id": 0x03,
"start region": rname.ck_main, "start map id": b"\x14", "start spawn id": b"\x02",
"mid region": rname.ck_main, "mid map id": b"\x14", "mid spawn id": b"\x03",
"end region": rname.ck_drac_chamber,
"save number offsets": [0x104AAD],
"regions": [rname.ck_main]