from typing import List from BaseClasses import Tutorial from worlds.AutoWorld import WebWorld, World from .Items import RLItem, RLItemData, event_item_table, get_items_by_category, item_table from .Locations import RLLocation, location_table from .Options import RLOptions from .Presets import rl_options_presets from .Regions import create_regions from .Rules import set_rules class RLWeb(WebWorld): theme = "stone" tutorials = [Tutorial( "Multiworld Setup Guide", "A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, " "multiworld, and related software.", "English", "rogue-legacy_en.md", "rogue-legacy/en", ["Phar"] )] bug_report_page = "https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=" \ "report-an-issue---.md&title=%5BIssue%5D" options_presets = rl_options_presets class RLWorld(World): """ Rogue Legacy is a genealogical rogue-"LITE" where anyone can be a hero. Each time you die, your child will succeed you. Every child is unique. One child might be colorblind, another might have vertigo-- they could even be a dwarf. But that's OK, because no one is perfect, and you don't have to be to succeed. """ game = "Rogue Legacy" options_dataclass = RLOptions options: RLOptions topology_present = True required_client_version = (0, 3, 5) web = RLWeb() item_name_to_id = {name: data.code for name, data in item_table.items() if data.code is not None} location_name_to_id = {name: data.code for name, data in location_table.items() if data.code is not None} def fill_slot_data(self) -> dict: return self.options.as_dict(*[name for name in self.options_dataclass.type_hints.keys()]) def generate_early(self): location_ids_used_per_game = { world.game: set(world.location_id_to_name) for world in self.multiworld.worlds.values() } item_ids_used_per_game = { world.game: set(world.item_id_to_name) for world in self.multiworld.worlds.values() } overlapping_games = set() for id_lookup in (location_ids_used_per_game, item_ids_used_per_game): for game_1, ids_1 in id_lookup.items(): for game_2, ids_2 in id_lookup.items(): if game_1 == game_2: continue if ids_1 & ids_2: overlapping_games.add(tuple(sorted([game_1, game_2]))) if overlapping_games: raise RuntimeError( "In this multiworld, there are games with overlapping item/location IDs.\n" "The current Rogue Legacy does not support these and a fix is not currently planned.\n" f"The overlapping games are: {overlapping_games}" ) # Check validation of names. additional_lady_names = len(self.options.additional_lady_names.value) additional_sir_names = len(self.options.additional_sir_names.value) if not self.options.allow_default_names: if additional_lady_names < int(self.options.number_of_children): raise Exception( f"allow_default_names is off, but not enough names are defined in additional_lady_names. " f"Expected {int(self.options.number_of_children)}, Got {additional_lady_names}") if additional_sir_names < int(self.options.number_of_children): raise Exception( f"allow_default_names is off, but not enough names are defined in additional_sir_names. " f"Expected {int(self.options.number_of_children)}, Got {additional_sir_names}") def create_items(self): item_pool: List[RLItem] = [] total_locations = len(self.multiworld.get_unfilled_locations(self.player)) for name, data in item_table.items(): quantity = data.max_quantity # Architect if name == "Architect": if self.options.architect == "disabled": continue if self.options.architect == "start_unlocked": self.multiworld.push_precollected(self.create_item(name)) continue if self.options.architect == "early": self.multiworld.local_early_items[self.player]["Architect"] = 1 # Blacksmith and Enchantress if name == "Blacksmith" or name == "Enchantress": if self.options.vendors == "start_unlocked": self.multiworld.push_precollected(self.create_item(name)) continue if self.options.vendors == "early": self.multiworld.local_early_items[self.player]["Blacksmith"] = 1 self.multiworld.local_early_items[self.player]["Enchantress"] = 1 # Haggling if name == "Haggling" and self.options.disable_charon: continue # Blueprints if data.category == "Blueprints": # No progressive blueprints if progressive_blueprints are disabled. if name == "Progressive Blueprints" and not self.options.progressive_blueprints: continue # No distinct blueprints if progressive_blueprints are enabled. elif name != "Progressive Blueprints" and self.options.progressive_blueprints: continue # Classes if data.category == "Classes": if name == "Progressive Knights": if "Knight" not in self.options.available_classes: continue if self.options.starting_class == "knight": quantity = 1 if name == "Progressive Mages": if "Mage" not in self.options.available_classes: continue if self.options.starting_class == "mage": quantity = 1 if name == "Progressive Barbarians": if "Barbarian" not in self.options.available_classes: continue if self.options.starting_class == "barbarian": quantity = 1 if name == "Progressive Knaves": if "Knave" not in self.options.available_classes: continue if self.options.starting_class == "knave": quantity = 1 if name == "Progressive Miners": if "Miner" not in self.options.available_classes: continue if self.options.starting_class == "miner": quantity = 1 if name == "Progressive Shinobis": if "Shinobi" not in self.options.available_classes: continue if self.options.starting_class == "shinobi": quantity = 1 if name == "Progressive Liches": if "Lich" not in self.options.available_classes: continue if self.options.starting_class == "lich": quantity = 1 if name == "Progressive Spellthieves": if "Spellthief" not in self.options.available_classes: continue if self.options.starting_class == "spellthief": quantity = 1 if name == "Dragons": if "Dragon" not in self.options.available_classes: continue if name == "Traitors": if "Traitor" not in self.options.available_classes: continue # Skills if name == "Health Up": quantity = self.options.health_pool.value elif name == "Mana Up": quantity = self.options.mana_pool.value elif name == "Attack Up": quantity = self.options.attack_pool.value elif name == "Magic Damage Up": quantity = self.options.magic_damage_pool.value elif name == "Armor Up": quantity = self.options.armor_pool.value elif name == "Equip Up": quantity = self.options.equip_pool.value elif name == "Crit Chance Up": quantity = self.options.crit_chance_pool.value elif name == "Crit Damage Up": quantity = self.options.crit_damage_pool.value # Ignore filler, it will be added in a later stage. if data.category == "Filler": continue item_pool += [self.create_item(name) for _ in range(0, quantity)] # Fill any empty locations with filler items. while len(item_pool) < total_locations: item_pool.append(self.create_item(self.get_filler_item_name())) self.multiworld.itempool += item_pool def get_filler_item_name(self) -> str: fillers = get_items_by_category("Filler") weights = [data.weight for data in fillers.values()] return self.random.choices([filler for filler in fillers.keys()], weights, k=1)[0] def create_item(self, name: str) -> RLItem: data = item_table[name] return RLItem(name, data.classification, data.code, self.player) def create_event(self, name: str) -> RLItem: data = event_item_table[name] return RLItem(name, data.classification, data.code, self.player) def set_rules(self): set_rules(self, self.player) def create_regions(self): create_regions(self) self._place_events() def _place_events(self): # Fountain self.multiworld.get_location("Fountain Room", self.player).place_locked_item( self.create_event("Defeat The Fountain")) # Khidr / Neo Khidr if self.options.khidr == "vanilla": self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item( self.create_event("Defeat Khidr")) else: self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item( self.create_event("Defeat Neo Khidr")) # Alexander / Alexander IV if self.options.alexander == "vanilla": self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item( self.create_event("Defeat Alexander")) else: self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item( self.create_event("Defeat Alexander IV")) # Ponce de Leon / Ponce de Freon if self.options.leon == "vanilla": self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item( self.create_event("Defeat Ponce de Leon")) else: self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item( self.create_event("Defeat Ponce de Freon")) # Herodotus / Astrodotus if self.options.herodotus == "vanilla": self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item( self.create_event("Defeat Herodotus")) else: self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item( self.create_event("Defeat Astrodotus"))