Archipelago/worlds/yugioh06/__init__.py

457 lines
21 KiB
Python

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"))
procedure = [("apply_bsdiff4", ["base_patch.bsdiff4"]), ("apply_tokens", ["token_data.bin"])]
if self.is_draft_mode:
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:
procedure.insert(1, ("apply_bsdiff4", ["ocg_patch.bsdiff4"]))
patch.write_file("ocg_patch.bsdiff4", pkgutil.get_data(__name__, "patches/ocg.bsdiff4"))
patch.procedure = procedure
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"