diff --git a/worlds/ffx/__init__.py b/worlds/ffx/__init__.py index 2f2d015269bb..a35d11af5f76 100644 --- a/worlds/ffx/__init__.py +++ b/worlds/ffx/__init__.py @@ -3,19 +3,19 @@ """ from typing import ClassVar, Any, Optional -from random import Random, shuffle +from random import choice, Random, shuffle from settings import Group, FilePath from BaseClasses import Tutorial, Item, ItemClassification, LocationProgressType from worlds.AutoWorld import WebWorld, World from Options import OptionGroup -from rule_builder.cached_world import CachedRuleBuilderWorld from Utils import visualize_regions from .client import FFXClient from .items import create_item_label_to_code_map, item_table, key_items, filler_items, AllItems, FFXItem, \ - party_member_items, stat_abilities, skill_abilities, region_unlock_items, trap_items, equip_items + party_member_items, stat_abilities, skill_abilities, region_unlock_items, trap_items, equip_items, \ + overdrive_items from .locations import create_location_label_to_id_map, FFXLocation, allLocations from .regions import create_regions from .options import FFXOptions, create_option_groups @@ -51,8 +51,7 @@ class UTPoptrackerPath(FilePath): ut_poptracker_path: UTPoptrackerPath | str = UTPoptrackerPath() -class FFXWorld(CachedRuleBuilderWorld): -# class FFXWorld(World): +class FFXWorld(World): """ Final Fantasy X is a game """ @@ -96,6 +95,7 @@ def create_items(self): required_items = [] + # ----------------------------- Key Items ---------------------------- # for item in key_items: match item.itemName: case "Progressive Jecht's Sphere": @@ -107,7 +107,7 @@ def create_items(self): case _: required_items.append(item.itemName) - # Progressive celestial weapons and Brotherhood + # ------------------ Celestial Weapons & Brotherhood ----------------- # for item in equip_items: if item.progression == ItemClassification.progression: if item.itemID & 0x0FFF == 0x0001: @@ -117,30 +117,34 @@ def create_items(self): # Celestial required_items.extend([item.itemName]*3) + # ----------------------------- Abilities ---------------------------- # # for item in skill_abilities: # required_items.append(item.itemName) # # for item in stat_abilities: # required_items.extend([item.itemName for _ in range(1)]) + # -------------------------- Starting Region ------------------------- # possible_starting_regions = [f"Region: {region}" for region, level in world_battle_levels.items() if 0 < level <= min(self.options.logic_difficulty.value, 3)] starting_region = self.random.choice(possible_starting_regions) self.multiworld.push_precollected(self.create_item(starting_region)) + # --------------------------- Monster Arena -------------------------- # if self.options.arena_access.value == self.options.arena_access.option_early: self.multiworld.early_items[self.player]["Region: Monster Arena"] = 1 elif self.options.arena_access.value == self.options.arena_access.option_always: self.multiworld.push_precollected(self.create_item("Region: Monster Arena")) + # ------------------------ Region Unlock Items ----------------------- # for item in region_unlock_items: if item.itemName != starting_region: required_items.append(item.itemName) if self.options.arena_access.value == self.options.arena_access.option_always: required_items.remove("Region: Monster Arena") - + # --------------------------- Party Members -------------------------- # starting_character = party_member_items[0] self.multiworld.push_precollected(self.create_item(starting_character.itemName)) for party_member in party_member_items: @@ -153,22 +157,37 @@ def create_items(self): shuffle(partyMembers) for i in range(self.options.early_party_members.value): self.multiworld.early_items[self.player][partyMembers.pop(0).itemName] = 1 - - + + # ---------------------------- Overdrives ---------------------------- # + for overdrive in overdrive_items: + required_items.append(overdrive.itemName) + + if self.options.tidus_early_overdrive_access.value is self.options.tidus_early_overdrive_access.option_early: + overdrive = choice(overdrive_items[:4]) + self.multiworld.early_items[self.player][overdrive.itemName] = 1 + if self.options.tidus_early_overdrive_access.value is self.options.tidus_early_overdrive_access.option_start_with: + overdrive = choice(overdrive_items[:4]) + self.multiworld.push_precollected(self.create_item(overdrive.itemName)) + required_items.remove(overdrive.itemName) + + # ------------------------ Unfilled Locations ------------------------ # unfilled_locations = len(self.multiworld.get_unfilled_locations(self.player)) - items_remaining = unfilled_locations - len(required_items) + # ------------------------ Excluded Locations ------------------------ # for _ in self.options.exclude_locations.value: self.multiworld.itempool.append(self.create_filler()) items_remaining -= 1 + # ---------------------- Items & Traps Remaining --------------------- # traps_remaining = int(items_remaining * self.options.trap_percentage.value / 100) items_remaining = items_remaining - traps_remaining + # ----------------- Add Required Items to Multiworld ----------------- # for itemName in required_items: self.multiworld.itempool.append(self.create_item(itemName)) + # ------------------------ Set up Useful Items ----------------------- # useful_items = [] for item in AllItems: if item.progression == ItemClassification.useful: @@ -176,11 +195,13 @@ def create_items(self): self.random.shuffle(useful_items) + # -------------------------- Generate Traps -------------------------- # traps = [trap.itemName for trap in self.random.choices(trap_items, k=traps_remaining)] for trap in traps: self.multiworld.itempool.append(self.create_item(trap)) + # ----------------------- Fill Remaining items ----------------------- # for i in range(items_remaining): if i > len(useful_items) - 1: self.multiworld.itempool.append(self.create_filler()) @@ -211,6 +232,8 @@ def fill_slot_data(self) -> dict[str, Any]: "mini_game_cactuar_village": self.options.mini_game_cactuar_village.value, "mini_game_chocobo_training": self.options.mini_game_chocobo_training.value, "mini_game_chocobo_race": self.options.mini_game_chocobo_race.value, + "tidus_overdrive": self.options.tidus_overdrives.value, + "kimahri_ronso_rage": self.options.kimahri_ronso_rages.value, "recruit_sanity": self.options.recruit_sanity.value, "capture_sanity": self.options.capture_sanity.value, "creation_rewards": self.options.creation_rewards.value, diff --git a/worlds/ffx/data/regions.json b/worlds/ffx/data/regions.json index fc7cb25d9cff..509ae5250aaf 100644 --- a/worlds/ffx/data/regions.json +++ b/worlds/ffx/data/regions.json @@ -42,7 +42,7 @@ "treasures": [268, 9, 283, 285, 284, 282, 90, 91, 92, 13, 14, 215, 216, 15, 459, 290, 288, 287, 289, 286], "party_members": [1, 4, 5, 8], "bosses": [], - "overdrives": [21], + "overdrives": [109], "other": [0, 2], "recruits": [52], "leads_to": [11], @@ -106,7 +106,7 @@ "overdrives": [], "other": [4], "recruits": [13, 14, 15, 16, 38], - "leads_to": [21], + "leads_to": [21, 1111], "rules": ["Kilika"] }, { @@ -176,7 +176,7 @@ "treasures": [497, 93, 271, 244], "party_members": [2], "bosses": [7], - "overdrives": [], + "overdrives": [21, 22, 23], "other": [], "recruits": [7, 8, 9, 10, 11, 12, 25, 26, 27, 28, 29, 30, 40], "leads_to": [32], @@ -204,7 +204,7 @@ "overdrives": [], "other": [8], "recruits": [44], - "leads_to": [41, 1010], + "leads_to": [41, 1010, 1110, 1112], "rules": ["Mi'ihen Highroad"] }, { @@ -241,7 +241,7 @@ "overdrives": [], "other": [10], "recruits": [], - "leads_to": [51, 1010, 1011, 1012, 1013], + "leads_to": [51, 1010, 1011, 1012, 1013, 1110, 1112], "rules": ["Mushroom Rock Road"] }, { @@ -278,7 +278,7 @@ "overdrives": [], "other": [11], "recruits": [39], - "leads_to": [61, 1012, 1013, 1014], + "leads_to": [61, 1012, 1013, 1014, 1114], "rules": ["Djose"] }, { @@ -390,7 +390,7 @@ "overdrives": [], "other": [15], "recruits": [], - "leads_to": [101], + "leads_to": [101, 1115], "rules": ["Macalania"] }, { @@ -487,7 +487,7 @@ "overdrives": [], "other": [18, 17], "recruits": [], - "leads_to": [112, 1001, 1002, 1003], + "leads_to": [112, 1001, 1002, 1003, 1111], "rules": ["Zu"] }, { @@ -499,7 +499,7 @@ "overdrives": [], "other": [19, 21, 20], "recruits": [], - "leads_to": [113], + "leads_to": [113, 1110, 1112, 1115], "rules": [] }, { @@ -524,7 +524,7 @@ "overdrives": [], "other": [], "recruits": [1, 19, 20, 21, 22, 23, 24, 37, 57], - "leads_to": [121], + "leads_to": [121, 1110, 1112], "rules": ["Airship"] }, { @@ -585,7 +585,7 @@ "overdrives": [], "other": [22], "recruits": [], - "leads_to": [131], + "leads_to": [131, 1113], "rules": ["Bevelle"] }, { @@ -646,7 +646,7 @@ "overdrives": [], "other": [23], "recruits": [48, 51], - "leads_to": [141, 142, 1015, 1101], + "leads_to": [141, 142, 1015, 1101, 1114, 1115, 1118], "rules": ["Calm Lands"] }, { @@ -695,7 +695,7 @@ "overdrives": [], "other": [25], "recruits": [49], - "leads_to": [151, 1016, 1015, 1022], + "leads_to": [151, 1016, 1015, 1022, 1110, 1116, 1118], "rules": ["Cavern of the Stolen Fayth"] }, @@ -716,15 +716,27 @@ { "name": "Monster Arena", "id": 200, - "treasures": [113, 276, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 496], + "treasures": [113, 276, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458], "party_members": [], - "bosses": [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83], + "bosses": [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82], "overdrives": [], "other": [], "recruits": [], - "leads_to": [], + "leads_to": [201], "rules": ["Monster Arena"] }, + { + "name": "Monster Arena: Post-Nemesis", + "id": 201, + "treasures": [496], + "party_members": [], + "bosses": [83], + "overdrives": [], + "other": [], + "recruits": [], + "leads_to": [1120], + "rules": ["Nemesis"] + }, { @@ -732,7 +744,7 @@ "id": 160, "treasures": [], "party_members": [], - "bosses": [32], + "bosses": [], "overdrives": [], "other": [], "recruits": [], @@ -744,11 +756,11 @@ "id": 161, "treasures": [128, 129, 130, 131, 132], "party_members": [], - "bosses": [], + "bosses": [32], "overdrives": [], "other": [33], "recruits": [], - "leads_to": [162, 1016, 1017], + "leads_to": [162, 1016, 1017, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1119], "rules": ["Biran and Yenke"] }, { @@ -760,7 +772,7 @@ "overdrives": [], "other": [], "recruits": [], - "leads_to": [163, 1018, 1019], + "leads_to": [163, 1018, 1019, 1110, 1117, 1119], "rules": ["Seymour Flux"] }, { @@ -785,7 +797,7 @@ "overdrives": [], "other": [], "recruits": [], - "leads_to": [171, 1017, 1018, 1019], + "leads_to": [171, 1017, 1018, 1019, 1110, 1113, 1117, 1119], "rules": ["Zanarkand Ruins"] }, { @@ -834,7 +846,7 @@ "overdrives": [], "other": [], "recruits": [], - "leads_to": [181, 1019, 1020, 1023], + "leads_to": [181, 1019, 1020, 1023, 1119], "rules": ["Sin"] }, { @@ -846,7 +858,7 @@ "overdrives": [], "other": [], "recruits": [], - "leads_to": [182, 1021], + "leads_to": [182, 1021, 1114, 1116, 1118], "rules": ["Seymour Omnis"] }, { @@ -871,7 +883,7 @@ "overdrives": [], "other": [26], "recruits": [], - "leads_to": [191, 1020, 1021], + "leads_to": [191, 1020, 1021, 1112, 1114, 1116, 1117, 1118], "rules": ["Omega Ruins"] }, { @@ -895,7 +907,7 @@ "overdrives": [], "other": [], "recruits": [], - "leads_to": [1023], + "leads_to": [1023, 1120], "rules": ["Omega Weapon"] }, @@ -947,6 +959,7 @@ "leads_to": [], "rules": [] }, + { "name": "Captures: Miihen Highroad & MRR", "id": 1010, @@ -1115,6 +1128,7 @@ "leads_to": [], "rules": [] }, + { "name": "Summoner's Soul", "id": 1100, @@ -1138,5 +1152,138 @@ "recruits": [], "leads_to": [], "rules": ["Belgemine"] + }, + + { + "name": "Fire Breath", + "id": 1110, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [9], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Seed Cannon", + "id": 1111, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [10], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Self Destruct", + "id": 1112, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [11], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Thrust Kick", + "id": 1113, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [12], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Stone Breath", + "id": 1114, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [13], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Aqua Breath", + "id": 1115, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [14], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Doom", + "id": 1116, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [15], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "White Wind", + "id": 1117, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [16], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Bad Breath", + "id": 1118, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [17], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Mighty Guard", + "id": 1119, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [18], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] + }, + { + "name": "Nova", + "id": 1120, + "treasures": [], + "party_members": [], + "bosses": [], + "overdrives": [19], + "other": [], + "recruits": [], + "leads_to": [], + "rules": ["Ronso Rage"] } ] \ No newline at end of file diff --git a/worlds/ffx/generate.py b/worlds/ffx/generate.py index 85845ff17d69..1e179d59e5f8 100644 --- a/worlds/ffx/generate.py +++ b/worlds/ffx/generate.py @@ -70,6 +70,7 @@ def generate_output(world: FFXWorld, player: int, output_directory: str) -> None "AlwaysCapture": world.options.always_capture.value, "CaptureDamage": world.options.capture_damage.value, "SkipContestOfAeons": world.options.skip_contest_of_aeons.value, + "OverdriveModes": world.options.overdrive_modes.value, } locations: dict[str, list[dict[str, int | str] | int] | str] = {x: list() for x in location_types.values()} diff --git a/worlds/ffx/items.py b/worlds/ffx/items.py index d8f5184d4497..42ebc9119da2 100644 --- a/worlds/ffx/items.py +++ b/worlds/ffx/items.py @@ -20,15 +20,16 @@ class FFXItem(Item): game: str = "Final Fantasy X" +gilItemOffset = 0x1000 normalItemOffset = 0x2000 -keyItemOffset = 0xA000 +overdriveItemOffset = 0x3000 equipItemOffset = 0x5000 -partyMemberItemOffset = 0xF000 -regionItemOffset = 0xE000 -abilityItemOffset = 0xD000 -gilItemOffset = 0x1000 trapItemOffset = 0x9000 +keyItemOffset = 0xA000 otherItemOffset = 0xC000 +abilityItemOffset = 0xD000 +regionItemOffset = 0xE000 +partyMemberItemOffset = 0xF000 @@ -376,6 +377,63 @@ class FFXItem(Item): "Magus Sisters", ] +overdrive_names = [ + "Spiral Cut", + "Slice and Dice", + "Energy Rain", + "Blitz Ace", + "Dragon Fang", + "Shooting Star", + "Banishing Blade", + "Tornado", + "Jump", + "Fire Breath", + "Seed Cannon", + "Self Destruct", + "Thrust Kick", + "Stone Breath", + "Aqua Breath", + "Doom", + "White Wind", + "Bad Breath", + "Mighty Guard", + "Nova", + "Element Reels", + "Attack Reels", + "Status Reels", + "Aurochs Reels", + "Requiem", +] + +overdrive_items: list[ItemData] = [ItemData(x[0], x[1], x[2] | overdriveItemOffset) for x in [ + ("Swordplay: Spiral Cut", ItemClassification.progression, 0x0060), + ("Swordplay: Slice and Dice", ItemClassification.progression, 0x0061), + ("Swordplay: Energy Rain", ItemClassification.progression, 0x0062), + ("Swordplay: Blitz Ace", ItemClassification.progression, 0x0063), + ("Bushido: Shooting Star", ItemClassification.progression, 0x0064), + ("Bushido: Dragon Fang", ItemClassification.progression, 0x0065), + ("Bushido: Banishing Blade", ItemClassification.progression, 0x0066), + ("Bushido: Tornado", ItemClassification.progression, 0x0067), + ("Ronso Rage: Jump", ItemClassification.progression, 0x0068), + ("Ronso Rage: Fire Breath", ItemClassification.progression, 0x0069), + ("Ronso Rage: Seed Cannon", ItemClassification.progression, 0x006A), + ("Ronso Rage: Self Destruct", ItemClassification.progression, 0x006B), + ("Ronso Rage: Thrust Kick", ItemClassification.progression, 0x006C), + ("Ronso Rage: Stone Breath", ItemClassification.progression, 0x006D), + ("Ronso Rage: Aqua Breath", ItemClassification.progression, 0x006E), + ("Ronso Rage: Doom", ItemClassification.progression, 0x006F), + ("Ronso Rage: White Wind", ItemClassification.progression, 0x0070), + ("Ronso Rage: Bad Breath", ItemClassification.progression, 0x0071), + ("Ronso Rage: Mighty Guard", ItemClassification.progression, 0x0072), + ("Ronso Rage: Nova", ItemClassification.progression, 0x0073), + ("Slots: Element Reels", ItemClassification.progression, 0x0074), + ("Slots: Attack Reels", ItemClassification.progression, 0x0075), + ("Slots: Status Reels", ItemClassification.progression, 0x0076), + ("Slots: Aurochs Reels", ItemClassification.progression, 0x0077), + ("Overdrive: Energy Blast", ItemClassification.progression, 0x00CD), + ("Overdrive: Requiem", ItemClassification.progression, 0x00E3), +]] + abilities_per_character: list[ItemData] = [ ItemData(f"{character_names[character]} {ability[0]}", ItemClassification.progression, ability[1] | abilityItemOffset | character << 8) for character in range(7) for ability in [ # Lvl 3 lock # Empty node @@ -902,7 +960,8 @@ class FFXItem(Item): party_member_items, region_unlock_items, trap_items, - other_items)) + other_items, + overdrive_items)) filler_items: list[ItemData] = [item for item in AllItems if item.progression == ItemClassification.filler] diff --git a/worlds/ffx/locations.py b/worlds/ffx/locations.py index 376681b4c967..1a3624473977 100644 --- a/worlds/ffx/locations.py +++ b/worlds/ffx/locations.py @@ -21,13 +21,13 @@ class FFXLocationData(NamedTuple): TreasureOffset: int = 0x1000 BossOffset: int = 0x2000 -PartyMemberOffset: int = 0x3000 -OverdriveOffset: int = 0x4000 +OverdriveOffset: int = 0x3000 OverdriveModeOffset: int = 0x5000 OtherOffset: int = 0x6000 RecruitOffset: int = 0x7000 SphereGridOffset: int = 0x8000 CaptureOffset: int = 0x9000 +PartyMemberOffset: int = 0xF000 location_types: Dict[int, str] = { TreasureOffset: "Treasure", @@ -185,27 +185,31 @@ def get_location_type(location_id: int): ]] FFXOverdriveLocations: List[FFXLocationData] = [ FFXLocationData(location[1]+OverdriveOffset, *location) for location in [ - ("Slice and Dice", 1, False), - ("Energy Rain", 2, False), - ("Blitz Ace", 3, False), - ("Shooting Star", 4, False), - ("Banishing Blade", 5, False), - ("Tornado", 6, False), - ("Attack Reels", 7, False), - ("Status Reels", 8, False), - ("Auroch Reels", 9, False), - ("Seed Cannon", 10, False), #Ragora, Grat, Sandragora, Ragora (m039, m040, m221, m234) - ("Stone Breath", 11, False), #Basilisk, Anacondaur, Demonolith(?), Yenke Ronso, (m185, m186, m095, m135) - ("Self Destruct", 12, False), #Bomb, Grenade, Puroboros, Biran Ronso (, m134) - ("Fire Breath", 13, False), #Dual Horn, Valaha, Grendel, Yenke Ronso (m055, m056, m057, m135) - ("Aqua Breath", 14, False), #Chimera, Chimera Brain, Chimera, Yenke Ronso (m087, m088, m227, m135) - ("Bad Breath", 15, False), #Malboro, Great Malboro (m064, m065) - ("Doom", 16, False), #Ghost, Wraith, Biran Ronso (m050, m220, m134) - ("Thrust Kick", 17, False), #YKT-63, YKT-11, Biran Ronso (m195, m196, m134) - ("White Wind", 18, False), #Dark Flan, Spirit, Yenke Ronso (m021, m219, m135) - ("Mighty Guard", 19, False), #Behemoth, Behemoth King, Biran Ronso (m085, m086, m134) - ("Nova", 20, False), #Omega Weapon, Nemesis (m100, m276) - ("BSIL: Village, House - Something Mangled and Slobbery from Dog (NPC)", 21, False), #Energy Blast + # ("Swordplay: Spiral Cut", 0, False), + ("Swordplay: Use Tidus's Overdrive 10 Times (Slice and Dice)", 1, False), + ("Swordplay: Use Tidus's Overdrive 20 Times (Energy Rain)", 2, False), + ("Swordplay: Use Tidus's Overdrive 40 Times (Blitz Ace)", 3, False), + ("Bushido: Collect 1 Progressive Jecht's Sphere (Shooting Star)", 4, False), + # ("Bushido: Dragon Fang", 5, False), + ("Bushido: Collect 3 Progressive Jecht's Spheres (Banishing Blade)", 6, False), + ("Bushido: Collect 10 Progressive Jecht's Spheres (Tornado)", 7, False), + # ("Ronso Rage: Jump", 8, False), + ("Ronso Rage: Use Lancet to Learn Fire Breath", 9, False), #Dual Horn, Valaha, Grendel, Yenke Ronso (m055, m056, m057, m135) + ("Ronso Rage: Use Lancet to Learn Seed Cannon", 10, False), #Ragora, Grat, Sandragora, Ragora (m039, m040, m221, m234) + ("Ronso Rage: Use Lancet to Learn Self Destruct", 11, False), #Bomb, Grenade, Puroboros, Biran Ronso (, m134) + ("Ronso Rage: Use Lancet to Learn Thrust Kick", 12, False), #YKT-63, YKT-11, Biran Ronso (m195, m196, m134) + ("Ronso Rage: Use Lancet to Learn Stone Breath", 13, False), #Basilisk, Anacondaur, Demonolith(?), Yenke Ronso, (m185, m186, m095, m135) + ("Ronso Rage: Use Lancet to Learn Aqua Breath", 14, False), #Chimera, Chimera Brain, Chimera, Yenke Ronso (m087, m088, m227, m135) + ("Ronso Rage: Use Lancet to Learn Doom", 15, False), #Ghost, Wraith, Biran Ronso (m050, m220, m134) + ("Ronso Rage: Use Lancet to Learn White Wind", 16, False), #Dark Flan, Spirit, Yenke Ronso (m021, m219, m135) + ("Ronso Rage: Use Lancet to Learn Bad Breath", 17, False), #Malboro, Great Malboro (m064, m065) + ("Ronso Rage: Use Lancet to Learn Mighty Guard", 18, False), #Behemoth, Behemoth King, Biran Ronso (m085, m086, m134) + ("Ronso Rage: Use Lancet to Learn Nova", 19, False), #Omega Weapon, Nemesis (m100, m276) + # ("Slots: Element Reels", 20, False), + ("Slots: Come 1st in a Blitzball Tournament (Attack Reels)", 21, False), + ("Slots: Come 1st in a Blitzball League After Obtaining Attack Reels (Status Reels)", 22, False), + ("Slots: Come 1st in a Blitzball Tournament After Obtaining both Attack & Status Reels (Aurochs Reels)", 23, False), + ("BSIL: Village, House - Something Mangled and Slobbery from Dog (NPC)", 109, False), #Energy Blast ]] FFXOverdriveModeLocations: List[FFXLocationData] = [ FFXLocationData(location[1]+OverdriveModeOffset, *location) for location in [ diff --git a/worlds/ffx/options.py b/worlds/ffx/options.py index bff1bd21dde6..151596c5584e 100644 --- a/worlds/ffx/options.py +++ b/worlds/ffx/options.py @@ -64,14 +64,18 @@ class MiniGameBlitzball(Choice): This includes: - Luca Story Blitzball win - World Champion + - Wakka's Overdrives - Jupiter Sigil """ display_name = "Blitzball" default = 0 option_off = 0 - option_up_to_story = 1 - option_up_to_celestial = 2 - option_up_to_sigil = 3 + option_up_to_story = 1 + option_up_to_world_champion = 2 + option_up_to_attack_reels = 3 + option_up_to_status_reels = 4 + option_up_to_aurochs_reels = 5 + option_up_to_sigil = 6 class MiniGameButterflies(Toggle): @@ -247,6 +251,57 @@ class JechtSpheres(Toggle): option_on = 1 +class TidusOverdrives(Choice): + """ + Sets whether Tidus's Overdrive locations are included or not. If off they will only have filler items. + Default is off. + """ + display_name = "Tidus's Overdrives" + default = 0 + option_off = 0 + option_up_to_slice_and_dice = 1 + option_up_to_energy_rain = 2 + option_up_to_blitz_ace = 3 + + +class TidusEarlyOverdriveAccess(Choice): + """ + This option can provide early access to a random one of Tidus's overdrives, to allow for earlier logical access to his overdrive locations. + Tidus requires at least one of his overdrives to be able to begin grinding towards his others. + - Off: Tidus begins with zero overdrives, and his overdrive locations will only be in logic once he has access to at least one, as well as an appropriate number of combat regions. + - Early: A random one of Tidus's overdrives will be placed globally in sphere 1. Otherwise, the same as 'Off'. + - Start With: Tidus will start with one random overdrive. His overdrive locations will be in logic immediately depending on the number of combat regions available. + Default is off. + """ + display_name = "Tidus's Early Overdrive Access" + default = 0 + option_off = 0 + option_early = 1 + option_start_with = 2 + + +class KimahriRonsoRages(Toggle): + """ + Sets whether Kimahri's Ronso Rage locations are included or not. If off they will only have filler items. + Default is off. + """ + display_name = "Kimahri's Ronso Rages" + default = 0 + option_off = 0 + option_on = 1 + + +class OverdriveModes(Toggle): + """ + Sets whether you will start with all overdrive modes enabled for each chararcter. + Default is off. + """ + display_name = "Overdrive Modes" + default = 0 + option_off = 0 + option_on = 1 + + class LogicDifficulty(Range): """ Sets how strict the logic is for region access. Higher is harder / less restrictive. @@ -359,6 +414,10 @@ class FFXOptions(PerGameCommonOptions): arena_bosses: MonsterArenaBosses super_bosses: SuperBosses jecht_spheres: JechtSpheres + tidus_overdrives: TidusOverdrives + tidus_early_overdrive_access: TidusEarlyOverdriveAccess + kimahri_ronso_rages: KimahriRonsoRages + overdrive_modes: OverdriveModes trap_percentage: TrapPercentage logic_difficulty: LogicDifficulty early_party_members: EarlyPartyMembers @@ -408,6 +467,13 @@ def create_option_groups() -> list[OptionGroup]: MiniGameChocoboRace, ], + "Overdrive Options": [ + TidusOverdrives, + TidusEarlyOverdriveAccess, + KimahriRonsoRages, + OverdriveModes, + ], + "Monster Arena Options": [ CaptureSanity, MonsterArenaAccess, diff --git a/worlds/ffx/regions.py b/worlds/ffx/regions.py index 8eef8c0cc6c5..e9389a83ae9a 100644 --- a/worlds/ffx/regions.py +++ b/worlds/ffx/regions.py @@ -7,7 +7,7 @@ from .locations import FFXLocation, FFXTreasureLocations, FFXPartyMemberLocations, FFXBossLocations, \ FFXOverdriveLocations, FFXOtherLocations, FFXRecruitLocations, \ FFXSphereGridLocations, FFXCaptureLocations, FFXLocationData, TreasureOffset, BossOffset, PartyMemberOffset, \ - RecruitOffset, CaptureOffset, OtherOffset + RecruitOffset, CaptureOffset, OtherOffset, OverdriveOffset from rule_builder.rules import Rule, Has from .rules import regionRuleDict, regionBossRuleDict, staticEncounterRuleDict, GoalRequirementRule, PrimerRequirementRule from .items import party_member_items, key_items, FFXItem @@ -202,7 +202,6 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: all_locations = [] # ------------------------ Add Locations by Region ----------------------- # - for region_data in region_data_list: new_region = Region(region_data.name, player, world.multiworld) region_dict[region_data.id] = new_region @@ -217,7 +216,7 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: add_locations_by_ids(new_region, region_data.bosses, FFXBossLocations, "Boss") # TODO: Implement in client - # add_locations_by_ids(new_region, region_data.overdrives, FFXOverdriveLocations, "Overdrive") + add_locations_by_ids(new_region, region_data.overdrives, FFXOverdriveLocations, "Overdrive") add_locations_by_ids(new_region, region_data.other, FFXOtherLocations, "Other") @@ -226,8 +225,11 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: for location_id, region_name in captureDict.items(): add_locations_by_ids(world.get_region(region_name), [location_id], FFXCaptureLocations, "Capture") + for location_data in FFXOverdriveLocations[:6]: + overdrive_location = FFXLocation(player, location_data.name, location_data.rom_address, menu_region) + menu_region.locations.append(overdrive_location) + # ---------------------------- Entrance Rules ---------------------------- # - for region_data in region_data_list: curr_region = region_dict[region_data.id] for region_id in region_data.leads_to: @@ -271,25 +273,41 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: # ------------------------------- Blitzball ------------------------------ # if not world.options.mini_game_blitzball.value is world.options.mini_game_blitzball.option_up_to_sigil: - blitzball_location_ids = [] + blitzball_treasure_location_ids = [] + blitzball_overdrive_location_ids = [] - up_to = world.options.mini_game_blitzball.value - up_to_story = world.options.mini_game_blitzball.option_up_to_story - up_to_celestial = world.options.mini_game_blitzball.option_up_to_celestial - up_to_sigil = world.options.mini_game_blitzball.option_up_to_sigil + up_to = world.options.mini_game_blitzball.value + up_to_story = world.options.mini_game_blitzball.option_up_to_story + up_to_world_champion = world.options.mini_game_blitzball.option_up_to_world_champion + up_to_attack_reels = world.options.mini_game_blitzball.option_up_to_attack_reels + up_to_status_reels = world.options.mini_game_blitzball.option_up_to_status_reels + up_to_aurochs_reels = world.options.mini_game_blitzball.option_up_to_aurochs_reels + up_to_sigil = world.options.mini_game_blitzball.option_up_to_sigil if up_to < up_to_sigil: - blitzball_location_ids.append(244) # "Blitzball: Obtain The Jupiter Sigil League Prize (Event)", + blitzball_treasure_location_ids.append(244) # "Blitzball: Obtain The Jupiter Sigil League Prize (Event)", - if up_to < up_to_celestial: - blitzball_location_ids.append(93) # "LUCA: Cafe - Talk to Owner After Placing at Least Third in a Tournament (Chest)" (World Champion), + if up_to < up_to_aurochs_reels: + blitzball_overdrive_location_ids.append(23) # Overdrive: Come 1st in a Blitzball Tournament After Obtaining both Attack & Status Reels (Aurochs Reels) + + if up_to < up_to_status_reels: + blitzball_overdrive_location_ids.append(22) # Overdrive: Come 1st in a Blitzball League After Obtaining Attack Reels (Status Reels) + + if up_to < up_to_attack_reels: + blitzball_overdrive_location_ids.append(21) # Overdrive: Come 1st in a Blitzball Tournament (Attack Reels) + + if up_to < up_to_world_champion: + blitzball_treasure_location_ids.append(93) # "LUCA: Cafe - Talk to Owner After Placing at Least Third in a Tournament (Chest)" (World Champion), if up_to < up_to_story: - blitzball_location_ids.append(497) # "LUCA: Win the Story Blitzball Tournament (Event)", + blitzball_treasure_location_ids.append(497) # "LUCA: Win the Story Blitzball Tournament (Event)", - for id in blitzball_location_ids: + for id in blitzball_treasure_location_ids: location_name = world.location_id_to_name[id | TreasureOffset] world.options.exclude_locations.value.add(location_name) + for id in blitzball_overdrive_location_ids: + location_name = world.location_id_to_name[id | OverdriveOffset] + world.options.exclude_locations.value.add(location_name) # ------------------------------ Butterflies ----------------------------- # if not world.options.mini_game_butterflies: @@ -579,12 +597,15 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: 44, # "Omega Ruins: Omega Weapon" ] super_boss_treasure_ids = [ - 332, # Omega Ruins - Behind Omega Weapon Chest + 332, # OMGR: Omega Boss Arena (Chest) 496, # MOAR: Become 'The One Who Conquered All' (Event) ] super_boss_other_ids = [ 27, # Jecht Sphere 2 - Requires Dark Valefor ] + super_boss_overdrive_ids = [ + 19, # Ronso Rage: Use Lancet to Learn Nova + ] for id in super_boss_location_ids: location_name = world.location_id_to_name[id | BossOffset] world.options.exclude_locations.value.add(location_name) @@ -593,7 +614,10 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: world.options.exclude_locations.value.add(location_name) for id in super_boss_other_ids: location_name = world.location_id_to_name[id | OtherOffset] - world.options.exclude_locations.value.add(location_name) + world.options.exclude_locations.value.add(location_name) + for id in super_boss_overdrive_ids: + location_name = world.location_id_to_name[id | OverdriveOffset] + world.options.exclude_locations.value.add(location_name) # ---------------------------- Jecht's Spheres --------------------------- # if not world.options.jecht_spheres.value: @@ -613,14 +637,55 @@ def add_locations_by_ids(region: Region, location_ids: list[int], location_data: world.options.exclude_locations.value.add(location_name) location_name = world.location_id_to_name[177 | TreasureOffset] world.options.exclude_locations.value.add(location_name) + + # ------------------------------ Overdrives ------------------------------ # + # Tidus + if not world.options.tidus_overdrives.value == world.options.tidus_overdrives.option_up_to_blitz_ace: + overdrive_location_ids = [] + + up_to = world.options.tidus_overdrives.value + slice_and_dice = world.options.tidus_overdrives.option_up_to_slice_and_dice + energy_rain = world.options.tidus_overdrives.option_up_to_energy_rain + blitz_ace = world.options.tidus_overdrives.option_up_to_blitz_ace + + if up_to < blitz_ace: + overdrive_location_ids.append(3) # Overdrive: Use Tidus's Overdrive 80 Times (Blitz Ace) + + if up_to < energy_rain: + overdrive_location_ids.append(2) # Overdrive: Use Tidus's Overdrive 30 Times (Energy Rain) + + if up_to < slice_and_dice: + overdrive_location_ids.append(1) # Overdrive: Use Tidus's Overdrive 10 Times (Slice and Dice) + + for id in overdrive_location_ids: + location_name = world.location_id_to_name[id | OverdriveOffset] + world.options.exclude_locations.value.add(location_name) + # Kimahri + if not world.options.kimahri_ronso_rages.value: + overdrive_location_ids = [ + # 8, # Ronso Rage: Jump + 9, # Ronso Rage: Use Lancet to Learn Fire Breath + 10, # Ronso Rage: Use Lancet to Learn Seed Cannon + 11, # Ronso Rage: Use Lancet to Learn Self Destruct + 12, # Ronso Rage: Use Lancet to Learn Thrust Kick + 13, # Ronso Rage: Use Lancet to Learn Stone Breath + 14, # Ronso Rage: Use Lancet to Learn Aqua Breath + 15, # Ronso Rage: Use Lancet to Learn Doom + 16, # Ronso Rage: Use Lancet to Learn White Wind + 17, # Ronso Rage: Use Lancet to Learn Bad Breath + 18, # Ronso Rage: Use Lancet to Learn Mighty Guard + 19, # Ronso Rage: Use Lancet to Learn Nova + ] + for id in overdrive_location_ids: + location_name = world.location_id_to_name[id | OverdriveOffset] + world.options.exclude_locations.value.add(location_name) + # ------------------------------------------------------------------------ # # Victory Condition # # ------------------------------------------------------------------------ # - - - + final_region = world.get_region("Sin: Braska's Final Aeon") final_region.add_event("Sin: Braska's Final Aeon", "Victory", location_type=FFXLocation, item_type=FFXItem) final_aeon = world.get_location("Sin: Braska's Final Aeon") diff --git a/worlds/ffx/rules.py b/worlds/ffx/rules.py index a57e13598f73..6dbc26189a71 100644 --- a/worlds/ffx/rules.py +++ b/worlds/ffx/rules.py @@ -3,13 +3,12 @@ from dataclasses import dataclass from typing_extensions import override -from BaseClasses import CollectionState, Location, Region +from BaseClasses import CollectionState, Location from rule_builder.rules import Rule, CanReachLocation, CanReachRegion, Has, HasAll, HasAny, HasFromListUnique, True_, False_ -# from rule_builder import set from worlds.generic.Rules import CollectionRule from . import key_items -from .items import character_names, stat_abilities, item_to_stat_value, aeon_names, party_member_items, region_unlock_items, equipItemOffset -from .locations import TreasureOffset, OtherOffset, BossOffset, PartyMemberOffset, CaptureOffset +from .items import character_names, stat_abilities, item_to_stat_value, aeon_names, overdrive_names, party_member_items, region_unlock_items, equipItemOffset +from .locations import TreasureOffset, OtherOffset, BossOffset, PartyMemberOffset, CaptureOffset, OverdriveOffset if typing.TYPE_CHECKING: from .__init__ import FFXWorld @@ -71,11 +70,15 @@ @dataclass() class AbilityRule(Rule[FFXWorld], game="Final Fantasy X"): ability_name: str + character_name: str | None @override def _instantiate(self, world: FFXWorld) -> Rule.Resolved: - if world.options.sphere_grid_randomization.value == world.options.sphere_grid_randomization.option_on: - return HasAny(HasAll([f"{name} Ability: {self.ability_name}", f"Party Member: {name}"]) for name in character_names).resolve(world) + if world.options.sphere_grid_randomization.value: + if self.character_name is not None: + return Has(f"{self.character_name} Ability: {self.ability_name}").resolve(world) + else: + return HasAny(HasAll([f"{name} Ability: {self.ability_name}", f"Party Member: {name}"]) for name in character_names).resolve(world) else: return True_().resolve(world) @@ -88,30 +91,47 @@ class CanReachMinimumLocationRule(Rule[FFXWorld], game="Final Fantasy X"): @override def _instantiate(self, world: FFXWorld) -> Rule.Resolved: - sum = 0 - for location in self.locations: - if CanReachLocation(location.name) is not None: - sum += 1 - if sum >= self.locations_required: - return True_().resolve(world) - return False_().resolve(world) + return self.Resolved(tuple([location.name for location in self.locations]), + self.locations_required, player=world.player) - + class Resolved(Rule.Resolved): + locations: tuple[str, ...] + locations_required: int + + @override + def _evaluate(self, state: CollectionState) -> bool: + sum = 0 + for location in self.locations: + if state.can_reach_location(location, self.player): + sum += 1 + if sum >= self.locations_required: + return True + return False + + @dataclass() class CanReachMinimumRegionRule(Rule[FFXWorld], game="Final Fantasy X"): """A rule that checks if a required number of regions are reachable from a given list of regions""" - regions: list[Region] + regions: list[str] regions_required: int @override def _instantiate(self, world: FFXWorld) -> Rule.Resolved: - sum = 0 - for region in self.regions: - if CanReachRegion(region.name) is not None: - sum += 1 - if sum >= self.regions_required: - return True_().resolve(world) - return False_().resolve(world) + return self.Resolved(tuple(self.regions), self.regions_required, player=world.player) + + class Resolved(Rule.Resolved): + regions: tuple[str, ...] + regions_required: int + + @override + def _evaluate(self, state: CollectionState) -> bool: + sum = 0 + for region in self.regions: + if state.can_reach_region(region, self.player): + sum += 1 + if sum >= self.regions_required: + return True + return False @dataclass() @@ -315,7 +335,7 @@ def _instantiate(self, world: FFXWorld) -> Rule.Resolved: "Evrae Altana": LogicDifficultyRule(12) & MinSwimmerRule(3), "Seymour Natus": LogicDifficultyRule(12) & MinPartyRule (3), "Defender X": LogicDifficultyRule(13) & MinPartyRule (3), - "Biran and Yenke": LogicDifficultyRule(14) & MinPartyRule (3), + "Biran and Yenke": LogicDifficultyRule(14) & Has("Party Member: Kimahri"), "Seymour Flux": LogicDifficultyRule(14) & MinPartyRule (3), "Sanctuary Keeper": LogicDifficultyRule(14) & MinPartyRule (3), "Spectral Keeper": LogicDifficultyRule(15) & MinPartyRule (3), @@ -327,10 +347,12 @@ def _instantiate(self, world: FFXWorld) -> Rule.Resolved: "Braska's Final Aeon": LogicDifficultyRule(16) & MinPartyRule (3), "Ultima Weapon": LogicDifficultyRule(17) & MinPartyRule (3), "Omega Weapon": LogicDifficultyRule(18) & MinPartyRule (3), + "Nemesis": LogicDifficultyRule(18) & MinPartyRule (3), } staticEncounterRuleDict: dict[str, Rule] = { - "Belgemine": MinSummonRule(2) + "Belgemine": MinSummonRule(2), + "Ronso Rage": Has("Party Member: Kimahri") } arenaBossRuleDict: dict[int, Rule] = { @@ -636,6 +658,12 @@ def set_rules(world: FFXWorld) -> None: # Mercury Sigil world.set_rule(world.get_location(world.location_id_to_name[279 | TreasureOffset]), CanReachRegion("Airship 1st visit: Post-Evrae")) + # Jupiter Sigil + world.set_rule(world.get_location(world.location_id_to_name[244 | TreasureOffset]), + CanReachLocation("Slots: Come 1st in a Blitzball Tournament (Attack Reels)") & + CanReachLocation("Slots: Come 1st in a Blitzball League After Obtaining Attack Reels (Status Reels)") & + CanReachLocation("Slots: Come 1st in a Blitzball Tournament After Obtaining both Attack & Status Reels (Aurochs Reels)")) + # -------------------------- Celestial Upgrades -------------------------- # celestial_upgrades = [ (38, 0x25, "Sun"), @@ -660,6 +688,62 @@ def set_rules(world: FFXWorld) -> None: world.set_rule(world.get_location(world.location_id_to_name[405 | TreasureOffset]), Has("Progressive Al Bhed Primer", count=26)) + # ---------------------------------------------------------------------------- # + # Overdrives # + # ---------------------------------------------------------------------------- # + + # ----------------------------------- Tidus ---------------------------------- # + combat_regions: list[str] = [ + "Besaid Island 1st visit", + "Kilika 1st visit: Pre-Geneaux", + "Mi'ihen Highroad 1st visit: Pre-Chocobo Eater", + "Mushroom Rock Road 1st visit: Pre-Sinspawn Gui", + "Djose 1st visit", + "Moonflow 1st visit: Pre-Extractor", + "Thunder Plains 1st visit", + "Macalania Woods 1st visit: Pre-Spherimorph", + "Bikanel 1st visit: Post-Zu", + "Airship 1st visit: Pre-Evrae", + "Bevelle 1st visit: Pre-Isaaru", + "Calm Lands 1st visit: Pre-Defender X", + "Cavern of the Stolen Fayth 1st visit", + "Mt. Gagazet 1st visit: Post-Biran and Yenke", + "Zanarkand Ruins 1st visit: Pre-Spectral Keeper", + "Sin: Pre-Seymour Omnis", + "Omega Ruins: Pre-Ultima Weapon" + ] + overdrive_regions = [world.get_region(region_name) for region_name in combat_regions] + + slice_and_dice = world.get_location(world.location_id_to_name[1 | OverdriveOffset]) + energy_rain = world.get_location(world.location_id_to_name[2 | OverdriveOffset]) + blitz_ace = world.get_location(world.location_id_to_name[3 | OverdriveOffset]) + + has_overdrive = HasFromListUnique(*[f"Swordplay: {overdrive}" for overdrive in overdrive_names[:4]], count=1) + + world.set_rule(slice_and_dice, has_overdrive & CanReachMinimumRegionRule(combat_regions, 4)) + world.set_rule(energy_rain, has_overdrive & CanReachMinimumRegionRule(combat_regions, 8)) + world.set_rule(blitz_ace, has_overdrive & CanReachMinimumRegionRule(combat_regions, 14)) + + # ----------------------------------- Auron ---------------------------------- # + shooting_star = world.get_location(world.location_id_to_name[4 | OverdriveOffset]) + banishing_blade = world.get_location(world.location_id_to_name[6 | OverdriveOffset]) + tornado = world.get_location(world.location_id_to_name[7 | OverdriveOffset]) + + world.set_rule(shooting_star, Has("Progressive Jecht's Sphere", count=1)) + world.set_rule(banishing_blade, Has("Progressive Jecht's Sphere", count=3)) + world.set_rule(tornado, Has("Progressive Jecht's Sphere", count=10)) + + # ----------------------------------- Wakka ---------------------------------- # + status_reels = world.get_location(world.location_id_to_name[22 | OverdriveOffset]) + aurochs_reels = world.get_location(world.location_id_to_name[23 | OverdriveOffset]) + + world.set_rule(status_reels, CanReachLocation("Slots: Come 1st in a Blitzball Tournament (Attack Reels)")) + world.set_rule(aurochs_reels, CanReachLocation("Slots: Come 1st in a Blitzball League After Obtaining Attack Reels (Status Reels)")) + + # ---------------------------------------------------------------------------- # + # Todo # + # ---------------------------------------------------------------------------- # + # TODO: Disabled for now due to multiple bugs related to this location (Ship softlocks + possible Macalania softlock) # Clasko S.S. Liki second visit (Talk to Clasko before Crawler and make sure to have him become a Chocobo Breeder) #add_rule(world.get_location(world.location_id_to_name[336 | TreasureOffset]), diff --git a/worlds/ffx/ut.py b/worlds/ffx/ut.py index fb1e2c893a84..5a6fe950e730 100644 --- a/worlds/ffx/ut.py +++ b/worlds/ffx/ut.py @@ -21,6 +21,8 @@ def setup_options_from_slot_data(world: FFXWorld) -> None: world.options.mini_game_cactuar_village.value = world.passthrough["mini_game_cactuar_village"] world.options.mini_game_chocobo_training.value = world.passthrough["mini_game_chocobo_training"] world.options.mini_game_chocobo_race.value = world.passthrough["mini_game_chocobo_race"] + world.options.tidus_overdrives.value = world.passthrough["tidus_overdrive"] + world.options.kimahri_ronso_rages.value = world.passthrough["kimahri_ronso_rage"] world.options.recruit_sanity.value = world.passthrough["recruit_sanity"] world.options.capture_sanity.value = world.passthrough["capture_sanity"] world.options.creation_rewards.value = world.passthrough["creation_rewards"]