From 79141afa9d6be6ead4d1c0da9926d6592175273a Mon Sep 17 00:00:00 2001 From: Dinopony Date: Sat, 24 Feb 2024 02:02:51 +0100 Subject: [PATCH] OoS: Major updates --- worlds/tloz_oos/Client.py | 52 ++++---- worlds/tloz_oos/Logic.py | 10 +- worlds/tloz_oos/__init__.py | 20 +-- .../data/{logic/constants.py => Constants.py} | 9 +- worlds/tloz_oos/data/Items.py | 34 ++--- worlds/tloz_oos/data/Locations.py | 17 ++- .../data/{logic/regions.py => Regions.py} | 2 +- .../logic/{dungeons.py => DungeonsLogic.py} | 4 +- .../{predicates.py => LogicPredicates.py} | 31 +++-- .../logic/{holodrum.py => OverworldLogic.py} | 18 +-- .../logic/{subrosia.py => SubrosiaLogic.py} | 11 +- ...n_The Legend of Zelda Oracle of Seasons.md | 60 +++++++++ worlds/tloz_oos/docs/oos_setup_en.md | 119 ++++++++++++++++++ 13 files changed, 308 insertions(+), 79 deletions(-) rename worlds/tloz_oos/data/{logic/constants.py => Constants.py} (95%) rename worlds/tloz_oos/data/{logic/regions.py => Regions.py} (99%) rename worlds/tloz_oos/data/logic/{dungeons.py => DungeonsLogic.py} (99%) rename worlds/tloz_oos/data/logic/{predicates.py => LogicPredicates.py} (95%) rename worlds/tloz_oos/data/logic/{holodrum.py => OverworldLogic.py} (98%) rename worlds/tloz_oos/data/logic/{subrosia.py => SubrosiaLogic.py} (94%) create mode 100644 worlds/tloz_oos/docs/en_The Legend of Zelda Oracle of Seasons.md create mode 100644 worlds/tloz_oos/docs/oos_setup_en.md diff --git a/worlds/tloz_oos/Client.py b/worlds/tloz_oos/Client.py index 0ce12516f428..8146b4814e23 100644 --- a/worlds/tloz_oos/Client.py +++ b/worlds/tloz_oos/Client.py @@ -11,15 +11,17 @@ ROM_ADDRS = { "game_identifier": (0x0134, 9, "ROM"), - "slot_name": (0x0000, 64, "ROM"), # TODO: Inject using patcher and put addr here + "slot_name": (0xFFFC0, 64, "ROM"), } RAM_ADDRS = { "game_state": (0xC2EE, 1, "System Bus"), "received_item_index": (0xC6A0, 2, "System Bus"), "received_item": (0xCBFB, 1, "System Bus"), - "location_flags": (0xC600, 0x500, "System Bus"), # TODO: Find and set the real address - "game_clear_byte": (0x0000, 1, "System Bus"), # TODO: Find and set the real address + "location_flags": (0xC600, 0x500, "System Bus"), + + "current_map_group": (0xCC49, 1, "System Bus"), + "current_map_id": (0xCC4C, 1, "System Bus"), } class OracleOfSeasonsClient(BizHawkClient): @@ -32,9 +34,9 @@ class OracleOfSeasonsClient(BizHawkClient): def __init__(self) -> None: super().__init__() - self.local_checked_locations = set() self.item_id_to_name = build_item_id_to_name_dict() self.location_name_to_id = build_location_name_to_id_dict() + self.local_checked_locations = set() async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: try: @@ -56,8 +58,8 @@ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: return True async def set_auth(self, ctx: "BizHawkClientContext") -> None: - # slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [ROM_ADDRS["slot_name"]]))[0] - # ctx.auth = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") + slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [ROM_ADDRS["slot_name"]]))[0] + ctx.auth = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") pass async def game_watcher(self, ctx: "BizHawkClientContext") -> None: @@ -72,7 +74,8 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: RAM_ADDRS["received_item_index"], # Number of received items RAM_ADDRS["received_item"], # Received item still pending? RAM_ADDRS["location_flags"], # Location flags - RAM_ADDRS["game_clear_byte"], # Has goal been completed? + RAM_ADDRS["current_map_group"], + RAM_ADDRS["current_map_id"], # Current map & id to check for goal completion ]) if read_result is None: return @@ -84,7 +87,7 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: num_received_items = int.from_bytes(read_result[1], "little") received_item_is_empty = (read_result[2][0] == 0) flag_bytes = read_result[3] - game_clear = (read_result[4] == 1) # TODO: Might be a different value + game_clear = (read_result[4][0] == 0x07 and read_result[5][0] == 0x90) # If the game hasn't received all items yet and the received item struct doesn't contain an item, then # fill it with the next item @@ -105,26 +108,33 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: ]) # Read location flags from RAM - local_checked_locations = set() for name, location in LOCATIONS_DATA.items(): if "local" in location and location["local"] is True: continue if "randomized" in location and location["randomized"] is False: continue - byte_offset = location["flag_byte"] - RAM_ADDRS["location_flags"][0] - bit_mask = location["bit_mask"] if "bit_mask" in location else 0x20 - if flag_bytes[byte_offset] & bit_mask == bit_mask: - location_id = self.location_name_to_id[name] - local_checked_locations.add(location_id) + + bytes_to_test = location["flag_byte"] + if not hasattr(bytes_to_test, "__len__"): + bytes_to_test = [bytes_to_test] + + for byte_addr in bytes_to_test: + byte_offset = byte_addr - RAM_ADDRS["location_flags"][0] + bit_mask = location["bit_mask"] if "bit_mask" in location else 0x20 + if flag_bytes[byte_offset] & bit_mask == bit_mask: + location_id = self.location_name_to_id[name] + self.local_checked_locations.add(location_id) + break + + for loc in ctx.locations_checked: + self.local_checked_locations.add(loc) # Send locations - if local_checked_locations != ctx.locations_checked: - self.local_checked_locations = local_checked_locations - if local_checked_locations is not None: - await ctx.send_msgs([{ - "cmd": "LocationChecks", - "locations": list(local_checked_locations) - }]) + if self.local_checked_locations != ctx.locations_checked: + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": list(self.local_checked_locations) + }]) # Send game clear if not ctx.finished_game and game_clear: diff --git a/worlds/tloz_oos/Logic.py b/worlds/tloz_oos/Logic.py index bcfeaeafc79e..ffd80dee2df4 100644 --- a/worlds/tloz_oos/Logic.py +++ b/worlds/tloz_oos/Logic.py @@ -1,9 +1,9 @@ from BaseClasses import MultiWorld -from worlds.tloz_oos.data.logic.dungeons import (make_d0_logic, make_d1_logic, make_d2_logic, make_d3_logic, - make_d4_logic, make_d5_logic, make_d6_logic, make_d7_logic, - make_d8_logic) -from worlds.tloz_oos.data.logic.holodrum import make_holodrum_logic -from worlds.tloz_oos.data.logic.subrosia import make_subrosia_logic +from worlds.tloz_oos.data.logic.DungeonsLogic import (make_d0_logic, make_d1_logic, make_d2_logic, make_d3_logic, + make_d4_logic, make_d5_logic, make_d6_logic, make_d7_logic, + make_d8_logic) +from worlds.tloz_oos.data.logic.OverworldLogic import make_holodrum_logic +from worlds.tloz_oos.data.logic.SubrosiaLogic import make_subrosia_logic def create_connections(multiworld: MultiWorld, player: int): diff --git a/worlds/tloz_oos/__init__.py b/worlds/tloz_oos/__init__.py index 6785bdb08185..b638580066b1 100644 --- a/worlds/tloz_oos/__init__.py +++ b/worlds/tloz_oos/__init__.py @@ -11,11 +11,12 @@ from .Logic import create_connections from .Options import * from .data import LOCATIONS_DATA -from .data.logic.constants import SEED_ITEMS, REGIONS_CONVERSION_TABLE, PORTALS_CONVERSION_TABLE, DUNGEON_NAMES, \ - SEASONS, COMPANIONS, ESSENCES, DIRECTIONS -from .data.logic.regions import REGIONS +from .data.Constants import SEED_ITEMS, REGIONS_CONVERSION_TABLE, PORTALS_CONVERSION_TABLE, DUNGEON_NAMES, \ + SEASONS, COMPANIONS, ESSENCES, DIRECTIONS, DUNGEON_ITEMS +from .data.Regions import REGIONS from .Client import OracleOfSeasonsClient # Unused, but required to register with BizHawkClient + class OracleOfSeasonsWeb(WebWorld): theme = "grass" tutorials = [Tutorial( @@ -200,7 +201,7 @@ def create_items(self): item_name = loc_data['vanilla_item'] if item_name == "Ricky's Gloves": # Ricky's gloves are useless in current logic - item_name = "Gasha Seed" + item_name = "Progressive Sword" elif item_name == "Rod of Seasons": # No lone rod of seasons supported for now item_name = "Fool's Ore" if self.options.fools_ore != "excluded" else "Gasha Seed" elif item_name == "Flute": @@ -208,6 +209,8 @@ def create_items(self): if "Ring" in item_name: ring_count += 1 + elif any([(string in item_name) for string in DUNGEON_ITEMS]): + self.pre_fill_items.append(self.create_item(item_name)) else: self.multiworld.itempool.append(self.create_item(item_name)) @@ -293,11 +296,12 @@ def generate_output(self, output_directory: str): yamlObj = { "settings": { "companion": self.options.animal_companion.value, - "warp_to_start": self.options.warp_to_start, + "warp_to_start": self.options.warp_to_start.current_key, "required_essences": self.options.required_essences.value, "fools_ore_damage": 3 if self.options.fools_ore == "balanced" else 12, - "heart_beep_interval": self.options.heart_beep_interval, - "lost_woods_item_sequence": "winter up winter right winter down summer left" + "heart_beep_interval": self.options.heart_beep_interval.current_key, + "lost_woods_item_sequence": ' '.join(self.lost_woods_item_sequence), + "slot_name": self.multiworld.get_player_name(self.player) }, "default seasons": {}, "locations": {} @@ -317,6 +321,8 @@ def generate_output(self, output_directory: str): yamlObj["subrosia portals"][PORTALS_CONVERSION_TABLE[portal_holo]] = PORTALS_CONVERSION_TABLE[portal_sub] for loc in self.multiworld.get_locations(self.player): + if loc.address is None: + continue item_name = loc.item.name if loc.item.player == loc.player else "Archipelago Item" yamlObj["locations"][loc.name] = item_name diff --git a/worlds/tloz_oos/data/logic/constants.py b/worlds/tloz_oos/data/Constants.py similarity index 95% rename from worlds/tloz_oos/data/logic/constants.py rename to worlds/tloz_oos/data/Constants.py index 90facfe1f5f7..2f110809ce18 100644 --- a/worlds/tloz_oos/data/logic/constants.py +++ b/worlds/tloz_oos/data/Constants.py @@ -84,4 +84,11 @@ "Blowing Wind", "Seed of Life", "Changing Seasons", -] \ No newline at end of file +] + +DUNGEON_ITEMS = [ + "Small Key", + "Boss Key", + "Compass", + "Dungeon Map" +] diff --git a/worlds/tloz_oos/data/Items.py b/worlds/tloz_oos/data/Items.py index dac808ac573b..f7a0a8f2dc4e 100644 --- a/worlds/tloz_oos/data/Items.py +++ b/worlds/tloz_oos/data/Items.py @@ -368,61 +368,51 @@ "Cuccodex": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x00 + 'id': 0x55 }, "Lon Lon Egg": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x01 + 'id': 0x56 }, "Ghastly Doll": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x02 + 'id': 0x57 }, "Iron Pot": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x03 + 'id': 0x35 }, "Lava Soup": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x04 + 'id': 0x38 }, "Goron Vase": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x05 + 'id': 0x39 }, "Fish": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x06 + 'id': 0x3a }, "Megaphone": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x07 + 'id': 0x3b }, "Mushroom": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x08 + 'id': 0x3c }, "Wooden Bird": { 'classification': ItemClassification.progression, - 'id': 0x41, - 'subid': 0x09 + 'id': 0x3d }, "Engine Grease": { 'classification': ItemClassification.progression, - 'id': 0x41 + 'id': 0x3e }, "Phonograph": { 'classification': ItemClassification.progression, - 'id': 0x41, + 'id': 0x3f }, "Gnarled Key": { diff --git a/worlds/tloz_oos/data/Locations.py b/worlds/tloz_oos/data/Locations.py index 1e7fc5a1c306..be4d74eec6a5 100644 --- a/worlds/tloz_oos/data/Locations.py +++ b/worlds/tloz_oos/data/Locations.py @@ -10,7 +10,8 @@ "maku tree": { "region_id": "maku tree", "vanilla_item": "Gnarled Key", - "flag_byte": 0xC80B # TODO: Might require more map flags (for different essence counts) + "flag_byte": [0xC80B, 0xC80C, 0xC82B, 0xC82C, 0xC82D, 0xC85B, 0xC85C, 0xC85D, 0xC87B] + # Maku Tree has several rooms depending on the amount of essences owned }, "horon village SW chest": { "region_id": "horon village SW chest", @@ -202,7 +203,8 @@ "shop, 150 rupees": { "region_id": "shop, 150 rupees", "vanilla_item": "Flute", - "local": True # TODO + "flag_byte": 0xC693, + "bit_mask": 0x80 }, "member's shop 1": { "region_id": "member's shop", @@ -256,13 +258,18 @@ "subrosia seaside": { "region_id": "subrosia seaside", "vanilla_item": "Star Ore", - "flag_byte": 0xC865 # or C866, C875, C876 + "flag_byte": [0xC865, 0xC866, 0xC875, 0xC876] }, "subrosian wilds chest": { "region_id": "subrosian wilds chest", "vanilla_item": "Blue Ore", "flag_byte": 0xC841 }, + "subrosian wilds digging spot": { + "region_id": "subrosian wilds digging spot", + "vanilla_item": "Rupees (100)", + "flag_byte": 0xC840 + }, "subrosia village chest": { "region_id": "subrosia village chest", "vanilla_item": "Red Ore", @@ -1010,8 +1017,8 @@ "vanilla_item": "Wooden Bird", "flag_byte": 0xC89C }, - "tick-tock trade": { - "region_id": "tick-tock trade", + "tick tock trade": { + "region_id": "tick tock trade", "vanilla_item": "Engine Grease", "flag_byte": 0xC883 }, diff --git a/worlds/tloz_oos/data/logic/regions.py b/worlds/tloz_oos/data/Regions.py similarity index 99% rename from worlds/tloz_oos/data/logic/regions.py rename to worlds/tloz_oos/data/Regions.py index 6c7e41d76b6d..1cb84aaeaa5b 100644 --- a/worlds/tloz_oos/data/logic/regions.py +++ b/worlds/tloz_oos/data/Regions.py @@ -15,7 +15,7 @@ "maple trade", "subrosian chef trade", "biggoron trade", - "tick-tock trade", + "tick tock trade", "ingo trade", "guru-guru trade", "mayor's gift", diff --git a/worlds/tloz_oos/data/logic/dungeons.py b/worlds/tloz_oos/data/logic/DungeonsLogic.py similarity index 99% rename from worlds/tloz_oos/data/logic/dungeons.py rename to worlds/tloz_oos/data/logic/DungeonsLogic.py index 309b03679e25..0aeb1c61f12f 100644 --- a/worlds/tloz_oos/data/logic/dungeons.py +++ b/worlds/tloz_oos/data/logic/DungeonsLogic.py @@ -1,4 +1,4 @@ -from worlds.tloz_oos.data.logic.predicates import * +from worlds.tloz_oos.data.logic.LogicPredicates import * def make_d0_logic(player: int): @@ -176,7 +176,7 @@ def make_d4_logic(player: int): ])], ["d4 north of entrance", "d4 pot puzzle", False, lambda state: all([ oos_has_bombs(state, player), - oos_can_break_pot(state, player) + oos_has_bracelet(state, player) ])], ["d4 north of entrance", "d4 maze chest", False, lambda state: any([ oos_can_trigger_lever_from_minecart(state, player), diff --git a/worlds/tloz_oos/data/logic/predicates.py b/worlds/tloz_oos/data/logic/LogicPredicates.py similarity index 95% rename from worlds/tloz_oos/data/logic/predicates.py rename to worlds/tloz_oos/data/logic/LogicPredicates.py index 58076306f314..c3379e9e0a67 100644 --- a/worlds/tloz_oos/data/logic/predicates.py +++ b/worlds/tloz_oos/data/logic/LogicPredicates.py @@ -1,5 +1,5 @@ from BaseClasses import CollectionState -from worlds.tloz_oos.data.logic.constants import DUNGEON_NAMES, SEASON_ITEMS, ESSENCES +from worlds.tloz_oos.data.Constants import DUNGEON_NAMES, SEASON_ITEMS, ESSENCES # Items predicates ############################################################ @@ -168,7 +168,13 @@ def oos_has_needed_essences(state: CollectionState, player: int): def oos_can_reach_lost_woods_pedestal(state: CollectionState, player: int): world = state.multiworld.worlds[player] return all([ - world.options.lost_woods_item_sequence == "vanilla" or state.has("Phonograph", player), + any([ + world.options.lost_woods_item_sequence == "vanilla", + all([ + oos_can_use_ember_seeds(state, player), + state.has("Phonograph", player) + ]) + ]), "winter" not in world.lost_woods_item_sequence or oos_has_winter(state, player), "spring" not in world.lost_woods_item_sequence or oos_has_spring(state, player), "summer" not in world.lost_woods_item_sequence or oos_has_summer(state, player), @@ -194,11 +200,15 @@ def oos_has_rupees(state: CollectionState, player: int, amount: int): if state.has("_reached_d6_rupee_room", player): rupees += 90 + # TODO: Count spendings by subtracting the price of all shop locations currently containing a progression item? + return rupees >= amount def oos_can_farm_rupees(state: CollectionState, player: int): - return oos_has_shovel(state, player) + # Farming rupees is not *that* hard by itself, but it's just really boring most of the time if you don't know how + # to optimize it efficiently doing RNG manips and such, so we let it in the hard logic domain for now + return oos_option_hard_logic(state, player) and oos_has_shovel(state, player) def oos_can_date_rosa(state: CollectionState, player: int): @@ -476,10 +486,17 @@ def oos_can_break_bush(state: CollectionState, player: int, can_summon_companion oos_has_sword(state, player), oos_has_magic_boomerang(state, player), oos_has_bracelet(state, player), - oos_has_bombs(state, player), - oos_can_use_ember_seeds(state, player), - oos_can_use_gale_seeds_offensively(state, player), - (can_summon_companion and oos_has_flute(state, player)) + (can_summon_companion and oos_has_flute(state, player)), + all([ + # Consumables need at least medium logic, since they need a good knowledge of the game + # not to be frustrating + oos_option_medium_logic(state, player), + any([ + oos_has_bombs(state, player), + oos_can_use_ember_seeds(state, player), + oos_can_use_gale_seeds_offensively(state, player), + ]) + ]), ]) diff --git a/worlds/tloz_oos/data/logic/holodrum.py b/worlds/tloz_oos/data/logic/OverworldLogic.py similarity index 98% rename from worlds/tloz_oos/data/logic/holodrum.py rename to worlds/tloz_oos/data/logic/OverworldLogic.py index 8c143f681d28..d32a2e74fabd 100644 --- a/worlds/tloz_oos/data/logic/holodrum.py +++ b/worlds/tloz_oos/data/logic/OverworldLogic.py @@ -1,4 +1,4 @@ -from worlds.tloz_oos.data.logic.predicates import * +from worlds.tloz_oos.data.logic.LogicPredicates import * def make_holodrum_logic(player: int): @@ -11,7 +11,7 @@ def make_holodrum_logic(player: int): ["horon village", "horon heart piece", False, lambda state: oos_can_use_ember_seeds(state, player)], ["horon village", "dr. left reward", False, lambda state: oos_can_use_ember_seeds(state, player)], ["horon village", "old man trade", False, lambda state: state.has("Fish", player)], - ["horon village", "tick-tock trade", False, lambda state: state.has("Wooden Bird", player)], + ["horon village", "tick tock trade", False, lambda state: state.has("Wooden Bird", player)], ["horon village", "maku tree", False, lambda state: oos_has_sword(state, player)], ["horon village", "horon village SE chest", False, lambda state: oos_has_bombs(state, player)], ["horon village", "horon village SW chest", False, lambda state: oos_can_break_mushroom(state, player, True)], @@ -336,18 +336,22 @@ def make_holodrum_logic(player: int): oos_can_break_bush(state, player, True), ])], - # Goron Mountain <-> North Horon <-> D1 <-> Treehouse <-> Spool swamp waterway - ["spool swamp south", "old man in treehouse", True, lambda state: oos_can_swim(state, player, True)], - ["old man in treehouse", "d1 island", True, lambda state: oos_can_swim(state, player, True)], - ["old man in treehouse", "cave south of mrs. ruul", False, lambda state: oos_can_swim(state, player, False)], + # Goron Mountain <-> North Horon <-> D1 island <-> Spool swamp waterway + ["spool swamp south", "d1 island", True, lambda state: oos_can_swim(state, player, True)], ["d1 island", "north horon", True, lambda state: oos_can_swim(state, player, True)], ["north horon", "goron mountain entrance", True, lambda state: oos_can_swim(state, player, True)], ["goron mountain entrance", "natzu region, across water", True, lambda state: oos_can_swim(state, player, True)], - ["ghastly stump", "old man in treehouse", True, lambda state: all([ + ["ghastly stump", "d1 island", True, lambda state: all([ oos_can_break_bush(state, player, True), oos_can_swim(state, player, True) ])], + ["d1 island", "old man in treehouse", False, lambda state: all([ + oos_can_swim(state, player, True), + oos_has_essences(state, player, 5) + ])], + ["d1 island", "cave south of mrs. ruul", False, lambda state: oos_can_swim(state, player, False)], + # SPOOL SWAMP ############################################################################################# ["spool swamp north", "spool swamp tree", False, lambda state: oos_can_harvest_tree(state, player, True)], diff --git a/worlds/tloz_oos/data/logic/subrosia.py b/worlds/tloz_oos/data/logic/SubrosiaLogic.py similarity index 94% rename from worlds/tloz_oos/data/logic/subrosia.py rename to worlds/tloz_oos/data/logic/SubrosiaLogic.py index 93a53c5e05cd..f6c06674a229 100644 --- a/worlds/tloz_oos/data/logic/subrosia.py +++ b/worlds/tloz_oos/data/logic/SubrosiaLogic.py @@ -1,4 +1,4 @@ -from worlds.tloz_oos.data.logic.predicates import * +from worlds.tloz_oos.data.logic.LogicPredicates import * def make_subrosia_logic(player: int): @@ -91,6 +91,15 @@ def make_subrosia_logic(player: int): oos_can_jump_4_wide_pit(state, player) ]) ])], + ["subrosian wilds chest", "subrosian wilds digging spot", False, lambda state: all([ + any([ + oos_can_jump_3_wide_pit(state, player), + oos_has_magnet_gloves(state, player) + ]), + oos_has_feather(state, player), + oos_has_shovel(state, player) + ])], + ["subrosia hide and seek sector", "subrosian house", False, lambda state: oos_has_feather(state, player)], ["subrosia hide and seek sector", "subrosian 2d cave", False, lambda state: oos_has_feather(state, player)], diff --git a/worlds/tloz_oos/docs/en_The Legend of Zelda Oracle of Seasons.md b/worlds/tloz_oos/docs/en_The Legend of Zelda Oracle of Seasons.md new file mode 100644 index 000000000000..90a79f8bd986 --- /dev/null +++ b/worlds/tloz_oos/docs/en_The Legend of Zelda Oracle of Seasons.md @@ -0,0 +1,60 @@ +# Landstalker: The Treasures of King Nole + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains most of the options you need to +configure and export a config file. + +## What does randomization do to this game? + +All items are shuffled while keeping a logic to make every seed completable. + +Some key items could be obtained in a very different order compared to the vanilla game, leading to very unusual situations. + +The world is made as open as possible while keeping the original locks behind the same items & triggers as vanilla +when that makes sense logic-wise. This puts the emphasis on exploration and gameplay by removing all the scenario +and story-related triggers, giving a wide open world to explore. + +## What items and locations get shuffled? + +All items and locations are shuffled. This includes **chests**, items on **ground**, in **shops**, and given by **NPCs**. + +It's also worth noting that all of these items are shuffled among all worlds, meaning every item can be sent to you +by other players. + +## What are the main differences compared to the vanilla game? + +The **Key** is now a unique item and can open several doors without being consumed, making it a standard progression item. +All key doors are gone, except three of them : + - the Mercator castle backdoor (giving access to Greenmaze sector) + - Thieves Hideout middle door (cutting the level in half) + - King Nole's Labyrinth door near entrance + +--- + +The secondary shop of Mercator requiring to do the traders sidequest in the original game is now unlocked by having +**Buyer Card** in your inventory. + +You will need as many **jewels** as specified in the settings to use the teleporter to go to Kazalt and the final dungeon. +If you find and use the **Lithograph**, it will tell you in which world are each one of your jewels. + +Each seed, there is a random dungeon which is chosen to be the "dark dungeon" where you won't see anything unless you +have the **Lantern** in your inventory. Unlike vanilla, King Nole's Labyrinth no longer has the few dark rooms the lantern +was originally intended for. + +The **Statue of Jypta** is introduced as a real item (instead of just being an intro gimmick) and gives you gold over +time while you're walking, the same way Healing Boots heal you when you walk. + + +## What do I need to know for my first seed? + +It's advised you keep Massan as your starting region for your first seed, since taking another starting region might +be significantly harder, both combat-wise and logic-wise. + +Having fully open & shuffled teleportation trees is an interesting way to play, but is discouraged for beginners +as well since it can force you to go in late-game zones with few Life Stocks. + +Overall, the default settings are good for a beginner-friendly seed, and if you don't feel too confident, you can also +lower the combat difficulty to make it more forgiving. + +*Have fun on your adventure!* diff --git a/worlds/tloz_oos/docs/oos_setup_en.md b/worlds/tloz_oos/docs/oos_setup_en.md new file mode 100644 index 000000000000..9f453c146de3 --- /dev/null +++ b/worlds/tloz_oos/docs/oos_setup_en.md @@ -0,0 +1,119 @@ +# Landstalker Setup Guide + +## Required Software + +- [Landstalker Archipelago Client](https://github.com/Dinopony/randstalker-archipelago/releases) (only available on Windows) +- A compatible emulator to run the game + - [RetroArch](https://retroarch.com?page=platforms) with the Genesis Plus GX core + - [Bizhawk 2.9.1 (x64)](https://tasvideos.org/BizHawk/ReleaseHistory) with the Genesis Plus GX core +- Your legally obtained Landstalker US ROM file (which can be acquired on [Steam](https://store.steampowered.com/app/71118/Landstalker_The_Treasures_of_King_Nole/)) + +## Installation Instructions + +- Unzip the Landstalker Archipelago Client archive into its own folder +- Put your Landstalker ROM (`LandStalker_USA.SGD` on the Steam release) inside this folder +- To launch the client, launch `randstalker_archipelago.exe` inside that folder + +Be aware that you might get antivirus warnings about the client program because one of its main features is to spy +on another process's memory (your emulator). This is something antiviruses obviously dislike, and sometimes mistake +for malicious software. + +If you're not trusting the program, you can check its [source code](https://github.com/Dinopony/randstalker-archipelago/) +or test it on a service like Virustotal. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The [Player Settings Page](../player-settings) on the website allows you to easily configure your personal settings +and export a config file from them. + +## How-to-play + +### Connecting to the Archipelago Server + +Once the game has been created, you need to connect to the server using the Landstalker Archipelago Client. + +To do so, run `randstalker_archipelago.exe` inside the folder you created while installing the software. + +A window will open with a few settings to enter: +- **Host**: Put the server address and port in this field (e.g. `archipelago.gg:12345`) +- **Slot name**: Put the player name you specified in your YAML config file in this field. +- **Password**: If the server has a password, put it there. + +![Landstalker Archipelago Client user interface](/static/generated/docs/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/ls_guide_ap.png) + +Once all those fields were filled appropriately, click on the `Connect to Archipelago` button below to try connecting to +the Archipelago server. + +If this didn't work, double-check your credentials. An error message should be displayed on the console log to the +right that might help you find the cause of the issue. + +### ROM Generation + +When you connected to the Archipelago server, the client fetched all the required data from the server to be able to +build a randomized ROM. + +You should see a window with settings to fill: +- **Input ROM file**: This is the path to your original ROM file for the game. If you are using the Steam release ROM + and placed it inside the client's folder as mentioned above, you don't need to change anything. +- **Output ROM directory**: This is where the randomized ROMs will be put. No need to change this unless you want them + to be created in a very specific folder. + +![Landstalker Archipelago Client user interface](/static/generated/docs/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/ls_guide_rom.png) + +There also a few cosmetic options you can fill before clicking the `Build ROM` button which should create your +randomized seed if everything went right. + +If it didn't, double-check your `Input ROM file` and `Output ROM path`, then retry building the ROM by clicking +the same button again. + +### Connecting to the emulator + +Now that you're connected to the Archipelago server and have a randomized ROM, all we need is to get the client +connected to the emulator. This way, the client will be able to see what's happening while you play and give you in-game +the items you have received from other players. + +You should see the following window: + +![Landstalker Archipelago Client user interface](/static/generated/docs/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/ls_guide_emu.png) + +As written, you have to open the newly generated ROM inside either Retroarch or Bizhawk using the Genesis Plus GX core. +Be careful to select that core, because any other core (e.g. BlastEm) won't work. + +The easiest way to do so is to: +- open the emu of your choice +- if you're using Retroarch and it's your first time, download the Genesis Plus GX core through Retroarch user interface +- click the `Show ROM file in explorer` button +- drag-and-drop the shown ROM file on the emulator window +- press Start to reach file select screen (to ensure game RAM is properly set-up) + +Then, you can click on the `Connect to emulator` button below and it should work. + +If this didn't work, try the following: +- ensure you have loaded your ROM and reached the save select screen +- ensure you are using Genesis Plus GX and not another core (e.g. BlastEm will not work) +- try launching the client in Administrator Mode (right-click on `randstalker_archipelago.exe`, then + `Run as administrator`) +- if all else fails, try using one of those specific emulator versions: + - RetroArch 1.9.0 and Genesis Plus GX 1.7.4 + - Bizhawk 2.9.1 (x64) + +### Play the game + +If all indicators are green and show "Connected," you're good to go! Play the game and enjoy the wonders of isometric +perspective. + +The client is packaged with both an **automatic item tracker** and an **automatic map tracker** for your comfort. + +If you don't know all checks in the game, don't be afraid: you can click the `Where is it?` button that will show +you a screenshot of where the location actually is. + +![Landstalker Archipelago Client user interface](/static/generated/docs/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/ls_guide_client.png) + +Have fun! \ No newline at end of file