diff --git a/src/open_prime_rando/echoes/asset_ids/agon_wastes.py b/src/open_prime_rando/echoes/asset_ids/agon_wastes.py index 6c919f4..7b61bfc 100644 --- a/src/open_prime_rando/echoes/asset_ids/agon_wastes.py +++ b/src/open_prime_rando/echoes/asset_ids/agon_wastes.py @@ -296,6 +296,155 @@ "Warrior's Walk": 0x1A2F3728, "Watering Hole": 0xB2FEB97B, } +# Generated by asset_id_files.py + +AGON_ENERGY_CONTROLLER_INTERNAL_ID = 0xE250F791 +AGON_MAP_STATION_INTERNAL_ID = 0x538BF04F +AGON_TEMPLE_INTERNAL_ID = 0xE13DA78B +BATTLEGROUND_INTERNAL_ID = 0xC307E36F +BIOENERGY_PRODUCTION_INTERNAL_ID = 0x7D231C5D +BIOSTORAGE_ACCESS_INTERNAL_ID = 0x4ECF9934 +BIOSTORAGE_STATION_INTERNAL_ID = 0x7C8E5C4D +BITTER_WELL_INTERNAL_ID = 0xAC9DB4AE +CENTRAL_MINING_STATION_INTERNAL_ID = 0xD16D7648 +CENTRAL_STATION_ACCESS_INTERNAL_ID = 0xABAF67E7 +COMMAND_CENTER_INTERNAL_ID = 0xAA657163 +COMMAND_CENTER_ACCESS_INTERNAL_ID = 0xF681BFC1 +CONTROLLER_ACCESS_INTERNAL_ID = 0x4B685014 +CROSSROADS_INTERNAL_ID = 0xD9FD4BCB +DARK_AGON_ENERGY_CONTROLLER_INTERNAL_ID = 0xEF855B84 +DARK_AGON_TEMPLE_INTERNAL_ID = 0x8DE34ED5 +DARK_AGON_TEMPLE_ACCESS_INTERNAL_ID = 0x8627988A +DARK_CONTROLLER_ACCESS_INTERNAL_ID = 0x350DAA91 +DARK_OASIS_INTERNAL_ID = 0xD5B16346 +DARK_TRANSIT_STATION_INTERNAL_ID = 0x99D40591 +DOOMED_ENTRY_INTERNAL_ID = 0x9244D4D5 +DOUBLE_PATH_INTERNAL_ID = 0x4EDCF903 +DUELLING_RANGE_INTERNAL_ID = 0x1BA88A01 +FEEDING_PIT_INTERNAL_ID = 0x2B9333CF +FEEDING_PIT_ACCESS_INTERNAL_ID = 0xA6333FA7 +HALL_OF_STAIRS_INTERNAL_ID = 0x5B81EF11 +ING_CACHE_1_INTERNAL_ID = 0x6845CF3A +ING_CACHE_2_INTERNAL_ID = 0x765DA1AE +ING_CACHE_3_INTERNAL_ID = 0x2C14A842 +ING_CACHE_4_INTERNAL_ID = 0x8C891383 +JUDGMENT_PIT_INTERNAL_ID = 0x2BB2B2D4 +JUNCTION_SITE_INTERNAL_ID = 0xB9C0A2BC +MAIN_ENERGY_CONTROLLER_INTERNAL_ID = 0x9C550062 +MAIN_REACTOR_INTERNAL_ID = 0x80CBA3E1 +MINE_SHAFT_INTERNAL_ID = 0xB80C0AB9 +MINING_PLAZA_INTERNAL_ID = 0xC6102F1B +MINING_STATION_A_INTERNAL_ID = 0xC3FE1203 +MINING_STATION_ACCESS_INTERNAL_ID = 0x69894388 +MINING_STATION_B_INTERNAL_ID = 0x3A9B7C8D +OASIS_ACCESS_INTERNAL_ID = 0xB36E29B5 +PHAZON_SITE_INTERNAL_ID = 0x3AC464B1 +PLAZA_ACCESS_INTERNAL_ID = 0x8CE9BD5B +PORTAL_ACCESS_INTERNAL_ID = 0x512F6418 +PORTAL_ACCESS_A_INTERNAL_ID = 0xD1C7657D +PORTAL_SITE_INTERNAL_ID = 0x3EFE8B8D +PORTAL_TERMINAL_INTERNAL_ID = 0x9ECAA95E +SAND_CACHE_INTERNAL_ID = 0x6E1F159C +SAND_PROCESSING_INTERNAL_ID = 0x7A2A2ED6 +SANDCANYON_INTERNAL_ID = 0x6B9AD531 +SAVE_STATION_1_INTERNAL_ID = 0x951030E9 +SAVE_STATION_2_INTERNAL_ID = 0x7B95483C +SAVE_STATION_3_INTERNAL_ID = 0xE92426C4 +SAVE_STATION_A_INTERNAL_ID = 0x13E14112 +SAVE_STATION_C_INTERNAL_ID = 0xDE113070 +SECURITY_STATION_A_INTERNAL_ID = 0x5D6CF46A +SECURITY_STATION_B_INTERNAL_ID = 0xC2640823 +STORAGE_A_INTERNAL_ID = 0x484C1B94 +STORAGE_B_INTERNAL_ID = 0x3694CC04 +STORAGE_C_INTERNAL_ID = 0x1CDC8174 +STORAGE_D_INTERNAL_ID = 0xCB256324 +TEMPLE_ACCESS_INTERNAL_ID = 0x2704F6F0 +TRANSIT_STATION_INTERNAL_ID = 0x00422C4C +TRANSPORT_CENTER_INTERNAL_ID = 0xD3D4F3C4 +TRANSPORT_TO_SANCTUARY_FORTRESS_INTERNAL_ID = 0xE6B9F017 +TRANSPORT_TO_TEMPLE_GROUNDS_INTERNAL_ID = 0x0B3919B6 +TRANSPORT_TO_TORVUS_BOG_INTERNAL_ID = 0x15B1250D +TRIAL_GROUNDS_INTERNAL_ID = 0x47B6E9D0 +TRIAL_TUNNEL_INTERNAL_ID = 0x713BC335 +VENTILATION_AREA_A_INTERNAL_ID = 0x9F4AD005 +VENTILATION_AREA_B_INTERNAL_ID = 0xE522D29F +WARRIORS_WALK_INTERNAL_ID = 0x937A8E98 +WATERING_HOLE_INTERNAL_ID = 0x4472720A + +NAME_TO_ID_INTERNAL_ID = { + "Agon Energy Controller": 0xE250F791, + "Agon Map Station": 0x538BF04F, + "Agon Temple": 0xE13DA78B, + "Battleground": 0xC307E36F, + "Bioenergy Production": 0x7D231C5D, + "Biostorage Access": 0x4ECF9934, + "Biostorage Station": 0x7C8E5C4D, + "Bitter Well": 0xAC9DB4AE, + "Central Mining Station": 0xD16D7648, + "Central Station Access": 0xABAF67E7, + "Command Center": 0xAA657163, + "Command Center Access": 0xF681BFC1, + "Controller Access": 0x4B685014, + "Crossroads": 0xD9FD4BCB, + "Dark Agon Energy Controller": 0xEF855B84, + "Dark Agon Temple": 0x8DE34ED5, + "Dark Agon Temple Access": 0x8627988A, + "Dark Controller Access": 0x350DAA91, + "Dark Oasis": 0xD5B16346, + "Dark Transit Station": 0x99D40591, + "Doomed Entry": 0x9244D4D5, + "Double Path": 0x4EDCF903, + "Duelling Range": 0x1BA88A01, + "Feeding Pit": 0x2B9333CF, + "Feeding Pit Access": 0xA6333FA7, + "Hall of Stairs": 0x5B81EF11, + "Ing Cache 1": 0x6845CF3A, + "Ing Cache 2": 0x765DA1AE, + "Ing Cache 3": 0x2C14A842, + "Ing Cache 4": 0x8C891383, + "Judgment Pit": 0x2BB2B2D4, + "Junction Site": 0xB9C0A2BC, + "Main Energy Controller": 0x9C550062, + "Main Reactor": 0x80CBA3E1, + "Mine Shaft": 0xB80C0AB9, + "Mining Plaza": 0xC6102F1B, + "Mining Station A": 0xC3FE1203, + "Mining Station Access": 0x69894388, + "Mining Station B": 0x3A9B7C8D, + "Oasis Access": 0xB36E29B5, + "Phazon Site": 0x3AC464B1, + "Plaza Access": 0x8CE9BD5B, + "Portal Access": 0x512F6418, + "Portal Access A": 0xD1C7657D, + "Portal Site": 0x3EFE8B8D, + "Portal Terminal": 0x9ECAA95E, + "Sand Cache": 0x6E1F159C, + "Sand Processing": 0x7A2A2ED6, + "Sandcanyon": 0x6B9AD531, + "Save Station 1": 0x951030E9, + "Save Station 2": 0x7B95483C, + "Save Station 3": 0xE92426C4, + "Save Station A": 0x13E14112, + "Save Station C": 0xDE113070, + "Security Station A": 0x5D6CF46A, + "Security Station B": 0xC2640823, + "Storage A": 0x484C1B94, + "Storage B": 0x3694CC04, + "Storage C": 0x1CDC8174, + "Storage D": 0xCB256324, + "Temple Access": 0x2704F6F0, + "Transit Station": 0x00422C4C, + "Transport Center": 0xD3D4F3C4, + "Transport to Sanctuary Fortress": 0xE6B9F017, + "Transport to Temple Grounds": 0x0B3919B6, + "Transport to Torvus Bog": 0x15B1250D, + "Trial Grounds": 0x47B6E9D0, + "Trial Tunnel": 0x713BC335, + "Ventilation Area A": 0x9F4AD005, + "Ventilation Area B": 0xE522D29F, + "Warrior's Walk": 0x937A8E98, + "Watering Hole": 0x4472720A, +} DOCK_NAMES = { "Agon Energy Controller": { diff --git a/src/open_prime_rando/echoes/asset_ids/great_temple.py b/src/open_prime_rando/echoes/asset_ids/great_temple.py index 459817d..a613c5a 100644 --- a/src/open_prime_rando/echoes/asset_ids/great_temple.py +++ b/src/open_prime_rando/echoes/asset_ids/great_temple.py @@ -56,6 +56,35 @@ "Transport B Access": 0xD762461A, "Transport C Access": 0x339827FD, } +# Generated by asset_id_files.py + +CONTROLLER_TRANSPORT_INTERNAL_ID = 0xFF8C5AD6 +MAIN_ENERGY_CONTROLLER_INTERNAL_ID = 0x7E6249F4 +SANCTUM_INTERNAL_ID = 0x1E265BC2 +SANCTUM_ACCESS_INTERNAL_ID = 0x05484014 +SKY_TEMPLE_ENERGY_CONTROLLER_INTERNAL_ID = 0x8D1B96EB +TEMPLE_SANCTUARY_INTERNAL_ID = 0xDC8B67D3 +TEMPLE_TRANSPORT_A_INTERNAL_ID = 0x6B65085B +TEMPLE_TRANSPORT_B_INTERNAL_ID = 0x49BFB670 +TEMPLE_TRANSPORT_C_INTERNAL_ID = 0x6546B2B9 +TRANSPORT_A_ACCESS_INTERNAL_ID = 0x628F4FC2 +TRANSPORT_B_ACCESS_INTERNAL_ID = 0x89F0943F +TRANSPORT_C_ACCESS_INTERNAL_ID = 0xFD0E6733 + +NAME_TO_ID_INTERNAL_ID = { + "Controller Transport": 0xFF8C5AD6, + "Main Energy Controller": 0x7E6249F4, + "Sanctum": 0x1E265BC2, + "Sanctum Access": 0x05484014, + "Sky Temple Energy Controller": 0x8D1B96EB, + "Temple Sanctuary": 0xDC8B67D3, + "Temple Transport A": 0x6B65085B, + "Temple Transport B": 0x49BFB670, + "Temple Transport C": 0x6546B2B9, + "Transport A Access": 0x628F4FC2, + "Transport B Access": 0x89F0943F, + "Transport C Access": 0xFD0E6733, +} DOCK_NAMES = { "Controller Transport": { diff --git a/src/open_prime_rando/echoes/asset_ids/sanctuary_fortress.py b/src/open_prime_rando/echoes/asset_ids/sanctuary_fortress.py index b945318..9177f71 100644 --- a/src/open_prime_rando/echoes/asset_ids/sanctuary_fortress.py +++ b/src/open_prime_rando/echoes/asset_ids/sanctuary_fortress.py @@ -276,6 +276,145 @@ "Watch Station Access": 0x79A302F6, "Workers Path": 0x2A395972, } +# Generated by asset_id_files.py + +AERIAL_TRAINING_SITE_INTERNAL_ID = 0x66B72B0E +AERIE_INTERNAL_ID = 0xE380B5A7 +AERIE_ACCESS_INTERNAL_ID = 0xADBAE729 +AERIE_TRANSPORT_STATION_INTERNAL_ID = 0xDCCFF2C6 +AGON_TRANSPORT_ACCESS_INTERNAL_ID = 0x9B7F9FE8 +CENTRAL_AREA_TRANSPORT_EAST_INTERNAL_ID = 0x28065DD3 +CENTRAL_AREA_TRANSPORT_WEST_INTERNAL_ID = 0xF16BAE04 +CENTRAL_HIVE_EAST_TRANSPORT_INTERNAL_ID = 0x3D646F91 +CENTRAL_HIVE_WEST_TRANSPORT_INTERNAL_ID = 0x527BF6C1 +CHECKPOINT_STATION_INTERNAL_ID = 0x46D11C3A +CONTROLLER_ACCESS_INTERNAL_ID = 0x188B5EAB +CULLING_CHAMBER_INTERNAL_ID = 0x3A73B33D +DYNAMO_ACCESS_INTERNAL_ID = 0x456FCC39 +DYNAMO_STORAGE_INTERNAL_ID = 0x6390C1F6 +DYNAMO_WORKS_INTERNAL_ID = 0xFB3385B5 +ENTRANCE_DEFENSE_HALL_INTERNAL_ID = 0xAEBE1A9E +GRAND_ABYSS_INTERNAL_ID = 0x9C023FEE +HALL_OF_COMBAT_MASTERY_INTERNAL_ID = 0x66D7D514 +HAZING_CLIFF_INTERNAL_ID = 0x401F39E7 +HIVE_AMMO_STATION_INTERNAL_ID = 0x5125F5DA +HIVE_CACHE_1_INTERNAL_ID = 0x5A61C7C3 +HIVE_CACHE_3_INTERNAL_ID = 0x33713618 +HIVE_CONTROLLER_ACCESS_INTERNAL_ID = 0x2B60A33B +HIVE_DYNAMO_ACCESS_INTERNAL_ID = 0x6CC65DFA +HIVE_DYNAMO_WORKS_INTERNAL_ID = 0x6F096BED +HIVE_ENERGY_CONTROLLER_INTERNAL_ID = 0xA9FB8A2B +HIVE_ENTRANCE_INTERNAL_ID = 0x59145ED7 +HIVE_GYRO_ACCESS_INTERNAL_ID = 0xA09DA07C +HIVE_GYRO_CHAMBER_INTERNAL_ID = 0x125001F3 +HIVE_PORTAL_CHAMBER_INTERNAL_ID = 0x62E5E718 +HIVE_REACTOR_INTERNAL_ID = 0x504DE08E +HIVE_REACTOR_ACCESS_INTERNAL_ID = 0x9E200B47 +HIVE_SAVE_STATION_1_INTERNAL_ID = 0x8F510E80 +HIVE_SAVE_STATION_2_INTERNAL_ID = 0x774E8CF0 +HIVE_SUMMIT_INTERNAL_ID = 0x36DE43EA +HIVE_TEMPLE_INTERNAL_ID = 0x57FF5720 +HIVE_TEMPLE_ACCESS_INTERNAL_ID = 0x5C584C23 +JUDGMENT_DROP_INTERNAL_ID = 0x03D9C4AA +MAIN_ENERGY_CONTROLLER_INTERNAL_ID = 0xD4B94644 +MAIN_GYRO_CHAMBER_INTERNAL_ID = 0xED4F0ADD +MAIN_RESEARCH_INTERNAL_ID = 0x57E7BDCA +MINIGYRO_CHAMBER_INTERNAL_ID = 0xBA0E2F4A +POWER_JUNCTION_INTERNAL_ID = 0x60DD0C9E +REACTOR_ACCESS_INTERNAL_ID = 0xF2D57E07 +REACTOR_CORE_INTERNAL_ID = 0xB54ABB81 +SANCTUARY_ENERGY_CONTROLLER_INTERNAL_ID = 0x3A186834 +SANCTUARY_ENTRANCE_INTERNAL_ID = 0xFF6D683B +SANCTUARY_MAP_STATION_INTERNAL_ID = 0xF4C6417F +SANCTUARY_TEMPLE_INTERNAL_ID = 0x64177921 +SAVE_STATION_A_INTERNAL_ID = 0xA4028DAD +SAVE_STATION_B_INTERNAL_ID = 0x98BC759E +SENTINELS_PATH_INTERNAL_ID = 0x0E0A4D77 +STAGING_AREA_INTERNAL_ID = 0x0073DF3D +TEMPLE_ACCESS_INTERNAL_ID = 0xD767BEA0 +TEMPLE_SECURITY_ACCESS_INTERNAL_ID = 0x3347D573 +TEMPLE_TRANSPORT_ACCESS_INTERNAL_ID = 0x0DB49D74 +TORVUS_TRANSPORT_ACCESS_INTERNAL_ID = 0x0977ED71 +TRANSIT_STATION_INTERNAL_ID = 0x6363DC9D +TRANSPORT_TO_AGON_WASTES_INTERNAL_ID = 0x0172721D +TRANSPORT_TO_TEMPLE_GROUNDS_INTERNAL_ID = 0x74EC5EB4 +TRANSPORT_TO_TORVUS_BOG_INTERNAL_ID = 0xE7CD5281 +UNSEEN_WAY_INTERNAL_ID = 0x0DFA7E48 +VAULT_INTERNAL_ID = 0x95FFACC2 +VAULT_ATTACK_PORTAL_INTERNAL_ID = 0x402BFBA9 +WATCH_STATION_INTERNAL_ID = 0xCA9B1A72 +WATCH_STATION_ACCESS_INTERNAL_ID = 0xB9B0FF49 +WORKERS_PATH_INTERNAL_ID = 0xD4D96EA3 + +NAME_TO_ID_INTERNAL_ID = { + "Aerial Training Site": 0x66B72B0E, + "Aerie": 0xE380B5A7, + "Aerie Access": 0xADBAE729, + "Aerie Transport Station": 0xDCCFF2C6, + "Agon Transport Access": 0x9B7F9FE8, + "Central Area Transport East": 0x28065DD3, + "Central Area Transport West": 0xF16BAE04, + "Central Hive East Transport": 0x3D646F91, + "Central Hive West Transport": 0x527BF6C1, + "Checkpoint Station": 0x46D11C3A, + "Controller Access": 0x188B5EAB, + "Culling Chamber": 0x3A73B33D, + "Dynamo Access": 0x456FCC39, + "Dynamo Storage": 0x6390C1F6, + "Dynamo Works": 0xFB3385B5, + "Entrance Defense Hall": 0xAEBE1A9E, + "Grand Abyss": 0x9C023FEE, + "Hall of Combat Mastery": 0x66D7D514, + "Hazing Cliff": 0x401F39E7, + "Hive Ammo Station": 0x5125F5DA, + "Hive Cache 1": 0x5A61C7C3, + "Hive Cache 3": 0x33713618, + "Hive Controller Access": 0x2B60A33B, + "Hive Dynamo Access": 0x6CC65DFA, + "Hive Dynamo Works": 0x6F096BED, + "Hive Energy Controller": 0xA9FB8A2B, + "Hive Entrance": 0x59145ED7, + "Hive Gyro Access": 0xA09DA07C, + "Hive Gyro Chamber": 0x125001F3, + "Hive Portal Chamber": 0x62E5E718, + "Hive Reactor": 0x504DE08E, + "Hive Reactor Access": 0x9E200B47, + "Hive Save Station 1": 0x8F510E80, + "Hive Save Station 2": 0x774E8CF0, + "Hive Summit": 0x36DE43EA, + "Hive Temple": 0x57FF5720, + "Hive Temple Access": 0x5C584C23, + "Judgment Drop": 0x03D9C4AA, + "Main Energy Controller": 0xD4B94644, + "Main Gyro Chamber": 0xED4F0ADD, + "Main Research": 0x57E7BDCA, + "Minigyro Chamber": 0xBA0E2F4A, + "Power Junction": 0x60DD0C9E, + "Reactor Access": 0xF2D57E07, + "Reactor Core": 0xB54ABB81, + "Sanctuary Energy Controller": 0x3A186834, + "Sanctuary Entrance": 0xFF6D683B, + "Sanctuary Map Station": 0xF4C6417F, + "Sanctuary Temple": 0x64177921, + "Save Station A": 0xA4028DAD, + "Save Station B": 0x98BC759E, + "Sentinel's Path": 0x0E0A4D77, + "Staging Area": 0x0073DF3D, + "Temple Access": 0xD767BEA0, + "Temple Security Access": 0x3347D573, + "Temple Transport Access": 0x0DB49D74, + "Torvus Transport Access": 0x0977ED71, + "Transit Station": 0x6363DC9D, + "Transport to Agon Wastes": 0x0172721D, + "Transport to Temple Grounds": 0x74EC5EB4, + "Transport to Torvus Bog": 0xE7CD5281, + "Unseen Way": 0x0DFA7E48, + "Vault": 0x95FFACC2, + "Vault Attack Portal": 0x402BFBA9, + "Watch Station": 0xCA9B1A72, + "Watch Station Access": 0xB9B0FF49, + "Workers Path": 0xD4D96EA3, +} DOCK_NAMES = { "Aerial Training Site": { diff --git a/src/open_prime_rando/echoes/asset_ids/temple_grounds.py b/src/open_prime_rando/echoes/asset_ids/temple_grounds.py index 621bed3..fae09e4 100644 --- a/src/open_prime_rando/echoes/asset_ids/temple_grounds.py +++ b/src/open_prime_rando/echoes/asset_ids/temple_grounds.py @@ -260,6 +260,137 @@ "game_end_part4": 0x67C0B23F, "game_end_part5": 0xAC9C619A, } +# Generated by asset_id_files.py + +SCANDUMMY_INTERNAL_ID = 0x775664A5 +ABANDONED_BASE_INTERNAL_ID = 0x89D924A7 +ACCURSED_LAKE_INTERNAL_ID = 0x012F9639 +AGON_TRANSPORT_ACCESS_INTERNAL_ID = 0x650909ED +BASE_ACCESS_INTERNAL_ID = 0x07CE0C72 +COLLAPSED_TUNNEL_INTERNAL_ID = 0x1671BCCE +COMMAND_CHAMBER_INTERNAL_ID = 0xCFB06832 +COMMUNICATION_AREA_INTERNAL_ID = 0xDCEC348B +DEFILED_SHRINE_INTERNAL_ID = 0xB23C2110 +DYNAMO_CHAMBER_INTERNAL_ID = 0x600D7227 +FORTRESS_TRANSPORT_ACCESS_INTERNAL_ID = 0xF080D688 +GFMC_COMPOUND_INTERNAL_ID = 0xEAA44A69 +GATEWAY_ACCESS_INTERNAL_ID = 0xBA85FBF7 +GRAND_WINDCHAMBER_INTERNAL_ID = 0xDD711FE2 +HALL_OF_EYES_INTERNAL_ID = 0x11F7FAE1 +HALL_OF_HONORED_DEAD_INTERNAL_ID = 0x1DA26BFF +HIVE_ACCESS_TUNNEL_INTERNAL_ID = 0x8CF4EFF5 +HIVE_CHAMBER_A_INTERNAL_ID = 0xB168BFA2 +HIVE_CHAMBER_B_INTERNAL_ID = 0xE5F82542 +HIVE_CHAMBER_C_INTERNAL_ID = 0x3AE18F4C +HIVE_SAVE_STATION_INTERNAL_ID = 0x298A46B7 +HIVE_STORAGE_INTERNAL_ID = 0xBB04ED2C +HIVE_TRANSPORT_AREA_INTERNAL_ID = 0x847E2584 +HIVE_TUNNEL_INTERNAL_ID = 0xBA47CB1F +INDUSTRIAL_SITE_INTERNAL_ID = 0xFBF7026A +ING_RELIQUARY_INTERNAL_ID = 0x4A3FAEBF +ING_WINDCHAMBER_INTERNAL_ID = 0x8FDF7C49 +LAKE_ACCESS_INTERNAL_ID = 0x02C294BE +LANDING_SITE_INTERNAL_ID = 0x469F86A0 +MEETING_GROUNDS_INTERNAL_ID = 0x263B762F +PATH_OF_EYES_INTERNAL_ID = 0xA1E4608C +PATH_OF_HONOR_INTERNAL_ID = 0xF202EB6D +PHAZON_GROUNDS_INTERNAL_ID = 0x7229A05E +PHAZON_PIT_INTERNAL_ID = 0xB65538F6 +PLAIN_OF_DARK_WORSHIP_INTERNAL_ID = 0xF7602CF0 +PORTAL_SITE_INTERNAL_ID = 0x140FDC57 +PROFANE_PATH_INTERNAL_ID = 0xE90B6B28 +RELIQUARY_ACCESS_INTERNAL_ID = 0x0044D8D8 +RELIQUARY_GROUNDS_INTERNAL_ID = 0x4136D070 +SACRED_BRIDGE_INTERNAL_ID = 0x8E76D210 +SACRED_PATH_INTERNAL_ID = 0xC328BC32 +SERVICE_ACCESS_INTERNAL_ID = 0x86FC1861 +SHRINE_ACCESS_INTERNAL_ID = 0xB94091A2 +SKY_TEMPLE_GATEWAY_INTERNAL_ID = 0x16828ED5 +STORAGE_CAVERN_A_INTERNAL_ID = 0xB09B5078 +STORAGE_CAVERN_B_INTERNAL_ID = 0xAF258380 +TEMPLE_ASSEMBLY_SITE_INTERNAL_ID = 0x163904FE +TEMPLE_TRANSPORT_A_INTERNAL_ID = 0x14F3812B +TEMPLE_TRANSPORT_B_INTERNAL_ID = 0x1375C704 +TEMPLE_TRANSPORT_C_INTERNAL_ID = 0x8B72A9DA +TORVUS_TRANSPORT_ACCESS_INTERNAL_ID = 0x678B3408 +TRANSPORT_TO_AGON_WASTES_INTERNAL_ID = 0xFA35B470 +TRANSPORT_TO_SANCTUARY_FORTRESS_INTERNAL_ID = 0x328E85F6 +TRANSPORT_TO_TORVUS_BOG_INTERNAL_ID = 0x287DEF75 +TROOPER_SECURITY_STATION_INTERNAL_ID = 0xF80A1CF9 +WAR_RITUAL_GROUNDS_INTERNAL_ID = 0x4767E520 +WINDCHAMBER_GATEWAY_INTERNAL_ID = 0x1C794B5A +WINDCHAMBER_TUNNEL_INTERNAL_ID = 0x6A0585B3 +GAME_END_PART1_INTERNAL_ID = 0x752B2850 +GAME_END_PART2_INTERNAL_ID = 0x9D221F4A +GAME_END_PART3_INTERNAL_ID = 0xC5250DBC +GAME_END_PART4_INTERNAL_ID = 0x9641773F +GAME_END_PART5_INTERNAL_ID = 0xCE4665C9 + +NAME_TO_ID_INTERNAL_ID = { + "00_scandummy": 0x775664A5, + "Abandoned Base": 0x89D924A7, + "Accursed Lake": 0x012F9639, + "Agon Transport Access": 0x650909ED, + "Base Access": 0x07CE0C72, + "Collapsed Tunnel": 0x1671BCCE, + "Command Chamber": 0xCFB06832, + "Communication Area": 0xDCEC348B, + "Defiled Shrine": 0xB23C2110, + "Dynamo Chamber": 0x600D7227, + "Fortress Transport Access": 0xF080D688, + "GFMC Compound": 0xEAA44A69, + "Gateway Access": 0xBA85FBF7, + "Grand Windchamber": 0xDD711FE2, + "Hall of Eyes": 0x11F7FAE1, + "Hall of Honored Dead": 0x1DA26BFF, + "Hive Access Tunnel": 0x8CF4EFF5, + "Hive Chamber A": 0xB168BFA2, + "Hive Chamber B": 0xE5F82542, + "Hive Chamber C": 0x3AE18F4C, + "Hive Save Station": 0x298A46B7, + "Hive Storage": 0xBB04ED2C, + "Hive Transport Area": 0x847E2584, + "Hive Tunnel": 0xBA47CB1F, + "Industrial Site": 0xFBF7026A, + "Ing Reliquary": 0x4A3FAEBF, + "Ing Windchamber": 0x8FDF7C49, + "Lake Access": 0x02C294BE, + "Landing Site": 0x469F86A0, + "Meeting Grounds": 0x263B762F, + "Path of Eyes": 0xA1E4608C, + "Path of Honor": 0xF202EB6D, + "Phazon Grounds": 0x7229A05E, + "Phazon Pit": 0xB65538F6, + "Plain of Dark Worship": 0xF7602CF0, + "Portal Site": 0x140FDC57, + "Profane Path": 0xE90B6B28, + "Reliquary Access": 0x0044D8D8, + "Reliquary Grounds": 0x4136D070, + "Sacred Bridge": 0x8E76D210, + "Sacred Path": 0xC328BC32, + "Service Access": 0x86FC1861, + "Shrine Access": 0xB94091A2, + "Sky Temple Gateway": 0x16828ED5, + "Storage Cavern A": 0xB09B5078, + "Storage Cavern B": 0xAF258380, + "Temple Assembly Site": 0x163904FE, + "Temple Transport A": 0x14F3812B, + "Temple Transport B": 0x1375C704, + "Temple Transport C": 0x8B72A9DA, + "Torvus Transport Access": 0x678B3408, + "Transport to Agon Wastes": 0xFA35B470, + "Transport to Sanctuary Fortress": 0x328E85F6, + "Transport to Torvus Bog": 0x287DEF75, + "Trooper Security Station": 0xF80A1CF9, + "War Ritual Grounds": 0x4767E520, + "Windchamber Gateway": 0x1C794B5A, + "Windchamber Tunnel": 0x6A0585B3, + "game_end_part1": 0x752B2850, + "game_end_part2": 0x9D221F4A, + "game_end_part3": 0xC5250DBC, + "game_end_part4": 0x9641773F, + "game_end_part5": 0xCE4665C9, +} DOCK_NAMES = { "00_scandummy": { diff --git a/src/open_prime_rando/echoes/asset_ids/torvus_bog.py b/src/open_prime_rando/echoes/asset_ids/torvus_bog.py index 82d0bdb..be6eb82 100644 --- a/src/open_prime_rando/echoes/asset_ids/torvus_bog.py +++ b/src/open_prime_rando/echoes/asset_ids/torvus_bog.py @@ -288,6 +288,151 @@ "Undertransit Two": 0x62295D04, "Venomous Pond": 0xAC5B17EE, } +# Generated by asset_id_files.py + +ABANDONED_WORKSITE_INTERNAL_ID = 0x8F9D66C0 +AMMO_STATION_INTERNAL_ID = 0xA0E73B00 +BROODING_GROUND_INTERNAL_ID = 0x3C156DDF +CACHE_A_INTERNAL_ID = 0x97B812A2 +CACHE_B_INTERNAL_ID = 0xF04E9059 +CATACOMBS_INTERNAL_ID = 0xAD9D5D9F +CATACOMBS_ACCESS_INTERNAL_ID = 0xC4F8E78E +CONTROLLER_ACCESS_INTERNAL_ID = 0x94182918 +CRYPT_INTERNAL_ID = 0x4280A031 +CRYPT_TUNNEL_INTERNAL_ID = 0xA29D33C8 +DARK_ARENA_TUNNEL_INTERNAL_ID = 0xA1ECA232 +DARK_CONTROLLER_ACCESS_INTERNAL_ID = 0x65B4E891 +DARK_FALLS_INTERNAL_ID = 0x9172B3EB +DARK_FORGOTTEN_BRIDGE_INTERNAL_ID = 0xFF42758B +DARK_TORVUS_ARENA_INTERNAL_ID = 0xF5F50ABC +DARK_TORVUS_ENERGY_CONTROLLER_INTERNAL_ID = 0x8E2AF917 +DARK_TORVUS_TEMPLE_INTERNAL_ID = 0x915E47CE +DARK_TORVUS_TEMPLE_ACCESS_INTERNAL_ID = 0x6DB75FB4 +DUNGEON_INTERNAL_ID = 0xFE8931FC +FORGOTTEN_BRIDGE_INTERNAL_ID = 0x2406DD54 +FORTRESS_TRANSPORT_ACCESS_INTERNAL_ID = 0x518D3511 +GATHERING_ACCESS_INTERNAL_ID = 0x3AD40963 +GATHERING_HALL_INTERNAL_ID = 0xF9F219B0 +GLOOM_VISTA_INTERNAL_ID = 0x279818B4 +GREAT_BRIDGE_INTERNAL_ID = 0x069D4087 +GROVE_ACCESS_INTERNAL_ID = 0x734255B0 +HYDROCHAMBER_STORAGE_INTERNAL_ID = 0x0112E539 +HYDRODYNAMO_SHAFT_INTERNAL_ID = 0x3CE4A4FB +HYDRODYNAMO_STATION_INTERNAL_ID = 0x2A621228 +MAIN_ENERGY_CONTROLLER_INTERNAL_ID = 0xDE01532E +MAIN_HYDROCHAMBER_INTERNAL_ID = 0x50039A19 +MEDITATION_VISTA_INTERNAL_ID = 0xA701265D +PATH_OF_ROOTS_INTERNAL_ID = 0xC746378D +PLAZA_ACCESS_INTERNAL_ID = 0xE14A2729 +POISONED_BOG_INTERNAL_ID = 0xA7DA4FD1 +POLLUTED_MIRE_INTERNAL_ID = 0x8D13B482 +PORTAL_CHAMBER_DARK_INTERNAL_ID = 0x9F510909 +PORTAL_CHAMBER_LIGHT_INTERNAL_ID = 0x554E4514 +PUTRID_ALCOVE_INTERNAL_ID = 0x0C8B7C06 +RUINED_ALCOVE_INTERNAL_ID = 0x1D951459 +SACRIFICIAL_CHAMBER_INTERNAL_ID = 0xC1AE6ECF +SACRIFICIAL_CHAMBER_TUNNEL_INTERNAL_ID = 0x7FF3F046 +SAVE_STATION_1_INTERNAL_ID = 0x59E57214 +SAVE_STATION_2_INTERNAL_ID = 0x0D580FBE +SAVE_STATION_A_INTERNAL_ID = 0x964465B0 +SAVE_STATION_B_INTERNAL_ID = 0xA74BE553 +TEMPLE_ACCESS_INTERNAL_ID = 0xE2F4F72A +TEMPLE_TRANSPORT_ACCESS_INTERNAL_ID = 0xAA2FA667 +TORVUS_ENERGY_CONTROLLER_INTERNAL_ID = 0x3B696A7A +TORVUS_GROVE_INTERNAL_ID = 0x72349FA8 +TORVUS_LAGOON_INTERNAL_ID = 0x0678EB72 +TORVUS_MAP_STATION_INTERNAL_ID = 0xC1EC09C2 +TORVUS_PLAZA_INTERNAL_ID = 0x1127C9E6 +TORVUS_TEMPLE_INTERNAL_ID = 0x9A2ACAFD +TRAINING_ACCESS_INTERNAL_ID = 0x92BE0B06 +TRAINING_CHAMBER_INTERNAL_ID = 0x1495E65B +TRANSIT_TUNNEL_EAST_INTERNAL_ID = 0x3B9904FD +TRANSIT_TUNNEL_SOUTH_INTERNAL_ID = 0xAEECD662 +TRANSIT_TUNNEL_WEST_INTERNAL_ID = 0xA9917664 +TRANSPORT_TO_AGON_WASTES_INTERNAL_ID = 0xA1197E74 +TRANSPORT_TO_SANCTUARY_FORTRESS_INTERNAL_ID = 0x1C4BDD72 +TRANSPORT_TO_TEMPLE_GROUNDS_INTERNAL_ID = 0x8D385440 +UNDERGROUND_TRANSPORT_INTERNAL_ID = 0x8C23B6C3 +UNDERGROUND_TUNNEL_INTERNAL_ID = 0x70FC85B3 +UNDERTEMPLE_INTERNAL_ID = 0x22690759 +UNDERTEMPLE_ACCESS_INTERNAL_ID = 0x416E3BA9 +UNDERTEMPLE_SHAFT_INTERNAL_ID = 0x4DC6C3E3 +UNDERTRANSIT_ONE_INTERNAL_ID = 0x3236D73D +UNDERTRANSIT_TWO_INTERNAL_ID = 0x02A8C6E4 +VENOMOUS_POND_INTERNAL_ID = 0xAF3C03B1 + +NAME_TO_ID_INTERNAL_ID = { + "Abandoned Worksite": 0x8F9D66C0, + "Ammo Station": 0xA0E73B00, + "Brooding Ground": 0x3C156DDF, + "Cache A": 0x97B812A2, + "Cache B": 0xF04E9059, + "Catacombs": 0xAD9D5D9F, + "Catacombs Access": 0xC4F8E78E, + "Controller Access": 0x94182918, + "Crypt": 0x4280A031, + "Crypt Tunnel": 0xA29D33C8, + "Dark Arena Tunnel": 0xA1ECA232, + "Dark Controller Access": 0x65B4E891, + "Dark Falls": 0x9172B3EB, + "Dark Forgotten Bridge": 0xFF42758B, + "Dark Torvus Arena": 0xF5F50ABC, + "Dark Torvus Energy Controller": 0x8E2AF917, + "Dark Torvus Temple": 0x915E47CE, + "Dark Torvus Temple Access": 0x6DB75FB4, + "Dungeon": 0xFE8931FC, + "Forgotten Bridge": 0x2406DD54, + "Fortress Transport Access": 0x518D3511, + "Gathering Access": 0x3AD40963, + "Gathering Hall": 0xF9F219B0, + "Gloom Vista": 0x279818B4, + "Great Bridge": 0x069D4087, + "Grove Access": 0x734255B0, + "Hydrochamber Storage": 0x0112E539, + "Hydrodynamo Shaft": 0x3CE4A4FB, + "Hydrodynamo Station": 0x2A621228, + "Main Energy Controller": 0xDE01532E, + "Main Hydrochamber": 0x50039A19, + "Meditation Vista": 0xA701265D, + "Path of Roots": 0xC746378D, + "Plaza Access": 0xE14A2729, + "Poisoned Bog": 0xA7DA4FD1, + "Polluted Mire": 0x8D13B482, + "Portal Chamber (Dark)": 0x9F510909, + "Portal Chamber (Light)": 0x554E4514, + "Putrid Alcove": 0x0C8B7C06, + "Ruined Alcove": 0x1D951459, + "Sacrificial Chamber": 0xC1AE6ECF, + "Sacrificial Chamber Tunnel": 0x7FF3F046, + "Save Station 1": 0x59E57214, + "Save Station 2": 0x0D580FBE, + "Save Station A": 0x964465B0, + "Save Station B": 0xA74BE553, + "Temple Access": 0xE2F4F72A, + "Temple Transport Access": 0xAA2FA667, + "Torvus Energy Controller": 0x3B696A7A, + "Torvus Grove": 0x72349FA8, + "Torvus Lagoon": 0x0678EB72, + "Torvus Map Station": 0xC1EC09C2, + "Torvus Plaza": 0x1127C9E6, + "Torvus Temple": 0x9A2ACAFD, + "Training Access": 0x92BE0B06, + "Training Chamber": 0x1495E65B, + "Transit Tunnel East": 0x3B9904FD, + "Transit Tunnel South": 0xAEECD662, + "Transit Tunnel West": 0xA9917664, + "Transport to Agon Wastes": 0xA1197E74, + "Transport to Sanctuary Fortress": 0x1C4BDD72, + "Transport to Temple Grounds": 0x8D385440, + "Underground Transport": 0x8C23B6C3, + "Underground Tunnel": 0x70FC85B3, + "Undertemple": 0x22690759, + "Undertemple Access": 0x416E3BA9, + "Undertemple Shaft": 0x4DC6C3E3, + "Undertransit One": 0x3236D73D, + "Undertransit Two": 0x02A8C6E4, + "Venomous Pond": 0xAF3C03B1, +} DOCK_NAMES = { "Abandoned Worksite": { diff --git a/src/open_prime_rando/echoes/custom_assets/__init__.py b/src/open_prime_rando/echoes/custom_assets/__init__.py index c607e1d..ca59de0 100644 --- a/src/open_prime_rando/echoes/custom_assets/__init__.py +++ b/src/open_prime_rando/echoes/custom_assets/__init__.py @@ -3,6 +3,7 @@ from pathlib import Path from retro_data_structures.asset_manager import AssetManager +from retro_data_structures.base_resource import RawResource from retro_data_structures.formats import Ancs, Cmdl @@ -127,6 +128,16 @@ def _create_split_ammo(editor: AssetManager): editor.add_new_asset(f"{ammo.name}_ancs", ancs, editor.find_paks(BEAM_AMMO_EXPANSION_ANCS)) -def create_custom_assets(editor: AssetManager): +def _import_premade_assets(editor: AssetManager): + assets = Path(__file__).parent.joinpath("custom_assets", "general") + for f in assets.glob("*.*"): + name = f.name + asset_type = f.suffix[1:].upper() # remove leading period, force uppercase + raw = f.read_bytes() + editor.add_new_asset(name, RawResource(asset_type, raw), ()) + + +def create_custom_assets(editor: AssetManager, include_premade: bool = False): _create_visor_derivatives(editor) _create_split_ammo(editor) + _import_premade_assets(editor) diff --git a/src/open_prime_rando/echoes/custom_assets/general/custom_knockback.RULE b/src/open_prime_rando/echoes/custom_assets/general/custom_knockback.RULE new file mode 100644 index 0000000..c82e8b1 Binary files /dev/null and b/src/open_prime_rando/echoes/custom_assets/general/custom_knockback.RULE differ diff --git a/src/open_prime_rando/echoes/schema.json b/src/open_prime_rando/echoes/schema.json index 28b2b6b..6decb11 100644 --- a/src/open_prime_rando/echoes/schema.json +++ b/src/open_prime_rando/echoes/schema.json @@ -6,6 +6,11 @@ "type": "string", "format": "uri" }, + "legacy_compatibility": { + "type": "boolean", + "default": false, + "description": "Operate assuming that Claris' Randomizer was already used in the input files" + }, "worlds": { "type": "object", "properties": { @@ -49,10 +54,10 @@ "area_patches": { "type": "object", "properties": { - "torvus_temple": { + "rebalance_world": { "type": "boolean", - "description": "Remove objects to minimize the chance of alloc failure", - "default": false + "description": "Apply patches that rebalance aspects of the game for a better rando experience", + "default": true } }, "description": "Pre-written patches for specific areas", diff --git a/src/open_prime_rando/echoes/specific_area_patches.py b/src/open_prime_rando/echoes/specific_area_patches.py deleted file mode 100644 index daa2619..0000000 --- a/src/open_prime_rando/echoes/specific_area_patches.py +++ /dev/null @@ -1,167 +0,0 @@ -import logging -import struct - -from construct import Container -from retro_data_structures.enums.echoes import Message, State -from retro_data_structures.formats.script_object import ScriptInstance -from retro_data_structures.game_check import Game -from retro_data_structures.properties.echoes.archetypes.EditorProperties import EditorProperties -from retro_data_structures.properties.echoes.archetypes.LayerSwitch import LayerSwitch -from retro_data_structures.properties.echoes.objects.Counter import Counter -from retro_data_structures.properties.echoes.objects.Relay import Relay -from retro_data_structures.properties.echoes.objects.ScriptLayerController import ScriptLayerController - -from open_prime_rando.echoes.asset_ids.agon_wastes import ( - COMMAND_CENTER_MREA, - MINING_STATION_B_MREA, - PORTAL_TERMINAL_MREA, -) -from open_prime_rando.echoes.asset_ids.torvus_bog import TORVUS_ENERGY_CONTROLLER_MREA, TORVUS_TEMPLE_MREA -from open_prime_rando.echoes.asset_ids.world import AGON_WASTES_MLVL, TORVUS_BOG_MLVL -from open_prime_rando.patcher_editor import PatcherEditor - -LOG = logging.getLogger("echoes_patcher") - - -def specific_patches(editor: PatcherEditor, area_patches: dict): - # sand_mining(editor) - # torvus_generator(editor) - if area_patches["torvus_temple"]: - torvus_temple_crash(editor) - command_center_door(editor) - - -def sand_mining(editor: PatcherEditor): - area = editor.get_mrea(MINING_STATION_B_MREA) - post_pickup_relay = area.get_instance(0x80121) - - properties = post_pickup_relay.get_properties() - assert isinstance(properties, Relay) - properties.editor_properties.active = True - post_pickup_relay.set_properties(properties) - - -def create_layer_controller(area_id: int, layer: int, dynamic: bool = False) -> ScriptInstance: - layer_controller = ScriptInstance.new_instance(Game.ECHOES, "SLCT") - props = layer_controller.get_properties() - assert isinstance(props, ScriptLayerController) - - props.editor_properties.name = "Layer Controller" - props.editor_properties.transform.Scale = Container({"X": 1.0, "Y": 1.0, "Z": 1.0}) - props.editor_properties.active = True - props.editor_properties.unknown = 3 - - props.layer.area_id = area_id - props.layer.layer_number = layer - props.IsDynamic = dynamic - - layer_controller.set_properties(props) - return layer_controller - - -def torvus_generator(editor: PatcherEditor): - area = editor.get_mrea(TORVUS_ENERGY_CONTROLLER_MREA) - layer_controller_ids = [2687307, 2687027, 2687028, 2687029] - - for _id in layer_controller_ids: - layer_controller = area.get_instance(_id) - props = layer_controller.get_properties() - assert isinstance(props, ScriptLayerController) - props.editor_properties.active = True - layer_controller.set_properties(props) - - # TODO: generate new instance IDs - layer_cont1 = create_layer_controller(0x9A2ACAFD, 3) - layer_cont2 = create_layer_controller(0x9A2ACAFD, 3) - - # TODO: add new layers - # TODO: add new objects to new layers - - obj = area.get_instance(2686994) - obj.add_connection(State.Zero, Message.Increment, layer_cont1) - obj.add_connection(State.Zero, Message.Increment, layer_cont2) - - -def torvus_temple_crash(editor: PatcherEditor): - """ - Remove cosmetic objects from Torvus Temple to minimize the chance of chance via alloc failure - """ - area = editor.get_area(TORVUS_BOG_MLVL, TORVUS_TEMPLE_MREA) - - to_remove = [ - "Thrust1", - "Thrust1", - "Thrust2", - "Thrust2", - "Looping Thrust w/Doppler", - "Looping Thrust w/Doppler", - "GENERATE GIBS", - ] - to_remove.extend(["SwampCrateDebris"] * 7) - - for obj in to_remove: - area.remove_instance(obj) - - -def agon_wastes_portal_terminal_puzzle_patch(editor: PatcherEditor): - """ - Patches Agon Wastes - Portal Terminal to behave like in GC NTSC-U/PAL - In GC NTSC-J version a counter was added to check for each cork to be broken - """ - area = editor.get_mrea(PORTAL_TERMINAL_MREA) - - """ - Remove counter increment on the 2 first corks to destroy - """ - relay_ids = [0x12033A, 0x120343] # 0x120307 is the last cork to destroy - for relay_id in relay_ids: - relay = area.get_instance(relay_id) - - properties = relay.get_properties() - assert isinstance(properties, Relay) - relay.remove_connections([0x12044E]) - relay.set_properties(properties) - - """ - Set the destroyed cork counter to expect only one cork to be destroyed - """ - counter = area.get_instance(0x12044E) - - with counter.edit_properties(Counter) as props: - props.editor_properties.unknown = 1 - props.max_count = 1 - - -def command_center_door(editor: PatcherEditor): - """ - Opening the blast door normally requires a room reload after they've been closed. - The DS cutscene in Security Station B reloads the room, but that cutscene has been removed. - """ - area = editor.get_area(AGON_WASTES_MLVL, COMMAND_CENTER_MREA) - default = area.get_layer("Default") - # committing a crime until RDS supports unsigned ints - internal_area_id = struct.unpack('>l', struct.pack('>L', 0xAA657163))[0] - - poi = default.get_instance("Blast Door Activation") - - # Deactivate layers - for layer in ("1st Pass Scripting", "1st pass parts"): - controller = default.add_instance_with(ScriptLayerController( - editor_properties=EditorProperties( - name=f"DYNAMIC Decrement {layer}", - ), - layer=LayerSwitch( - area_id=internal_area_id, - layer_number=area.get_layer(layer).index, - ), - is_dynamic=True, - )) - poi.add_connection(State.ScanDone, Message.Decrement, controller) - - # Ensure the blast door instances are active for the cutscene - for instance in ("Upper Blast Door", "Lower Blast Door"): - poi.add_connection( - State.ScanDone, - Message.Activate, - default.get_instance(instance), - ) diff --git a/src/open_prime_rando/echoes/specific_area_patches/__init__.py b/src/open_prime_rando/echoes/specific_area_patches/__init__.py new file mode 100644 index 0000000..8020bf5 --- /dev/null +++ b/src/open_prime_rando/echoes/specific_area_patches/__init__.py @@ -0,0 +1,14 @@ +from open_prime_rando.echoes.specific_area_patches import rebalance_patches, required_fixes +from open_prime_rando.echoes.specific_area_patches.version_differences import EchoesVersion, patch_version_differences +from open_prime_rando.patcher_editor import PatcherEditor + + +def specific_patches(editor: PatcherEditor, area_patches: dict, legacy_compatibility: bool): + if legacy_compatibility: + required_fixes.torvus_temple(editor) + required_fixes.command_center_door(editor) + else: + required_fixes.apply_all(editor) + patch_version_differences(editor, EchoesVersion.NTSC_U) # TODO: detect version + if area_patches["rebalance_world"]: + rebalance_patches.apply_all(editor) diff --git a/src/open_prime_rando/echoes/specific_area_patches/rebalance_patches.py b/src/open_prime_rando/echoes/specific_area_patches/rebalance_patches.py new file mode 100644 index 0000000..1d4c225 --- /dev/null +++ b/src/open_prime_rando/echoes/specific_area_patches/rebalance_patches.py @@ -0,0 +1,237 @@ +import logging +from collections.abc import Iterable + +from retro_data_structures.enums.echoes import Message, State +from retro_data_structures.formats.mrea import Area +from retro_data_structures.formats.script_object import InstanceId, InstanceRef +from retro_data_structures.properties.echoes.archetypes.LayerSwitch import LayerSwitch +from retro_data_structures.properties.echoes.objects import ( + Actor, + IngSpiderballGuardian, + MemoryRelay, + Pickup, + ScriptLayerController, + Splinter, + Timer, +) + +from open_prime_rando.echoes.asset_ids import agon_wastes, great_temple, sanctuary_fortress, temple_grounds, torvus_bog +from open_prime_rando.echoes.asset_ids.world import ( + AGON_WASTES_MLVL, + GREAT_TEMPLE_MLVL, + SANCTUARY_FORTRESS_MLVL, + TEMPLE_GROUNDS_MLVL, + TORVUS_BOG_MLVL, +) +from open_prime_rando.patcher_editor import PatcherEditor + +LOG = logging.getLogger("echoes_patcher") + + +def apply_all(editor: PatcherEditor): + """ + Applies patches that rebalance aspects of the game for a better rando experience. + """ + landing_site(editor) + bionergy_production(editor) + remove_luminoth_barriers(editor) + torvus_energy_controller(editor) + hive_tunnel(editor) + agon_temple(editor) + temple_sanctuary(editor) + main_reactor(editor) + dynamo_works(editor) + torvus_temple(editor) + gfmc_compound(editor) + + +def landing_site(editor: PatcherEditor): + """ + Removes the intro cinematic. + """ + area = editor.get_area(TEMPLE_GROUNDS_MLVL, temple_grounds.LANDING_SITE_MREA) + + remove_intro = True + + # FIXME: use layers API + area.get_layer("1st Pass - Intro Cinematic").active = not remove_intro + for layer_name in ( + "Save Station Load", + "Ship Repair", + "Luminoth Key Bearer", + "WAR CHEST" + ): + area.get_layer(layer_name).active = remove_intro + + if remove_intro: + timer = area.get_layer("Default").add_instance_with(Timer( + time=0.01, + auto_start=True + )) + for inst in ( + "Keep Samus Ship", + "Savestation Recharge Always Plays", + "Ambient Music Memory Relay" + ): + # TODO: unclear whether the timer is necessary, or if we can just activate these immediately + timer.add_connection(State.Zero, Message.Activate, area.get_instance(inst)) + + +def bionergy_production(editor: PatcherEditor): + """ + Deactivate the trigger for flying pirates after killing them all. + """ + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.BIOENERGY_PRODUCTION_MREA) + + counter = area.get_instance("Dead Pirates") + counter.add_connection(State.MaxReached, Message.Deactivate, + area.get_instance("Turn On Flying Pirates")) + + +def _disable_layer_controllers(area: Area, layer_controllers: Iterable[InstanceRef]): + for inst in layer_controllers: + with area.get_instance(inst).edit_properties(ScriptLayerController) as controller: + controller.editor_properties.active = False + + +def remove_luminoth_barriers(editor: PatcherEditor): + """ + Remove Luminoth barriers throughout the game. + """ + # Agon Energy Controller + agon = editor.get_area(AGON_WASTES_MLVL, agon_wastes.AGON_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(agon, ["Increment - 07_Temple - Luminoth Barrier Swamp"]) + + # Torvus Energy Controller + torvus = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.TORVUS_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(torvus, ["Increment - 07_Temple - Luminoth Barrier Cliffs"]) + + # Dark Agon Energy Controller + dark_agon = editor.get_area(AGON_WASTES_MLVL, agon_wastes.DARK_AGON_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(dark_agon, ["Increment - 0A_Sand_Hall - Luminoth Barriers"]) + + # Dark Torvus Energy Controller + dark_torvus = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.DARK_TORVUS_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(dark_torvus, ( + "Increment - 0A_Swamp - Luminoth Barriers", + "Increment - 04_Swamp - Luminoth Barriers" + )) + + # Hive Energy Controller + hive = editor.get_area(SANCTUARY_FORTRESS_MLVL, sanctuary_fortress.HIVE_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(hive, ( + "Increment - 0P_Cliff - Luminoth Barriers", + "Increment - 0A_Cliff - Luminoth Barriers", + "Increment - 0Q_Cliff - Luminoth Barriers", + )) + + +def torvus_energy_controller(editor: PatcherEditor): + """ + Don't remove the Torvus Temple fight. + """ + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.TORVUS_ENERGY_CONTROLLER_MREA) + _disable_layer_controllers(area, ( + "DECREMENT 04_Swamp_Temple 1st Pass", + "Decrement - 04_Swamp_Temple - 1st Pass", + "Increment - 04_Swamp_Temple - 2ndPass" + )) + + +def hive_tunnel(editor: PatcherEditor): + """ + Unknown purpose. + """ + area = editor.get_area(TEMPLE_GROUNDS_MLVL, temple_grounds.HIVE_TUNNEL_MREA) + + with area.get_instance("Timer 001").edit_properties(Timer) as timer: + timer.time = 0.02 + with area.get_instance("Decrement - Webbing").edit_properties(ScriptLayerController) as controller: + controller.is_dynamic = False + + +def agon_temple(editor: PatcherEditor): + """ + No longer locks the doors after the Bomb Guardian fight. + """ + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.AGON_TEMPLE_MREA) + + area.get_layer("Lock Doors").active = False # FIXME: use layers API + + +def temple_sanctuary(editor: PatcherEditor): + """ + Patches Alpha Splinter to take damage properly from Power Bombs. + Keep the Emerald gate active from the beginning. + Fade in the Pickup. + """ + area = editor.get_area(GREAT_TEMPLE_MLVL, great_temple.TEMPLE_SANCTUARY_MREA) + + with area.get_instance("MEGA Splinter Light").edit_properties(Splinter) as alpha: + custom_rule = editor._resolve_asset_id("custom_knockback.RULE") + alpha.patterned.knockback_rules = custom_rule + alpha.ing_possession_data.ing_vulnerability.power_bomb.damage_multiplier = 3000.0 + + with area.get_instance(0x0200B0).edit_properties(MemoryRelay) as relay: + relay.editor_properties.active = True + + with area.get_instance("Pickup Object").edit_properties(Pickup) as pickup: + pickup.fadetime = 1.0 + + +def main_reactor(editor: PatcherEditor): + """ + Change layers properly after DS1's death. + """ + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.MAIN_REACTOR_MREA) + + layer_switcher = area.get_instance("Switch Layers To Post-Dark Samus") + layer_controller = area.get_layer("Default").add_instance_with(ScriptLayerController( + layer=LayerSwitch( + area_id=agon_wastes.BIOSTORAGE_STATION_INTERNAL_ID, + layer_number=3 # 1st Pass + ) + )) + layer_switcher.add_connection(State.Zero, Message.Decrement, layer_controller) + + +def dynamo_works(editor: PatcherEditor): + """ + Fix Spider Guardian's response to PBs. + """ + area = editor.get_area(SANCTUARY_FORTRESS_MLVL, sanctuary_fortress.DYNAMO_WORKS_MREA) + + with area.get_instance("IngSpiderballGuardian 001").edit_properties(IngSpiderballGuardian) as spider: + custom_rule = editor._resolve_asset_id("custom_knockback.RULE") + spider.patterned.knockback_rules = custom_rule + + +def torvus_temple(editor: PatcherEditor): + """ + Reduce the size of the barrier such that it doesn't block the path to lower Torvus. + """ + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.TORVUS_TEMPLE_MREA) + with area.get_instance(0x1B00E0).edit_properties(Actor) as barrier: + barrier.editor_properties.transform.position.x = -226.4198 + barrier.editor_properties.transform.position.z = 58.7156 + barrier.editor_properties.transform.scale.y = 2.019 + + +def gfmc_compound(editor: PatcherEditor): + """ + Move the gate to the Default layer. + """ + area = editor.get_area(TEMPLE_GROUNDS_MLVL, temple_grounds.GFMC_COMPOUND_MREA) + + gate_instances = ( + 0x2b00db, 0x2b00dc, 0x2b00ff, 0x2b0101, + 0x2b013c, 0x2b0238, 0x2b0288, 0x2b02e9, + ) + gate_instances += tuple(range(0x2b0277, 0x2b0287)) + for raw_id in gate_instances: + # TODO: implement a function to move an instance from one layer to another in RDS + inst_id = InstanceId(raw_id) + old_inst = area.get_instance(inst_id) + new_inst = area.get_layer("Default").add_instance_with(old_inst.get_properties()) + area.remove_instance(old_inst) + new_inst.id = InstanceId.new(new_inst.id.layer, new_inst.id.area, inst_id.instance) diff --git a/src/open_prime_rando/echoes/specific_area_patches/required_fixes.py b/src/open_prime_rando/echoes/specific_area_patches/required_fixes.py new file mode 100644 index 0000000..41ce018 --- /dev/null +++ b/src/open_prime_rando/echoes/specific_area_patches/required_fixes.py @@ -0,0 +1,278 @@ +import logging +import struct +from collections.abc import Iterable + +from retro_data_structures.enums.echoes import Message, State +from retro_data_structures.formats.mrea import Area +from retro_data_structures.formats.script_object import InstanceIdRef +from retro_data_structures.properties.echoes.archetypes.EditorProperties import EditorProperties +from retro_data_structures.properties.echoes.archetypes.LayerSwitch import LayerSwitch +from retro_data_structures.properties.echoes.archetypes.Transform import Transform +from retro_data_structures.properties.echoes.core.Vector import Vector +from retro_data_structures.properties.echoes.objects import ( + HUDMemo, + Pickup, + Relay, + ScriptLayerController, + SpawnPoint, + Switch, + Timer, + Trigger, +) + +from open_prime_rando.echoes.asset_ids import agon_wastes, sanctuary_fortress, temple_grounds, torvus_bog +from open_prime_rando.echoes.asset_ids.agon_wastes import COMMAND_CENTER_MREA +from open_prime_rando.echoes.asset_ids.world import ( + AGON_WASTES_MLVL, + SANCTUARY_FORTRESS_MLVL, + TEMPLE_GROUNDS_MLVL, + TORVUS_BOG_MLVL, +) +from open_prime_rando.patcher_editor import PatcherEditor + +LOG = logging.getLogger("echoes_patcher") + + +def apply_all(editor: PatcherEditor): + """ + Applies changes necessary for the game to function properly. + """ + mining_station_b(editor) + undertemple_access(editor) + main_reactor(editor) + sacrificial_chamber(editor) + aerie(editor) + main_research(editor) + hive_chamber_b(editor) + gfmc_compound(editor) + torvus_temple(editor) + command_center_door(editor) + + +def mining_station_b(editor: PatcherEditor): + """ + Normally this relay is activated by the cutscene, but has an incoming connection from the pickup. + Activating it like this means the pickup will trigger the relay without the cutscene + """ + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.MINING_STATION_B_MREA) + with area.get_instance(0x80121).edit_properties(Relay) as post_pickup_relay: + post_pickup_relay.editor_properties.active = True + + +def undertemple_access(editor: PatcherEditor): + """ + Move the default spawn point in-bounds. + """ + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.UNDERTEMPLE_ACCESS_MREA) + + with area.get_instance("Spawn point 001").edit_properties(SpawnPoint) as spawn: + spawn.editor_properties.transform.position.x = -149.25 + + +def main_reactor(editor: PatcherEditor): + """ + Save some memory during DS1 fight. + """ + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.MAIN_REACTOR_MREA) + + unload_relay = area.get_instance("Unload dock when door closed") + trigger = area.get_instance("Trigger Start DS Intro") + trigger.add_connection(State.Entered, Message.SetToZero, unload_relay) + + +def sacrificial_chamber(editor: PatcherEditor): + """ + Makes the pickup persistent, even if you exit the area and reload. + """ + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.SACRIFICIAL_CHAMBER_MREA) + + with area.get_instance("If grapple attainment is loaded, then end battle").edit_properties(Switch) as switch: + switch.is_open = True + + +def _patch_echo_gate_softlock( + area: Area, + counter: InstanceIdRef, + relays: Iterable[tuple[InstanceIdRef, InstanceIdRef]] +): + for shot_relay_id, memory_relay_id in relays: + shot_relay = area.get_instance(shot_relay_id) + memory_relay = area.get_instance(memory_relay_id) + + shot_relay.remove_connections_from(counter) + memory_relay.add_connection(State.Active, Message.Increment, counter) + + +def aerie(editor: PatcherEditor): + """ + Patches an echo gate softlock. + """ + area = editor.get_area(SANCTUARY_FORTRESS_MLVL, sanctuary_fortress.AERIE_MREA) + + relays = ( + (0x410094, 0x41008D), + (0x410077, 0x41007F), + (0x4100B5, 0x4100B6) + ) + _patch_echo_gate_softlock(area, 0x4100BE, relays) + + +def main_research(editor: PatcherEditor): + """ + Patches an echo gate softlock. + Disables the Contraption layer by default, activating it dynamically when exiting the lower portal. + This hopefully prevents OOM/object list full crashes. + """ + area = editor.get_area(SANCTUARY_FORTRESS_MLVL, sanctuary_fortress.MAIN_RESEARCH_MREA) + + relays = ( + (0x0B02E6, 0x0B02DE), + (0x0B0303, 0x0B030D), + (0x0B02F6, 0x0B02FA) + ) + _patch_echo_gate_softlock(area, 0x0B0315, relays) + + area.get_layer("Contraption").active = False + portal_spawn = area.get_instance(0x0B0056) + spawn_xfm = portal_spawn.get_properties_as(SpawnPoint).editor_properties.transform + + contraption_controller = area.get_layer("Default").add_instance_with(ScriptLayerController( + editor_properties=EditorProperties( + name="Dynamic Increment Contraption", + transform=spawn_xfm + ), + layer=LayerSwitch( + area_id=sanctuary_fortress.MAIN_RESEARCH_INTERNAL_ID, + layer_number=area.get_layer("Contraption").index + ), + is_dynamic=True + )) + spider_controller = area.get_layer("Default").add_instance_with(ScriptLayerController( + editor_properties=EditorProperties( + name="Dynamic Increment Column Spiderball", + transform=spawn_xfm + ), + layer=LayerSwitch( + area_id=sanctuary_fortress.MAIN_RESEARCH_INTERNAL_ID, + layer_number=area.get_layer("Column Spiderball").index + ), + is_dynamic=True + )) + + for controller in (contraption_controller, spider_controller): + portal_spawn.add_connection(State.Arrived, Message.Load, controller) + controller.add_connection(State.Arrived, Message.Play, controller) + + +def hive_chamber_b(editor: PatcherEditor): + """ + Removes item loss sequence. + """ + area = editor.get_area(TEMPLE_GROUNDS_MLVL, temple_grounds.HIVE_CHAMBER_B_MREA) + + # FIXME: use layers API + area.get_layer("DS Appears Part1").active = False + area.get_layer("Pre Dark Samus Music").active = False + + area.get_layer("Pickup").active = True + area.get_layer("Post Dark Samus Music").active = True + + +def gfmc_compound(editor: PatcherEditor): + """ + Add a HUDMemo for the ship missile. + """ + area = editor.get_area(TEMPLE_GROUNDS_MLVL, temple_grounds.GFMC_COMPOUND_MREA) + + pickup_xfm = area.get_instance(0x2B0324).get_properties_as(Pickup).editor_properties.transform + ship_trigger = area.get_layer("Default").add_instance_with(Trigger( + editor_properties=EditorProperties( + name="Show Ship Missile HudMemo", + transform=Transform( + position=pickup_xfm.position, + rotation=Vector(0.0, 0.0, 45.0), + scale=Vector(50.0, 50.0, 10.0) + ) + ), + deactivate_on_enter=True + )) + strg_id, _ = editor.create_strg( + "gfmc_jump_hudmemo.STRG", + ["Defeating Jump Guardian is required for this item to appear."] + ) + hud_memo = area.get_layer("Default").add_instance_with(HUDMemo( + editor_properties=EditorProperties( + name="Ship Missile HudMemo", + transform=pickup_xfm + ), + display_time=6.0, + display_type=0, + string=strg_id + )) + ship_trigger.add_connection(State.Entered, Message.SetToZero, hud_memo) + + timer = area.get_layer("Space Jump").add_instance_with(Timer( + editor_properties=EditorProperties( + name="Disable Ship Missile Trigger", + transform=pickup_xfm + ), + time=0.1, + auto_start=True + )) + timer.add_connection(State.Zero, Message.Deactivate, ship_trigger) + + +def torvus_temple(editor: PatcherEditor): + """ + Remove cosmetic objects from Torvus Temple to minimize the chance of crash via alloc failure + """ + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.TORVUS_TEMPLE_MREA) + + to_remove = [ + "Thrust1", + "Thrust1", + "Thrust2", + "Thrust2", + "Looping Thrust w/Doppler", + "Looping Thrust w/Doppler", + "GENERATE GIBS", + ] + to_remove.extend(["SwampCrateDebris"] * 7) + + for obj in to_remove: + area.remove_instance(obj) + + +def command_center_door(editor: PatcherEditor): + """ + Opening the blast door normally requires a room reload after they've been closed. + The DS cutscene in Security Station B reloads the room, but that cutscene has been removed. + """ + area = editor.get_area(AGON_WASTES_MLVL, COMMAND_CENTER_MREA) + default = area.get_layer("Default") + # committing a crime until RDS supports unsigned ints + internal_area_id = struct.unpack('>l', struct.pack('>L', 0xAA657163))[0] + + poi = default.get_instance("Blast Door Activation") + + # Deactivate layers + for layer in ("1st Pass Scripting", "1st pass parts"): + controller = default.add_instance_with(ScriptLayerController( + editor_properties=EditorProperties( + name=f"DYNAMIC Decrement {layer}", + ), + layer=LayerSwitch( + area_id=internal_area_id, + layer_number=area.get_layer(layer).index, + ), + is_dynamic=True, + )) + poi.add_connection(State.ScanDone, Message.Decrement, controller) + + # Ensure the blast door instances are active for the cutscene + for instance in ("Upper Blast Door", "Lower Blast Door"): + poi.add_connection( + State.ScanDone, + Message.Activate, + default.get_instance(instance), + ) diff --git a/src/open_prime_rando/echoes/specific_area_patches/version_differences.py b/src/open_prime_rando/echoes/specific_area_patches/version_differences.py new file mode 100644 index 0000000..bfcc92e --- /dev/null +++ b/src/open_prime_rando/echoes/specific_area_patches/version_differences.py @@ -0,0 +1,106 @@ +import logging +from enum import Enum + +from retro_data_structures.properties.echoes.archetypes.Transform import Transform +from retro_data_structures.properties.echoes.core.Vector import Vector +from retro_data_structures.properties.echoes.objects import Actor, Counter, Platform + +from open_prime_rando.echoes.asset_ids import agon_wastes, great_temple, torvus_bog +from open_prime_rando.echoes.asset_ids.world import ( + AGON_WASTES_MLVL, + GREAT_TEMPLE_MLVL, + TORVUS_BOG_MLVL, +) +from open_prime_rando.patcher_editor import PatcherEditor + +LOG = logging.getLogger("echoes_patcher") + + +class EchoesVersion(float, Enum): + NTSC_U = 1.028 + PAL = 1.035 + NTSC_J = 1.036 + NEW_PLAY_CONTROL = 3.561 + TRILOGY_NTSC = 3.593 + TRILOGY_PAL = 3.629 + + +def patch_version_differences(editor: PatcherEditor, version: EchoesVersion): + """ + Patches version differences that affect gameplay or logic. Usually restores NTSC-U functionality. + Note: only NTSC-U and PAL are officially supported. Version differences introduced in later versions + do not strictly need to be patched. + """ + transport_a_access(editor, version) + path_of_eyes(editor, version) + venomous_pond(editor, version) + portal_terminal(editor, version) + + +def transport_a_access(editor: PatcherEditor, version: EchoesVersion): + """ + Ensures the vulnerability for the blocker is correct. + """ + if version == EchoesVersion.NTSC_U: + return + + area = editor.get_area(GREAT_TEMPLE_MLVL, great_temple.TRANSPORT_A_ACCESS_MREA) + + with area.get_instance("blocker").edit_properties(Platform) as blocker: + blocker.vulnerability.boost_ball.damage_multiplier = 100.0 + blocker.vulnerability.screw_attack.damage_multiplier = 100.0 + blocker.vulnerability.super_missle.damage_multiplier = 100.0 + + +def path_of_eyes(editor: PatcherEditor, version: EchoesVersion): + """ + Edits area collision to restore a gap you can boost through. + """ + if version == EchoesVersion.NTSC_U: + return + + LOG.warning( + "Path of Eyes version differences have not been patched! " + "Area collision edits are not yet implemented." + ) + + +def venomous_pond(editor: PatcherEditor, version: EchoesVersion): + """ + Edits the collision of an Ing Bearerpod to restore a standable to reach the item. + """ + if version == EchoesVersion.NTSC_U: + return + + area = editor.get_area(TORVUS_BOG_MLVL, torvus_bog.VENOMOUS_POND_MREA) + + with area.get_instance(0x10003E).edit_properties(Actor) as piggyplant: + piggyplant.editor_properties.transform = Transform( + position=Vector(17.589201, -148.844849, 37.929966), + scale=Vector(1.5, 1.5, 1.5), + rotation=piggyplant.editor_properties.transform.rotation + ) + piggyplant.collision_model = 0x8E4170D5 + + +def portal_terminal(editor: PatcherEditor, version: EchoesVersion): + """ + In GC NTSC-J version a counter was added to check for each cork to be broken + """ + if version < EchoesVersion.NTSC_J: + return + + area = editor.get_area(AGON_WASTES_MLVL, agon_wastes.PORTAL_TERMINAL_MREA) + + counter = area.get_instance(0x12044E) + + # Remove counter increment on the 2 first corks to destroy + relay_ids = [0x12033A, 0x120343] # 0x120307 is the last cork to destroy + for relay_id in relay_ids: + relay = area.get_instance(relay_id) + relay.remove_connections_from(counter) + + # Set the destroyed cork counter to expect only one cork to be destroyed + with counter.edit_properties(Counter) as props: + props.editor_properties.unknown = 1 + props.max_count = 1 diff --git a/src/open_prime_rando/echoes_patcher.py b/src/open_prime_rando/echoes_patcher.py index 2ebfe5b..dbaf3d0 100644 --- a/src/open_prime_rando/echoes_patcher.py +++ b/src/open_prime_rando/echoes_patcher.py @@ -10,7 +10,7 @@ from retro_data_structures.game_check import Game from open_prime_rando import dynamic_schema -from open_prime_rando.echoes import asset_ids, dock_lock_rando, specific_area_patches +from open_prime_rando.echoes import asset_ids, custom_assets, dock_lock_rando, specific_area_patches from open_prime_rando.echoes.elevators import auto_enabled_elevator_patches from open_prime_rando.echoes.elevators.elevator_rando import patch_elevator from open_prime_rando.echoes.inverted import apply_inverted @@ -138,13 +138,20 @@ def patch_paks(file_provider: FileProvider, status_update("Validating schema", 0) DefaultValidatingDraft7Validator(schema).validate(configuration) + legacy_compatibility: bool = configuration["legacy_compatibility"] + status_update("Applying small patches", 0) + if not legacy_compatibility: + custom_assets.create_custom_assets(editor, include_premade=True) + dock_lock_rando.add_custom_models(editor) if configuration["auto_enabled_elevators"]: auto_enabled_elevator_patches.apply_auto_enabled_elevators_patch(editor) - specific_area_patches.specific_patches(editor, configuration["area_patches"]) + specific_area_patches.specific_patches(editor, configuration["area_patches"], legacy_compatibility) apply_small_randomizations(editor, configuration["small_randomizations"]) apply_corrupted_memory_card_change(editor) + + status_update("Modifying areas", 0) apply_area_modifications(editor, configuration["worlds"], status_update) if configuration["inverted"]: diff --git a/src/open_prime_rando/patcher_editor.py b/src/open_prime_rando/patcher_editor.py index 273942e..03ba6a4 100644 --- a/src/open_prime_rando/patcher_editor.py +++ b/src/open_prime_rando/patcher_editor.py @@ -5,9 +5,11 @@ from ppc_asm.dol_file import DolEditor, DolHeader from retro_data_structures.asset_manager import AssetManager, FileProvider -from retro_data_structures.base_resource import BaseResource, NameOrAssetId +from retro_data_structures.base_resource import AssetId, BaseResource, NameOrAssetId, Resource +from retro_data_structures.crc import crc32 from retro_data_structures.formats.mlvl import Mlvl from retro_data_structures.formats.mrea import Area +from retro_data_structures.formats.strg import Strg from retro_data_structures.game_check import Game T = typing.TypeVar("T") @@ -56,6 +58,18 @@ def flush_modified_assets(self): executor.submit(self.replace_asset, name, resource) self.memory_files = {} + def add_file(self, + name: str, + asset: Resource, + ) -> AssetId: + asset_id = crc32(name) + self.register_custom_asset_name(name, asset_id) + self.add_new_asset(name, asset, ()) + return asset_id + + def duplicate_file(self, name: str, asset: AssetId) -> AssetId: + return self.add_file(name, self.get_raw_asset(asset)) + def save_modifications(self, output_path: Path): super().save_modifications(output_path) @@ -63,3 +77,35 @@ def save_modifications(self, output_path: Path): target_dol = output_path.joinpath("sys/main.dol") target_dol.parent.mkdir(exist_ok=True, parents=True) target_dol.write_bytes(self.dol.dol_file.getvalue()) + + def add_or_replace_custom_asset(self, name: str, new_data: Resource) -> AssetId: + if self.does_asset_exists(name): + asset_id = self.replace_asset(name, new_data) + else: + asset_id = self.add_file(name, new_data) + return asset_id + + def create_strg(self, + name: str, + strings: str | typing.Iterable[str] = (), + ) -> tuple[AssetId, Strg]: + template_id = None + if self.target_game == Game.ECHOES: + # Strings/Worlds/TempleHub/01_Temple_LandingSite.STRG + template_id = 0x2E681FEF + + if template_id is None: + raise NotImplementedError() + + asset_id = self.duplicate_file(name, template_id) + + strg = self.get_file(asset_id, Strg) + + if isinstance(strings, str): + strings = strings + strings = list(strings) + + for lang in strg.languages: + strg.set_strings(lang, strings) + + return asset_id, strg diff --git a/tests/echoes/test_full_patch.py b/tests/echoes/test_full_patch.py index 29071bc..22977f4 100644 --- a/tests/echoes/test_full_patch.py +++ b/tests/echoes/test_full_patch.py @@ -1,16 +1,89 @@ +import hashlib +from pathlib import Path + +import pytest + from open_prime_rando import echoes_patcher -def test_ntsc_paks(prime2_iso_provider, tmp_path, test_files_dir): +@pytest.fixture( + params=[False, True], +) +def is_legacy(request: pytest.FixtureRequest) -> bool: + return request.param + + +def hash_all_paks(base_path: Path) -> dict[str, bytes]: + return { + pak_path.relative_to(base_path).as_posix(): hashlib.sha256(pak_path.read_bytes()).digest() + for pak_path in base_path.rglob("*.pak") + } + + +def test_ntsc_paks(prime2_iso_provider, tmp_path, test_files_dir, is_legacy: bool) -> None: output_path = tmp_path.joinpath("out") configuration = test_files_dir.read_json("echoes", "door_lock.json") + configuration["legacy_compatibility"] = is_legacy + + expected_hashes = { + False: { + 'AudioGrp.pak': b'U\xc6\xfe\\\xac\xcd\xdc\x02\x0c\xed\xdd\xb3\xfarbP' + b'\x90\xac\x8d\x1b=\xd4\x9fl\x82\xc8\x12\xd2\x85X{+', + 'LogBook.pak': b'\x05\xdf2\xb5\xf4\xe5\xce\xa3"\tk_\x91\x05\xe9\xa9' + b'\xab\x8c.\x9c\xe0\xee\x89\xb7\x04\xa1\x7fI\x0bU,\xb4', + 'Metroid1.pak': b'\x11%\x80\xe4t\x9e?\x1d\xaa\xf2\xc9\xc3\x1d&\xa6v\xfc?E\xcf' + b'\xc4\x06\x13\x83"A#\xfb\xa1\x84\xcf\xca', + 'Metroid2.pak': b'\x96lH\x85\xae\xddX\xceo\xd1\x0cfnk\xbex\xaa\x94Y\x08' + b'\x05\\-\xf9?\xa4\x9a]\xa8\x0ez\x93', + 'Metroid3.pak': b'\xe8\xee\x80\xf6\\\xd5\x1c4\xf5\x1b\xe1*\xfbY\x9a\xe0' + b'\x8c\xb9\xd1\xe0(\x96\xb4R-\xe0\xf1\x86y\x88T\xa9', + 'Metroid4.pak': b'\xce\xc5\x838\x19q\xd0\xf7\xf4\x86hQ\xb3\xf5\xf5p\xf9\xd3}8' + b'\xfemx\xb5x\tQ\xf8\xc6\xdc.\xb6', + 'Metroid5.pak': b'\xd0\x9fX\xab\xae\x06\xda\xbfz\xfd\xc5\xc9\xcb+\x0f\x04' + b'\xd7\x9c\xa0\xb7\xd4\xf4\xea\x85\x14\xa4\x08\xe5' + b'\xf6\x9cY\x11', + 'MiscData.pak': b'\x0bM\xedR\xb7+\xaf+#\x06\x87\x88\xe5\xec\xbbxg@i\xbc' + b'\xa3n\xb1\xabi\xfajb\x00\xf3\xd1\xaa', + 'SamusGun.pak': b'\xdd\xa99\x0b\x14b\x97&\xee\xdf\nE\xd0GR`c\x85\xf7m;\xdbX^' + b'\xe4\x87;\\?\xc9\x08\x1f', + 'SamusGunLow.pak': b'o8\xb8\xab^\xc3y\xc7\xd8v\xf1?\xe7a\x9enIE\xb7\xa5' + b'q\x9f\xa2\xf6\xc2\xc6m&\x80G\x8f\x83', + 'TestAnim.pak': b'\xc0n\xfa2\x87\x96o\xd9\xf8\nL\xc8\xd5vVJi\xe2\xa0\xdb' + b'\xdbq\t\x7f\xaf\x96\x8d\x02\x1f\xe7\x07\x12' + }, + True: { + 'AudioGrp.pak': b'U\xc6\xfe\\\xac\xcd\xdc\x02\x0c\xed\xdd\xb3\xfarbP' + b'\x90\xac\x8d\x1b=\xd4\x9fl\x82\xc8\x12\xd2\x85X{+', + 'LogBook.pak': b'K=z\x02\x9c>\x8a\xfbN\xbe\xb6\xb5\xc8\xad\xba\xb8' + b'\x99\xae\x85_\xab55\xfe3\x15\x0b\xdc\xd1\xc7\x02^', + 'Metroid1.pak': b'7\x18\xe7+\xdbR\xbf6\x0e\xa2=\x8b\xc8\xcc\x98-' + b'\x8f\xdc\xe5\x8d\x17\xedz\xf9c\xe1\xd5\xfc\xf2\x18\xf2$', + 'Metroid2.pak': b'U\x9b\xa7;\xbf\xad\xac\xf7\x05\xe9hKC\xcd\xa5\x0f' + b'\xc9C\xc7\xf2\xe7A$\xcc\x1c&0A\xcb\xaa(\xf1', + 'Metroid3.pak': b'\xeaO\x17\x0e\xaa\xbd\xaa\xabJvO\x9d\x08\x15Y\xca' + b'\xe7\x97z\x0c\xcb\xf6],\xf9\xfa\xee*\x17\xc2\xf6(', + 'Metroid4.pak': b'9\xc2\x87\x91\x91%\x82\x0f>\x7f;Q\xacLP\xca-\xb6\xc1\x1b' + b'+\xc7\xecy{\xc0\x12\x19\xa7\x04\x7ft', + 'Metroid5.pak': b'g\x91Rx\xb0+\xe2=\x06~\xbb\\\x15\xae3\xaft\xdcG\xc2' + b'+\xea0\xbf\x8a\xaf\xfc/5`0z', + 'MiscData.pak': b'\x0bM\xedR\xb7+\xaf+#\x06\x87\x88\xe5\xec\xbbxg@i\xbc' + b'\xa3n\xb1\xabi\xfajb\x00\xf3\xd1\xaa', + 'SamusGun.pak': b'\xdd\xa99\x0b\x14b\x97&\xee\xdf\nE\xd0GR`c\x85\xf7m;\xdbX^' + b'\xe4\x87;\\?\xc9\x08\x1f', + 'SamusGunLow.pak': b'o8\xb8\xab^\xc3y\xc7\xd8v\xf1?\xe7a\x9enIE\xb7\xa5' + b'q\x9f\xa2\xf6\xc2\xc6m&\x80G\x8f\x83', + 'TestAnim.pak': b'\xc0n\xfa2\x87\x96o\xd9\xf8\nL\xc8\xd5vVJi\xe2\xa0\xdb' + b'\xdbq\t\x7f\xaf\x96\x8d\x02\x1f\xe7\x07\x12' + }, + } echoes_patcher.patch_paks( file_provider=prime2_iso_provider, output_path=output_path, configuration=configuration, ) - assert len(list(output_path.rglob("*.pak"))) == 11 + hashes = hash_all_paks(output_path) + assert hashes == expected_hashes[is_legacy] def test_pal_paks(pal_prime2_iso_provider, tmp_path, test_files_dir): diff --git a/tools/asset_id_files.py b/tools/asset_id_files.py index 089d104..48adbdc 100644 --- a/tools/asset_id_files.py +++ b/tools/asset_id_files.py @@ -104,6 +104,7 @@ def create_asset_id_files(editor: PatcherEditor, output_path: Path): area_names = {} mapa_names = {} dock_names = {} + area_id_names = {} for area, mapa in zip(mlvl.raw.areas, mlvl.mapw.mapa_ids): try: @@ -118,6 +119,7 @@ def create_asset_id_files(editor: PatcherEditor, output_path: Path): assert area_name not in area_names, area_name area_names[area_name] = area.area_mrea_id mapa_names[area_name] = mapa + area_id_names[area_name] = area.internal_area_id mrea = editor.get_file(area_names[area_name], type_hint=Mrea) docks = {} @@ -136,6 +138,7 @@ def create_asset_id_files(editor: PatcherEditor, output_path: Path): world_file_body = generate_template(area_names, "_MREA") world_file_body += generate_template(mapa_names, "_MAPA") + world_file_body += generate_template(area_id_names, "_INTERNAL_ID") world_file_body += dock_name_templates(dock_names) output_path.joinpath(f"{filter_and_lower_name(world_name)}.py").write_text(world_file_body)