diff --git a/worlds/tww/Macros.py b/worlds/tww/Macros.py index 5ed405ed1d4c..fdd194b5b3ce 100644 --- a/worlds/tww/Macros.py +++ b/worlds/tww/Macros.py @@ -304,7 +304,11 @@ def can_reach_wind_temple_tall_basement_room(state: CollectionState, player: int def can_access_dungeon_entrance_on_dragon_roost_island(state: CollectionState, player: int) -> bool: - return True + return state._tww_open_drc(player) or ( + can_move_boulders(state, player) + or can_fly_with_deku_leaf_outdoors(state, player) + or state.has("Empty Bottle", player, 1) + ) def can_access_forest_haven(state: CollectionState, player: int) -> bool: diff --git a/worlds/tww/Options.py b/worlds/tww/Options.py index b6f2c1511b9c..2faffa62e184 100644 --- a/worlds/tww/Options.py +++ b/worlds/tww/Options.py @@ -422,6 +422,14 @@ class EnableTunerLogic(Toggle): display_name = "Enable Tuner Logic" +class OpenDRC(Toggle): + """ + If enabled, the boulder in front of the DRC entrance will be removed and the location will be accessible with no items + """ + + display_name = "Open DRC Entrance" + + class RandomizeDungeonEntrances(Toggle): """ Shuffles around which dungeon entrances take you into which dungeons. @@ -727,6 +735,7 @@ class TWWOptions(PerGameCommonOptions): hero_mode: HeroMode logic_obscurity: LogicObscurity logic_precision: LogicPrecision + open_drc: OpenDRC enable_tuner_logic: EnableTunerLogic randomize_dungeon_entrances: RandomizeDungeonEntrances randomize_secret_cave_entrances: RandomizeSecretCavesEntrances @@ -791,6 +800,7 @@ def get_slot_data_dict(self) -> dict[str, Any]: "logic_obscurity", "logic_precision", "enable_tuner_logic", + "open_drc", "randomize_dungeon_entrances", "randomize_secret_cave_entrances", "randomize_miniboss_entrances", @@ -847,6 +857,7 @@ def get_output_dict(self) -> dict[str, Any]: "hero_mode", "logic_obscurity", "logic_precision", + "open_drc", "randomize_dungeon_entrances", "randomize_secret_cave_entrances", "randomize_miniboss_entrances", @@ -916,6 +927,7 @@ def get_output_dict(self) -> dict[str, Any]: RandomizeSecretCaveInnerEntrances, RandomizeFairyFountainEntrances, MixEntrances, + OpenDRC, ], ), OptionGroup( diff --git a/worlds/tww/Rules.py b/worlds/tww/Rules.py index d4c743783c09..11e1ec24517f 100644 --- a/worlds/tww/Rules.py +++ b/worlds/tww/Rules.py @@ -73,6 +73,9 @@ def _tww_precise_3(self, player: int) -> bool: def _tww_tuner_logic_enabled(self, player: int) -> bool: return self.multiworld.worlds[player].logic_tuner_logic_enabled + + def _tww_open_drc(self, player: int) -> bool: + return self.multiworld.worlds[player].logic_open_drc def set_rules(world: "TWWWorld") -> None: # noqa: F405 diff --git a/worlds/tww/TWWClient.py b/worlds/tww/TWWClient.py index ea46bdd6b1e3..c65b03f17d56 100644 --- a/worlds/tww/TWWClient.py +++ b/worlds/tww/TWWClient.py @@ -1,4 +1,5 @@ import asyncio +import os.path import time import traceback from typing import TYPE_CHECKING, Any, Optional @@ -29,6 +30,15 @@ CONNECTION_INITIAL_STATUS = "Dolphin connection has not been initiated." +def load_custom_symbols(filename: str) -> dict[str, int]: + """Loads asm offsets from a custom_symbols.txt file generated by the Wind Waker Randomizer program""" + with open(filename) as stream: + symbols = Utils.parse_yaml(stream) + return symbols["sys/main.dol"] + + +CUSTOM_SYMBOLS = load_custom_symbols(os.path.join(os.path.dirname(__file__), "custom_symbols.txt")) + # This address is used to check/set the player's health for DeathLink. CURR_HEALTH_ADDR = 0x803C4C0A @@ -60,17 +70,17 @@ # This address contains the byte used to give the player an item. # 0xFF represents no item. The byte is read and cleared every frame. -GIVE_ITEM_BYTE_ADDR = 0x803FE868 +GIVE_ITEM_BYTE_ADDR = CUSTOM_SYMBOLS["give_archipelago_item_byte"] # This is the address that holds the player's slot name. # This way, the player does not have to manually authenticate their slot name. -SLOT_NAME_ADDR = 0x803FE880 +SLOT_NAME_ADDR = CUSTOM_SYMBOLS["archipelago_slot_name"] # This address is the start of an array that we use to inform us of which charts lead where. # The array is of length 49, and each element is two bytes. The index represents the chart's original destination, and # the value represents the new destination. # The chart name is inferrable from the chart's original destination. -CHARTS_MAPPING_ADDR = 0x803FE8C0 +CHARTS_MAPPING_ADDR = CUSTOM_SYMBOLS["archipelago_charts_mapping"] # This address contains the most recent spawn ID from which the player spawned. MOST_RECENT_SPAWN_ID_ADDR = 0x803C9D44 diff --git a/worlds/tww/__init__.py b/worlds/tww/__init__.py index 3ad242cee9f2..5fea9deb216d 100644 --- a/worlds/tww/__init__.py +++ b/worlds/tww/__init__.py @@ -312,6 +312,7 @@ def generate_early(self) -> None: self.logic_precise_2 = self.logic_precise_3 or options.logic_precision == "hard" self.logic_precise_1 = self.logic_precise_2 or options.logic_precision == "normal" self.logic_tuner_logic_enabled = bool(options.enable_tuner_logic.value) + self.logic_open_drc = bool(options.open_drc) # Determine any item classification overrides based on user options. self._determine_item_classification_overrides() diff --git a/worlds/tww/custom_symbols.txt b/worlds/tww/custom_symbols.txt new file mode 100644 index 000000000000..6c64b1e788b5 --- /dev/null +++ b/worlds/tww/custom_symbols.txt @@ -0,0 +1,200 @@ +sys/main.dol: + hurricane_spin_item_resource_arc_name: 0x803FCFA8 + ballad_of_gales_warp_table: 0x803FCFB0 + ballad_of_gales_warp_float_bank: 0x803FCFF8 + custom_DynamicNameTable: 0x803FD050 + custom_l_objectName: 0x803FD084 + custom_l_objectName_end: 0x803FD09C + custom_DMC: 0x803FD09C + init_save_with_tweaks: 0x803FD87C + init_starting_gear: 0x803FDC34 + num_triforce_shards_to_start_with: 0x803FDC7C + should_start_with_heros_clothes: 0x803FDC7D + sword_mode: 0x803FDC7E + skip_rematch_bosses: 0x803FDC7F + starting_gear: 0x803FDC80 + starting_quarter_hearts: 0x803FDCD8 + starting_magic: 0x803FDCDA + captured_prologue_pigs_bitfield: 0x803FDCDB + option_targeting_mode: 0x803FDCDC + open_drc: 0x803FDCDD + convert_progressive_item_id: 0x803FDCE0 + progressive_sword_item_func: 0x803FDF70 + progressive_shield_item_func: 0x803FDFD8 + progressive_bow_func: 0x803FE020 + progressive_wallet_item_func: 0x803FE078 + progressive_bomb_bag_item_func: 0x803FE0B0 + progressive_quiver_item_func: 0x803FE0F0 + progressive_picto_box_item_func: 0x803FE130 + magic_meter_item_func: 0x803FE178 + magic_meter_upgrade_item_func: 0x803FE198 + normal_magic_meter_item_func: 0x803FE1B8 + hurricane_spin_item_func: 0x803FE1D0 + custom_createItem: 0x803FE1FC + generic_on_dungeon_bit: 0x803FE248 + generic_small_key_item_get_func: 0x803FE2A4 + drc_small_key_item_get_func: 0x803FE33C + fw_small_key_item_get_func: 0x803FE360 + totg_small_key_item_get_func: 0x803FE384 + et_small_key_item_get_func: 0x803FE3A8 + wt_small_key_item_get_func: 0x803FE3CC + drc_big_key_item_get_func: 0x803FE3F0 + fw_big_key_item_get_func: 0x803FE418 + totg_big_key_item_get_func: 0x803FE440 + et_big_key_item_get_func: 0x803FE468 + wt_big_key_item_get_func: 0x803FE490 + drc_dungeon_map_item_get_func: 0x803FE4B8 + fw_dungeon_map_item_get_func: 0x803FE4E0 + totg_dungeon_map_item_get_func: 0x803FE508 + ff_dungeon_map_item_get_func: 0x803FE530 + et_dungeon_map_item_get_func: 0x803FE558 + wt_dungeon_map_item_get_func: 0x803FE580 + drc_compass_item_get_func: 0x803FE5A8 + fw_compass_item_get_func: 0x803FE5D0 + totg_compass_item_get_func: 0x803FE5F8 + ff_compass_item_get_func: 0x803FE620 + et_compass_item_get_func: 0x803FE648 + wt_compass_item_get_func: 0x803FE670 + dragon_tingle_statue_item_get_func: 0x803FE698 + forbidden_tingle_statue_item_get_func: 0x803FE6C4 + goddess_tingle_statue_item_get_func: 0x803FE6F0 + earth_tingle_statue_item_get_func: 0x803FE71C + wind_tingle_statue_item_get_func: 0x803FE748 + check_tingle_statue_owned: 0x803FE774 + custom_isTbox_for_unloaded_stage_save_info: 0x803FE7FC + give_archipelago_item: 0x803FE82C + give_archipelago_item_byte: 0x803FE87C + modify_triforce_count: 0x803FE880 + archipelago_slot_name: 0x803FE894 + archipelago_charts_mapping: 0x803FE8D4 + hookshot_sight_failsafe_check: 0x803FE938 + deluxe_picto_box_item_func_fix_equipped_picto_box: 0x803FE950 + stop_sub_bgm_when_unloading_stage: 0x803FE984 + zero_out_arrow_on_hit_callback: 0x803FE99C + check_can_defend: 0x803FE9AC + check_ganondorf_in_phase_3: 0x803FE9DC + check_play_special_item_get_music: 0x803FE9F8 + multiply_damage_amount: 0x803FEA60 + damage_multiplier: 0x803FEA84 + invert_camera_horizontal_axis: 0x803FEA88 + convert_progressive_item_id_for_createDemoItem: 0x803FEA94 + convert_progressive_item_id_for_daItem_create: 0x803FEAC0 + convert_progressive_item_id_for_dProcGetItem_init_1: 0x803FEAEC + convert_progressive_item_id_for_dProcGetItem_init_2: 0x803FEB14 + check_open_map_select: 0x803FEB3C + set_starting_health: 0x803FEB9C + get_current_health_for_file_select_screen: 0x803FEBB8 + get_max_health_for_file_select_screen: 0x803FEBE0 + fully_refill_magic_meter_and_cap_health_on_load_save: 0x803FEC04 + turn_while_swinging: 0x803FEC34 + check_animate_rainbow_rupee_color: 0x803FEC94 + rainbow_rupee_keyframe: 0x803FECD4 + check_run_new_text_commands: 0x803FECE4 + exec_curr_num_keys_text_command: 0x803FED24 + set_warp_confirm_dialog_message_id_for_custom_warps: 0x803FEE04 + check_player_in_casual_clothes: 0x803FEE1C + read_custom_DynamicNameTable_loop: 0x803FEE58 + read_custom_l_objectName_loop_for_dStage_searchName: 0x803FEE84 + read_custom_l_objectName_loop_for_dStage_getName: 0x803FEEAC + get_num_owned_tingle_statues: 0x803FEED8 + ladder_up_check_unequip_held_item: 0x803FEF4C + ladder_down_check_unequip_held_item: 0x803FEF64 + gameover_continue_reset_life: 0x803FEF7C + give_pearl_and_raise_totg_if_necessary: 0x803FEF98 + remove_low_health_anim_at_full_health: 0x803FF02C + give_temporary_sword_during_ganondorf_fight_in_swordless: 0x803FF050 + give_temporary_sword_in_orcas_house_in_swordless: 0x803FF06C + remove_temporary_sword_when_loading_stage_in_swordless: 0x803FF0B4 + test_room_stage_name: 0x8022D034 + test_room_starting_items_list: 0x8022D03C + test_room_spawn_id: 0x800531E3 + test_room_room_index: 0x800531E7 + test_room_override_layer_num: 0x800531EB +files/rels/d_a_demo_item.rel: + check_give_item: 0x1344 +files/rels/d_a_npc_bs1.rel: + set_shop_item_in_bait_bag_slot_sold_out: 0x61F0 + check_shop_item_in_bait_bag_slot_sold_out: 0x6220 + custom_getSelectItemNo_progressive: 0x624C +files/rels/d_a_st.rel: + stalfos_kill_lower_body_when_upper_body_light_arrowed: 0xA778 + stalfos_set_death_switch: 0xA7C0 +files/rels/d_a_pt.rel: + miniblin_set_death_switch_when_light_arrowed: 0x60BC +files/rels/d_a_pw.rel: + poe_fix_light_arrows_bug: 0x7FB4 +files/rels/d_a_mt.rel: + magtail_respawn_when_head_light_arrowed: 0xA04C +files/rels/d_a_fganon.rel: + phantom_ganon_check_link_within_y_diff: 0xB020 + check_ganons_tower_chest_opened: 0xB03C +files/rels/d_a_bdk.rel: + initialize_helmaroc_king_landing_timer: 0xEA88 + check_helmaroc_king_landing_timeout: 0xEA98 +files/rels/d_a_boko.rel: + check_phantom_ganons_sword_should_disappear: 0x4B88 + phantom_ganon_maze_stage_name: 0x4BC4 +files/rels/d_a_rd.rel: + redead_check_disable_spawn_switch: 0x61E4 + redead_set_death_switch: 0x6224 +files/rels/d_a_ph.rel: + peahat_check_disable_spawn_switch: 0x7F70 + peahat_set_death_switch: 0x7FB8 + peahat_check_enable_spawn_switch: 0x7FE4 + peahat_check_enable_spawn_switch_for_draw: 0x803C +files/rels/d_a_mo2.rel: + moblin_set_death_switch: 0xE2D4 +files/rels/d_a_tn.rel: + darknut_set_death_switch: 0xEA24 +files/rels/d_a_nzg.rel: + rat_hole_all_rats_dead: 0xE00 +files/rels/d_a_obj_ftree.rel: + create_item_for_withered_trees_without_setting_speeds: 0x535C + withered_tree_item_try_give_momentum: 0x5384 + withered_tree_item_speeds: 0x53F8 +files/rels/d_a_npc_people.rel: + create_item_and_set_event_bit_for_townsperson: 0xCF18 +files/rels/d_a_npc_photo.rel: + lenzo_set_deluxe_picto_box_event_bit: 0x5EC8 +files/rels/d_a_npc_rsh1.rel: + zunari_give_item_and_set_magic_armor_event_bit: 0x5D18 + zunari_magic_armor_slot_item_id: 0x5D70 +files/rels/d_a_npc_sv.rel: + salvage_corp_give_item_and_set_event_bit: 0x3580 +files/rels/d_a_npc_kp1.rel: + maggie_give_item_and_set_event_bit: 0x338C +files/rels/d_a_npc_bm1.rel: + rito_cafe_postman_start_event_and_set_event_bit: 0xAFD0 +files/rels/d_a_npc_ds1.rel: + doc_bandam_check_new_potion_and_give_free_item: 0x60F8 + doc_bandam_green_potion_slot_item_id: 0x6154 + doc_bandam_blue_potion_slot_item_id: 0x6155 +files/rels/d_a_tsubo.rel: + create_pot_item: 0x9964 +files/rels/d_a_obj_homen.rel: + create_stone_head_item: 0x36DC +files/rels/d_a_ship.rel: + set_wind_dir_to_ship_dir: 0xE3A8 + slow_down_ship_when_stopping: 0xE3F0 + ship_stopping_deceleration: 0xE420 + slow_down_ship_when_idle: 0xE428 + ship_idle_deceleration: 0xE458 +files/rels/d_a_npc_md.rel: + reset_medli_position: 0x15D78 + medli_possible_et_spawn_positions: 0x15E50 +files/rels/d_a_npc_cb1.rel: + reset_makar_position: 0xB214 + makar_possible_wt_spawn_positions: 0xB2EC +files/rels/d_a_warpdm20.rel: + check_hyrule_warp_unlocked: 0x1EBC +files/rels/d_a_npc_os.rel: + set_inactive_servant_when_player_leaves_room: 0x8AB4 +files/rels/d_a_obj_hsehi1.rel: + set_item_obtained_from_totg_tablet_event_bit: 0x29D0 +files/rels/d_a_shop_item.rel: + convert_progressive_item_id_for_shop_item: 0x1174 +files/rels/d_a_obj_warpt.rel: + set_prm_color_for_warp_pot_particles: 0x3280 + set_env_color_for_warp_pot_particles: 0x32A8 + custom_warp_pot_prm_color: 0x32D0 + custom_warp_pot_env_color: 0x32D4