From 539ee1c5daca96d9cfbb87b9a8bb98113ee6e419 Mon Sep 17 00:00:00 2001 From: Rensen3 <127029481+Rensen3@users.noreply.github.com> Date: Fri, 17 May 2024 19:23:05 +0200 Subject: [PATCH] Yu-Gi-oh! 2006: implement new game (#2795) * Initial implementation of Yu-Gi-Oh! WC 2006 * Added Opponents and banlists * Initial implementation of Yu-Gi-Oh! WC 2006 * Added Opponents and banlists * Added Campaign Logic * Added Bonuses Logic * Added challenge logic * fixed yugioh client * ygo06 rom cleanup and include lua * ygo06 patch cleanup * ygo06 move client to world folder * lots of small changes * bug fixes * implemented filler item for yugioh06 * BizHawkClient: Add client and connector * BizHawkClient: Add launcher component and inno_setup lines * BizHawkClient: Misc stability updates and small improvements Bad commit organization a consequence of working with two different branches and not keeping the commits separated * BizHawkClient: Add docstrings * BizHawkClient: Pull in changes from other branch * BizHawkClient: Fix no handler message not displaying after changed ROMs * BizHawkClient: Remove extra print statement from lua * BizHawkClient: Change version command to use raw strings * BizHawkClient: Change script version to single integer * YGO06: added logic for "all expect type forbidden" limited duels * YGO06: Structure Deck choice now affects logic. Fixed a bug with tier 5 campaign opponents. Added logic for TD16 Union. * BizHawkClient: Add newline to version for lua script * BizHawkClient: Call send_connect from BizHawkClient's watcher loop * BizHawkClient: Add handling for failed request getting script version * BizHawkClient: Have base64.lua check lua version explicitly for bit operations On 2.9, it would detect LuaJIT and flood the console with deprecation warnings * BizHawkClient: Update connector script for slightly better errors and address Gambatte frame sync issue * BizHawkClient: Remove accidentally added print statements * BizHawkClient: Fix connector server not closing correctly * BizHawkClient: Move some connector code around, some linting * BizHawkClient: Small cleanup in lua * BizHawkClient: Lua linting * BizHawkClient: Remove outdated sentences in docstrings * YGO06: Logic additions and bug fixes * BizHawkClient: Correctly null check patch file arg * BizHawkClient: Initialize logging * BizHawkClient: Move code to worlds/_bizhawk Also splits out BizHawk communication functions to their own file for use outside this client * BizHawkClient: Add license to connector lua, add types to docs * BizHawkClient: Add module docstrings * YGO06: Logic additions * BizHawkClient: Allow clients to define multiple systems * BizHawkClient: Better logging and handling of interruptions to connection to script * YGO06: Logic additions * YGO06: Added text to options * YGO06: Ported to bizhawk client * YGO06: fix goal not being detected * YGO06: fix access item rule for tier 5 column 1 and 2 * YGO06: docu and bug fixes * YGO06: change name * YGO06: some fixes * YGO06: fix starting opponent and booster not applying * YGO06: added option to reduce the amount of challenges and remove the no ban list from pool. * YGO06: added rom being asked for on first use * YGO06: fix rules for challenges * YGO06: create proper rules for TD04 Ritual Summon * YGO06: mark most banlists as usefull instead of progression * YGO06: reduce the required core boosters across the board * YGO06: fix client not loading if another game already loaded the bizhawk client * YGO06: fix client not finding the bizhawk client. * YGO06: fix TD08 Draw not giving out an item * YGO06: small text changes * YGO06: update to version 0.4.4 * YGO06: logic mixin clean-up * YGO06: added option for campaign opponents as goal * Pokemon Emerald add encounter table randomization * Pokemon Emerald: Item ball randomization working * Pokemon Emerald: Clean up code a little * Pokemon Emerald: Partial rework of region/location creation * Pokemon Emerald: Dedupe items and add more readable names * Refactor region creation to manually defined regions * Split region json * Use new data.json with flattened constants and add HM locations * YGO06: bug fixes * YGO06: bug fix * YGO06: changes default options to be more beginner friendly * YGO06: attempt at universal tracker support. Settings are stored in slot data now. * YGO06: fix for older python versions * YGO06: fix slot data * YGO06: added diiferent opponents to the campaign * YGO06: fix small bug with opponent icons * YGO06: fix unwanted changes * YGO06: repair merge with main * YGO06: map out all of the opponents * YGO06: added opponent shuffle * YGO06: added logic to opponent shuffle * YGO06: added option to use ocg art * YGO06: bug_fixes * YGO06: removed todos, since they are not needed anymore * YGO06: added draft mode * YGO06: added logic to draft mode * YGO06: Added Money multiplier when you lose * YGO06: Fixed Unit Test errors * YGO06: Added Random deck option * YGO06: Bug fix with registering client * YGO06: client clean-up * YGO06: fixed card misspellings * YGO06: removed unused imports and other small changes * YGO06: small changes * YGO06: fix generation error when the combination of starting with "No Banlist" and not adding "No Banlist" to the pool is selected * YGO06: fix ocg art path overwriting Huge Revolution bugfix * YGO06: added comments and other minor changes * YGO06: fixed byte length in client for money * YGO06: fixes for webhost and options * YGO06: use the proper random function * YGO06: change settings to options * YGO06: move to procedure patch * YGO06: fix imports * YGO06: fix download link for patch not showing * YGO06: remove unnecessary Optional * YGO06: fix universal tracker stuff * YGO06: add typings * YGO06: small cleanup * yugioh06: small change to setup Co-authored-by: Scipio Wright * YGO06: remove logic mixin * YGO06: fix create item and implement create filler and get filler item name * YGO06: remove double lambdas * YGO06: use pkgutil.get_data instaed pf zipFile * YGO06: fix starting items being duplicated * YGO06: lots of small changes * YGO06: moved functions to match execution order * YGO06: run ruff * YGO06: run ruff format * YGO06: fix ruff errors * YGO06: undo ruff format for rules * YGO06: move import to prevent circular dependency * YGO06: remove unused class * YGO06: optimizing rules * YGO06: some optimization and small bug fix --------- Co-authored-by: Zunawe Co-authored-by: Scipio Wright --- test/general/test_items.py | 2 + worlds/yugioh06/__init__.py | 454 +++++++++++ worlds/yugioh06/boosterpacks.py | 923 ++++++++++++++++++++++ worlds/yugioh06/client_bh.py | 139 ++++ worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md | 53 ++ worlds/yugioh06/docs/setup_en.md | 72 ++ worlds/yugioh06/fusions.py | 72 ++ worlds/yugioh06/items.py | 369 +++++++++ worlds/yugioh06/locations.py | 213 +++++ worlds/yugioh06/logic.py | 28 + worlds/yugioh06/opponents.py | 264 +++++++ worlds/yugioh06/options.py | 195 +++++ worlds/yugioh06/patch.bsdiff4 | Bin 0 -> 2959 bytes worlds/yugioh06/patches/draft.bsdiff4 | Bin 0 -> 306 bytes worlds/yugioh06/patches/ocg.bsdiff4 | Bin 0 -> 396 bytes worlds/yugioh06/rom.py | 163 ++++ worlds/yugioh06/rom_values.py | 38 + worlds/yugioh06/ruff.toml | 12 + worlds/yugioh06/rules.py | 868 ++++++++++++++++++++ worlds/yugioh06/structure_deck.py | 81 ++ 20 files changed, 3946 insertions(+) create mode 100644 worlds/yugioh06/__init__.py create mode 100644 worlds/yugioh06/boosterpacks.py create mode 100644 worlds/yugioh06/client_bh.py create mode 100644 worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md create mode 100644 worlds/yugioh06/docs/setup_en.md create mode 100644 worlds/yugioh06/fusions.py create mode 100644 worlds/yugioh06/items.py create mode 100644 worlds/yugioh06/locations.py create mode 100644 worlds/yugioh06/logic.py create mode 100644 worlds/yugioh06/opponents.py create mode 100644 worlds/yugioh06/options.py create mode 100644 worlds/yugioh06/patch.bsdiff4 create mode 100644 worlds/yugioh06/patches/draft.bsdiff4 create mode 100644 worlds/yugioh06/patches/ocg.bsdiff4 create mode 100644 worlds/yugioh06/rom.py create mode 100644 worlds/yugioh06/rom_values.py create mode 100644 worlds/yugioh06/ruff.toml create mode 100644 worlds/yugioh06/rules.py create mode 100644 worlds/yugioh06/structure_deck.py diff --git a/test/general/test_items.py b/test/general/test_items.py index 25623d4d..7c0b7050 100644 --- a/test/general/test_items.py +++ b/test/general/test_items.py @@ -25,6 +25,8 @@ class TestBase(unittest.TestCase): {"medallions", "stones", "rewards", "logic_bottles"}, "Starcraft 2": {"Missions", "WoL Missions"}, + "Yu-Gi-Oh! 2006": + {"Campaign Boss Beaten"} } for game_name, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game_name, game_name=game_name): diff --git a/worlds/yugioh06/__init__.py b/worlds/yugioh06/__init__.py new file mode 100644 index 00000000..ec7e769f --- /dev/null +++ b/worlds/yugioh06/__init__.py @@ -0,0 +1,454 @@ +import os +import pkgutil +from typing import Any, ClassVar, Dict, List + +import settings +from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial + +import Utils +from worlds.AutoWorld import WebWorld, World + +from .boosterpacks import booster_contents as booster_contents +from .boosterpacks import get_booster_locations +from .items import ( + Banlist_Items, + booster_packs, + draft_boosters, + draft_opponents, + excluded_items, + item_to_index, + tier_1_opponents, + useful, +) +from .items import ( + challenges as challenges, +) +from .locations import ( + Bonuses, + Campaign_Opponents, + Limited_Duels, + Required_Cards, + Theme_Duels, + collection_events, + get_beat_challenge_events, + special, +) +from .logic import core_booster, yugioh06_difficulty +from .opponents import OpponentData, get_opponent_condition, get_opponent_locations, get_opponents +from .opponents import challenge_opponents as challenge_opponents +from .options import Yugioh06Options +from .rom import MD5America, MD5Europe, YGO06ProcedurePatch, write_tokens +from .rom import get_base_rom_path as get_base_rom_path +from .rom_values import banlist_ids as banlist_ids +from .rom_values import function_addresses as function_addresses +from .rom_values import structure_deck_selection as structure_deck_selection +from .rules import set_rules +from .structure_deck import get_deck_content_locations +from .client_bh import YuGiOh2006Client + + +class Yugioh06Web(WebWorld): + theme = "stone" + setup = Tutorial( + "Multiworld Setup Tutorial", + "A guide to setting up Yu-Gi-Oh! - Ultimate Masters Edition - World Championship Tournament 2006 " + "for Archipelago on your computer.", + "English", + "docs/setup_en.md", + "setup/en", + ["Rensen"], + ) + tutorials = [setup] + + +class Yugioh2006Setting(settings.Group): + class Yugioh2006RomFile(settings.UserFilePath): + """File name of your Yu-Gi-Oh 2006 ROM""" + + description = "Yu-Gi-Oh 2006 ROM File" + copy_to = "YuGiOh06.gba" + md5s = [MD5Europe, MD5America] + + rom_file: Yugioh2006RomFile = Yugioh2006RomFile(Yugioh2006RomFile.copy_to) + + +class Yugioh06World(World): + """ + Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 is the definitive Yu-Gi-Oh + simulator on the GBA. Featuring over 2000 cards and over 90 Challenges. + """ + + game = "Yu-Gi-Oh! 2006" + web = Yugioh06Web() + options: Yugioh06Options + options_dataclass = Yugioh06Options + settings_key = "yugioh06_settings" + settings: ClassVar[Yugioh2006Setting] + + item_name_to_id = {} + start_id = 5730000 + for k, v in item_to_index.items(): + item_name_to_id[k] = v + start_id + + location_name_to_id = {} + for k, v in Bonuses.items(): + location_name_to_id[k] = v + start_id + + for k, v in Limited_Duels.items(): + location_name_to_id[k] = v + start_id + + for k, v in Theme_Duels.items(): + location_name_to_id[k] = v + start_id + + for k, v in Campaign_Opponents.items(): + location_name_to_id[k] = v + start_id + + for k, v in special.items(): + location_name_to_id[k] = v + start_id + + for k, v in Required_Cards.items(): + location_name_to_id[k] = v + start_id + + item_name_groups = { + "Core Booster": core_booster, + "Campaign Boss Beaten": ["Tier 1 Beaten", "Tier 2 Beaten", "Tier 3 Beaten", "Tier 4 Beaten", "Tier 5 Beaten"], + } + + removed_challenges: List[str] + starting_booster: str + starting_opponent: str + campaign_opponents: List[OpponentData] + is_draft_mode: bool + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + + def generate_early(self): + self.starting_opponent = "" + self.starting_booster = "" + self.removed_challenges = [] + # Universal tracker stuff, shouldn't do anything in standard gen + if hasattr(self.multiworld, "re_gen_passthrough"): + if "Yu-Gi-Oh! 2006" in self.multiworld.re_gen_passthrough: + # bypassing random yaml settings + slot_data = self.multiworld.re_gen_passthrough["Yu-Gi-Oh! 2006"] + self.options.structure_deck.value = slot_data["structure_deck"] + self.options.banlist.value = slot_data["banlist"] + self.options.final_campaign_boss_unlock_condition.value = slot_data[ + "final_campaign_boss_unlock_condition" + ] + self.options.fourth_tier_5_campaign_boss_unlock_condition.value = slot_data[ + "fourth_tier_5_campaign_boss_unlock_condition" + ] + self.options.third_tier_5_campaign_boss_unlock_condition.value = slot_data[ + "third_tier_5_campaign_boss_unlock_condition" + ] + self.options.final_campaign_boss_challenges.value = slot_data["final_campaign_boss_challenges"] + self.options.fourth_tier_5_campaign_boss_challenges.value = slot_data[ + "fourth_tier_5_campaign_boss_challenges" + ] + self.options.third_tier_5_campaign_boss_challenges.value = slot_data[ + "third_tier_5_campaign_boss_challenges" + ] + self.options.final_campaign_boss_campaign_opponents.value = slot_data[ + "final_campaign_boss_campaign_opponents" + ] + self.options.fourth_tier_5_campaign_boss_campaign_opponents.value = slot_data[ + "fourth_tier_5_campaign_boss_campaign_opponents" + ] + self.options.third_tier_5_campaign_boss_campaign_opponents.value = slot_data[ + "third_tier_5_campaign_boss_campaign_opponents" + ] + self.options.number_of_challenges.value = slot_data["number_of_challenges"] + self.removed_challenges = slot_data["removed challenges"] + self.starting_booster = slot_data["starting_booster"] + self.starting_opponent = slot_data["starting_opponent"] + + if self.options.structure_deck.current_key == "none": + self.is_draft_mode = True + boosters = draft_boosters + if self.options.campaign_opponents_shuffle.value: + opponents = tier_1_opponents + else: + opponents = draft_opponents + else: + self.is_draft_mode = False + boosters = booster_packs + opponents = tier_1_opponents + + if self.options.structure_deck.current_key == "random_deck": + self.options.structure_deck.value = self.random.randint(0, 5) + for item in self.options.start_inventory: + if item in opponents: + self.starting_opponent = item + if item in boosters: + self.starting_booster = item + if not self.starting_opponent: + self.starting_opponent = self.random.choice(opponents) + self.multiworld.push_precollected(self.create_item(self.starting_opponent)) + if not self.starting_booster: + self.starting_booster = self.random.choice(boosters) + self.multiworld.push_precollected(self.create_item(self.starting_booster)) + banlist = self.options.banlist.value + self.multiworld.push_precollected(self.create_item(Banlist_Items[banlist])) + + if not self.removed_challenges: + challenge = list((Limited_Duels | Theme_Duels).keys()) + noc = len(challenge) - max( + self.options.third_tier_5_campaign_boss_challenges.value + if self.options.third_tier_5_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.fourth_tier_5_campaign_boss_challenges.value + if self.options.fourth_tier_5_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.final_campaign_boss_challenges.value + if self.options.final_campaign_boss_unlock_condition == "challenges" + else 0, + self.options.number_of_challenges.value, + ) + + self.random.shuffle(challenge) + excluded = self.options.exclude_locations.value.intersection(challenge) + prio = self.options.priority_locations.value.intersection(challenge) + normal = [e for e in challenge if e not in excluded and e not in prio] + total = list(excluded) + normal + list(prio) + self.removed_challenges = total[:noc] + + self.campaign_opponents = get_opponents( + self.multiworld, self.player, self.options.campaign_opponents_shuffle.value + ) + + def create_region(self, name: str, locations=None, exits=None): + region = Region(name, self.player, self.multiworld) + if locations: + for location_name, lid in locations.items(): + if lid is not None and isinstance(lid, int): + lid = self.location_name_to_id[location_name] + else: + lid = None + location = Yugioh2006Location(self.player, location_name, lid, region) + region.locations.append(location) + + if exits: + for _exit in exits: + region.exits.append(Entrance(self.player, _exit, region)) + return region + + def create_regions(self): + structure_deck = self.options.structure_deck.current_key + self.multiworld.regions += [ + self.create_region("Menu", None, ["to Deck Edit", "to Campaign", "to Challenges", "to Card Shop"]), + self.create_region("Campaign", Bonuses | Campaign_Opponents), + self.create_region("Challenges"), + self.create_region("Card Shop", Required_Cards | collection_events), + self.create_region("Structure Deck", get_deck_content_locations(structure_deck)), + ] + + self.get_entrance("to Campaign").connect(self.get_region("Campaign")) + self.get_entrance("to Challenges").connect(self.get_region("Challenges")) + self.get_entrance("to Card Shop").connect(self.get_region("Card Shop")) + self.get_entrance("to Deck Edit").connect(self.get_region("Structure Deck")) + + campaign = self.get_region("Campaign") + # Campaign Opponents + for opponent in self.campaign_opponents: + unlock_item = "Campaign Tier " + str(opponent.tier) + " Column " + str(opponent.column) + region = self.create_region(opponent.name, get_opponent_locations(opponent)) + entrance = Entrance(self.player, unlock_item, campaign) + if opponent.tier == 5 and opponent.column > 2: + unlock_amount = 0 + is_challenge = True + if opponent.column == 3: + if self.options.third_tier_5_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.third_tier_5_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.third_tier_5_campaign_boss_campaign_opponents.value + is_challenge = False + if opponent.column == 4: + if self.options.fourth_tier_5_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.fourth_tier_5_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.fourth_tier_5_campaign_boss_campaign_opponents.value + is_challenge = False + if opponent.column == 5: + if self.options.final_campaign_boss_unlock_condition.value == 1: + unlock_item = "Challenge Beaten" + unlock_amount = self.options.final_campaign_boss_challenges.value + is_challenge = True + else: + unlock_item = "Campaign Boss Beaten" + unlock_amount = self.options.final_campaign_boss_campaign_opponents.value + is_challenge = False + entrance.access_rule = get_opponent_condition( + opponent, unlock_item, unlock_amount, self.player, is_challenge + ) + else: + entrance.access_rule = lambda state, unlock=unlock_item, opp=opponent: state.has( + unlock, self.player + ) and yugioh06_difficulty(state, self.player, opp.difficulty) + campaign.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + card_shop = self.get_region("Card Shop") + # Booster Contents + for booster in booster_packs: + region = self.create_region(booster, get_booster_locations(booster)) + entrance = Entrance(self.player, booster, card_shop) + entrance.access_rule = lambda state, unlock=booster: state.has(unlock, self.player) + card_shop.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + challenge_region = self.get_region("Challenges") + # Challenges + for challenge, lid in (Limited_Duels | Theme_Duels).items(): + if challenge in self.removed_challenges: + continue + region = self.create_region(challenge, {challenge: lid, challenge + " Complete": None}) + entrance = Entrance(self.player, challenge, challenge_region) + entrance.access_rule = lambda state, unlock=challenge: state.has(unlock + " Unlock", self.player) + challenge_region.exits.append(entrance) + entrance.connect(region) + self.multiworld.regions.append(region) + + def create_item(self, name: str) -> Item: + classification: ItemClassification = ItemClassification.progression + if name == "5000DP": + classification = ItemClassification.filler + if name in useful: + classification = ItemClassification.useful + return Item(name, classification, self.item_name_to_id[name], self.player) + + def create_filler(self) -> Item: + return self.create_item("5000DP") + + def get_filler_item_name(self) -> str: + return "5000DP" + + def create_items(self): + start_inventory = self.options.start_inventory.value.copy() + item_pool = [] + items = item_to_index.copy() + starting_list = Banlist_Items[self.options.banlist.value] + if not self.options.add_empty_banlist.value and starting_list != "No Banlist": + items.pop("No Banlist") + for rc in self.removed_challenges: + items.pop(rc + " Unlock") + items.pop(self.starting_opponent) + items.pop(self.starting_booster) + items.pop(starting_list) + for name in items: + if name in excluded_items or name in start_inventory: + continue + item = self.create_item(name) + item_pool.append(item) + + needed_item_pool_size = sum(loc not in self.removed_challenges for loc in self.location_name_to_id) + needed_filler_amount = needed_item_pool_size - len(item_pool) + item_pool += [self.create_item("5000DP") for _ in range(needed_filler_amount)] + + self.multiworld.itempool += item_pool + + for challenge in get_beat_challenge_events(self): + item = Yugioh2006Item("Challenge Beaten", ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(challenge, self.player) + location.place_locked_item(item) + + for opponent in self.campaign_opponents: + for location_name, event in get_opponent_locations(opponent).items(): + if event is not None and not isinstance(event, int): + item = Yugioh2006Item(event, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + for booster in booster_packs: + for location_name, content in get_booster_locations(booster).items(): + item = Yugioh2006Item(content, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + structure_deck = self.options.structure_deck.current_key + for location_name, content in get_deck_content_locations(structure_deck).items(): + item = Yugioh2006Item(content, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(location_name, self.player) + location.place_locked_item(item) + + for event in collection_events: + item = Yugioh2006Item(event, ItemClassification.progression, None, self.player) + location = self.multiworld.get_location(event, self.player) + location.place_locked_item(item) + + def set_rules(self): + set_rules(self) + + def generate_output(self, output_directory: str): + outfilepname = f"_P{self.player}" + outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" + self.rom_name_text = f'YGO06{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0' + self.romName = bytearray(self.rom_name_text, "utf8")[:0x20] + self.romName.extend([0] * (0x20 - len(self.romName))) + self.rom_name = self.romName + self.playerName = bytearray(self.multiworld.player_name[self.player], "utf8")[:0x20] + self.playerName.extend([0] * (0x20 - len(self.playerName))) + patch = YGO06ProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player]) + patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "patch.bsdiff4")) + if self.is_draft_mode: + patch.procedure.insert(1, ("apply_bsdiff4", ["draft_patch.bsdiff4"])) + patch.write_file("draft_patch.bsdiff4", pkgutil.get_data(__name__, "patches/draft.bsdiff4")) + if self.options.ocg_arts: + patch.procedure.insert(1, ("apply_bsdiff4", ["ocg_patch.bsdiff4"])) + patch.write_file("ocg_patch.bsdiff4", pkgutil.get_data(__name__, "patches/ocg.bsdiff4")) + write_tokens(self, patch) + + # Write Output + out_file_name = self.multiworld.get_out_file_name_base(self.player) + patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}")) + + def fill_slot_data(self) -> Dict[str, Any]: + slot_data: Dict[str, Any] = { + "structure_deck": self.options.structure_deck.value, + "banlist": self.options.banlist.value, + "final_campaign_boss_unlock_condition": self.options.final_campaign_boss_unlock_condition.value, + "fourth_tier_5_campaign_boss_unlock_condition": + self.options.fourth_tier_5_campaign_boss_unlock_condition.value, + "third_tier_5_campaign_boss_unlock_condition": + self.options.third_tier_5_campaign_boss_unlock_condition.value, + "final_campaign_boss_challenges": self.options.final_campaign_boss_challenges.value, + "fourth_tier_5_campaign_boss_challenges": + self.options.fourth_tier_5_campaign_boss_challenges.value, + "third_tier_5_campaign_boss_challenges": + self.options.third_tier_5_campaign_boss_campaign_opponents.value, + "final_campaign_boss_campaign_opponents": + self.options.final_campaign_boss_campaign_opponents.value, + "fourth_tier_5_campaign_boss_campaign_opponents": + self.options.fourth_tier_5_campaign_boss_unlock_condition.value, + "third_tier_5_campaign_boss_campaign_opponents": + self.options.third_tier_5_campaign_boss_campaign_opponents.value, + "number_of_challenges": self.options.number_of_challenges.value, + } + + slot_data["removed challenges"] = self.removed_challenges + slot_data["starting_booster"] = self.starting_booster + slot_data["starting_opponent"] = self.starting_opponent + return slot_data + + # for the universal tracker, doesn't get called in standard gen + @staticmethod + def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: + # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough + return slot_data + + +class Yugioh2006Item(Item): + game: str = "Yu-Gi-Oh! 2006" + + +class Yugioh2006Location(Location): + game: str = "Yu-Gi-Oh! 2006" diff --git a/worlds/yugioh06/boosterpacks.py b/worlds/yugioh06/boosterpacks.py new file mode 100644 index 00000000..f6f4ec77 --- /dev/null +++ b/worlds/yugioh06/boosterpacks.py @@ -0,0 +1,923 @@ +from typing import Dict, Set + +booster_contents: Dict[str, Set[str]] = { + "LEGEND OF B.E.W.D.": { + "Exodia", + "Dark Magician", + "Polymerization", + "Skull Servant" + }, + "METAL RAIDERS": { + "Petit Moth", + "Cocoon of Evolution", + "Time Wizard", + "Gate Guardian", + "Kazejin", + "Suijin", + "Sanga of the Thunder", + "Sangan", + "Castle of Dark Illusions", + "Soul Release", + "Magician of Faith", + "Dark Elf", + "Summoned Skull", + "Sangan", + "7 Colored Fish", + "Tribute to the Doomed", + "Horn of Heaven", + "Magic Jammer", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Dream Clown", + "Heavy Storm" + }, + "PHARAOH'S SERVANT": { + "Beast of Talwar", + "Jinzo", + "Gearfried the Iron Knight", + "Harpie's Brother", + "Gravity Bind", + "Solemn Wishes", + "Kiseitai", + "Morphing Jar #2", + "The Shallow Grave", + "Nobleman of Crossout", + "Magic Drain" + }, + "PHARAONIC GUARDIAN": { + "Don Zaloog", + "Reasoning", + "Dark Snake Syndrome", + "Helpoemer", + "Newdoria", + "Spirit Reaper", + "Yomi Ship", + "Pyramid Turtle", + "Master Kyonshee", + "Book of Life", + "Call of the Mummy", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "A Cat of Ill Omen", + "Jowls of Dark Demise", + "Non Aggression Area", + "Terraforming", + "Des Lacooda", + "Swarm of Locusts", + "Swarm of Scarabs", + "Wandering Mummy", + "Royal Keeper", + "Book of Moon", + "Book of Taiyou", + "Dust Tornado", + "Raigeki Break" + }, + "SPELL RULER": { + "Ritual", + "Messenger of Peace", + "Megamorph", + "Shining Angel", + "Mystic Tomato", + "Giant Rat", + "Mother Grizzly", + "UFO Turtle", + "Flying Kamakiri 1", + "Giant Germ", + "Nimble Momonga", + "Cyber Jar", + "Spear Cretin", + "Toon Mermaid", + "Toon Summoned Skull", + "Toon World", + "Rush Recklessly", + "The Reliable Guardian", + "Senju of the Thousand Hands", + "Sonic Bird", + "Mystical Space Typhoon" + }, + "LABYRINTH OF NIGHTMARE": { + "Destiny Board", + "Spirit Message 'I'", + "Spirit Message 'N'", + "Spirit Message 'A'", + "Spirit Message 'L'", + "Fusion Gate", + "Jowgen the Spiritualist", + "Fairy Box", + "Aqua Spirit", + "Rock Spirit", + "Spirit of Flames", + "Garuda the Wind Spirit", + "Hysteric Fairy", + "Kycoo the Ghost Destroyer", + "Gemini Elf", + "Amphibian Beast", + "Revival Jam", + "Dancing Fairy", + "Cure Mermaid", + "The Last Warrior from Another Planet", + "United We Stand", + "Earthbound Spirit", + "The Masked Beast" + }, + "LEGACY OF DARKNESS": { + "Last Turn", + "Yata-Garasu", + "Opticlops", + "Dark Ruler Ha Des", + "Exiled Force", + "Injection Fairy Lily", + "Spear Dragon", + "Luster Dragon #2", + "Twin-Headed Behemoth", + "Airknight Parshath", + "Freed the Matchless General", + "Marauding Captain", + "Reinforcement of the Army", + "Cave Dragon", + "Troop Dragon", + "Stamping Destruction", + "Creature Swap", + "Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier", + "Emergency Provisions", + }, + "MAGICIAN'S FORCE": { + "Huge Revolution", + "Oppressed People", + "United Resistance", + "People Running About", + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "XY-Dragon Cannon", + "XZ-Tank Cannon", + "YZ-Tank Dragon", + "XYZ-Dragon Cannon", + "Cliff the Trap Remover", + "Wave-Motion Cannon", + "Ritual", + "Magical Merchant", + "Poison of the Old Man", + "Chaos Command Magician", + "Skilled Dark Magician", + "Dark Blade", + "Great Angus", + "Luster Dragon", + "Breaker the magical Warrior", + "Old Vindictive Magician", + "Apprentice Magician", + "Burning Beast", + "Freezing Beast", + "Pitch-Dark Dragon", + "Giant Orc", + "Second Goblin", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Frontline Base", + "Formation Union", + "Pitch-Black Power Stone", + "Magical Marionette", + "Royal Magical Library", + "Spell Shield Type-8", + "Tribute Doll", + }, + "DARK CRISIS": { + "Final Countdown", + "Ojama Green", + "Dark Scorpion Combination", + "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", + "Dark Scorpion - Gorg the Strong", + "Ritual", + "Tsukuyomi", + "Ojama Trio", + "Kaiser Glider", + "D.D. Warrior Lady", + "Archfiend Soldier", + "Skull Archfiend of Lightning", + "Blindly Loyal Goblin", + "Gagagigo", + "Nin-Ken Dog", + "Zolga", + "Kelbek", + "Mudora", + "Cestus of Dagla", + "Vampire Lord", + "Metallizing Parasite - Lunatite", + "D. D. Trainer", + "Spell Reproduction", + "Contract with the Abyss", + "Dark Master - Zorc" + }, + "INVASION OF CHAOS": { + "Ojama Delta Hurricane", + "Ojama Yellow", + "Ojama Black", + "Heart of the Underdog", + "Chaos Emperor Dragon - Envoy of the End", + "Self-Destruct Button", + "Manticore of Darkness", + "Dimension Fusion", + "Gigantes", + "Inferno", + "Silpheed", + "Mad Dog of Darkness", + "Ryu Kokki", + "Berserk Gorilla", + "Neo Bug", + "Dark Driceratops", + "Hyper Hammerhead", + "Sea Serpent Warrior of Darkness", + "Giga Gagagigo", + "Terrorking Salmon", + "Blazing Inpachi", + "Stealth Bird", + "Reload", + "Cursed Seal of the Forbidden Spell", + "Stray Lambs", + "Manju of the Ten Thousand Hands" + }, + "ANCIENT SANCTUARY": { + "Monster Gate", + "Wall of Revealing Light", + "Mystik Wok", + "The Agent of Judgment - Saturn", + "Zaborg the Thunder Monarch", + "Regenerating Mummy", + "The End of Anubis", + "Solar Flare Dragon", + "Level Limit - Area B", + "King of the Swamp", + "Enemy Controller", + "Enchanting Fitting Room" + }, + "SOUL OF THE DUELIST": { + "Ninja Grandmaster Sasuke", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Enraged Muka Muka", + "Mobius the Frost Monarch", + "Horus the Black Flame Dragon LV6", + "Ultimate Baseball Kid", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Masked Dragon", + "Element Dragon", + "Horus the Black Flame Dragon LV4", + "Level Up!", + "Howling Insect", + "Mobius the Frost Monarch" + }, + "RISE OF DESTINY": { + "Homunculus the Alchemic Being", + "Thestalos the Firestorm Monarch", + "Roc from the Valley of Haze", + "Harpie Lady 1", + "Silent Swordsman Lv3", + "Mystic Swordsman LV6", + "Ultimate Insect Lv3", + "Divine Wrath", + "Serial Spell" + }, + "FLAMING ETERNITY": { + "Insect Knight", + "Chiron the Mage", + "Granmarg the Rock Monarch", + "Silent Swordsman Lv5", + "The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Ultimate Insect Lv5", + "Blast Magician", + "Golem Sentry", + "Rescue Cat", + "Blade Rabbit" + }, + "THE LOST MILLENIUM": { + "Ritual", + "Megarock Dragon", + "D.D. Survivor", + "Hieracosphinx", + "Elemental Hero Flame Wingman", + "Elemental Hero Avian", + "Elemental Hero Burstinatrix", + "Elemental Hero Clayman", + "Elemental Hero Sparkman", + "Elemental Hero Thunder Giant", + "Aussa the Earth Charmer", + "Brain Control" + }, + "CYBERNETIC REVOLUTION": { + "Power Bond", + "Cyber Dragon", + "Cyber Twin Dragon", + "Cybernetic Magician", + "Indomitable Fighter Lei Lei", + "Protective Soul Ailin", + "Miracle Fusion", + "Elemental Hero Bubbleman", + "Jerry Beans Man" + }, + "ELEMENTAL ENERGY": { + "V-Tiger Jet", + "W-Wing Catapult", + "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Oxygeddon", + "Roll Out!", + "Dark World Lightning", + "Elemental Hero Rampart Blaster", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge", + "Elemental Hero Wildheart", + "Elemental Hero Bladedge", + "Pot of Avarice", + "B.E.S. Tetran" + }, + "SHADOW OF INFINITY": { + "Hamon, Lord of Striking Thunder", + "Raviel, Lord of Phantasms", + "Uria, Lord of Searing Flames", + "Ritual", + "Treeborn Frog", + "Saber Beetle", + "Tenkabito Shien", + "Princess Pikeru", + "Gokipon", + "Demise, King of Armageddon", + "Anteatereatingant" + }, + "GAME GIFT COLLECTION": { + "Ritual", + "Valkyrion the Magna Warrior", + "Alpha the Magnet Warrior", + "Beta the Magnet Warrior", + "Gamma the Magnet Warrior", + "Magical Blast", + "Dunames Dark Witch", + "Vorse Raider", + "Exarion Universe", + "Abyss Soldier", + "Slate Warrior", + "Cyber-Tech Alligator", + "D.D. Assailant", + "Goblin Zombie", + "Elemental Hero Madballman", + "Mind Control", + "Toon Dark Magician Girl", + "Great Spirit", + "Graceful Dice", + "Negate Attack", + "Foolish Burial", + "Card Destruction", + "Dark Magic Ritual", + "Calamity of the Wicked" + }, + "Special Gift Collection": { + "Gate Guardian", + "Scapegoat", + "Gil Garth", + "La Jinn the Mystical Genie of the Lamp", + "Summoned Skull", + "Inferno Hammer", + "Gemini Elf", + "Cyber Harpie Lady", + "Dandylion", + "Blade Knight", + "Curse of Vampire", + "Elemental Hero Flame Wingman", + "Magician of Black Chaos" + }, + "Fairy Collection": { + "Silpheed", + "Dunames Dark Witch", + "Hysteric Fairy", + "The Agent of Judgment - Saturn", + "Shining Angel", + "Airknight Parshath", + "Dancing Fairy", + "Zolga", + "Kelbek", + "Mudora", + "Protective Soul Ailin", + "Marshmallon", + "Goddess with the Third Eye", + "Asura Priest", + "Manju of the Ten Thousand Hands", + "Senju of the Thousand Hands" + }, + "Dragon Collection": { + "Victory D.", + "Chaos Emperor Dragon - Envoy of the End", + "Kaiser Glider", + "Horus the Black Flame Dragon LV6", + "Luster Dragon", + "Luster Dragon #2" + "Spear Dragon", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Twin-Headed Behemoth", + "Cave Dragon", + "Masked Dragon", + "Element Dragon", + "Troop Dragon", + "Horus the Black Flame Dragon LV4", + "Pitch-Dark Dragon" + }, + "Warrior Collection A": { + "Gate Guardian", + "Gearfried the Iron Knight", + "Dimensional Warrior", + "Command Knight", + "The Last Warrior from Another Planet", + "Dream Clown" + }, + "Warrior Collection B": { + "Don Zaloog", + "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", + "Dark Scorpion - Gorg the Strong", + "Cliff the Trap Remover", + "Ninja Grandmaster Sasuke", + "D.D. Warrior Lady", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Dark Blade", + "Blindly Loyal Goblin", + "Exiled Force", + "Ultimate Baseball Kid", + "Freed the Matchless General", + "Holy Knight Ishzark", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Warrior Lady of the Wasteland", + "D.D. Assailant", + "Blade Knight", + "Marauding Captain", + "Toon Goblin Attack Force" + }, + "Fiend Collection A": { + "Sangan", + "Castle of Dark Illusions", + "Barox", + "La Jinn the Mystical Genie of the Lamp", + "Summoned Skull", + "Beast of Talwar", + "Sangan", + "Giant Germ", + "Spear Cretin", + "Versago the Destroyer", + "Toon Summoned Skull" + }, + "Fiend Collection B": { + "Raviel, Lord of Phantasms", + "Yata-Garasu", + "Helpoemer", + "Archfiend Soldier", + "Skull Descovery Knight", + "Gil Garth", + "Opticlops", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "D.D. Survivor", + "Skull Archfiend of Lightning", + "The End of Anubis", + "Dark Ruler Ha Des", + "Inferno Hammer", + "Legendary Fiend", + "Newdoria", + "Slate Warrior", + "Giant Orc", + "Second Goblin", + "Kiseitai", + "Jowls of Dark Demise", + "D. D. Trainer", + "Earthbound Spirit" + }, + "Machine Collection A": { + "Cyber-Stein", + "Mechanicalchaser", + "Jinzo", + "UFO Turtle", + "Cyber-Tech Alligator" + }, + "Machine Collection B": { + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "XY-Dragon Cannon", + "XZ-Tank Cannon", + "YZ-Tank Dragon", + "XYZ-Dragon Cannon", + "V-Tiger Jet", + "W-Wing Catapult", + "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon", + "Cyber Dragon", + "Cyber Twin Dragon", + "Green Gadget", + "Red Gadget", + "Yellow Gadget", + "B.E.S. Tetran" + }, + "Spellcaster Collection A": { + "Exodia", + "Dark Sage", + "Dark Magician", + "Time Wizard", + "Kazejin", + "Magician of Faith", + "Dark Elf", + "Gemini Elf", + "Injection Fairy Lily", + "Cosmo Queen", + "Magician of Black Chaos" + }, + "Spellcaster Collection B": { + "Jowgen the Spiritualist", + "Tsukuyomi", + "Manticore of Darkness", + "Chaos Command Magician", + "Cybernetic Magician", + "Skilled Dark Magician", + "Kycoo the Ghost Destroyer", + "Toon Gemini Elf", + "Toon Masked Sorcerer", + "Toon Dark Magician Girl", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Breaker the magical Warrior", + "The Tricky", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Summon Priest", + "Old Vindictive Magician", + "Apprentice Magician", + "Princess Pikeru", + "Blast Magician", + "Magical Marionette", + "Mythical Beast Cerberus", + "Royal Magical Library", + "Aussa the Earth Charmer", + + }, + "Zombie Collection": { + "Skull Servant", + "Regenerating Mummy", + "Ryu Kokki", + "Spirit Reaper", + "Pyramid Turtle", + "Master Kyonshee", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Decayed Commander", + "Zombie Tiger", + "Des Lacooda", + "Wandering Mummy", + "Royal Keeper" + }, + "Special Monsters A": { + "X-Head Cannon", + "Y-Dragon Head", + "Z-Metal Tank", + "V-Tiger Jet", + "W-Wing Catapult", + "Yata-Garasu", + "Tsukuyomi", + "Dark Blade", + "Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Dark Magician Girl", + "Toon Summoned Skull", + "Toon World", + "Burning Beast", + "Freezing Beast", + "Metallizing Parasite - Lunatite", + "Pitch-Dark Dragon", + "Giant Orc", + "Second Goblin", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Indomitable Fighter Lei Lei", + "Protective Soul Ailin", + "Frontline Base", + "Formation Union", + "Roll Out!", + "Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier" + }, + "Special Monsters B": { + "Polymerization", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Horus the Black Flame Dragon LV6", + "Horus the Black Flame Dragon LV4", + "Armed Dragon LV3" + "Armed Dragon LV5", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Elemental Hero Flame Wingman", + "Elemental Hero Avian", + "Elemental Hero Burstinatrix", + "Miracle Fusion", + "Elemental Hero Madballman", + "Elemental Hero Bubbleman", + "Elemental Hero Clayman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Sparkman", + "Elemental Hero Steam Healer", + "Elemental Hero Thunder Giant", + "Elemental Hero Wildedge", + "Elemental Hero Wildheart", + "Elemental Hero Bladedge", + "Level Up!", + "Ultimate Insect Lv3", + "Ultimate Insect Lv5" + }, + "Reverse Collection": { + "Magical Merchant", + "Castle of Dark Illusions", + "Magician of Faith", + "Penguin Soldier", + "Blade Knight", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Old Vindictive Magician", + "A Cat of Ill Omen", + "Jowls of Dark Demise", + "Cyber Jar", + "Morphing Jar", + "Morphing Jar #2", + "Needle Worm", + "Spear Cretin", + "Nobleman of Crossout", + "Aussa the Earth Charmer" + }, + "LP Recovery Collection": { + "Mystik Wok", + "Poison of the Old Man", + "Hysteric Fairy", + "Dancing Fairy", + "Zolga", + "Cestus of Dagla", + "Nimble Momonga", + "Solemn Wishes", + "Cure Mermaid", + "Princess Pikeru", + "Kiseitai", + "Elemental Hero Steam Healer", + "Fushi No Tori", + "Emergency Provisions" + }, + "Special Summon Collection A": { + "Perfectly Ultimate Great Moth", + "Dark Sage", + "Polymerization", + "Ritual", + "Cyber-Stein", + "Scapegoat", + "Aqua Spirit", + "Rock Spirit", + "Spirit of Flames", + "Garuda the Wind Spirit", + "Shining Angel", + "Mystic Tomato", + "Giant Rat", + "Mother Grizzly", + "UFO Turtle", + "Flying Kamakiri 1", + "Giant Germ", + "Revival Jam", + "Pyramid Turtle", + "Troop Dragon", + "Gravekeeper's Spy", + "Pitch-Dark Dragon", + "Decayed Commander", + "Zombie Tiger", + "Vampire Orchis", + "Des Dendle", + "Nimble Momonga", + "The Last Warrior from Another Planet", + "Embodiment of Apophis", + "Cyber Jar", + "Morphing Jar #2", + "Spear Cretin", + "Dark Magic Curtain" + }, + "Special Summon Collection B": { + "Monster Gate", + "Chaos Emperor Dragon - Envoy of the End", + "Ojama Trio", + "Dimension Fusion", + "Return from the Different Dimension", + "Gigantes", + "Inferno", + "Silpheed", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Skilled Dark Magician", + "Horus the Black Flame Dragon LV6", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Marauding Captain", + "Masked Dragon", + "The Tricky", + "Magical Dimension", + "Frontline Base", + "Formation Union", + "Princess Pikeru", + "Skull Zoma", + "Metal Reflect Slime" + "Level Up!", + "Howling Insect", + "Tribute Doll", + "Enchanting Fitting Room", + "Stray Lambs" + }, + "Special Summon Collection C": { + "Hamon, Lord of Striking Thunder", + "Raviel, Lord of Phantasms", + "Uria, Lord of Searing Flames", + "Treeborn Frog", + "Cyber Dragon", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Silent Swordsman Lv3", + "Silent Swordsman Lv5", + "Warrior Lady of the Wasteland", + "Dandylion", + "Curse of Vampire", + "Summon Priest", + "Miracle Fusion", + "Elemental Hero Bubbleman", + "The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Ultimate Insect Lv3", + "Ultimate Insect Lv5", + "Rescue Cat", + "Anteatereatingant" + }, + "Equipment Collection": { + "Megamorph", + "Cestus of Dagla", + "United We Stand" + }, + "Continuous Spell/Trap A": { + "Destiny Board", + "Spirit Message 'I'", + "Spirit Message 'N'", + "Spirit Message 'A'", + "Spirit Message 'L'", + "Messenger of Peace", + "Fairy Box", + "Ultimate Offering", + "Gravity Bind", + "Solemn Wishes", + "Embodiment of Apophis", + "Toon World" + }, + "Continuous Spell/Trap B": { + "Hamon, Lord of Striking Thunder", + "Uria, Lord of Searing Flames", + "Wave-Motion Cannon", + "Heart of the Underdog", + "Wall of Revealing Light", + "Dark Snake Syndrome", + "Call of the Mummy", + "Frontline Base", + "Level Limit - Area B", + "Skull Zoma", + "Pitch-Black Power Stone", + "Metal Reflect Slime" + }, + "Quick/Counter Collection": { + "Mystik Wok", + "Poison of the Old Man", + "Scapegoat", + "Magical Dimension", + "Enemy Controller", + "Collapse", + "Emergency Provisions", + "Graceful Dice", + "Offerings to the Doomed", + "Reload", + "Rush Recklessly", + "The Reliable Guardian", + "Cursed Seal of the Forbidden Spell", + "Divine Wrath", + "Horn of Heaven", + "Magic Drain", + "Magic Jammer", + "Negate Attack", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Spell Shield Type-8", + "Book of Moon", + "Serial Spell", + "Mystical Space Typhoon" + }, + "Direct Damage Collection": { + "Hamon, Lord of Striking Thunder", + "Chaos Emperor Dragon - Envoy of the End", + "Dark Snake Syndrome", + "Inferno", + "Exarion Universe", + "Kycoo the Ghost Destroyer", + "Giant Germ", + "Familiar-Possessed - Aussa", + "Familiar-Possessed - Eria", + "Familiar-Possessed - Hiita", + "Familiar-Possessed - Wynn", + "Dark Driceratops", + "Saber Beetle", + "Thestalos the Firestorm Monarch", + "Solar Flare Dragon", + "Ultimate Baseball Kid", + "Spear Dragon", + "Oxygeddon", + "Airknight Parshath", + "Vampire Lord", + "Stamping Destruction", + "Decayed Commander", + "Jowls of Dark Demise", + "Stealth Bird", + "Elemental Hero Bladedge", + }, + "Direct Attack Collection": { + "Victory D.", + "Dark Scorpion Combination", + "Spirit Reaper", + "Elemental Hero Rampart Blaster", + "Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Summoned Skull", + "Toon Dark Magician Girl" + }, + "Monster Destroy Collection": { + "Hamon, Lord of Striking Thunder", + "Inferno", + "Ninja Grandmaster Sasuke", + "Zaborg the Thunder Monarch", + "Mystic Swordsman LV2", + "Mystic Swordsman LV4", + "Mystic Swordsman LV6", + "Skull Descovery Knight", + "Inferno Hammer", + "Ryu Kokki", + "Newdoria", + "Exiled Force", + "Yomi Ship", + "Armed Dragon LV5", + "Element Dragon", + "Old Vindictive Magician", + "Magical Dimension", + "Des Dendle", + "Nobleman of Crossout", + "Shield Crash", + "Tribute to the Doomed", + "Elemental Hero Flame Wingman", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Steam Healer", + "Blast Magician", + "Magical Marionette", + "Swarm of Scarabs", + "Offerings to the Doomed", + "Divine Wrath", + "Dream Clown" + }, +} + + +def get_booster_locations(booster: str) -> Dict[str, str]: + return { + f"{booster} {i}": content + for i, content in enumerate(booster_contents[booster]) + } diff --git a/worlds/yugioh06/client_bh.py b/worlds/yugioh06/client_bh.py new file mode 100644 index 00000000..910eba7c --- /dev/null +++ b/worlds/yugioh06/client_bh.py @@ -0,0 +1,139 @@ +import math +from typing import TYPE_CHECKING, List, Optional, Set + +from NetUtils import ClientStatus, NetworkItem + +import worlds._bizhawk as bizhawk +from worlds._bizhawk.client import BizHawkClient +from worlds.yugioh06 import item_to_index + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class YuGiOh2006Client(BizHawkClient): + game = "Yu-Gi-Oh! 2006" + system = "GBA" + patch_suffix = ".apygo06" + local_checked_locations: Set[int] + goal_flag: int + rom_slot_name: Optional[str] + + def __init__(self) -> None: + super().__init__() + self.local_checked_locations = set() + self.rom_slot_name = None + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + from CommonClient import logger + + try: + # Check if ROM is some version of Yu-Gi-Oh! 2006 + game_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 11, "ROM")]))[0]).decode("ascii") + if game_name != "YUGIOHWCT06": + return False + + # Check if we can read the slot name. Doing this here instead of set_auth as a protection against + # validating a ROM where there's no slot name to read. + try: + slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(0x30, 32, "ROM")]))[0] + self.rom_slot_name = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") + except UnicodeDecodeError: + logger.info("Could not read slot name from ROM. Are you sure this ROM matches this client version?") + return False + except UnicodeDecodeError: + return False + except bizhawk.RequestFailedError: + return False # Should verify on the next pass + + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = False + return True + + async def set_auth(self, ctx: "BizHawkClientContext") -> None: + ctx.auth = self.rom_slot_name + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + try: + read_state = await bizhawk.read( + ctx.bizhawk_ctx, + [ + (0x0, 8, "EWRAM"), + (0x52E8, 32, "EWRAM"), + (0x5308, 32, "EWRAM"), + (0x5325, 1, "EWRAM"), + (0x6C38, 4, "EWRAM"), + ], + ) + game_state = read_state[0].decode("utf-8") + locations = read_state[1] + items = read_state[2] + amount_items = int.from_bytes(read_state[3], "little") + money = int.from_bytes(read_state[4], "little") + + # make sure save was created + if game_state != "YWCT2006": + return + local_items = bytearray(items) + await bizhawk.guarded_write( + ctx.bizhawk_ctx, + [(0x5308, parse_items(bytearray(items), ctx.items_received), "EWRAM")], + [(0x5308, local_items, "EWRAM")], + ) + money_received = 0 + for item in ctx.items_received: + if item.item == item_to_index["5000DP"] + 5730000: + money_received += 1 + if money_received > amount_items: + await bizhawk.guarded_write( + ctx.bizhawk_ctx, + [ + (0x6C38, (money + (money_received - amount_items) * 5000).to_bytes(4, "little"), "EWRAM"), + (0x5325, money_received.to_bytes(2, "little"), "EWRAM"), + ], + [ + (0x6C38, money.to_bytes(4, "little"), "EWRAM"), + (0x5325, amount_items.to_bytes(2, "little"), "EWRAM"), + ], + ) + + locs_to_send = set() + + # Check for set location flags. + for byte_i, byte in enumerate(bytearray(locations)): + for i in range(8): + and_value = 1 << i + if byte & and_value != 0: + flag_id = byte_i * 8 + i + + location_id = flag_id + 5730001 + if location_id in ctx.server_locations: + locs_to_send.add(location_id) + + # Send locations if there are any to send. + if locs_to_send != self.local_checked_locations: + self.local_checked_locations = locs_to_send + + if locs_to_send is not None: + await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}]) + + # Send game clear if we're in either any ending cutscene or the credits state. + if not ctx.finished_game and locations[18] & (1 << 5) != 0: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + + except bizhawk.RequestFailedError: + # Exit handler and return to main loop to reconnect. + pass + + +# Parses bit-map for local items and adds the received items to that bit-map +def parse_items(local_items: bytearray, items: List[NetworkItem]) -> bytearray: + array = local_items + for item in items: + index = item.item - 5730001 + if index != 254: + byte = math.floor(index / 8) + bit = index % 8 + array[byte] = array[byte] | (1 << bit) + return array diff --git a/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md b/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md new file mode 100644 index 00000000..ee8c95a3 --- /dev/null +++ b/worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md @@ -0,0 +1,53 @@ +# Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and +export a config file. + +## What does randomization do to this game? + +Unlocking Booster Packs, Campaign, Limited and Theme Duel Opponents has been changed. +You only need to beat each Campaign Opponent once. +Logic expects you to have access to the Booster Packs necessary to get the locations at a reasonable pace and consistency. +Logic remains, so the game is always able to be completed, but because of the shuffle, the player may need to defeat certain opponents before they +would in the vanilla game. + +You can change how much money you receive and how much booster packs cost. + +## What is the goal of Yu-Gi-Oh! 2006 when randomized? + +Defeat a certain amount of Limited/Theme Duels to Unlock the final Campaign Opponent and beat it. + +## What items and locations get shuffled? + +Locations in which items can be found: +- Getting a Duel Bonus for the first time +- Beating a certain amount campaign opponents of the same level. +- Beating a Limited/Theme Duel +- Obtaining certain cards (same that unlock a theme duel in vanilla) + +Items that are shuffled: +- Unlocking Booster Packs (the "ALL" Booster Packs are excluded) +- Unlocking Campaign Opponents +- Unlocking Limited/Theme Duels +- Banlists + +## What items are _not_ randomized? +Certain Key Items are kept in their original locations: +- Duel Puzzles +- Survival Mode +- Booster Pack Contents + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + + +## What does another world's item look like in Yu-Gi-Oh! 2006? + +You can only tell when and what you got via the client. + +## When the player receives an item, what happens? + +The Opponent/Pack becomes available to you. diff --git a/worlds/yugioh06/docs/setup_en.md b/worlds/yugioh06/docs/setup_en.md new file mode 100644 index 00000000..1beeaa6c --- /dev/null +++ b/worlds/yugioh06/docs/setup_en.md @@ -0,0 +1,72 @@ +# Setup Guide for Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 Archipelago + +## Important + +As we are using Bizhawk, this guide is only applicable to Windows and Linux systems. + +## Required Software + +- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory) + - Version 2.7.0 and later are supported. + - Detailed installation instructions for Bizhawk can be found at the above link. + - Windows users must run the prereq installer first, which can also be found at the above link. +- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) +- A US or European Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 Rom + +## Configuring Bizhawk + +Once Bizhawk has been installed, open Bizhawk and change the following settings: + +- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to + "Lua+LuaInterface". This is required for the Lua script to function correctly. + **NOTE: Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs** + **of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load** + **"NLua+KopiLua" until this step is done.** +- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button. + This reduces the possibility of losing save data in emulator crashes. +- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to + continue playing in the background, even if another window is selected, such as the Client. +- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want + to disable most of these, which you can do quickly using `Esc`. + +It is strongly recommended to associate GBA rom extensions (\*.gba) to the Bizhawk we've just installed. +To do so, we simply have to search any GBA rom we happened to own, right click and select "Open with...", unfold +the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder +and select EmuHawk.exe. + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your options by visiting the +[Yu-Gi-Oh! 2006 Player Options Page](/games/Yu-Gi-Oh!%202006/player-options) + +## Joining a MultiWorld Game + +### Obtain your GBA patch file + +When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your data file, or with a zip file containing everyone's data +files. Your data file should have a `.apygo06` extension. + +Double-click on your `.apygo06` file to start your client and start the ROM patch process. Once the process is finished +(this can take a while), the client and the emulator will be started automatically (if you associated the extension +to the emulator as recommended). + +### Connect to the Multiserver + +Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools" +menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script. + +Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`. + +To connect the client to the multiserver simply put `
:` on the textfield on top and press enter (if the +server uses password, type in the bottom textfield `/connect
: [password]`) + +Don't forget to start manipulating RNG early by shouting "Heart of the Cards!" during generation. \ No newline at end of file diff --git a/worlds/yugioh06/fusions.py b/worlds/yugioh06/fusions.py new file mode 100644 index 00000000..22d03b38 --- /dev/null +++ b/worlds/yugioh06/fusions.py @@ -0,0 +1,72 @@ +from typing import List, NamedTuple + + +class FusionData(NamedTuple): + name: str + materials: List[str] + replaceable: bool + additional_spells: List[str] + + +fusions = { + "Elemental Hero Flame Wingman": FusionData( + "Elemental Hero Flame Wingman", + ["Elemental Hero Avian", "Elemental Hero Burstinatrix"], + True, + ["Miracle Fusion"]), + "Elemental Hero Madballman": FusionData( + "Elemental Hero Madballman", + ["Elemental Hero Bubbleman", "Elemental Hero Clayman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Rampart Blaster": FusionData( + "Elemental Hero Rampart Blaster", + ["Elemental Hero Burstinatrix", "Elemental Hero Clayman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Shining Flare Wingman": FusionData( + "Elemental Hero Shining Flare Wingman", + ["Elemental Hero Flame Wingman", "Elemental Hero Sparkman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Steam Healer": FusionData( + "Elemental Hero Steam Healer", + ["Elemental Hero Burstinatrix", "Elemental Hero Bubbleman"], + True, + ["Miracle Fusion"]), + "Elemental Hero Wildedge": FusionData( + "Elemental Hero Wildedge", + ["Elemental Hero Wildheart", "Elemental Hero Bladedge"], + True, + ["Miracle Fusion"]) +} + +fusion_subs = ["The Dark - Hex-Sealed Fusion", + "The Earth - Hex-Sealed Fusion", + "The Light - Hex-Sealed Fusion", + "Goddess with the Third Eye", + "King of the Swamp", + "Versago the Destroyer", + # Only in All-packs + "Beastking of the Swamps", + "Mystical Sheep #1"] + + +def has_all_materials(state, monster, player): + data = fusions.get(monster) + if not state.has(monster, player): + return False + if data is None: + return True + else: + materials = data.replaceable and state.has_any(fusion_subs, player) + for material in data.materials: + materials += has_all_materials(state, material, player) + return materials >= len(data.materials) + + +def count_has_materials(state, monsters, player): + amount = 0 + for monster in monsters: + amount += has_all_materials(state, monster, player) + return amount diff --git a/worlds/yugioh06/items.py b/worlds/yugioh06/items.py new file mode 100644 index 00000000..f0f877fd --- /dev/null +++ b/worlds/yugioh06/items.py @@ -0,0 +1,369 @@ +from typing import Dict, List + +item_to_index: Dict[str, int] = { + "LEGEND OF B.E.W.D.": 1, + "METAL RAIDERS": 2, + "PHARAOH'S SERVANT": 3, + "PHARAONIC GUARDIAN": 4, + "SPELL RULER": 5, + "LABYRINTH OF NIGHTMARE": 6, + "LEGACY OF DARKNESS": 7, + "MAGICIAN'S FORCE": 8, + "DARK CRISIS": 9, + "INVASION OF CHAOS": 10, + "ANCIENT SANCTUARY": 11, + "SOUL OF THE DUELIST": 12, + "RISE OF DESTINY": 13, + "FLAMING ETERNITY": 14, + "THE LOST MILLENIUM": 15, + "CYBERNETIC REVOLUTION": 16, + "ELEMENTAL ENERGY": 17, + "SHADOW OF INFINITY": 18, + "GAME GIFT COLLECTION": 19, + "Special Gift Collection": 20, + "Fairy Collection": 21, + "Dragon Collection": 22, + "Warrior Collection A": 23, + "Warrior Collection B": 24, + "Fiend Collection A": 25, + "Fiend Collection B": 26, + "Machine Collection A": 27, + "Machine Collection B": 28, + "Spellcaster Collection A": 29, + "Spellcaster Collection B": 30, + "Zombie Collection": 31, + "Special Monsters A": 32, + "Special Monsters B": 33, + "Reverse Collection": 34, + "LP Recovery Collection": 35, + "Special Summon Collection A": 36, + "Special Summon Collection B": 37, + "Special Summon Collection C": 38, + "Equipment Collection": 39, + "Continuous Spell/Trap A": 40, + "Continuous Spell/Trap B": 41, + "Quick/Counter Collection": 42, + "Direct Damage Collection": 43, + "Direct Attack Collection": 44, + "Monster Destroy Collection": 45, + "All Normal Monsters": 46, + "All Effect Monsters": 47, + "All Fusion Monsters": 48, + "All Traps": 49, + "All Spells": 50, + "All at Random": 51, + "LD01 All except Level 4 forbidden Unlock": 52, + "LD02 Medium/high Level forbidden Unlock": 53, + "LD03 ATK 1500 or more forbidden Unlock": 54, + "LD04 Flip Effects forbidden Unlock": 55, + "LD05 Tributes forbidden Unlock": 56, + "LD06 Traps forbidden Unlock": 57, + "LD07 Large Deck A Unlock": 58, + "LD08 Large Deck B Unlock": 59, + "LD09 Sets Forbidden Unlock": 60, + "LD10 All except LV monsters forbidden Unlock": 61, + "LD11 All except Fairies forbidden Unlock": 62, + "LD12 All except Wind forbidden Unlock": 63, + "LD13 All except monsters forbidden Unlock": 64, + "LD14 Level 3 or below forbidden Unlock": 65, + "LD15 DEF 1500 or less forbidden Unlock": 66, + "LD16 Effect Monsters forbidden Unlock": 67, + "LD17 Spells forbidden Unlock": 68, + "LD18 Attacks forbidden Unlock": 69, + "LD19 All except E-Hero's forbidden Unlock": 70, + "LD20 All except Warriors forbidden Unlock": 71, + "LD21 All except Dark forbidden Unlock": 72, + "LD22 All limited cards forbidden Unlock": 73, + "LD23 Refer to Mar 05 Banlist Unlock": 74, + "LD24 Refer to Sept 04 Banlist Unlock": 75, + "LD25 Low Life Points Unlock": 76, + "LD26 All except Toons forbidden Unlock": 77, + "LD27 All except Spirits forbidden Unlock": 78, + "LD28 All except Dragons forbidden Unlock": 79, + "LD29 All except Spellcasters forbidden Unlock": 80, + "LD30 All except Light forbidden Unlock": 81, + "LD31 All except Fire forbidden Unlock": 82, + "LD32 Decks with multiples forbidden Unlock": 83, + "LD33 Special Summons forbidden Unlock": 84, + "LD34 Normal Summons forbidden Unlock": 85, + "LD35 All except Zombies forbidden Unlock": 86, + "LD36 All except Earth forbidden Unlock": 87, + "LD37 All except Water forbidden Unlock": 88, + "LD38 Refer to Mar 04 Banlist Unlock": 89, + "LD39 Monsters forbidden Unlock": 90, + "LD40 Refer to Sept 05 Banlist Unlock": 91, + "LD41 Refer to Sept 03 Banlist Unlock": 92, + "TD01 Battle Damage Unlock": 93, + "TD02 Deflected Damage Unlock": 94, + "TD03 Normal Summon Unlock": 95, + "TD04 Ritual Summon Unlock": 96, + "TD05 Special Summon A Unlock": 97, + "TD06 20x Spell Unlock": 98, + "TD07 10x Trap Unlock": 99, + "TD08 Draw Unlock": 100, + "TD09 Hand Destruction Unlock": 101, + "TD10 During Opponent's Turn Unlock": 102, + "TD11 Recover Unlock": 103, + "TD12 Remove Monsters by Effect Unlock": 104, + "TD13 Flip Summon Unlock": 105, + "TD14 Special Summon B Unlock": 106, + "TD15 Token Unlock": 107, + "TD16 Union Unlock": 108, + "TD17 10x Quick Spell Unlock": 109, + "TD18 The Forbidden Unlock": 110, + "TD19 20 Turns Unlock": 111, + "TD20 Deck Destruction Unlock": 112, + "TD21 Victory D. Unlock": 113, + "TD22 The Preventers Fight Back Unlock": 114, + "TD23 Huge Revolution Unlock": 115, + "TD24 Victory in 5 Turns Unlock": 116, + "TD25 Moth Grows Up Unlock": 117, + "TD26 Magnetic Power Unlock": 118, + "TD27 Dark Sage Unlock": 119, + "TD28 Direct Damage Unlock": 120, + "TD29 Destroy Monsters in Battle Unlock": 121, + "TD30 Tribute Summon Unlock": 122, + "TD31 Special Summon C Unlock": 123, + "TD32 Toon Unlock": 124, + "TD33 10x Counter Unlock": 125, + "TD34 Destiny Board Unlock": 126, + "TD35 Huge Damage in a Turn Unlock": 127, + "TD36 V-Z In the House Unlock": 128, + "TD37 Uria, Lord of Searing Flames Unlock": 129, + "TD38 Hamon, Lord of Striking Thunder Unlock": 130, + "TD39 Raviel, Lord of Phantasms Unlock": 131, + "TD40 Make a Chain Unlock": 132, + "TD41 The Gatekeeper Stands Tall Unlock": 133, + "TD42 Serious Damage Unlock": 134, + "TD43 Return Monsters with Effects Unlock": 135, + "TD44 Fusion Summon Unlock": 136, + "TD45 Big Damage at once Unlock": 137, + "TD46 XYZ In the House Unlock": 138, + "TD47 Spell Counter Unlock": 139, + "TD48 Destroy Monsters with Effects Unlock": 140, + "TD49 Plunder Unlock": 141, + "TD50 Dark Scorpion Combination Unlock": 142, + "Campaign Tier 1 Column 1": 143, + "Campaign Tier 1 Column 2": 144, + "Campaign Tier 1 Column 3": 145, + "Campaign Tier 1 Column 4": 146, + "Campaign Tier 1 Column 5": 147, + "Campaign Tier 2 Column 1": 148, + "Campaign Tier 2 Column 2": 149, + "Campaign Tier 2 Column 3": 150, + "Campaign Tier 2 Column 4": 151, + "Campaign Tier 2 Column 5": 152, + "Campaign Tier 3 Column 1": 153, + "Campaign Tier 3 Column 2": 154, + "Campaign Tier 3 Column 3": 155, + "Campaign Tier 3 Column 4": 156, + "Campaign Tier 3 Column 5": 157, + "Campaign Tier 4 Column 1": 158, + "Campaign Tier 4 Column 2": 159, + "Campaign Tier 4 Column 3": 160, + "Campaign Tier 4 Column 4": 161, + "Campaign Tier 4 Column 5": 162, + "Campaign Tier 5 Column 1": 163, + "Campaign Tier 5 Column 2": 164, + "No Banlist": 167, + "Banlist September 2003": 168, + "Banlist March 2004": 169, + "Banlist September 2004": 170, + "Banlist March 2005": 171, + "Banlist September 2005": 172, + "5000DP": 254, + "Remote": 255, +} + +tier_1_opponents: List[str] = [ + "Campaign Tier 1 Column 1", + "Campaign Tier 1 Column 2", + "Campaign Tier 1 Column 3", + "Campaign Tier 1 Column 4", + "Campaign Tier 1 Column 5", +] + +Banlist_Items: List[str] = [ + "No Banlist", + "Banlist September 2003", + "Banlist March 2004", + "Banlist September 2004", + "Banlist March 2005", + "Banlist September 2005", +] + +draft_boosters: List[str] = [ + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "RISE OF DESTINY", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", +] + +draft_opponents: List[str] = ["Campaign Tier 1 Column 1", "Campaign Tier 1 Column 5"] + +booster_packs: List[str] = [ + "LEGEND OF B.E.W.D.", + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "SPELL RULER", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "ANCIENT SANCTUARY", + "SOUL OF THE DUELIST", + "RISE OF DESTINY", + "FLAMING ETERNITY", + "THE LOST MILLENIUM", + "CYBERNETIC REVOLUTION", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", + "GAME GIFT COLLECTION", + "Special Gift Collection", + "Fairy Collection", + "Dragon Collection", + "Warrior Collection A", + "Warrior Collection B", + "Fiend Collection A", + "Fiend Collection B", + "Machine Collection A", + "Machine Collection B", + "Spellcaster Collection A", + "Spellcaster Collection B", + "Zombie Collection", + "Special Monsters A", + "Special Monsters B", + "Reverse Collection", + "LP Recovery Collection", + "Special Summon Collection A", + "Special Summon Collection B", + "Special Summon Collection C", + "Equipment Collection", + "Continuous Spell/Trap A", + "Continuous Spell/Trap B", + "Quick/Counter Collection", + "Direct Damage Collection", + "Direct Attack Collection", + "Monster Destroy Collection", +] + +challenges: List[str] = [ + "LD01 All except Level 4 forbidden Unlock", + "LD02 Medium/high Level forbidden Unlock", + "LD03 ATK 1500 or more forbidden Unlock", + "LD04 Flip Effects forbidden Unlock", + "LD05 Tributes forbidden Unlock", + "LD06 Traps forbidden Unlock", + "LD07 Large Deck A Unlock", + "LD08 Large Deck B Unlock", + "LD09 Sets Forbidden Unlock", + "LD10 All except LV monsters forbidden Unlock", + "LD11 All except Fairies forbidden Unlock", + "LD12 All except Wind forbidden Unlock", + "LD13 All except monsters forbidden Unlock", + "LD14 Level 3 or below forbidden Unlock", + "LD15 DEF 1500 or less forbidden Unlock", + "LD16 Effect Monsters forbidden Unlock", + "LD17 Spells forbidden Unlock", + "LD18 Attacks forbidden Unlock", + "LD19 All except E-Hero's forbidden Unlock", + "LD20 All except Warriors forbidden Unlock", + "LD21 All except Dark forbidden Unlock", + "LD22 All limited cards forbidden Unlock", + "LD23 Refer to Mar 05 Banlist Unlock", + "LD24 Refer to Sept 04 Banlist Unlock", + "LD25 Low Life Points Unlock", + "LD26 All except Toons forbidden Unlock", + "LD27 All except Spirits forbidden Unlock", + "LD28 All except Dragons forbidden Unlock", + "LD29 All except Spellcasters forbidden Unlock", + "LD30 All except Light forbidden Unlock", + "LD31 All except Fire forbidden Unlock", + "LD32 Decks with multiples forbidden Unlock", + "LD33 Special Summons forbidden Unlock", + "LD34 Normal Summons forbidden Unlock", + "LD35 All except Zombies forbidden Unlock", + "LD36 All except Earth forbidden Unlock", + "LD37 All except Water forbidden Unlock", + "LD38 Refer to Mar 04 Banlist Unlock", + "LD39 Monsters forbidden Unlock", + "LD40 Refer to Sept 05 Banlist Unlock", + "LD41 Refer to Sept 03 Banlist Unlock", + "TD01 Battle Damage Unlock", + "TD02 Deflected Damage Unlock", + "TD03 Normal Summon Unlock", + "TD04 Ritual Summon Unlock", + "TD05 Special Summon A Unlock", + "TD06 20x Spell Unlock", + "TD07 10x Trap Unlock", + "TD08 Draw Unlock", + "TD09 Hand Destruction Unlock", + "TD10 During Opponent's Turn Unlock", + "TD11 Recover Unlock", + "TD12 Remove Monsters by Effect Unlock", + "TD13 Flip Summon Unlock", + "TD14 Special Summon B Unlock", + "TD15 Token Unlock", + "TD16 Union Unlock", + "TD17 10x Quick Spell Unlock", + "TD18 The Forbidden Unlock", + "TD19 20 Turns Unlock", + "TD20 Deck Destruction Unlock", + "TD21 Victory D. Unlock", + "TD22 The Preventers Fight Back Unlock", + "TD23 Huge Revolution Unlock", + "TD24 Victory in 5 Turns Unlock", + "TD25 Moth Grows Up Unlock", + "TD26 Magnetic Power Unlock", + "TD27 Dark Sage Unlock", + "TD28 Direct Damage Unlock", + "TD29 Destroy Monsters in Battle Unlock", + "TD30 Tribute Summon Unlock", + "TD31 Special Summon C Unlock", + "TD32 Toon Unlock", + "TD33 10x Counter Unlock", + "TD34 Destiny Board Unlock", + "TD35 Huge Damage in a Turn Unlock", + "TD36 V-Z In the House Unlock", + "TD37 Uria, Lord of Searing Flames Unlock", + "TD38 Hamon, Lord of Striking Thunder Unlock", + "TD39 Raviel, Lord of Phantasms Unlock", + "TD40 Make a Chain Unlock", + "TD41 The Gatekeeper Stands Tall Unlock", + "TD42 Serious Damage Unlock", + "TD43 Return Monsters with Effects Unlock", + "TD44 Fusion Summon Unlock", + "TD45 Big Damage at once Unlock", + "TD46 XYZ In the House Unlock", + "TD47 Spell Counter Unlock", + "TD48 Destroy Monsters with Effects Unlock", + "TD49 Plunder Unlock", + "TD50 Dark Scorpion Combination Unlock", +] + +excluded_items: List[str] = [ + "All Normal Monsters", + "All Effect Monsters", + "All Fusion Monsters", + "All Traps", + "All Spells", + "All at Random", + "5000DP", + "Remote", +] + +useful: List[str] = [ + "Banlist March 2004", + "Banlist September 2004", + "Banlist March 2005", + "Banlist September 2005", +] diff --git a/worlds/yugioh06/locations.py b/worlds/yugioh06/locations.py new file mode 100644 index 00000000..f495bfed --- /dev/null +++ b/worlds/yugioh06/locations.py @@ -0,0 +1,213 @@ +Bonuses = { + "Duelist Bonus Level 1": 1, + "Duelist Bonus Level 2": 2, + "Duelist Bonus Level 3": 3, + "Duelist Bonus Level 4": 4, + "Duelist Bonus Level 5": 5, + "Battle Damage": 6, + "Battle Damage Only Bonus": 7, + "Max ATK Bonus": 8, + "Max Damage Bonus": 9, + "Destroyed in Battle Bonus": 10, + "Spell Card Bonus": 11, + "Trap Card Bonus": 12, + "Tribute Summon Bonus": 13, + "Fusion Summon Bonus": 14, + "Ritual Summon Bonus": 15, + "No Special Summon Bonus": 16, + "No Spell Cards Bonus": 17, + "No Trap Cards Bonus": 18, + "No Damage Bonus": 19, + "Over 20000 LP Bonus": 20, + "Low LP Bonus": 21, + "Extremely Low LP Bonus": 22, + "Low Deck Bonus": 23, + "Extremely Low Deck Bonus": 24, + "Effect Damage Only Bonus": 25, + "No More Cards Bonus": 26, + "Opponent's Turn Finish Bonus": 27, + "Exactly 0 LP Bonus": 28, + "Reversal Finish Bonus": 29, + "Quick Finish Bonus": 30, + "Exodia Finish Bonus": 31, + "Last Turn Finish Bonus": 32, + "Final Countdown Finish Bonus": 33, + "Destiny Board Finish Bonus": 34, + "Yata-Garasu Finish Bonus": 35, + "Skull Servant Finish Bonus": 36, + "Konami Bonus": 37, +} + +Limited_Duels = { + "LD01 All except Level 4 forbidden": 38, + "LD02 Medium/high Level forbidden": 39, + "LD03 ATK 1500 or more forbidden": 40, + "LD04 Flip Effects forbidden": 41, + "LD05 Tributes forbidden": 42, + "LD06 Traps forbidden": 43, + "LD07 Large Deck A": 44, + "LD08 Large Deck B": 45, + "LD09 Sets Forbidden": 46, + "LD10 All except LV monsters forbidden": 47, + "LD11 All except Fairies forbidden": 48, + "LD12 All except Wind forbidden": 49, + "LD13 All except monsters forbidden": 50, + "LD14 Level 3 or below forbidden": 51, + "LD15 DEF 1500 or less forbidden": 52, + "LD16 Effect Monsters forbidden": 53, + "LD17 Spells forbidden": 54, + "LD18 Attacks forbidden": 55, + "LD19 All except E-Hero's forbidden": 56, + "LD20 All except Warriors forbidden": 57, + "LD21 All except Dark forbidden": 58, + "LD22 All limited cards forbidden": 59, + "LD23 Refer to Mar 05 Banlist": 60, + "LD24 Refer to Sept 04 Banlist": 61, + "LD25 Low Life Points": 62, + "LD26 All except Toons forbidden": 63, + "LD27 All except Spirits forbidden": 64, + "LD28 All except Dragons forbidden": 65, + "LD29 All except Spellcasters forbidden": 66, + "LD30 All except Light forbidden": 67, + "LD31 All except Fire forbidden": 68, + "LD32 Decks with multiples forbidden": 69, + "LD33 Special Summons forbidden": 70, + "LD34 Normal Summons forbidden": 71, + "LD35 All except Zombies forbidden": 72, + "LD36 All except Earth forbidden": 73, + "LD37 All except Water forbidden": 74, + "LD38 Refer to Mar 04 Banlist": 75, + "LD39 Monsters forbidden": 76, + "LD40 Refer to Sept 05 Banlist": 77, + "LD41 Refer to Sept 03 Banlist": 78, +} + +Theme_Duels = { + "TD01 Battle Damage": 79, + "TD02 Deflected Damage": 80, + "TD03 Normal Summon": 81, + "TD04 Ritual Summon": 82, + "TD05 Special Summon A": 83, + "TD06 20x Spell": 84, + "TD07 10x Trap": 85, + "TD08 Draw": 86, + "TD09 Hand Destruction": 87, + "TD10 During Opponent's Turn": 88, + "TD11 Recover": 89, + "TD12 Remove Monsters by Effect": 90, + "TD13 Flip Summon": 91, + "TD14 Special Summon B": 92, + "TD15 Token": 93, + "TD16 Union": 94, + "TD17 10x Quick Spell": 95, + "TD18 The Forbidden": 96, + "TD19 20 Turns": 97, + "TD20 Deck Destruction": 98, + "TD21 Victory D.": 99, + "TD22 The Preventers Fight Back": 100, + "TD23 Huge Revolution": 101, + "TD24 Victory in 5 Turns": 102, + "TD25 Moth Grows Up": 103, + "TD26 Magnetic Power": 104, + "TD27 Dark Sage": 105, + "TD28 Direct Damage": 106, + "TD29 Destroy Monsters in Battle": 107, + "TD30 Tribute Summon": 108, + "TD31 Special Summon C": 109, + "TD32 Toon": 110, + "TD33 10x Counter": 111, + "TD34 Destiny Board": 112, + "TD35 Huge Damage in a Turn": 113, + "TD36 V-Z In the House": 114, + "TD37 Uria, Lord of Searing Flames": 115, + "TD38 Hamon, Lord of Striking Thunder": 116, + "TD39 Raviel, Lord of Phantasms": 117, + "TD40 Make a Chain": 118, + "TD41 The Gatekeeper Stands Tall": 119, + "TD42 Serious Damage": 120, + "TD43 Return Monsters with Effects": 121, + "TD44 Fusion Summon": 122, + "TD45 Big Damage at once": 123, + "TD46 XYZ In the House": 124, + "TD47 Spell Counter": 125, + "TD48 Destroy Monsters with Effects": 126, + "TD49 Plunder": 127, + "TD50 Dark Scorpion Combination": 128, +} + +Campaign_Opponents = { + "Campaign Tier 1: 1 Win": 129, + "Campaign Tier 1: 3 Wins A": 130, + "Campaign Tier 1: 3 Wins B": 131, + "Campaign Tier 1: 5 Wins A": 132, + "Campaign Tier 1: 5 Wins B": 133, + "Campaign Tier 2: 1 Win": 134, + "Campaign Tier 2: 3 Wins A": 135, + "Campaign Tier 2: 3 Wins B": 136, + "Campaign Tier 2: 5 Wins A": 137, + "Campaign Tier 2: 5 Wins B": 138, + "Campaign Tier 3: 1 Win": 139, + "Campaign Tier 3: 3 Wins A": 140, + "Campaign Tier 3: 3 Wins B": 141, + "Campaign Tier 3: 5 Wins A": 142, + "Campaign Tier 3: 5 Wins B": 143, + "Campaign Tier 4: 5 Wins A": 144, + "Campaign Tier 4: 5 Wins B": 145, +} + +special = { + "Campaign Tier 5: Column 1 Win": 146, + "Campaign Tier 5: Column 2 Win": 147, + "Campaign Tier 5: Column 3 Win": 148, + "Campaign Tier 5: Column 4 Win": 149, + # "Campaign Final Boss Win": 150, +} + +Required_Cards = { + "Obtain all pieces of Exodia": 154, + "Obtain Final Countdown": 155, + "Obtain Victory Dragon": 156, + "Obtain Ojama Delta Hurricane and its required cards": 157, + "Obtain Huge Revolution and its required cards": 158, + "Obtain Perfectly Ultimate Great Moth and its required cards": 159, + "Obtain Valkyrion the Magna Warrior and its pieces": 160, + "Obtain Dark Sage and its required cards": 161, + "Obtain Destiny Board and its letters": 162, + "Obtain all XYZ-Dragon Cannon fusions and their materials": 163, + "Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials": 164, + "Obtain Hamon, Lord of Striking Thunder": 165, + "Obtain Raviel, Lord of Phantasms": 166, + "Obtain Uria, Lord of Searing Flames": 167, + "Obtain Gate Guardian and its pieces": 168, + "Obtain Dark Scorpion Combination and its required cards": 169, +} + +collection_events = { + "Ojama Delta Hurricane and required cards": None, + "Huge Revolution and its required cards": None, + "Perfectly Ultimate Great Moth and its required cards": None, + "Valkyrion the Magna Warrior and its pieces": None, + "Dark Sage and its required cards": None, + "Destiny Board and its letters": None, + "XYZ-Dragon Cannon fusions and their materials": None, + "VWXYZ-Dragon Catapult Cannon and the fusion materials": None, + "Gate Guardian and its pieces": None, + "Dark Scorpion Combination and its required cards": None, + "Can Exodia Win": None, + "Can Yata Lock": None, + "Can Stall with Monsters": None, + "Can Stall with ST": None, + "Can Last Turn Win": None, + "Has Back-row removal": None, +} + + +def get_beat_challenge_events(self): + beat_events = {} + for limited in Limited_Duels.keys(): + if limited not in self.removed_challenges: + beat_events[limited + " Complete"] = None + for theme in Theme_Duels.keys(): + if theme not in self.removed_challenges: + beat_events[theme + " Complete"] = None + return beat_events diff --git a/worlds/yugioh06/logic.py b/worlds/yugioh06/logic.py new file mode 100644 index 00000000..3227cbfe --- /dev/null +++ b/worlds/yugioh06/logic.py @@ -0,0 +1,28 @@ +from typing import List + +from BaseClasses import CollectionState + +core_booster: List[str] = [ + "LEGEND OF B.E.W.D.", + "METAL RAIDERS", + "PHARAOH'S SERVANT", + "PHARAONIC GUARDIAN", + "SPELL RULER", + "LABYRINTH OF NIGHTMARE", + "LEGACY OF DARKNESS", + "MAGICIAN'S FORCE", + "DARK CRISIS", + "INVASION OF CHAOS", + "ANCIENT SANCTUARY", + "SOUL OF THE DUELIST", + "RISE OF DESTINY", + "FLAMING ETERNITY", + "THE LOST MILLENIUM", + "CYBERNETIC REVOLUTION", + "ELEMENTAL ENERGY", + "SHADOW OF INFINITY", +] + + +def yugioh06_difficulty(state: CollectionState, player: int, amount: int): + return state.has_from_list(core_booster, player, amount) diff --git a/worlds/yugioh06/opponents.py b/worlds/yugioh06/opponents.py new file mode 100644 index 00000000..1746b565 --- /dev/null +++ b/worlds/yugioh06/opponents.py @@ -0,0 +1,264 @@ +from typing import Dict, List, NamedTuple, Optional, Union + +from BaseClasses import MultiWorld +from worlds.generic.Rules import CollectionRule + +from worlds.yugioh06 import item_to_index, tier_1_opponents, yugioh06_difficulty +from worlds.yugioh06.locations import special + + +class OpponentData(NamedTuple): + id: int + name: str + campaign_info: List[str] + tier: int + column: int + card_id: int = 0 + deck_name_id: int = 0 + deck_file: str = "" + difficulty: int = 1 + additional_info: List[str] = [] + + def tier(self, tier): + self.tier = tier + + def column(self, column): + self.column = column + + +challenge_opponents = [ + # Theme + OpponentData(27, "Exarion Universe", [], 1, 1, 5452, 13001, "deck/theme_001.ydc\x00\x00\x00\x00", 0), + OpponentData(28, "Stone Statue of the Aztecs", [], 4, 1, 4754, 13002, "deck/theme_002.ydc\x00\x00\x00\x00", 3), + OpponentData(29, "Raging Flame Sprite", [], 1, 1, 6189, 13003, "deck/theme_003.ydc\x00\x00\x00\x00", 0), + OpponentData(30, "Princess Pikeru", [], 1, 1, 6605, 13004, "deck/theme_004.ydc\x00\x00\x00\x00", 0), + OpponentData(31, "Princess Curran", ["Quick-Finish"], 1, 1, 6606, 13005, "deck/theme_005.ydc\x00\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(32, "Gearfried the Iron Knight", ["Quick-Finish"], 2, 1, 5059, 13006, + "deck/theme_006.ydc\x00\x00\x00\x00", 1), + OpponentData(33, "Zaborg the Thunder Monarch", [], 3, 1, 5965, 13007, "deck/theme_007.ydc\x00\x00\x00\x00", 2), + OpponentData(34, "Kycoo the Ghost Destroyer", ["Quick-Finish"], 3, 1, 5248, 13008, + "deck/theme_008.ydc\x00\x00\x00\x00"), + OpponentData(35, "Penguin Soldier", ["Quick-Finish"], 1, 1, 4608, 13009, "deck/theme_009.ydc\x00\x00\x00\x00", 0), + OpponentData(36, "Green Gadget", [], 5, 1, 6151, 13010, "deck/theme_010.ydc\x00\x00\x00\x00", 5), + OpponentData(37, "Guardian Sphinx", ["Quick-Finish"], 3, 1, 5422, 13011, "deck/theme_011.ydc\x00\x00\x00\x00", 3), + OpponentData(38, "Cyber-Tech Alligator", [], 2, 1, 4790, 13012, "deck/theme_012.ydc\x00\x00\x00\x00", 1), + OpponentData(39, "UFOroid Fighter", [], 3, 1, 6395, 13013, "deck/theme_013.ydc\x00\x00\x00\x00", 2), + OpponentData(40, "Relinquished", [], 3, 1, 4737, 13014, "deck/theme_014.ydc\x00\x00\x00\x00", 2), + OpponentData(41, "Manticore of Darkness", [], 2, 1, 5881, 13015, "deck/theme_015.ydc\x00\x00\x00\x00", 1), + OpponentData(42, "Vampire Lord", [], 3, 1, 5410, 13016, "deck/theme_016.ydc\x00\x00\x00\x00", 2), + OpponentData(43, "Gigantes", ["Quick-Finish"], 3, 1, 5831, 13017, "deck/theme_017.ydc\x00\x00\x00\x00", 2), + OpponentData(44, "Insect Queen", ["Quick-Finish"], 2, 1, 4768, 13018, "deck/theme_018.ydc\x00\x00\x00\x00", 1), + OpponentData(45, "Second Goblin", ["Quick-Finish"], 1, 1, 5587, 13019, "deck/theme_019.ydc\x00\x00\x00\x00", 0), + OpponentData(46, "Toon Summoned Skull", [], 4, 1, 4735, 13020, "deck/theme_020.ydc\x00\x00\x00\x00", 3), + OpponentData(47, "Iron Blacksmith Kotetsu", [], 2, 1, 5769, 13021, "deck/theme_021.ydc\x00\x00\x00\x00", 1), + OpponentData(48, "Magician of Faith", [], 1, 1, 4434, 13022, "deck/theme_022.ydc\x00\x00\x00\x00", 0), + OpponentData(49, "Mask of Darkness", [], 1, 1, 4108, 13023, "deck/theme_023.ydc\x00\x00\x00\x00", 0), + OpponentData(50, "Dark Ruler Vandalgyon", [], 3, 1, 6410, 13024, "deck/theme_024.ydc\x00\x00\x00\x00", 2), + OpponentData(51, "Aussa the Earth Charmer", ["Quick-Finish"], 2, 1, 6335, 13025, + "deck/theme_025.ydc\x00\x00\x00\x00", 1), + OpponentData(52, "Exodia Necross", ["Quick-Finish"], 2, 1, 5701, 13026, "deck/theme_026.ydc\x00\x00\x00\x00", 1), + OpponentData(53, "Dark Necrofear", [], 3, 1, 5222, 13027, "deck/theme_027.ydc\x00\x00\x00\x00", 2), + OpponentData(54, "Demise, King of Armageddon", [], 4, 1, 6613, 13028, "deck/theme_028.ydc\x00\x00\x00\x00", 2), + OpponentData(55, "Yamata Dragon", [], 3, 1, 5377, 13029, "deck/theme_029.ydc\x00\x00\x00\x00", 2), + OpponentData(56, "Blue-Eyes Ultimate Dragon", [], 3, 1, 4386, 13030, "deck/theme_030.ydc\x00\x00\x00\x00", 2), + OpponentData(57, "Wave-Motion Cannon", [], 4, 1, 5614, 13031, "deck/theme_031.ydc\x00\x00\x00\x00", 3, + ["Has Back-row removal"]), + # Unused opponent + # OpponentData(58, "Yata-Garasu", [], 1, 1, 5375, 13032, "deck/theme_031.ydc\x00\x00\x00\x00"), + # Unused opponent + # OpponentData(59, "Makyura the Destructor", [], 1, 1, 5285, 13033, "deck/theme_031.ydc\x00\x00\x00\x00"), + OpponentData(60, "Morphing Jar", [], 5, 1, 4597, 13034, "deck/theme_034.ydc\x00\x00\x00\x00", 4), + OpponentData(61, "Spirit Reaper", [], 2, 1, 5526, 13035, "deck/theme_035.ydc\x00\x00\x00\x00", 1), + OpponentData(62, "Victory D.", [], 3, 1, 5868, 13036, "deck/theme_036.ydc\x00\x00\x00\x00", 2), + OpponentData(63, "VWXYZ-Dragon Catapult Cannon", ["Quick-Finish"], 3, 1, 6484, 13037, + "deck/theme_037.ydc\x00\x00\x00\x00", 2), + OpponentData(64, "XYZ-Dragon Cannon", [], 2, 1, 5556, 13038, "deck/theme_038.ydc\x00\x00\x00\x00", 1), + OpponentData(65, "Uria, Lord of Searing Flames", [], 4, 1, 6563, 13039, "deck/theme_039.ydc\x00\x00\x00\x00", 3), + OpponentData(66, "Hamon, Lord of Striking Thunder", [], 4, 1, 6564, 13040, "deck/theme_040.ydc\x00\x00\x00\x00", 3), + OpponentData(67, "Raviel, Lord of Phantasms TD", [], 4, 1, 6565, 13041, "deck/theme_041.ydc\x00\x00\x00\x00", 3), + OpponentData(68, "Ojama Trio", [], 1, 1, 5738, 13042, "deck/theme_042.ydc\x00\x00\x00\x00", 0), + OpponentData(69, "People Running About", ["Quick-Finish"], 1, 1, 5578, 13043, "deck/theme_043.ydc\x00\x00\x00\x00", + 0), + OpponentData(70, "Cyber-Stein", [], 5, 1, 4426, 13044, "deck/theme_044.ydc\x00\x00\x00\x00", 4), + OpponentData(71, "Winged Kuriboh LV10", [], 4, 1, 6406, 13045, "deck/theme_045.ydc\x00\x00\x00\x00", 3), + OpponentData(72, "Blue-Eyes Shining Dragon", [], 3, 1, 6082, 13046, "deck/theme_046.ydc\x00\x00\x00\x00", 2), + OpponentData(73, "Perfectly Ultimate Great Moth", ["Quick-Finish"], 3, 1, 4073, 13047, + "deck/theme_047.ydc\x00\x00\x00\x00", 2), + OpponentData(74, "Gate Guardian", [], 4, 1, 4380, 13048, "deck/theme_048.ydc\x00\x00\x00\x00", 2), + OpponentData(75, "Valkyrion the Magna Warrior", [], 3, 1, 5002, 13049, "deck/theme_049.ydc\x00\x00\x00\x00", 2), + OpponentData(76, "Dark Sage", [], 4, 1, 5230, 13050, "deck/theme_050.ydc\x00\x00\x00\x00", 3), + OpponentData(77, "Don Zaloog", [], 4, 1, 5426, 13051, "deck/theme_051.ydc\x00\x00\x00\x00", 3), + OpponentData(78, "Blast Magician", ["Quick-Finish"], 2, 1, 6250, 13052, "deck/theme_052.ydc\x00\x00\x00\x00", 1), + # Limited + OpponentData(79, "Zombyra the Dark", [], 5, 1, 5245, 23000, "deck/limit_000.ydc\x00\x00\x00\x00", 5), + OpponentData(80, "Goblin Attack Force", [], 4, 1, 5145, 23001, "deck/limit_001.ydc\x00\x00\x00\x00", 3), + OpponentData(81, "Giant Kozaky", [], 4, 1, 6420, 23002, "deck/limit_002.ydc\x00\x00\x00\x00", 4), + OpponentData(82, "Big Shield Gardna", ["Quick-Finish"], 2, 1, 4764, 23003, "deck/limit_003.ydc\x00\x00\x00\x00", 1), + OpponentData(83, "Panther Warrior", [], 3, 1, 4751, 23004, "deck/limit_004.ydc\x00\x00\x00\x00", 2), + OpponentData(84, "Silent Magician LV4", ["Quick-Finish"], 2, 1, 6167, 23005, "deck/limit_005.ydc\x00\x00\x00\x00", + 1), + OpponentData(85, "Summoned Skull", [], 4, 1, 4028, 23006, "deck/limit_006.ydc\x00\x00\x00\x00", 3), + OpponentData(86, "Ancient Gear Golem", [], 5, 1, 6315, 23007, "deck/limit_007.ydc\x00\x00\x00\x00", 5), + OpponentData(87, "Chaos Sorcerer", [], 5, 1, 5833, 23008, "deck/limit_008.ydc\x00\x00\x00\x00", 5), + OpponentData(88, "Breaker the Magical Warrior", [], 5, 1, 5655, 23009, "deck/limit_009.ydc\x00\x00\x00\x00", 4), + OpponentData(89, "Dark Magician of Chaos", [], 4, 1, 5880, 23010, "deck/limit_010.ydc\x00\x00\x00\x00", 3), + OpponentData(90, "Stealth Bird", ["Quick-Finish"], 2, 1, 5882, 23011, "deck/limit_011.ydc\x00\x00\x00\x00", 1), + OpponentData(91, "Rapid-Fire Magician", ["Quick-Finish"], 2, 1, 6500, 23012, "deck/limit_012.ydc\x00\x00\x00\x00", + 1), + OpponentData(92, "Morphing Jar #2", [], 5, 1, 4969, 23013, "deck/limit_013.ydc\x00\x00\x00\x00", 4), + OpponentData(93, "Cyber Jar", [], 5, 1, 4913, 23014, "deck/limit_014.ydc\x00\x00\x00\x00", 4), + # Unused/Broken + # OpponentData(94, "Exodia the Forbidden One", [], 1, 1, 4027, 23015, "deck/limit_015.ydc\x00\x00\x00\x00"), + OpponentData(94, "Dark Paladin", [], 4, 1, 5628, 23016, "deck/limit_016.ydc\x00\x00\x00\x00", 3), + OpponentData(95, "F.G.D.", [], 5, 1, 5502, 23017, "deck/limit_017.ydc\x00\x00\x00\x00", 4), + OpponentData(96, "Blue-Eyes Toon Dragon", ["Quick-Finish"], 2, 1, 4773, 23018, "deck/limit_018.ydc\x00\x00\x00\x00", + 1), + OpponentData(97, "Tsukuyomi", [], 3, 1, 5780, 23019, "deck/limit_019.ydc\x00\x00\x00\x00", 2), + OpponentData(98, "Silent Swordsman LV3", ["Quick-Finish"], 2, 1, 6162, 23020, "deck/limit_020.ydc\x00\x00\x00\x00", + 2), + OpponentData(99, "Elemental Hero Flame Wingman", ["Quick-Finish"], 2, 1, 6344, 23021, + "deck/limit_021.ydc\x00\x00\x00\x00", 0), + OpponentData(100, "Armed Dragon LV7", ["Quick-Finish"], 2, 1, 6107, 23022, "deck/limit_022.ydc\x00\x00\x00\x00", 0), + OpponentData(101, "Alkana Knight Joker", ["Quick-Finish"], 1, 1, 6454, 23023, "deck/limit_023.ydc\x00\x00\x00\x00", + 0), + OpponentData(102, "Sorcerer of Dark Magic", [], 4, 1, 6086, 23024, "deck/limit_024.ydc\x00\x00\x00\x00", 3), + OpponentData(103, "Shinato, King of a Higher Plane", [], 4, 1, 5697, 23025, "deck/limit_025.ydc\x00\x00\x00\x00", + 3), + OpponentData(104, "Ryu Kokki", [], 5, 1, 5902, 23026, "deck/limit_026.ydc\x00\x00\x00\x00", 4), + OpponentData(105, "Cyber Dragon", [], 5, 1, 6390, 23027, "deck/limit_027.ydc\x00\x00\x00\x00", 4), + OpponentData(106, "Dark Dreadroute", ["Quick-Finish"], 3, 1, 6405, 23028, "deck/limit_028.ydc\x00\x00\x00\x00", 2), + OpponentData(107, "Ultimate Insect LV7", ["Quick-Finish"], 3, 1, 6319, 23029, "deck/limit_029.ydc\x00\x00\x00\x00", + 2), + OpponentData(108, "Thestalos the Firestorm Monarch", ["Quick-Finish"], 3, 1, 6190, 23030, + "deck/limit_030.ydc\x00\x00\x00\x00"), + OpponentData(109, "Master of Oz", ["Quick-Finish"], 3, 1, 6127, 23031, "deck/limit_031.ydc\x00\x00\x00\x00", 2), + OpponentData(110, "Orca Mega-Fortress of Darkness", ["Quick-Finish"], 3, 1, 5896, 23032, + "deck/limit_032.ydc\x00\x00\x00\x00", 2), + OpponentData(111, "Airknight Parshath", ["Quick-Finish"], 4, 1, 5023, 23033, "deck/limit_033.ydc\x00\x00\x00\x00", + 3), + OpponentData(112, "Dark Scorpion Burglars", ["Quick-Finish"], 4, 1, 5425, 23034, + "deck/limit_034.ydc\x00\x00\x00\x00", 3), + OpponentData(113, "Gilford the Lightning", [], 4, 1, 5451, 23035, "deck/limit_035.ydc\x00\x00\x00\x00", 3), + OpponentData(114, "Embodiment of Apophis", [], 2, 1, 5234, 23036, "deck/limit_036.ydc\x00\x00\x00\x00", 1), + OpponentData(115, "Great Maju Garzett", [], 5, 1, 5768, 23037, "deck/limit_037.ydc\x00\x00\x00\x00", 4), + OpponentData(116, "Black Luster Soldier - Envoy of the Beginning", [], 5, 1, 5835, 23038, + "deck/limit_038.ydc\x00\x00\x00\x00", 4), + OpponentData(117, "Red-Eyes B. Dragon", [], 4, 1, 4088, 23039, "deck/limit_039.ydc\x00\x00\x00\x00", 3), + OpponentData(118, "Blue-Eyes White Dragon", [], 4, 1, 4007, 23040, "deck/limit_040.ydc\x00\x00\x00\x00", 3), + OpponentData(119, "Dark Magician", [], 4, 1, 4041, 23041, "deck/limit_041.ydc\x00\x00\x00\x00", 3), + OpponentData(0, "Starter", ["Quick-Finish"], 1, 1, 4064, 1510, "deck/SD0_STARTER.ydc\x00\x00", 0), + OpponentData(10, "DRAGON'S ROAR", ["Quick-Finish"], 2, 1, 6292, 1511, "deck/SD1_DRAGON.ydc\x00\x00\x00", 1), + OpponentData(11, "ZOMBIE MADNESS", ["Quick-Finish"], 2, 1, 6293, 1512, "deck/SD2_UNDEAD.ydc\x00\x00\x00", 1), + OpponentData(12, "BLAZING DESTRUCTION", ["Quick-Finish"], 2, 1, 6368, 1513, "deck/SD3_FIRE.ydc\x00\x00\x00\x00\x00", + 1, + ["Has Back-row removal"]), + OpponentData(13, "FURY FROM THE DEEP", [], 2, 1, 6376, 1514, + "deck/SD4_UMI.ydc\x00\x00\x00\x00\x00\x00", 1, ["Has Back-row removal"]), + OpponentData(15, "WARRIORS TRIUMPH", ["Quick-Finish"], 2, 1, 6456, 1515, "deck/SD5_SOLDIER.ydc\x00\x00", 1), + OpponentData(16, "SPELLCASTERS JUDGEMENT", ["Quick-Finish"], 2, 1, 6530, 1516, "deck/SD6_MAGICIAN.ydc\x00", 1), + OpponentData(17, "INVICIBLE FORTRESS", [], 2, 1, 6640, 1517, "deck/SD7_GANSEKI.ydc\x00\x00", 1), + OpponentData(7, "Goblin King 2", ["Quick-Finish"], 3, 3, 5973, 8007, "deck/LV2_kingG2.ydc\x00\x00\x00", 2), +] + + +def get_opponents(multiworld: Optional[MultiWorld], player: Optional[int], randomize: bool = False) -> List[ + OpponentData]: + opponents_table: List[OpponentData] = [ + # Tier 1 + OpponentData(0, "Kuriboh", [], 1, 1, 4064, 8000, "deck/LV1_kuriboh.ydc\x00\x00"), + OpponentData(1, "Scapegoat", [], 1, 2, 4818, 8001, "deck/LV1_sukego.ydc\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(2, "Skull Servant", [], 1, 3, 4030, 8002, "deck/LV1_waito.ydc\x00\x00\x00\x00", 0, + ["Has Back-row removal"]), + OpponentData(3, "Watapon", [], 1, 4, 6092, 8003, "deck/LV1_watapon.ydc\x00\x00", 0, ["Has Back-row removal"]), + OpponentData(4, "White Magician Pikeru", [], 1, 5, 5975, 8004, "deck/LV1_pikeru.ydc\x00\x00\x00"), + # Tier 2 + OpponentData(5, "Battery Man C", ["Quick-Finish"], 2, 1, 6428, 8005, "deck/LV2_denti.ydc\x00\x00\x00\x00", 1), + OpponentData(6, "Ojama Yellow", [], 2, 2, 5811, 8006, "deck/LV2_ojama.ydc\x00\x00\x00\x00", 1, + ["Has Back-row removal"]), + OpponentData(7, "Goblin King", ["Quick-Finish"], 2, 3, 5973, 8007, "deck/LV2_kingG.ydc\x00\x00\x00\x00", 1), + OpponentData(8, "Des Frog", ["Quick-Finish"], 2, 4, 6424, 8008, "deck/LV2_kaeru.ydc\x00\x00\x00\x00", 1), + OpponentData(9, "Water Dragon", ["Quick-Finish"], 2, 5, 6481, 8009, "deck/LV2_waterD.ydc\x00\x00\x00", 1), + # Tier 3 + OpponentData(10, "Red-Eyes Darkness Dragon", ["Quick-Finish"], 3, 1, 6292, 8010, "deck/LV3_RedEyes.ydc\x00\x00", + 2), + OpponentData(11, "Vampire Genesis", ["Quick-Finish"], 3, 2, 6293, 8011, "deck/LV3_vamp.ydc\x00\x00\x00\x00\x00", + 2), + OpponentData(12, "Infernal Flame Emperor", [], 3, 3, 6368, 8012, "deck/LV3_flame.ydc\x00\x00\x00\x00", 2, + ["Has Back-row removal"]), + OpponentData(13, "Ocean Dragon Lord - Neo-Daedalus", [], 3, 4, 6376, 8013, "deck/LV3_daidaros.ydc\x00", 2, + ["Has Back-row removal"]), + OpponentData(14, "Helios Duo Megiste", ["Quick-Finish"], 3, 5, 6647, 8014, "deck/LV3_heriosu.ydc\x00\x00", 2), + # Tier 4 + OpponentData(15, "Gilford the Legend", ["Quick-Finish"], 4, 1, 6456, 8015, "deck/LV4_gilfo.ydc\x00\x00\x00\x00", + 3), + OpponentData(16, "Dark Eradicator Warlock", ["Quick-Finish"], 4, 2, 6530, 8016, "deck/LV4_kuromadou.ydc", 3), + OpponentData(17, "Guardian Exode", [], 4, 3, 6640, 8017, "deck/LV4_exodo.ydc\x00\x00\x00\x00", 3), + OpponentData(18, "Goldd, Wu-Lord of Dark World", ["Quick-Finish"], 4, 4, 6505, 8018, "deck/LV4_ankokukai.ydc", + 3), + OpponentData(19, "Elemental Hero Erikshieler", ["Quick-Finish"], 4, 5, 6639, 8019, + "deck/LV4_Ehero.ydc\x00\x00\x00\x00", 3), + # Tier 5 + OpponentData(20, "Raviel, Lord of Phantasms", [], 5, 1, 6565, 8020, "deck/LV5_ravieru.ydc\x00\x00", 4), + OpponentData(21, "Horus the Black Flame Dragon LV8", [], 5, 2, 6100, 8021, "deck/LV5_horus.ydc\x00\x00\x00\x00", + 4), + OpponentData(22, "Stronghold", [], 5, 3, 6153, 8022, "deck/LV5_gadget.ydc\x00\x00\x00", 5), + OpponentData(23, "Sacred Phoenix of Nephthys", [], 5, 4, 6236, 8023, "deck/LV5_nephthys.ydc\x00", 6), + OpponentData(24, "Cyber End Dragon", ["Goal"], 5, 5, 6397, 8024, "deck/LV5_cyber.ydc\x00\x00\x00\x00", 7), + ] + world = multiworld.worlds[player] + if not randomize: + return opponents_table + opponents = opponents_table + challenge_opponents + start = world.random.choice([o for o in opponents if o.tier == 1 and len(o.additional_info) == 0]) + opponents.remove(start) + goal = world.random.choice([o for o in opponents if "Goal" in o.campaign_info]) + opponents.remove(goal) + world.random.shuffle(opponents) + chosen_ones = opponents[:23] + for item in (multiworld.precollected_items[player]): + if item.name in tier_1_opponents: + # convert item index to opponent index + chosen_ones.insert(item_to_index[item.name] - item_to_index["Campaign Tier 1 Column 1"], start) + break + chosen_ones.append(goal) + tier = 1 + column = 1 + recreation = [] + for opp in chosen_ones: + recreation.append(OpponentData(opp.id, opp.name, opp.campaign_info, tier, column, opp.card_id, + opp.deck_name_id, opp.deck_file, opp.difficulty)) + column += 1 + if column > 5: + column = 1 + tier += 1 + + return recreation + + +def get_opponent_locations(opponent: OpponentData) -> Dict[str, Optional[Union[str, int]]]: + location = {opponent.name + " Beaten": "Tier " + str(opponent.tier) + " Beaten"} + if opponent.tier > 4 and opponent.column != 5: + name = "Campaign Tier 5: Column " + str(opponent.column) + " Win" + # return a int instead so a item can be placed at this location later + location[name] = special[name] + for info in opponent.campaign_info: + location[opponent.name + "-> " + info] = info + return location + + +def get_opponent_condition(opponent: OpponentData, unlock_item: str, unlock_amount: int, player: int, + is_challenge: bool) -> CollectionRule: + if is_challenge: + return lambda state: ( + state.has(unlock_item, player, unlock_amount) + and yugioh06_difficulty(state, player, opponent.difficulty) + and state.has_all(opponent.additional_info, player) + ) + else: + return lambda state: ( + state.has_group(unlock_item, player, unlock_amount) + and yugioh06_difficulty(state, player, opponent.difficulty) + and state.has_all(opponent.additional_info, player) + ) diff --git a/worlds/yugioh06/options.py b/worlds/yugioh06/options.py new file mode 100644 index 00000000..3100f517 --- /dev/null +++ b/worlds/yugioh06/options.py @@ -0,0 +1,195 @@ +from dataclasses import dataclass + +from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Range, Toggle + + +class StructureDeck(Choice): + """Which Structure Deck you start with""" + + display_name = "Structure Deck" + option_dragons_roar = 0 + option_zombie_madness = 1 + option_blazing_destruction = 2 + option_fury_from_the_deep = 3 + option_warriors_triumph = 4 + option_spellcasters_judgement = 5 + option_none = 6 + option_random_deck = 7 + default = 7 + + +class Banlist(Choice): + """Which Banlist you start with""" + + display_name = "Banlist" + option_no_banlist = 0 + option_september_2003 = 1 + option_march_2004 = 2 + option_september_2004 = 3 + option_march_2005 = 4 + option_september_2005 = 5 + default = option_september_2005 + + +class FinalCampaignBossUnlockCondition(Choice): + """How to unlock the final campaign boss and goal for the world""" + + display_name = "Final Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class FourthTier5UnlockCondition(Choice): + """How to unlock the fourth campaign boss""" + + display_name = "Fourth Tier 5 Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class ThirdTier5UnlockCondition(Choice): + """How to unlock the third campaign boss""" + + display_name = "Third Tier 5 Campaign Boss unlock Condition" + option_campaign_opponents = 0 + option_challenges = 1 + + +class FinalCampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Final Campaign Boss to appear""" + + display_name = "Final Campaign Boss challenges unlock amount" + range_start = 0 + range_end = 91 + default = 10 + + +class FourthTier5CampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Fourth Level 5 Campaign Opponent to appear""" + + display_name = "Fourth Tier 5 Campaign Boss unlock amount" + range_start = 0 + range_end = 91 + default = 5 + + +class ThirdTier5CampaignBossChallenges(Range): + """Number of Limited/Theme Duels completed for the Third Level 5 Campaign Opponent to appear""" + + display_name = "Third Tier 5 Campaign Boss unlock amount" + range_start = 0 + range_end = 91 + default = 2 + + +class FinalCampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Final Campaign Boss to appear""" + + display_name = "Final Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 24 + default = 12 + + +class FourthTier5CampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Fourth Level 5 Campaign Opponent to appear""" + + display_name = "Fourth Tier 5 Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 23 + default = 7 + + +class ThirdTier5CampaignBossCampaignOpponents(Range): + """Number of Campaign Opponents Duels defeated for the Third Level 5 Campaign Opponent to appear""" + + display_name = "Third Tier 5 Campaign Boss campaign opponent unlock amount" + range_start = 0 + range_end = 22 + default = 3 + + +class NumberOfChallenges(Range): + """Number of random Limited/Theme Duels that are included. The rest will be inaccessible.""" + + display_name = "Number of Challenges" + range_start = 0 + range_end = 91 + default = 10 + + +class StartingMoney(Range): + """The amount of money you start with""" + + display_name = "Starting Money" + range_start = 0 + range_end = 100000 + default = 3000 + + +class MoneyRewardMultiplier(Range): + """By which amount the campaign reward money is multiplied""" + + display_name = "Money Reward Multiplier" + range_start = 1 + range_end = 255 + default = 20 + + +class NormalizeBoostersPacks(DefaultOnToggle): + """If enabled every booster pack costs the same otherwise vanilla cost is used""" + + display_name = "Normalize Booster Packs" + + +class BoosterPackPrices(Range): + """ + Only Works if normalize booster packs is enabled. + Sets the amount that what every booster pack costs. + """ + + display_name = "Booster Pack Prices" + range_start = 1 + range_end = 3000 + default = 100 + + +class AddEmptyBanList(Toggle): + """Adds a Ban List where everything is at 3 to the item pool""" + + display_name = "Add Empty Ban List" + + +class CampaignOpponentsShuffle(Toggle): + """Replaces the campaign with random opponents from the entire game""" + + display_name = "Campaign Opponents Shuffle" + + +class OCGArts(Toggle): + """Always use the OCG artworks for cards""" + + display_name = "OCG Arts" + + +@dataclass +class Yugioh06Options(PerGameCommonOptions): + structure_deck: StructureDeck + banlist: Banlist + final_campaign_boss_unlock_condition: FinalCampaignBossUnlockCondition + fourth_tier_5_campaign_boss_unlock_condition: FourthTier5UnlockCondition + third_tier_5_campaign_boss_unlock_condition: ThirdTier5UnlockCondition + final_campaign_boss_challenges: FinalCampaignBossChallenges + fourth_tier_5_campaign_boss_challenges: FourthTier5CampaignBossChallenges + third_tier_5_campaign_boss_challenges: ThirdTier5CampaignBossChallenges + final_campaign_boss_campaign_opponents: FinalCampaignBossCampaignOpponents + fourth_tier_5_campaign_boss_campaign_opponents: FourthTier5CampaignBossCampaignOpponents + third_tier_5_campaign_boss_campaign_opponents: ThirdTier5CampaignBossCampaignOpponents + number_of_challenges: NumberOfChallenges + starting_money: StartingMoney + money_reward_multiplier: MoneyRewardMultiplier + normalize_boosters_packs: NormalizeBoostersPacks + booster_pack_prices: BoosterPackPrices + add_empty_banlist: AddEmptyBanList + campaign_opponents_shuffle: CampaignOpponentsShuffle + ocg_arts: OCGArts diff --git a/worlds/yugioh06/patch.bsdiff4 b/worlds/yugioh06/patch.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..877884d5c946fcae2186b3ddd1b69470e1225cd1 GIT binary patch literal 2959 zcmZvbdpOhm8^=F8;5!+UITzc^9;dXep`xDIY{+Deg>sh7A%_l1r#a0WDmf(UQ9@&= z5S64Pc^o>-A*JMrQc{XgPxbTcx8LviUDxx^@BPR9d0+SEzCYJ>f3Ew*@OE-#vuWg4 z;BVc6|IYyMU&jdG*-#w)N#6cn15O_Tph7PH>!43-FI0)7=fMCD0Q>>io@@*Qa=j}M zy6OWb1PyXGMSQVqmC`S|(gQa_;KD z8#wF~p>q`@**!Vb&*=U=_P!C73a+L~N{_Emz89dFY!2~CsfF|y`FpcQIlL+Ht;Bx( zTP=y$d!zfmcJ^W?7|hXV|BwI7x9$Cf5CCtN%YV+4Jm9Z=9LlnN>VJ3%9TUN05DSv% zWSHM#$dNI*yaVRs5I?z{&v_!fAL9+g3}(C;2weIWIHbzoYlYCj zAcfBVw(PqazE*^XULD<$v7J|1?q@`-$n9hx)iUv_k`tMy{aMOHa)@3{c@W)#P=G2D z-8Q>MXE^C=a_{6^YT)WT%l7o)+@g1PAeOb;MHYY1HL)y?IFnzcrXuY$9~U&J+RWX=O)mJ<2+R<>vtEYm z5Xa3X9ctchFC7{p4KJ++Y(}vaoYO*grpawC}o3G*#J2STyJ3Y^yeD zuQW_Ku^b@{2g62^bk}@bYd#K2(8nMl3=R;-ZdQ?Sfi3kiW0-!_OYz!O@%K>zNA;JIz=<4O5 zAfsv+z(mRds78_}rtu5uoe|64TqTxa6;9NM^Tfi5gxZXwVo+he)2zlKXX<9e*V4W% zIFX1zxcbn~_OgkXUi_5f5I%aY|6nV4lR6+Xr#*JDz43~mTG8NiOgD+Oo|_-#w6Nu$ z-EA9!uLdyYG5P=1hd<=s<7?bb&YO>F*G!8sc(sAmaX zMcYK@_3&hIY%zj4Xmm<)(o-=3!m*N*j_KkJxzm=`F^)>JXDA4Kc}F_agDn)JB;v3X z0nH$sdjX4@P7xfZhOHZnc5qqtCZP)5(PR>)OHWt|h7jHyCW#5Dk+euiDmhhh1{W5` z5i6fLu1A8=Xx$m1D=8ircGXZQi9f{=$QH$D#4tc3*aDe?00doGU|^s)08POVTBQak zKtcn;IZg9lSq>LwsEQgMNXb&<`Q`fh1`2otifSqtVv^E1$EmPN94+EMD9I4+cxbqj zf6*2iFqTS&ho7J1j+e8=hTM7fNpF00(U;WnAtY}RBcm!_0QyxCLu_{gih*7!g%-gt zB%P~_hL!VD-0d9B5aVZe#ZxoCUC$5FYkuSB256l;K_@AZ5U9ocXaz^zGs~eoK`|_t$ zi5TR!=E+kTX`^8-8Al6-?-@gBTFDq#e4B~`d%yMHJzf2~I930u zEj17t8+Vg{kyS74!Q-rBU;ET|-qD;R=1V?eV>M^FF36)}NcEspTz6SsWBcdd11g`j z#Cc31D)(O2CX4U(Fx-sFZWsZE1Ju2)q$ayG4xrP6gYT25eZS%Fo$61hi;>pWc?;gP z9r|lS^0wq`55wc_EyYXJ0AdV!;1ce5){YU!3@FaS$r07H6g`qQb0}&D137&^cUP;K zbbzC3m8$oUCOJ@8+p=EML;J~o{A}S&YDID^6l^pFVb^>ZZQFCx_UwEJSD;y#wWBp@ zYH450qTbAt@6OmSuO1bh^xE*H@+*gwH0Qr@3W8lTYD{#JMaIT9H3Zy`v0HQLLFb~c zTP?;xM+a^NhijZ)UsE@syhcC1x|`Z3r0srAggQos+-<&YWm`~us;V3>{p-oDTA%vf zh1VOX{!!cD2>~gY9rj&$c-32%1@op53<8I(nN4Q#xA}ozQr=fe!j~}Je1}Qb*VVFd-jg6Uku^pi<$s0_C)a_xeW(pR1XDy1ll zTmVb2cKqEYl45e)6khzCAK;o8#vU_4X1j)tAN9Y+%hkI-Q>!95jsVoSLV@ z>8@SoPFPtoIX5N6UmVb``*J*Gb-m8~K2OfBw_zH=PiQUBJwbOmZ9mh{DzRM2^$LqY z)f{@y@muZ0blSR9wE{Txo*Q*eJr9?M8Rr2+L7#+V)+gh^)DA zCA>KJsEc!v+4{8M5OuRoK*aHD&0Ox@eyw*Q)z|MwoY9fF`IV>M{1&t0E&QTqc#}gi zn)=HL7wyXlGSLe4!x}u)7Ts&xBi(GVX+rDlFCQ&lvApm}oESb(vo$qfx}%Q^pI;+l zm@5L@d|iEgef@f|z6D!JbhX!!d99L`JAn@`#s|Gr#i8*)S*2}%{GV8jom0QRdTAlp z7*Ia?#?8ruGD!YJ2uT%cn}X-2{n*|yIcH->yG*49o_9Ap=PnY!u9iA^{CM?^y z-;|%(VwljgVrS7I8WwpRQhGG>24RIb5fRk1}a zUT|ooDiL^YXnB;0H*>m?yRoZh{`NxAG2?fSnk4RtTjrSe zSLgm7#ch`iZD07B2YHfY4FzeCZ$q`-d}v2Y~tn7&sUl7#cVf7(zr=WUy+n z&Csa!5NSH6snyY+R;V#W{7gWY=ag%4+`INL@aM`u|IjSu(B#0t!sx)@;0V;qz~I54 z64C-RW9q3629<@A92hR}vR%nw3F^4)Yr??nzM^i*lv)QR%~OJbp-K#_E?f=^w6w2V zaV+ZkGh=q)-`1tqZ}xukTg?(M!9(Si%!T)D_s$6TNyIfI>G1?KybuiC(0r0Y!oa|IAR@rQgMkGotH7XS z$nPY5k(b2)d!NI`L(7@2Z$RM$S z;b&II%HN*;zP__WGHwYjz8vh}>VL>z)%a3>u=~naymHG|WC(htnl0>*Vz|J#Ok+_J zS5pVGC`S_)!=Y}DjxG*G3-vT1{e3rGr(U&>%Cz#@v1E%|Mdv}z2=2{tZNF62t`vxw zPHB(T(^>P<@@SaLX@e8jr%76}81LMA<=(m}(&v7!ZQb@*_9dr%)W)tGQ#)Hdd=9F{Mcn@dVk6GWb! z;FLP^lIf4Zzoe#3KFc=u`JMU2P~+0T+Q7izz#!nj;OLcMEYYI1O+_*E(L0ajTtYmD P9lRPqfdvUVP*?*1t)h(- literal 0 HcmV?d00001 diff --git a/worlds/yugioh06/rom.py b/worlds/yugioh06/rom.py new file mode 100644 index 00000000..0bd3f1cb --- /dev/null +++ b/worlds/yugioh06/rom.py @@ -0,0 +1,163 @@ +import hashlib +import math +import os +import struct + +from settings import get_settings + +import Utils +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes + +from worlds.AutoWorld import World +from .items import item_to_index +from .rom_values import banlist_ids, function_addresses, structure_deck_selection + +MD5Europe = "020411d3b08f5639eb8cb878283f84bf" +MD5America = "b8a7c976b28172995fe9e465d654297a" + + +class YGO06ProcedurePatch(APProcedurePatch, APTokenMixin): + game = "Yu-Gi-Oh! 2006" + hash = MD5America + patch_file_ending = ".apygo06" + result_file_ending = ".gba" + + procedure = [("apply_bsdiff4", ["base_patch.bsdiff4"]), ("apply_tokens", ["token_data.bin"])] + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def write_tokens(world: World, patch: YGO06ProcedurePatch): + structure_deck = structure_deck_selection.get(world.options.structure_deck.value) + # set structure deck + patch.write_token(APTokenTypes.WRITE, 0x000FD0AA, struct.pack(" bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + md5hash = basemd5.hexdigest() + if MD5Europe != md5hash and MD5America != md5hash: + raise Exception( + "Supplied Base Rom does not match known MD5 for" + "Yu-Gi-Oh! World Championship 2006 America or Europe " + "Get the correct game and version, then dump it" + ) + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not file_name: + file_name = get_settings().yugioh06_settings.rom_file + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/yugioh06/rom_values.py b/worlds/yugioh06/rom_values.py new file mode 100644 index 00000000..4fb31008 --- /dev/null +++ b/worlds/yugioh06/rom_values.py @@ -0,0 +1,38 @@ +structure_deck_selection = { + # DRAGON'S ROAR + 0: 0x1, + # ZOMBIE MADNESS + 1: 0x5, + # BLAZING DESTRUCTION + 2: 0x9, + # FURY FROM THE DEEP + 3: 0xD, + # Warrior'S TRIUMPH + 4: 0x11, + # SPELLCASTER'S JUDGEMENT + 5: 0x15, + # Draft Mode + 6: 0x1, +} + +banlist_ids = { + # NoList + 0: 0x0, + # September 2003 + 1: 0x5, + # March 2004 + 2: 0x6, + # September 2004 + 3: 0x7, + # March 2005 + 4: 0x8, + # September 2005 + 5: 0x9, +} + +function_addresses = { + # Count Campaign Opponents + 0: 0xF0C8, + # Count Challenges + 1: 0xEF3A, +} diff --git a/worlds/yugioh06/ruff.toml b/worlds/yugioh06/ruff.toml new file mode 100644 index 00000000..8acb3b14 --- /dev/null +++ b/worlds/yugioh06/ruff.toml @@ -0,0 +1,12 @@ +line-length = 120 + +[lint] +preview = true +select = ["E", "F", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"] +ignore = ["RUF012", "RUF100"] + +[per-file-ignores] +# The way options definitions work right now, world devs are forced to break line length requirements. +"options.py" = ["E501"] +# Yu Gi Oh specific: The structure of the Opponents.py file makes the line length violations acceptable. +"Opponents.py" = ["E501"] \ No newline at end of file diff --git a/worlds/yugioh06/rules.py b/worlds/yugioh06/rules.py new file mode 100644 index 00000000..53ea95b2 --- /dev/null +++ b/worlds/yugioh06/rules.py @@ -0,0 +1,868 @@ +from worlds.generic.Rules import add_rule + +from . import yugioh06_difficulty +from .fusions import count_has_materials + + +def set_rules(world): + player = world.player + multiworld = world.multiworld + + location_rules = { + # Campaign + "Campaign Tier 1: 1 Win": lambda state: state.has("Tier 1 Beaten", player), + "Campaign Tier 1: 3 Wins A": lambda state: state.has("Tier 1 Beaten", player, 3), + "Campaign Tier 1: 3 Wins B": lambda state: state.has("Tier 1 Beaten", player, 3), + "Campaign Tier 1: 5 Wins A": lambda state: state.has("Tier 1 Beaten", player, 5), + "Campaign Tier 1: 5 Wins B": lambda state: state.has("Tier 1 Beaten", player, 5), + "Campaign Tier 2: 1 Win": lambda state: state.has("Tier 2 Beaten", player), + "Campaign Tier 2: 3 Wins A": lambda state: state.has("Tier 2 Beaten", player, 3), + "Campaign Tier 2: 3 Wins B": lambda state: state.has("Tier 2 Beaten", player, 3), + "Campaign Tier 2: 5 Wins A": lambda state: state.has("Tier 2 Beaten", player, 5), + "Campaign Tier 2: 5 Wins B": lambda state: state.has("Tier 2 Beaten", player, 5), + "Campaign Tier 3: 1 Win": lambda state: state.has("Tier 3 Beaten", player), + "Campaign Tier 3: 3 Wins A": lambda state: state.has("Tier 3 Beaten", player, 3), + "Campaign Tier 3: 3 Wins B": lambda state: state.has("Tier 3 Beaten", player, 3), + "Campaign Tier 3: 5 Wins A": lambda state: state.has("Tier 3 Beaten", player, 5), + "Campaign Tier 3: 5 Wins B": lambda state: state.has("Tier 3 Beaten", player, 5), + "Campaign Tier 4: 5 Wins A": lambda state: state.has("Tier 4 Beaten", player, 5), + "Campaign Tier 4: 5 Wins B": lambda state: state.has("Tier 4 Beaten", player, 5), + + # Bonuses + "Duelist Bonus Level 1": lambda state: state.has("Tier 1 Beaten", player), + "Duelist Bonus Level 2": lambda state: state.has("Tier 2 Beaten", player), + "Duelist Bonus Level 3": lambda state: state.has("Tier 3 Beaten", player), + "Duelist Bonus Level 4": lambda state: state.has("Tier 4 Beaten", player), + "Duelist Bonus Level 5": lambda state: state.has("Tier 5 Beaten", player), + "Max ATK Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Spell Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Trap Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "No Damage Bonus": lambda state: state.has_group("Campaign Boss Beaten", player, 3), + "Low Deck Bonus": lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and + yugioh06_difficulty(state, player, 3), + "Extremely Low Deck Bonus": + lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and + yugioh06_difficulty(state, player, 2), + "Opponent's Turn Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Exactly 0 LP Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Reversal Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2), + "Quick Finish Bonus": lambda state: state.has("Quick-Finish", player) or yugioh06_difficulty(state, player, 6), + "Exodia Finish Bonus": lambda state: state.has("Can Exodia Win", player), + "Last Turn Finish Bonus": lambda state: state.has("Can Last Turn Win", player), + "Yata-Garasu Finish Bonus": lambda state: state.has("Can Yata Lock", player), + "Skull Servant Finish Bonus": lambda state: state.has("Skull Servant", player) and + yugioh06_difficulty(state, player, 3), + "Konami Bonus": lambda state: state.has_all(["Messenger of Peace", "Castle of Dark Illusions", "Mystik Wok"], + player) or (state.has_all(["Mystik Wok", "Barox", "Cyber-Stein", + "Poison of the Old Man"], + player) and yugioh06_difficulty(state, + player, 8)), + "Max Damage Bonus": lambda state: state.has_any(["Wave-Motion Cannon", "Megamorph", "United We Stand", + "Mage Power"], player), + "Fusion Summon Bonus": lambda state: state.has_any(["Polymerization", "Fusion Gate", "Power Bond"], player), + "Ritual Summon Bonus": lambda state: state.has("Ritual", player), + "Over 20000 LP Bonus": lambda state: can_gain_lp_every_turn(state, player) + and state.has("Can Stall with ST", player), + "Low LP Bonus": lambda state: state.has("Wall of Revealing Light", player) and yugioh06_difficulty(state, player, + 2), + "Extremely Low LP Bonus": lambda state: state.has_all(["Wall of Revealing Light", "Messenger of Peace"], player) + and yugioh06_difficulty(state, player, 4), + "Effect Damage Only Bonus": lambda state: state.has_all(["Solar Flare Dragon", "UFO Turtle"], player) + or state.has("Wave-Motion Cannon", player) + or state.can_reach("Final Countdown Finish Bonus", "Location", player) + or state.can_reach("Destiny Board Finish Bonus", "Location", player) + or state.has("Can Exodia Win", player) + or state.has("Can Last Turn Win", player), + "No More Cards Bonus": lambda state: state.has_any(["Cyber Jar", "Morphing Jar", + "Morphing Jar #2", "Needle Worm"], player) + and state.has_any(["The Shallow Grave", "Spear Cretin"], + player) and yugioh06_difficulty(state, player, 5), + "Final Countdown Finish Bonus": lambda state: state.has("Final Countdown", player) + and state.has("Can Stall with ST", player), + "Destiny Board Finish Bonus": lambda state: state.has("Can Stall with Monsters", player) and + state.has("Destiny Board and its letters", player) and + state.has("A Cat of Ill Omen", player), + + # Cards + "Obtain all pieces of Exodia": lambda state: state.has("Exodia", player), + "Obtain Final Countdown": lambda state: state.has("Final Countdown", player), + "Obtain Victory Dragon": lambda state: state.has("Victory D.", player), + "Obtain Ojama Delta Hurricane and its required cards": + lambda state: state.has("Ojama Delta Hurricane and required cards", player), + "Obtain Huge Revolution and its required cards": + lambda state: state.has("Huge Revolution and its required cards", player), + "Obtain Perfectly Ultimate Great Moth and its required cards": + lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player), + "Obtain Valkyrion the Magna Warrior and its pieces": + lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player), + "Obtain Dark Sage and its required cards": lambda state: state.has("Dark Sage and its required cards", player), + "Obtain Destiny Board and its letters": lambda state: state.has("Destiny Board and its letters", player), + "Obtain all XYZ-Dragon Cannon fusions and their materials": + lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player), + "Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials": + lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player), + "Obtain Hamon, Lord of Striking Thunder": + lambda state: state.has("Hamon, Lord of Striking Thunder", player), + "Obtain Raviel, Lord of Phantasms": + lambda state: state.has("Raviel, Lord of Phantasms", player), + "Obtain Uria, Lord of Searing Flames": + lambda state: state.has("Uria, Lord of Searing Flames", player), + "Obtain Gate Guardian and its pieces": + lambda state: state.has("Gate Guardian and its pieces", player), + "Obtain Dark Scorpion Combination and its required cards": + lambda state: state.has("Dark Scorpion Combination and its required cards", player), + # Collection Events + "Ojama Delta Hurricane and required cards": + lambda state: state.has_all(["Ojama Delta Hurricane", "Ojama Green", "Ojama Yellow", "Ojama Black"], + player), + "Huge Revolution and its required cards": + lambda state: state.has_all(["Huge Revolution", "Oppressed People", "United Resistance", + "People Running About"], player), + "Perfectly Ultimate Great Moth and its required cards": + lambda state: state.has_all(["Perfectly Ultimate Great Moth", "Petit Moth", "Cocoon of Evolution"], player), + "Valkyrion the Magna Warrior and its pieces": + lambda state: state.has_all(["Valkyrion the Magna Warrior", "Alpha the Magnet Warrior", + "Beta the Magnet Warrior", "Gamma the Magnet Warrior"], player), + "Dark Sage and its required cards": + lambda state: state.has_all(["Dark Sage", "Dark Magician", "Time Wizard"], player), + "Destiny Board and its letters": + lambda state: state.has_all(["Destiny Board", "Spirit Message 'I'", "Spirit Message 'N'", + "Spirit Message 'A'", "Spirit Message 'L'"], player), + "XYZ-Dragon Cannon fusions and their materials": + lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", + "XY-Dragon Cannon", "XZ-Tank Cannon", "YZ-Tank Dragon", "XYZ-Dragon Cannon"], + player), + "VWXYZ-Dragon Catapult Cannon and the fusion materials": + lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "XYZ-Dragon Cannon", + "V-Tiger Jet", "W-Wing Catapult", "VW-Tiger Catapult", + "VWXYZ-Dragon Catapult Cannon"], + player), + "Gate Guardian and its pieces": + lambda state: state.has_all(["Gate Guardian", "Kazejin", "Suijin", "Sanga of the Thunder"], player), + "Dark Scorpion Combination and its required cards": + lambda state: state.has_all(["Dark Scorpion Combination", "Don Zaloog", "Dark Scorpion - Chick the Yellow", + "Dark Scorpion - Meanae the Thorn", "Dark Scorpion - Gorg the Strong", + "Cliff the Trap Remover"], player), + "Can Exodia Win": + lambda state: state.has_all(["Exodia", "Heart of the Underdog"], player), + "Can Last Turn Win": + lambda state: state.has_all(["Last Turn", "Wall of Revealing Light"], player) and + (state.has_any(["Jowgen the Spiritualist", "Jowls of Dark Demise", "Non Aggression Area"], + player) + or state.has_all(["Cyber-Stein", "The Last Warrior from Another Planet"], player)), + "Can Yata Lock": + lambda state: state.has_all(["Yata-Garasu", "Chaos Emperor Dragon - Envoy of the End", "Sangan"], player) + and state.has_any(["No Banlist", "Banlist September 2003"], player), + "Can Stall with Monsters": + lambda state: state.count_from_list_exclusive( + ["Spirit Reaper", "Giant Germ", "Marshmallon", "Nimble Momonga"], player) >= 2, + "Can Stall with ST": + lambda state: state.count_from_list_exclusive(["Level Limit - Area B", "Gravity Bind", "Messenger of Peace"], + player) >= 2, + "Has Back-row removal": + lambda state: back_row_removal(state, player) + + } + access_rules = { + # Limited + "LD01 All except Level 4 forbidden": + lambda state: yugioh06_difficulty(state, player, 2), + "LD02 Medium/high Level forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD03 ATK 1500 or more forbidden": + lambda state: yugioh06_difficulty(state, player, 4), + "LD04 Flip Effects forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD05 Tributes forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD06 Traps forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD07 Large Deck A": + lambda state: yugioh06_difficulty(state, player, 4), + "LD08 Large Deck B": + lambda state: yugioh06_difficulty(state, player, 4), + "LD09 Sets Forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD10 All except LV monsters forbidden": + lambda state: only_level(state, player) and yugioh06_difficulty(state, player, 2), + "LD11 All except Fairies forbidden": + lambda state: only_fairy(state, player) and yugioh06_difficulty(state, player, 2), + "LD12 All except Wind forbidden": + lambda state: only_wind(state, player) and yugioh06_difficulty(state, player, 2), + "LD13 All except monsters forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD14 Level 3 or below forbidden": + lambda state: yugioh06_difficulty(state, player, 1), + "LD15 DEF 1500 or less forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD16 Effect Monsters forbidden": + lambda state: only_normal(state, player) and yugioh06_difficulty(state, player, 4), + "LD17 Spells forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD18 Attacks forbidden": + lambda state: state.has_all(["Wave-Motion Cannon", "Stealth Bird"], player) + and state.count_from_list_exclusive(["Dark World Lightning", "Nobleman of Crossout", + "Shield Crash", "Tribute to the Doomed"], player) >= 2 + and yugioh06_difficulty(state, player, 3), + "LD19 All except E-Hero's forbidden": + lambda state: state.has_any(["Polymerization", "Fusion Gate"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 3 and + yugioh06_difficulty(state, player, 3), + "LD20 All except Warriors forbidden": + lambda state: only_warrior(state, player) and yugioh06_difficulty(state, player, 2), + "LD21 All except Dark forbidden": + lambda state: only_dark(state, player) and yugioh06_difficulty(state, player, 2), + "LD22 All limited cards forbidden": + lambda state: yugioh06_difficulty(state, player, 3), + "LD23 Refer to Mar 05 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD24 Refer to Sept 04 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD25 Low Life Points": + lambda state: yugioh06_difficulty(state, player, 5), + "LD26 All except Toons forbidden": + lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 2), + "LD27 All except Spirits forbidden": + lambda state: only_spirit(state, player) and yugioh06_difficulty(state, player, 2), + "LD28 All except Dragons forbidden": + lambda state: only_dragon(state, player) and yugioh06_difficulty(state, player, 2), + "LD29 All except Spellcasters forbidden": + lambda state: only_spellcaster(state, player) and yugioh06_difficulty(state, player, 2), + "LD30 All except Light forbidden": + lambda state: only_light(state, player) and yugioh06_difficulty(state, player, 2), + "LD31 All except Fire forbidden": + lambda state: only_fire(state, player) and yugioh06_difficulty(state, player, 2), + "LD32 Decks with multiples forbidden": + lambda state: yugioh06_difficulty(state, player, 4), + "LD33 Special Summons forbidden": + lambda state: yugioh06_difficulty(state, player, 2), + "LD34 Normal Summons forbidden": + lambda state: state.has_all(["Polymerization", "King of the Swamp"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 3 and + yugioh06_difficulty(state, player, 4), + "LD35 All except Zombies forbidden": + lambda state: only_zombie(state, player) and yugioh06_difficulty(state, player, 2), + "LD36 All except Earth forbidden": + lambda state: only_earth(state, player) and yugioh06_difficulty(state, player, 2), + "LD37 All except Water forbidden": + lambda state: only_water(state, player) and yugioh06_difficulty(state, player, 2), + "LD38 Refer to Mar 04 Banlist": + lambda state: yugioh06_difficulty(state, player, 4), + "LD39 Monsters forbidden": + lambda state: state.has_all(["Skull Zoma", "Embodiment of Apophis"], player) + and yugioh06_difficulty(state, player, 5), + "LD40 Refer to Sept 05 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + "LD41 Refer to Sept 03 Banlist": + lambda state: yugioh06_difficulty(state, player, 5), + # Theme Duels + "TD01 Battle Damage": + lambda state: yugioh06_difficulty(state, player, 1), + "TD02 Deflected Damage": + lambda state: state.has("Fairy Box", player) and yugioh06_difficulty(state, player, 1), + "TD03 Normal Summon": + lambda state: yugioh06_difficulty(state, player, 3), + "TD04 Ritual Summon": + lambda state: yugioh06_difficulty(state, player, 3) and + state.has_all(["Contract with the Abyss", + "Manju of the Ten Thousand Hands", + "Senju of the Thousand Hands", + "Sonic Bird", + "Pot of Avarice", + "Dark Master - Zorc", + "Demise, King of Armageddon", + "The Masked Beast", + "Magician of Black Chaos", + "Dark Magic Ritual"], player), + "TD05 Special Summon A": + lambda state: yugioh06_difficulty(state, player, 3), + "TD06 20x Spell": + lambda state: state.has("Magical Blast", player) and yugioh06_difficulty(state, player, 3), + "TD07 10x Trap": + lambda state: yugioh06_difficulty(state, player, 3), + "TD08 Draw": + lambda state: state.has_any(["Self-Destruct Button", "Dark Snake Syndrome"], player) and + yugioh06_difficulty(state, player, 3), + "TD09 Hand Destruction": + lambda state: state.has_all(["Cyber Jar", + "Morphing Jar", + "Book of Moon", + "Book of Taiyou", + "Card Destruction", + "Serial Spell", + "Spell Reproduction", + "The Shallow Grave"], player) and yugioh06_difficulty(state, player, 3), + "TD10 During Opponent's Turn": + lambda state: yugioh06_difficulty(state, player, 3), + "TD11 Recover": + lambda state: can_gain_lp_every_turn(state, player) and yugioh06_difficulty(state, player, 3), + "TD12 Remove Monsters by Effect": + lambda state: state.has("Soul Release", player) and yugioh06_difficulty(state, player, 2), + "TD13 Flip Summon": + lambda state: pacman_deck(state, player) and yugioh06_difficulty(state, player, 2), + "TD14 Special Summon B": + lambda state: state.has_any(["Manticore of Darkness", "Treeborn Frog"], player) and + state.has("Foolish Burial", player) and + yugioh06_difficulty(state, player, 2), + "TD15 Token": + lambda state: state.has_all(["Dandylion", "Ojama Trio", "Stray Lambs"], player) and + yugioh06_difficulty(state, player, 3), + "TD16 Union": + lambda state: equip_unions(state, player) and + yugioh06_difficulty(state, player, 2), + "TD17 10x Quick Spell": + lambda state: quick_plays(state, player) and + yugioh06_difficulty(state, player, 3), + "TD18 The Forbidden": + lambda state: state.has("Can Exodia Win", player), + "TD19 20 Turns": + lambda state: state.has("Final Countdown", player) and state.has("Can Stall with ST", player) and + yugioh06_difficulty(state, player, 3), + "TD20 Deck Destruction": + lambda state: state.has_any(["Cyber Jar", "Morphing Jar", "Morphing Jar #2", "Needle Worm"], player) + and state.has_any(["The Shallow Grave", "Spear Cretin"], + player) and yugioh06_difficulty(state, player, 2), + "TD21 Victory D.": + lambda state: state.has("Victory D.", player) and only_dragon(state, player) + and yugioh06_difficulty(state, player, 3), + "TD22 The Preventers Fight Back": + lambda state: state.has("Ojama Delta Hurricane and required cards", player) and + state.has_all(["Rescue Cat", "Enchanting Fitting Room", "Jerry Beans Man"], player) and + yugioh06_difficulty(state, player, 3), + "TD23 Huge Revolution": + lambda state: state.has("Huge Revolution and its required cards", player) and + state.has_all(["Enchanting Fitting Room", "Jerry Beans Man"], player) and + yugioh06_difficulty(state, player, 3), + "TD24 Victory in 5 Turns": + lambda state: yugioh06_difficulty(state, player, 3), + "TD25 Moth Grows Up": + lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player) and + state.has_all(["Gokipon", "Howling Insect"], player) and + yugioh06_difficulty(state, player, 3), + "TD26 Magnetic Power": + lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player) and + yugioh06_difficulty(state, player, 2), + "TD27 Dark Sage": + lambda state: state.has("Dark Sage and its required cards", player) and + state.has_any(["Skilled Dark Magician", "Dark Magic Curtain"], player) and + yugioh06_difficulty(state, player, 2), + "TD28 Direct Damage": + lambda state: yugioh06_difficulty(state, player, 2), + "TD29 Destroy Monsters in Battle": + lambda state: yugioh06_difficulty(state, player, 2), + "TD30 Tribute Summon": + lambda state: state.has("Treeborn Frog", player) and yugioh06_difficulty(state, player, 2), + "TD31 Special Summon C": + lambda state: state.count_from_list_exclusive( + ["Aqua Spirit", "Rock Spirit", "Spirit of Flames", + "Garuda the Wind Spirit", "Gigantes", "Inferno", "Megarock Dragon", "Silpheed"], + player) > 4 and yugioh06_difficulty(state, player, 3), + "TD32 Toon": + lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 3), + "TD33 10x Counter": + lambda state: counter_traps(state, player) and yugioh06_difficulty(state, player, 2), + "TD34 Destiny Board": + lambda state: state.has("Destiny Board and its letters", player) + and state.has("Can Stall with Monsters", player) + and state.has("A Cat of Ill Omen", player) + and yugioh06_difficulty(state, player, 2), + "TD35 Huge Damage in a Turn": + lambda state: state.has_all(["Cyber-Stein", "Cyber Twin Dragon", "Megamorph"], player) + and yugioh06_difficulty(state, player, 3), + "TD36 V-Z In the House": + lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player) + and yugioh06_difficulty(state, player, 3), + "TD37 Uria, Lord of Searing Flames": + lambda state: state.has_all(["Uria, Lord of Searing Flames", + "Embodiment of Apophis", + "Skull Zoma", + "Metal Reflect Slime"], player) + and yugioh06_difficulty(state, player, 3), + "TD38 Hamon, Lord of Striking Thunder": + lambda state: state.has("Hamon, Lord of Striking Thunder", player) + and yugioh06_difficulty(state, player, 3), + "TD39 Raviel, Lord of Phantasms": + lambda state: state.has_all(["Raviel, Lord of Phantasms", "Giant Germ"], player) and + state.count_from_list_exclusive(["Archfiend Soldier", + "Skull Descovery Knight", + "Slate Warrior", + "D. D. Trainer", + "Earthbound Spirit"], player) >= 3 + and yugioh06_difficulty(state, player, 3), + "TD40 Make a Chain": + lambda state: state.has("Ultimate Offering", player) + and yugioh06_difficulty(state, player, 4), + "TD41 The Gatekeeper Stands Tall": + lambda state: state.has("Gate Guardian and its pieces", player) and + state.has_all(["Treeborn Frog", "Tribute Doll"], player) + and yugioh06_difficulty(state, player, 4), + "TD42 Serious Damage": + lambda state: yugioh06_difficulty(state, player, 3), + "TD43 Return Monsters with Effects": + lambda state: state.has_all(["Penguin Soldier", "Messenger of Peace"], player) + and yugioh06_difficulty(state, player, 4), + "TD44 Fusion Summon": + lambda state: state.has_all(["Fusion Gate", "Terraforming", "Dimension Fusion", + "Return from the Different Dimension"], player) and + count_has_materials(state, ["Elemental Hero Flame Wingman", + "Elemental Hero Madballman", + "Elemental Hero Rampart Blaster", + "Elemental Hero Steam Healer", + "Elemental Hero Shining Flare Wingman", + "Elemental Hero Wildedge"], player) >= 4 and + yugioh06_difficulty(state, player, 7), + "TD45 Big Damage at once": + lambda state: state.has("Wave-Motion Cannon", player) + and yugioh06_difficulty(state, player, 3), + "TD46 XYZ In the House": + lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player) and + state.has("Dimension Fusion", player), + "TD47 Spell Counter": + lambda state: spell_counter(state, player) and yugioh06_difficulty(state, player, 3), + "TD48 Destroy Monsters with Effects": + lambda state: state.has_all(["Blade Rabbit", "Dream Clown"], player) and + state.has("Can Stall with ST", player) and + yugioh06_difficulty(state, player, 3), + "TD49 Plunder": + lambda state: take_control(state, player) and yugioh06_difficulty(state, player, 5), + "TD50 Dark Scorpion Combination": + lambda state: state.has("Dark Scorpion Combination and its required cards", player) and + state.has_all(["Reinforcement of the Army", "Mystic Tomato"], player) and + yugioh06_difficulty(state, player, 3) + } + multiworld.completion_condition[player] = lambda state: state.has("Goal", player) + + for loc in multiworld.get_locations(player): + if loc.name in location_rules: + add_rule(loc, location_rules[loc.name]) + if loc.name in access_rules: + add_rule(multiworld.get_entrance(loc.name, player), access_rules[loc.name]) + + +def only_light(state, player): + return state.has_from_list_exclusive([ + "Dunames Dark Witch", + "X-Head Cannon", + "Homunculus the Alchemic Being", + "Hysteric Fairy", + "Ninja Grandmaster Sasuke"], player, 2)\ + and state.has_from_list_exclusive([ + "Chaos Command Magician", + "Cybernetic Magician", + "Kaiser Glider", + "The Agent of Judgment - Saturn", + "Zaborg the Thunder Monarch", + "Cyber Dragon"], player, 1) \ + and state.has_from_list_exclusive([ + "D.D. Warrior Lady", + "Mystic Swordsman LV2", + "Y-Dragon Head", + "Z-Metal Tank", + ], player, 2) and state.has("Shining Angel", player) + + +def only_dark(state, player): + return state.has_from_list_exclusive([ + "Dark Elf", + "Archfiend Soldier", + "Mad Dog of Darkness", + "Vorse Raider", + "Skilled Dark Magician", + "Skull Descovery Knight", + "Mechanicalchaser", + "Dark Blade", + "Gil Garth", + "La Jinn the Mystical Genie of the Lamp", + "Opticlops", + "Zure, Knight of Dark World", + "Brron, Mad King of Dark World", + "D.D. Survivor", + "Exarion Universe", + "Kycoo the Ghost Destroyer", + "Regenerating Mummy" + ], player, 2) \ + and state.has_any([ + "Summoned Skull", + "Skull Archfiend of Lightning", + "The End of Anubis", + "Dark Ruler Ha Des", + "Beast of Talwar", + "Inferno Hammer", + "Jinzo", + "Ryu Kokki" + ], player) \ + and state.has_from_list_exclusive([ + "Legendary Fiend", + "Don Zaloog", + "Newdoria", + "Sangan", + "Spirit Reaper", + "Giant Germ" + ], player, 2) and state.has("Mystic Tomato", player) + + +def only_earth(state, player): + return state.has_from_list_exclusive([ + "Berserk Gorilla", + "Gemini Elf", + "Insect Knight", + "Toon Gemini Elf", + "Familiar-Possessed - Aussa", + "Neo Bug", + "Blindly Loyal Goblin", + "Chiron the Mage", + "Gearfried the Iron Knight" + ], player, 2) and state.has_any([ + "Dark Driceratops", + "Granmarg the Rock Monarch", + "Hieracosphinx", + "Saber Beetle" + ], player) and state.has_from_list_exclusive([ + "Hyper Hammerhead", + "Green Gadget", + "Red Gadget", + "Yellow Gadget", + "Dimensional Warrior", + "Enraged Muka Muka", + "Exiled Force" + ], player, 2) and state.has("Giant Rat", player) + + +def only_water(state, player): + return state.has_from_list_exclusive([ + "Gagagigo", + "Familiar-Possessed - Eria", + "7 Colored Fish", + "Sea Serpent Warrior of Darkness", + "Abyss Soldier" + ], player, 2) and state.has_any([ + "Giga Gagagigo", + "Amphibian Beast", + "Terrorking Salmon", + "Mobius the Frost Monarch" + ], player) and state.has_from_list_exclusive([ + "Revival Jam", + "Yomi Ship", + "Treeborn Frog" + ], player, 2) and state.has("Mother Grizzly", player) + + +def only_fire(state, player): + return state.has_from_list_exclusive([ + "Blazing Inpachi", + "Familiar-Possessed - Hiita", + "Great Angus", + "Fire Beaters" + ], player, 2) and state.has_any([ + "Thestalos the Firestorm Monarch", + "Horus the Black Flame Dragon LV6" + ], player) and state.has_from_list_exclusive([ + "Solar Flare Dragon", + "Tenkabito Shien", + "Ultimate Baseball Kid" + ], player, 2) and state.has("UFO Turtle", player) + + +def only_wind(state, player): + return state.has_from_list_exclusive([ + "Luster Dragon", + "Slate Warrior", + "Spear Dragon", + "Familiar-Possessed - Wynn", + "Harpie's Brother", + "Nin-Ken Dog", + "Cyber Harpie Lady", + "Oxygeddon" + ], player, 2) and state.has_any([ + "Cyber-Tech Alligator", + "Luster Dragon #2", + "Armed Dragon LV5", + "Roc from the Valley of Haze" + ], player) and state.has_from_list_exclusive([ + "Armed Dragon LV3", + "Twin-Headed Behemoth", + "Harpie Lady 1" + ], player, 2) and state.has("Flying Kamakiri 1", player) + + +def only_fairy(state, player): + return state.has_any([ + "Dunames Dark Witch", + "Hysteric Fairy" + ], player) and (state.count_from_list_exclusive([ + "Dunames Dark Witch", + "Hysteric Fairy", + "Dancing Fairy", + "Zolga", + "Shining Angel", + "Kelbek", + "Mudora", + "Asura Priest", + "Cestus of Dagla" + ], player) + (state.has_any([ + "The Agent of Judgment - Saturn", + "Airknight Parshath" + ], player))) >= 7 + + +def only_warrior(state, player): + return state.has_any([ + "Dark Blade", + "Blindly Loyal Goblin", + "D.D. Survivor", + "Gearfried the Iron knight", + "Ninja Grandmaster Sasuke", + "Warrior Beaters" + ], player) and (state.count_from_list_exclusive([ + "Warrior Lady of the Wasteland", + "Exiled Force", + "Mystic Swordsman LV2", + "Dimensional Warrior", + "Dandylion", + "D.D. Assailant", + "Blade Knight", + "D.D. Warrior Lady", + "Marauding Captain", + "Command Knight", + "Reinforcement of the Army" + ], player) + (state.has_any([ + "Freed the Matchless General", + "Holy Knight Ishzark", + "Silent Swordsman Lv5" + ], player))) >= 7 + + +def only_zombie(state, player): + return state.has("Pyramid Turtle", player) \ + and state.has_from_list_exclusive([ + "Regenerating Mummy", + "Ryu Kokki", + "Spirit Reaper", + "Master Kyonshee", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Curse of Vampire", + "Vampire Lord", + "Goblin Zombie", + "Book of Life", + "Call of the Mummy" + ], player, 6) + + +def only_dragon(state, player): + return state.has_any([ + "Luster Dragon", + "Spear Dragon", + "Cave Dragon" + ], player) and (state.count_from_list_exclusive([ + "Luster Dragon", + "Spear Dragon", + "Cave Dragon" + "Armed Dragon LV3", + "Masked Dragon", + "Twin-Headed Behemoth", + "Element Dragon", + "Troop Dragon", + "Horus the Black Flame Dragon LV4", + "Stamping Destruction" + ], player) + (state.has_any([ + "Luster Dragon #2", + "Armed Dragon LV5", + "Kaiser Glider", + "Horus the Black Flame Dragon LV6" + ], player))) >= 7 + + +def only_spellcaster(state, player): + return state.has_any([ + "Dark Elf", + "Gemini Elf", + "Skilled Dark Magician", + "Toon Gemini Elf", + "Kycoo the Ghost Destroyer", + "Familiar-Possessed - Aussa" + ], player) and (state.count_from_list_exclusive([ + "Dark Elf", + "Gemini Elf", + "Skilled Dark Magician", + "Toon Gemini Elf", + "Kycoo the Ghost Destroyer", + "Familiar-Possessed - Aussa", + "Breaker the magical Warrior", + "The Tricky", + "Injection Fairy Lily", + "Magician of Faith", + "Tsukuyomi", + "Gravekeeper's Spy", + "Gravekeeper's Guard", + "Summon Priest", + "Old Vindictive Magician", + "Apprentice Magician", + "Magical Dimension" + ], player) + (state.has_any([ + "Chaos Command Magician", + "Cybernetic Magician" + ], player))) >= 7 + + +def equip_unions(state, player): + return (state.has_all(["Burning Beast", "Freezing Beast", + "Metallizing Parasite - Lunatite", "Mother Grizzly"], player) or + state.has_all(["Dark Blade", "Pitch-Dark Dragon", + "Giant Orc", "Second Goblin", "Mystic Tomato"], player) or + state.has_all(["Decayed Commander", "Zombie Tiger", + "Vampire Orchis", "Des Dendle", "Giant Rat"], player) or + state.has_all(["Indomitable Fighter Lei Lei", "Protective Soul Ailin", + "V-Tiger Jet", "W-Wing Catapult", "Shining Angel"], player) or + state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "Shining Angel"], player)) and\ + state.has_any(["Frontline Base", "Formation Union", "Roll Out!"], player) + + +def can_gain_lp_every_turn(state, player): + return state.count_from_list_exclusive([ + "Solemn Wishes", + "Cure Mermaid", + "Dancing Fairy", + "Princess Pikeru", + "Kiseitai"], player) >= 3 + + +def only_normal(state, player): + return (state.has_from_list_exclusive([ + "Archfiend Soldier", + "Gemini Elf", + "Insect Knight", + "Luster Dragon", + "Mad Dog of Darkness", + "Vorse Raider", + "Blazing Inpachi", + "Gagagigo", + "Mechanicalchaser", + "7 Colored Fish", + "Dark Blade", + "Dunames Dark Witch", + "Giant Red Snake", + "Gil Garth", + "Great Angus", + "Harpie's Brother", + "La Jinn the Mystical Genie of the Lamp", + "Neo Bug", + "Nin-Ken Dog", + "Opticlops", + "Sea Serpent Warrior of Darkness", + "X-Head Cannon", + "Zure, Knight of Dark World"], player, 6) and + state.has_any([ + "Cyber-Tech Alligator", + "Summoned Skull", + "Giga Gagagigo", + "Amphibian Beast", + "Beast of Talwar", + "Luster Dragon #2", + "Terrorking Salmon"], player)) + + +def only_level(state, player): + return (state.has("Level Up!", player) and + (state.has_all(["Armed Dragon LV3", "Armed Dragon LV5"], player) + + state.has_all(["Horus the Black Flame Dragon LV4", "Horus the Black Flame Dragon LV6"], player) + + state.has_all(["Mystic Swordsman LV4", "Mystic Swordsman LV6"], player) + + state.has_all(["Silent Swordsman Lv3", "Silent Swordsman Lv5"], player) + + state.has_all(["Ultimate Insect Lv3", "Ultimate Insect Lv5"], player)) >= 3) + + +def spell_counter(state, player): + return (state.has("Pitch-Black Power Stone", player) and + state.has_from_list_exclusive(["Blast Magician", + "Magical Marionette", + "Mythical Beast Cerberus", + "Royal Magical Library", + "Spell-Counter Cards"], player, 2)) + + +def take_control(state, player): + return state.has_from_list_exclusive(["Aussa the Earth Charmer", + "Jowls of Dark Demise", + "Brain Control", + "Creature Swap", + "Enemy Controller", + "Mind Control", + "Magician of Faith"], player, 5) + + +def only_toons(state, player): + return state.has_all(["Toon Gemini Elf", + "Toon Goblin Attack Force", + "Toon Masked Sorcerer", + "Toon Mermaid", + "Toon Dark Magician Girl", + "Toon World"], player) + + +def only_spirit(state, player): + return state.has_all(["Asura Priest", + "Fushi No Tori", + "Maharaghi", + "Susa Soldier"], player) + + +def pacman_deck(state, player): + return state.has_from_list_exclusive(["Des Lacooda", + "Swarm of Locusts", + "Swarm of Scarabs", + "Wandering Mummy", + "Golem Sentry", + "Great Spirit", + "Royal Keeper", + "Stealth Bird"], player, 4) + + +def quick_plays(state, player): + return state.has_from_list_exclusive(["Collapse", + "Emergency Provisions", + "Enemy Controller", + "Graceful Dice", + "Mystik Wok", + "Offerings to the Doomed", + "Poison of the Old Man", + "Reload", + "Rush Recklessly", + "The Reliable Guardian"], player, 4) + + +def counter_traps(state, player): + return state.has_from_list_exclusive(["Cursed Seal of the Forbidden Spell", + "Divine Wrath", + "Horn of Heaven", + "Magic Drain", + "Magic Jammer", + "Negate Attack", + "Seven Tools of the Bandit", + "Solemn Judgment", + "Spell Shield Type-8"], player, 5) + + +def back_row_removal(state, player): + return state.has_from_list_exclusive(["Anteatereatingant", + "B.E.S. Tetran", + "Breaker the Magical Warrior", + "Calamity of the Wicked", + "Chiron the Mage", + "Dust Tornado", + "Heavy Storm", + "Mystical Space Typhoon", + "Mobius the Frost Monarch", + "Raigeki Break", + "Stamping Destruction", + "Swarm of Locusts"], player, 2) diff --git a/worlds/yugioh06/structure_deck.py b/worlds/yugioh06/structure_deck.py new file mode 100644 index 00000000..8454c55e --- /dev/null +++ b/worlds/yugioh06/structure_deck.py @@ -0,0 +1,81 @@ +structure_contents: dict[str, set] = { + "dragons_roar": { + "Luster Dragon", + "Armed Dragon LV3", + "Armed Dragon LV5", + "Masked Dragon", + "Twin-Headed Behemoth", + "Stamping Destruction", + "Nobleman of Crossout", + "Creature Swap", + "Reload", + "Stamping Destruction", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "zombie_madness": { + "Pyramid Turtle", + "Regenerating Mummy", + "Ryu Kokki", + "Book of Life", + "Call of the Mummy", + "Creature Swap", + "Reload", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "blazing_destruction": { + "Inferno", + "Solar Flare Dragon", + "UFO Turtle", + "Ultimate Baseball Kid", + "Fire Beaters", + "Tribute to The Doomed", + "Level Limit - Area B", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "fury_from_the_deep": { + "Mother Grizzly", + "Water Beaters", + "Gravity Bind", + "Reload", + "Mobius the Frost Monarch", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "warriors_triumph": { + "Gearfried the Iron Knight", + "D.D. Warrior Lady", + "Marauding Captain", + "Exiled Force", + "Reinforcement of the Army", + "Warrior Beaters", + "Reload", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "spellcasters_judgement": { + "Dark Magician", + "Apprentice Magician", + "Breaker the Magical Warrior", + "Magician of Faith", + "Skilled Dark Magician", + "Tsukuyomi", + "Magical Dimension", + "Mage PowerSpell-Counter Cards", + "Heavy Storm", + "Dust Tornado", + "Mystical Space Typhoon", + }, + "none": {}, +} + + +def get_deck_content_locations(deck: str) -> dict[str, str]: + return {f"{deck} {i}": content for i, content in enumerate(structure_contents[deck])}