KH2: Version 2 (#2009)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> Co-authored-by: Joe Prochaska <prochaska.joseph@gmail.com>
This commit is contained in:
		| @@ -1,15 +1,25 @@ | ||||
| from BaseClasses import Tutorial, ItemClassification | ||||
| import logging | ||||
| from typing import List | ||||
|  | ||||
| from BaseClasses import Tutorial, ItemClassification | ||||
| from Fill import fill_restrictive | ||||
| from worlds.LauncherComponents import Component, components, Type, launch_subprocess | ||||
| from worlds.AutoWorld import World, WebWorld | ||||
| from .Items import * | ||||
| from .Locations import all_locations, setup_locations, exclusion_table, AllWeaponSlot | ||||
| from .Names import ItemName, LocationName | ||||
| from .Locations import * | ||||
| from .Names import ItemName, LocationName, RegionName | ||||
| from .OpenKH import patch_kh2 | ||||
| from .Options import KH2_Options | ||||
| from .Options import KingdomHearts2Options | ||||
| from .Regions import create_regions, connect_regions | ||||
| from .Rules import set_rules | ||||
| from ..AutoWorld import World, WebWorld | ||||
| from .logic import KH2Logic | ||||
| from .Rules import * | ||||
|  | ||||
|  | ||||
| def launch_client(): | ||||
|     from .Client import launch | ||||
|     launch_subprocess(launch, name="KH2Client") | ||||
|  | ||||
|  | ||||
| components.append(Component("KH2 Client", "KH2Client", func=launch_client, component_type=Type.CLIENT)) | ||||
|  | ||||
|  | ||||
| class KingdomHearts2Web(WebWorld): | ||||
| @@ -23,99 +33,119 @@ class KingdomHearts2Web(WebWorld): | ||||
|     )] | ||||
|  | ||||
|  | ||||
| # noinspection PyUnresolvedReferences | ||||
| class KH2World(World): | ||||
|     """ | ||||
|     Kingdom Hearts II is an action role-playing game developed and published by Square Enix and released in 2005. | ||||
|     It is the sequel to Kingdom Hearts and Kingdom Hearts: Chain of Memories, and like the two previous games, | ||||
|     focuses on Sora and his friends' continued battle against the Darkness. | ||||
|     """ | ||||
|     game: str = "Kingdom Hearts 2" | ||||
|     game = "Kingdom Hearts 2" | ||||
|     web = KingdomHearts2Web() | ||||
|     data_version = 1 | ||||
|     required_client_version = (0, 4, 0) | ||||
|     option_definitions = KH2_Options | ||||
|     item_name_to_id = {name: data.code for name, data in item_dictionary_table.items()} | ||||
|     location_name_to_id = {item_name: data.code for item_name, data in all_locations.items() if data.code} | ||||
|  | ||||
|     required_client_version = (0, 4, 4) | ||||
|     options_dataclass = KingdomHearts2Options | ||||
|     options: KingdomHearts2Options | ||||
|     item_name_to_id = {item: item_id | ||||
|                        for item_id, item in enumerate(item_dictionary_table.keys(), 0x130000)} | ||||
|     location_name_to_id = {item: location | ||||
|                            for location, item in enumerate(all_locations.keys(), 0x130000)} | ||||
|     item_name_groups = item_groups | ||||
|  | ||||
|     visitlocking_dict: Dict[str, int] | ||||
|     plando_locations: Dict[str, str] | ||||
|     lucky_emblem_amount: int | ||||
|     lucky_emblem_required: int | ||||
|     bounties_required: int | ||||
|     bounties_amount: int | ||||
|     filler_items: List[str] | ||||
|     item_quantity_dict: Dict[str, int] | ||||
|     local_items: Dict[int, int] | ||||
|     sora_ability_dict: Dict[str, int] | ||||
|     goofy_ability_dict: Dict[str, int] | ||||
|     donald_ability_dict: Dict[str, int] | ||||
|     total_locations: int | ||||
|  | ||||
|     # growth_list: list[str] | ||||
|  | ||||
|     def __init__(self, multiworld: "MultiWorld", player: int): | ||||
|         super().__init__(multiworld, player) | ||||
|         self.valid_abilities = None | ||||
|         self.visitlocking_dict = None | ||||
|         self.plando_locations = None | ||||
|         self.luckyemblemamount = None | ||||
|         self.luckyemblemrequired = None | ||||
|         self.BountiesRequired = None | ||||
|         self.BountiesAmount = None | ||||
|         self.hitlist = None | ||||
|         self.LocalItems = {} | ||||
|         self.RandomSuperBoss = list() | ||||
|         self.filler_items = list() | ||||
|         self.item_quantity_dict = {} | ||||
|         self.donald_ability_pool = list() | ||||
|         self.goofy_ability_pool = list() | ||||
|         self.sora_keyblade_ability_pool = list() | ||||
|         self.keyblade_slot_copy = list(Locations.Keyblade_Slots.keys()) | ||||
|         self.keyblade_slot_copy.remove(LocationName.KingdomKeySlot) | ||||
|         self.totalLocations = len(all_locations.items()) | ||||
|         # random_super_boss_list List[str] | ||||
|         # has to be in __init__ or else other players affect each other's bounties | ||||
|         self.random_super_boss_list = list() | ||||
|         self.growth_list = list() | ||||
|         for x in range(4): | ||||
|             self.growth_list.extend(Movement_Table.keys()) | ||||
|         self.slotDataDuping = set() | ||||
|         self.localItems = dict() | ||||
|         # lists of KH2Item | ||||
|         self.keyblade_ability_pool = list() | ||||
|  | ||||
|         self.goofy_get_bonus_abilities = list() | ||||
|         self.goofy_weapon_abilities = list() | ||||
|         self.donald_get_bonus_abilities = list() | ||||
|         self.donald_weapon_abilities = list() | ||||
|  | ||||
|         self.slot_data_goofy_weapon = dict() | ||||
|         self.slot_data_sora_weapon = dict() | ||||
|         self.slot_data_donald_weapon = dict() | ||||
|  | ||||
|     def fill_slot_data(self) -> dict: | ||||
|         for values in CheckDupingItems.values(): | ||||
|             if isinstance(values, set): | ||||
|                 self.slotDataDuping = self.slotDataDuping.union(values) | ||||
|             else: | ||||
|                 for inner_values in values.values(): | ||||
|                     self.slotDataDuping = self.slotDataDuping.union(inner_values) | ||||
|         self.LocalItems = {location.address: item_dictionary_table[location.item.name].code | ||||
|                            for location in self.multiworld.get_filled_locations(self.player) | ||||
|                            if location.item.player == self.player | ||||
|                            and location.item.name in self.slotDataDuping | ||||
|                            and location.name not in AllWeaponSlot} | ||||
|         for ability in self.slot_data_sora_weapon: | ||||
|             if ability in self.sora_ability_dict and self.sora_ability_dict[ability] >= 1: | ||||
|                 self.sora_ability_dict[ability] -= 1 | ||||
|         self.donald_ability_dict = {k: v.quantity for k, v in DonaldAbility_Table.items()} | ||||
|         for ability in self.slot_data_donald_weapon: | ||||
|             if ability in self.donald_ability_dict and self.donald_ability_dict[ability] >= 1: | ||||
|                 self.donald_ability_dict[ability] -= 1 | ||||
|         self.goofy_ability_dict = {k: v.quantity for k, v in GoofyAbility_Table.items()} | ||||
|         for ability in self.slot_data_goofy_weapon: | ||||
|             if ability in self.goofy_ability_dict and self.goofy_ability_dict[ability] >= 1: | ||||
|                 self.goofy_ability_dict[ability] -= 1 | ||||
|  | ||||
|         return {"hitlist":              self.hitlist, | ||||
|                 "LocalItems":           self.LocalItems, | ||||
|                 "Goal":                 self.multiworld.Goal[self.player].value, | ||||
|                 "FinalXemnas":          self.multiworld.FinalXemnas[self.player].value, | ||||
|                 "LuckyEmblemsRequired": self.multiworld.LuckyEmblemsRequired[self.player].value, | ||||
|                 "BountyRequired":       self.multiworld.BountyRequired[self.player].value} | ||||
|         slot_data = self.options.as_dict("Goal", "FinalXemnas", "LuckyEmblemsRequired", "BountyRequired") | ||||
|         slot_data.update({ | ||||
|             "hitlist":                [],  # remove this after next update | ||||
|             "PoptrackerVersionCheck": 4.3, | ||||
|             "KeybladeAbilities":      self.sora_ability_dict, | ||||
|             "StaffAbilities":         self.donald_ability_dict, | ||||
|             "ShieldAbilities":        self.goofy_ability_dict, | ||||
|         }) | ||||
|         return slot_data | ||||
|  | ||||
|     def create_item(self, name: str, ) -> Item: | ||||
|         data = item_dictionary_table[name] | ||||
|         if name in Progression_Dicts["Progression"]: | ||||
|     def create_item(self, name: str) -> Item: | ||||
|         """ | ||||
|         Returns created KH2Item | ||||
|         """ | ||||
|         # data = item_dictionary_table[name] | ||||
|         if name in progression_set: | ||||
|             item_classification = ItemClassification.progression | ||||
|         elif name in useful_set: | ||||
|             item_classification = ItemClassification.useful | ||||
|         else: | ||||
|             item_classification = ItemClassification.filler | ||||
|  | ||||
|         created_item = KH2Item(name, item_classification, data.code, self.player) | ||||
|         created_item = KH2Item(name, item_classification, self.item_name_to_id[name], self.player) | ||||
|  | ||||
|         return created_item | ||||
|  | ||||
|     def create_items(self) -> None: | ||||
|         self.visitlocking_dict = Progression_Dicts["AllVisitLocking"].copy() | ||||
|         if self.multiworld.Schmovement[self.player] != "level_0": | ||||
|             for _ in range(self.multiworld.Schmovement[self.player].value): | ||||
|                 for name in {ItemName.HighJump, ItemName.QuickRun, ItemName.DodgeRoll, ItemName.AerialDodge, | ||||
|                              ItemName.Glide}: | ||||
|         """ | ||||
|         Fills ItemPool and manages schmovement, random growth, visit locking and random starting visit locking. | ||||
|         """ | ||||
|         self.visitlocking_dict = visit_locking_dict["AllVisitLocking"].copy() | ||||
|         if self.options.Schmovement != "level_0": | ||||
|             for _ in range(self.options.Schmovement.value): | ||||
|                 for name in Movement_Table.keys(): | ||||
|                     self.item_quantity_dict[name] -= 1 | ||||
|                     self.growth_list.remove(name) | ||||
|                     self.multiworld.push_precollected(self.create_item(name)) | ||||
|  | ||||
|         if self.multiworld.RandomGrowth[self.player] != 0: | ||||
|             max_growth = min(self.multiworld.RandomGrowth[self.player].value, len(self.growth_list)) | ||||
|         if self.options.RandomGrowth: | ||||
|             max_growth = min(self.options.RandomGrowth.value, len(self.growth_list)) | ||||
|             for _ in range(max_growth): | ||||
|                 random_growth = self.multiworld.per_slot_randoms[self.player].choice(self.growth_list) | ||||
|                 random_growth = self.random.choice(self.growth_list) | ||||
|                 self.item_quantity_dict[random_growth] -= 1 | ||||
|                 self.growth_list.remove(random_growth) | ||||
|                 self.multiworld.push_precollected(self.create_item(random_growth)) | ||||
|  | ||||
|         if self.multiworld.Visitlocking[self.player] == "no_visit_locking": | ||||
|             for item, amount in Progression_Dicts["AllVisitLocking"].items(): | ||||
|         if self.options.Visitlocking == "no_visit_locking": | ||||
|             for item, amount in visit_locking_dict["AllVisitLocking"].items(): | ||||
|                 for _ in range(amount): | ||||
|                     self.multiworld.push_precollected(self.create_item(item)) | ||||
|                     self.item_quantity_dict[item] -= 1 | ||||
| @@ -123,19 +153,19 @@ class KH2World(World): | ||||
|                     if self.visitlocking_dict[item] == 0: | ||||
|                         self.visitlocking_dict.pop(item) | ||||
|  | ||||
|         elif self.multiworld.Visitlocking[self.player] == "second_visit_locking": | ||||
|             for item in Progression_Dicts["2VisitLocking"]: | ||||
|         elif self.options.Visitlocking == "second_visit_locking": | ||||
|             for item in visit_locking_dict["2VisitLocking"]: | ||||
|                 self.item_quantity_dict[item] -= 1 | ||||
|                 self.visitlocking_dict[item] -= 1 | ||||
|                 if self.visitlocking_dict[item] == 0: | ||||
|                     self.visitlocking_dict.pop(item) | ||||
|                 self.multiworld.push_precollected(self.create_item(item)) | ||||
|  | ||||
|         for _ in range(self.multiworld.RandomVisitLockingItem[self.player].value): | ||||
|         for _ in range(self.options.RandomVisitLockingItem.value): | ||||
|             if sum(self.visitlocking_dict.values()) <= 0: | ||||
|                 break | ||||
|             visitlocking_set = list(self.visitlocking_dict.keys()) | ||||
|             item = self.multiworld.per_slot_randoms[self.player].choice(visitlocking_set) | ||||
|             item = self.random.choice(visitlocking_set) | ||||
|             self.item_quantity_dict[item] -= 1 | ||||
|             self.visitlocking_dict[item] -= 1 | ||||
|             if self.visitlocking_dict[item] == 0: | ||||
| @@ -145,175 +175,258 @@ class KH2World(World): | ||||
|         itempool = [self.create_item(item) for item, data in self.item_quantity_dict.items() for _ in range(data)] | ||||
|  | ||||
|         # Creating filler for unfilled locations | ||||
|         itempool += [self.create_filler() | ||||
|                      for _ in range(self.totalLocations - len(itempool))] | ||||
|         itempool += [self.create_filler() for _ in range(self.total_locations - len(itempool))] | ||||
|  | ||||
|         self.multiworld.itempool += itempool | ||||
|  | ||||
|     def generate_early(self) -> None: | ||||
|         # Item Quantity dict because Abilities can be a problem for KH2's Software. | ||||
|         """ | ||||
|         Determines the quantity of items and maps plando locations to items. | ||||
|         """ | ||||
|         # Item: Quantity Map | ||||
|         # Example. Quick Run: 4 | ||||
|         self.total_locations = len(all_locations.keys()) | ||||
|         for x in range(4): | ||||
|             self.growth_list.extend(Movement_Table.keys()) | ||||
|  | ||||
|         self.item_quantity_dict = {item: data.quantity for item, data in item_dictionary_table.items()} | ||||
|         self.sora_ability_dict = {k: v.quantity for dic in [SupportAbility_Table, ActionAbility_Table] for k, v in | ||||
|                                   dic.items()} | ||||
|         # Dictionary to mark locations with their plandoed item | ||||
|         # Example. Final Xemnas: Victory | ||||
|         # 3 random support abilities because there are left over slots | ||||
|         support_abilities = list(SupportAbility_Table.keys()) | ||||
|         for _ in range(6): | ||||
|             random_support_ability = self.random.choice(support_abilities) | ||||
|             self.item_quantity_dict[random_support_ability] += 1 | ||||
|             self.sora_ability_dict[random_support_ability] += 1 | ||||
|  | ||||
|         self.plando_locations = dict() | ||||
|         self.hitlist = [] | ||||
|         self.starting_invo_verify() | ||||
|  | ||||
|         for k, v in self.options.CustomItemPoolQuantity.value.items(): | ||||
|             # kh2's items cannot hold more than a byte | ||||
|             if 255 > v > self.item_quantity_dict[k] and k in default_itempool_option.keys(): | ||||
|                 self.item_quantity_dict[k] = v | ||||
|             elif 255 <= v: | ||||
|                 logging.warning( | ||||
|                         f"{self.player} has too many {k} in their CustomItemPool setting. Setting to default quantity") | ||||
|         # Option to turn off Promise Charm Item | ||||
|         if not self.multiworld.Promise_Charm[self.player]: | ||||
|             self.item_quantity_dict[ItemName.PromiseCharm] = 0 | ||||
|         if not self.options.Promise_Charm: | ||||
|             del self.item_quantity_dict[ItemName.PromiseCharm] | ||||
|  | ||||
|         if not self.options.AntiForm: | ||||
|             del self.item_quantity_dict[ItemName.AntiForm] | ||||
|  | ||||
|         self.set_excluded_locations() | ||||
|  | ||||
|         if self.multiworld.Goal[self.player] == "lucky_emblem_hunt": | ||||
|             self.luckyemblemamount = self.multiworld.LuckyEmblemsAmount[self.player].value | ||||
|             self.luckyemblemrequired = self.multiworld.LuckyEmblemsRequired[self.player].value | ||||
|         if self.options.Goal not in ["hitlist", "three_proofs"]: | ||||
|             self.lucky_emblem_amount = self.options.LuckyEmblemsAmount.value | ||||
|             self.lucky_emblem_required = self.options.LuckyEmblemsRequired.value | ||||
|             self.emblem_verify() | ||||
|  | ||||
|         # hitlist | ||||
|         elif self.multiworld.Goal[self.player] == "hitlist": | ||||
|             self.RandomSuperBoss.extend(exclusion_table["Hitlist"]) | ||||
|             self.BountiesAmount = self.multiworld.BountyAmount[self.player].value | ||||
|             self.BountiesRequired = self.multiworld.BountyRequired[self.player].value | ||||
|         if self.options.Goal not in ["lucky_emblem_hunt", "three_proofs"]: | ||||
|             self.random_super_boss_list.extend(exclusion_table["Hitlist"]) | ||||
|             self.bounties_amount = self.options.BountyAmount.value | ||||
|             self.bounties_required = self.options.BountyRequired.value | ||||
|  | ||||
|             self.hitlist_verify() | ||||
|  | ||||
|             for bounty in range(self.BountiesAmount): | ||||
|                 randomBoss = self.multiworld.per_slot_randoms[self.player].choice(self.RandomSuperBoss) | ||||
|                 self.plando_locations[randomBoss] = ItemName.Bounty | ||||
|                 self.hitlist.append(self.location_name_to_id[randomBoss]) | ||||
|                 self.RandomSuperBoss.remove(randomBoss) | ||||
|                 self.totalLocations -= 1 | ||||
|             prio_hitlist = [location for location in self.multiworld.priority_locations[self.player].value if | ||||
|                             location in self.random_super_boss_list] | ||||
|             for bounty in range(self.options.BountyAmount.value): | ||||
|                 if prio_hitlist: | ||||
|                     random_boss = self.random.choice(prio_hitlist) | ||||
|                     prio_hitlist.remove(random_boss) | ||||
|                 else: | ||||
|                     random_boss = self.random.choice(self.random_super_boss_list) | ||||
|                 self.plando_locations[random_boss] = ItemName.Bounty | ||||
|                 self.random_super_boss_list.remove(random_boss) | ||||
|                 self.total_locations -= 1 | ||||
|  | ||||
|         self.donald_fill() | ||||
|         self.goofy_fill() | ||||
|         self.keyblade_fill() | ||||
|         self.donald_gen_early() | ||||
|         self.goofy_gen_early() | ||||
|         self.keyblade_gen_early() | ||||
|  | ||||
|         if self.multiworld.FinalXemnas[self.player]: | ||||
|             self.plando_locations[LocationName.FinalXemnas] = ItemName.Victory | ||||
|         else: | ||||
|             self.plando_locations[LocationName.FinalXemnas] = self.create_filler().name | ||||
|         self.total_locations -= 1 | ||||
|  | ||||
|         # same item placed because you can only get one of these 2 locations | ||||
|         # they are both under the same flag so the player gets both locations just one of the two items | ||||
|         random_stt_item = self.create_filler().name | ||||
|         for location in {LocationName.JunkMedal, LocationName.JunkMedal}: | ||||
|             self.plando_locations[location] = random_stt_item | ||||
|         self.level_subtraction() | ||||
|         # subtraction from final xemnas and stt | ||||
|         self.totalLocations -= 3 | ||||
|         if self.options.WeaponSlotStartHint: | ||||
|             for location in all_weapon_slot: | ||||
|                 self.multiworld.start_location_hints[self.player].value.add(location) | ||||
|  | ||||
|         if self.options.FillerItemsLocal: | ||||
|             for item in filler_items: | ||||
|                 self.multiworld.local_items[self.player].value.add(item) | ||||
|         # By imitating remote this doesn't have to be plandoded filler anymore | ||||
|         #  for location in {LocationName.JunkMedal, LocationName.JunkMedal}: | ||||
|         #    self.plando_locations[location] = random_stt_item | ||||
|         if not self.options.SummonLevelLocationToggle: | ||||
|             self.total_locations -= 6 | ||||
|  | ||||
|         self.total_locations -= self.level_subtraction() | ||||
|  | ||||
|     def pre_fill(self): | ||||
|         """ | ||||
|         Plandoing Events and Fill_Restrictive for donald,goofy and sora | ||||
|         """ | ||||
|         self.donald_pre_fill() | ||||
|         self.goofy_pre_fill() | ||||
|         self.keyblade_pre_fill() | ||||
|  | ||||
|         for location, item in self.plando_locations.items(): | ||||
|             self.multiworld.get_location(location, self.player).place_locked_item( | ||||
|                     self.create_item(item)) | ||||
|  | ||||
|     def create_regions(self): | ||||
|         location_table = setup_locations() | ||||
|         create_regions(self.multiworld, self.player, location_table) | ||||
|         connect_regions(self.multiworld, self.player) | ||||
|         """ | ||||
|         Creates the Regions and Connects them. | ||||
|         """ | ||||
|         create_regions(self) | ||||
|         connect_regions(self) | ||||
|  | ||||
|     def set_rules(self): | ||||
|         set_rules(self.multiworld, self.player) | ||||
|         """ | ||||
|         Sets the Logic for the Regions and Locations. | ||||
|         """ | ||||
|         universal_logic = Rules.KH2WorldRules(self) | ||||
|         form_logic = Rules.KH2FormRules(self) | ||||
|         fight_rules = Rules.KH2FightRules(self) | ||||
|         fight_rules.set_kh2_fight_rules() | ||||
|         universal_logic.set_kh2_rules() | ||||
|         form_logic.set_kh2_form_rules() | ||||
|  | ||||
|     def generate_output(self, output_directory: str): | ||||
|         """ | ||||
|         Generates the .zip for OpenKH (The KH Mod Manager) | ||||
|         """ | ||||
|         patch_kh2(self, output_directory) | ||||
|  | ||||
|     def donald_fill(self): | ||||
|         for item in DonaldAbility_Table: | ||||
|             data = self.item_quantity_dict[item] | ||||
|             for _ in range(data): | ||||
|                 self.donald_ability_pool.append(item) | ||||
|             self.item_quantity_dict[item] = 0 | ||||
|             # 32 is the amount of donald abilities | ||||
|         while len(self.donald_ability_pool) < 32: | ||||
|             self.donald_ability_pool.append( | ||||
|                     self.multiworld.per_slot_randoms[self.player].choice(self.donald_ability_pool)) | ||||
|             # Placing Donald Abilities on donald locations | ||||
|         for donaldLocation in Locations.Donald_Checks.keys(): | ||||
|             random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.donald_ability_pool) | ||||
|             self.plando_locations[donaldLocation] = random_ability | ||||
|             self.totalLocations -= 1 | ||||
|             self.donald_ability_pool.remove(random_ability) | ||||
|  | ||||
|     def goofy_fill(self): | ||||
|         for item in GoofyAbility_Table.keys(): | ||||
|             data = self.item_quantity_dict[item] | ||||
|             for _ in range(data): | ||||
|                 self.goofy_ability_pool.append(item) | ||||
|             self.item_quantity_dict[item] = 0 | ||||
|             # 32 is the amount of goofy abilities | ||||
|         while len(self.goofy_ability_pool) < 33: | ||||
|             self.goofy_ability_pool.append( | ||||
|                     self.multiworld.per_slot_randoms[self.player].choice(self.goofy_ability_pool)) | ||||
|         # Placing Goofy Abilities on goofy locations | ||||
|         for goofyLocation in Locations.Goofy_Checks.keys(): | ||||
|             random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.goofy_ability_pool) | ||||
|             self.plando_locations[goofyLocation] = random_ability | ||||
|             self.totalLocations -= 1 | ||||
|             self.goofy_ability_pool.remove(random_ability) | ||||
|  | ||||
|     def keyblade_fill(self): | ||||
|         if self.multiworld.KeybladeAbilities[self.player] == "support": | ||||
|             self.sora_keyblade_ability_pool = { | ||||
|                 **{item: data for item, data in self.item_quantity_dict.items() if item in SupportAbility_Table}, | ||||
|                 **{ItemName.NegativeCombo: 1, ItemName.AirComboPlus: 1, ItemName.ComboPlus: 1, | ||||
|                    ItemName.FinishingPlus: 1}} | ||||
|  | ||||
|         elif self.multiworld.KeybladeAbilities[self.player] == "action": | ||||
|             self.sora_keyblade_ability_pool = {item: data for item, data in self.item_quantity_dict.items() if | ||||
|                                                item in ActionAbility_Table} | ||||
|             # there are too little action abilities so 2 random support abilities are placed | ||||
|             for _ in range(3): | ||||
|                 randomSupportAbility = self.multiworld.per_slot_randoms[self.player].choice( | ||||
|                         list(SupportAbility_Table.keys())) | ||||
|                 while randomSupportAbility in self.sora_keyblade_ability_pool: | ||||
|                     randomSupportAbility = self.multiworld.per_slot_randoms[self.player].choice( | ||||
|                             list(SupportAbility_Table.keys())) | ||||
|                 self.sora_keyblade_ability_pool[randomSupportAbility] = 1 | ||||
|         else: | ||||
|             # both action and support on keyblades. | ||||
|             # TODO: make option to just exclude scom | ||||
|             self.sora_keyblade_ability_pool = { | ||||
|                 **{item: data for item, data in self.item_quantity_dict.items() if item in SupportAbility_Table}, | ||||
|                 **{item: data for item, data in self.item_quantity_dict.items() if item in ActionAbility_Table}, | ||||
|                 **{ItemName.NegativeCombo: 1, ItemName.AirComboPlus: 1, ItemName.ComboPlus: 1, | ||||
|                    ItemName.FinishingPlus: 1}} | ||||
|  | ||||
|         for ability in self.multiworld.BlacklistKeyblade[self.player].value: | ||||
|             if ability in self.sora_keyblade_ability_pool: | ||||
|                 self.sora_keyblade_ability_pool.pop(ability) | ||||
|  | ||||
|         # magic number for amount of keyblades | ||||
|         if sum(self.sora_keyblade_ability_pool.values()) < 28: | ||||
|             raise Exception( | ||||
|                     f"{self.multiworld.get_file_safe_player_name(self.player)} has too little Keyblade Abilities in the Keyblade Pool") | ||||
|  | ||||
|         self.valid_abilities = list(self.sora_keyblade_ability_pool.keys()) | ||||
|         #  Kingdom Key cannot have No Experience so plandoed here instead of checking 26 times if its kingdom key | ||||
|         random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities) | ||||
|         while random_ability == ItemName.NoExperience: | ||||
|             random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities) | ||||
|         self.plando_locations[LocationName.KingdomKeySlot] = random_ability | ||||
|         self.item_quantity_dict[random_ability] -= 1 | ||||
|         self.sora_keyblade_ability_pool[random_ability] -= 1 | ||||
|         if self.sora_keyblade_ability_pool[random_ability] == 0: | ||||
|             self.valid_abilities.remove(random_ability) | ||||
|             self.sora_keyblade_ability_pool.pop(random_ability) | ||||
|  | ||||
|         # plando keyblades because they can only have abilities | ||||
|         for keyblade in self.keyblade_slot_copy: | ||||
|             random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.valid_abilities) | ||||
|             self.plando_locations[keyblade] = random_ability | ||||
|     def donald_gen_early(self): | ||||
|         random_prog_ability = self.random.choice([ItemName.Fantasia, ItemName.FlareForce]) | ||||
|         donald_master_ability = [donald_ability for donald_ability in DonaldAbility_Table.keys() for _ in | ||||
|                                  range(self.item_quantity_dict[donald_ability]) if | ||||
|                                  donald_ability != random_prog_ability] | ||||
|         self.donald_weapon_abilities = [] | ||||
|         self.donald_get_bonus_abilities = [] | ||||
|         # fill goofy weapons first | ||||
|         for _ in range(15): | ||||
|             random_ability = self.random.choice(donald_master_ability) | ||||
|             donald_master_ability.remove(random_ability) | ||||
|             self.donald_weapon_abilities += [self.create_item(random_ability)] | ||||
|             self.item_quantity_dict[random_ability] -= 1 | ||||
|             self.sora_keyblade_ability_pool[random_ability] -= 1 | ||||
|             if self.sora_keyblade_ability_pool[random_ability] == 0: | ||||
|                 self.valid_abilities.remove(random_ability) | ||||
|                 self.sora_keyblade_ability_pool.pop(random_ability) | ||||
|             self.totalLocations -= 1 | ||||
|             self.total_locations -= 1 | ||||
|         self.slot_data_donald_weapon = [item_name.name for item_name in self.donald_weapon_abilities] | ||||
|         if not self.multiworld.DonaldGoofyStatsanity[self.player]: | ||||
|             # pre plando donald get bonuses | ||||
|             self.donald_get_bonus_abilities += [self.create_item(random_prog_ability)] | ||||
|             self.total_locations -= 1 | ||||
|             for item_name in donald_master_ability: | ||||
|                 self.donald_get_bonus_abilities += [self.create_item(item_name)] | ||||
|                 self.item_quantity_dict[item_name] -= 1 | ||||
|                 self.total_locations -= 1 | ||||
|  | ||||
|     def goofy_gen_early(self): | ||||
|         random_prog_ability = self.random.choice([ItemName.Teamwork, ItemName.TornadoFusion]) | ||||
|         goofy_master_ability = [goofy_ability for goofy_ability in GoofyAbility_Table.keys() for _ in | ||||
|                                 range(self.item_quantity_dict[goofy_ability]) if goofy_ability != random_prog_ability] | ||||
|         self.goofy_weapon_abilities = [] | ||||
|         self.goofy_get_bonus_abilities = [] | ||||
|         # fill goofy weapons first | ||||
|         for _ in range(15): | ||||
|             random_ability = self.random.choice(goofy_master_ability) | ||||
|             goofy_master_ability.remove(random_ability) | ||||
|             self.goofy_weapon_abilities += [self.create_item(random_ability)] | ||||
|             self.item_quantity_dict[random_ability] -= 1 | ||||
|             self.total_locations -= 1 | ||||
|  | ||||
|         self.slot_data_goofy_weapon = [item_name.name for item_name in self.goofy_weapon_abilities] | ||||
|  | ||||
|         if not self.options.DonaldGoofyStatsanity: | ||||
|             # pre plando goofy get bonuses | ||||
|             self.goofy_get_bonus_abilities += [self.create_item(random_prog_ability)] | ||||
|             self.total_locations -= 1 | ||||
|             for item_name in goofy_master_ability: | ||||
|                 self.goofy_get_bonus_abilities += [self.create_item(item_name)] | ||||
|                 self.item_quantity_dict[item_name] -= 1 | ||||
|                 self.total_locations -= 1 | ||||
|  | ||||
|     def keyblade_gen_early(self): | ||||
|         keyblade_master_ability = [ability for ability in SupportAbility_Table.keys() if ability not in progression_set | ||||
|                                    for _ in range(self.item_quantity_dict[ability])] | ||||
|         self.keyblade_ability_pool = [] | ||||
|  | ||||
|         for _ in range(len(Keyblade_Slots)): | ||||
|             random_ability = self.random.choice(keyblade_master_ability) | ||||
|             keyblade_master_ability.remove(random_ability) | ||||
|             self.keyblade_ability_pool += [self.create_item(random_ability)] | ||||
|             self.item_quantity_dict[random_ability] -= 1 | ||||
|             self.total_locations -= 1 | ||||
|         self.slot_data_sora_weapon = [item_name.name for item_name in self.keyblade_ability_pool] | ||||
|  | ||||
|     def goofy_pre_fill(self): | ||||
|         """ | ||||
|         Removes donald locations from the location pool maps random donald items to be plandoded. | ||||
|         """ | ||||
|         goofy_weapon_location_list = [self.multiworld.get_location(location, self.player) for location in | ||||
|                                       Goofy_Checks.keys() if Goofy_Checks[location].yml == "Keyblade"] | ||||
|         # take one of the 2 out | ||||
|         # randomize the list with only | ||||
|         for location in goofy_weapon_location_list: | ||||
|             random_ability = self.random.choice(self.goofy_weapon_abilities) | ||||
|             location.place_locked_item(random_ability) | ||||
|             self.goofy_weapon_abilities.remove(random_ability) | ||||
|  | ||||
|         if not self.multiworld.DonaldGoofyStatsanity[self.player]: | ||||
|             # plando goofy get bonuses | ||||
|             goofy_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in | ||||
|                                              Goofy_Checks.keys() if Goofy_Checks[location].yml != "Keyblade"] | ||||
|             for location in goofy_get_bonus_location_pool: | ||||
|                 self.random.choice(self.goofy_get_bonus_abilities) | ||||
|                 random_ability = self.random.choice(self.goofy_get_bonus_abilities) | ||||
|                 location.place_locked_item(random_ability) | ||||
|                 self.goofy_get_bonus_abilities.remove(random_ability) | ||||
|  | ||||
|     def donald_pre_fill(self): | ||||
|         donald_weapon_location_list = [self.multiworld.get_location(location, self.player) for location in | ||||
|                                        Donald_Checks.keys() if Donald_Checks[location].yml == "Keyblade"] | ||||
|  | ||||
|         # take one of the 2 out | ||||
|         # randomize the list with only | ||||
|         for location in donald_weapon_location_list: | ||||
|             random_ability = self.random.choice(self.donald_weapon_abilities) | ||||
|             location.place_locked_item(random_ability) | ||||
|             self.donald_weapon_abilities.remove(random_ability) | ||||
|  | ||||
|         if not self.multiworld.DonaldGoofyStatsanity[self.player]: | ||||
|             # plando goofy get bonuses | ||||
|             donald_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in | ||||
|                                               Donald_Checks.keys() if Donald_Checks[location].yml != "Keyblade"] | ||||
|             for location in donald_get_bonus_location_pool: | ||||
|                 random_ability = self.random.choice(self.donald_get_bonus_abilities) | ||||
|                 location.place_locked_item(random_ability) | ||||
|                 self.donald_get_bonus_abilities.remove(random_ability) | ||||
|  | ||||
|     def keyblade_pre_fill(self): | ||||
|         """ | ||||
|         Fills keyblade slots with abilities determined on player's setting | ||||
|         """ | ||||
|         keyblade_locations = [self.multiworld.get_location(location, self.player) for location in Keyblade_Slots.keys()] | ||||
|         state = self.multiworld.get_all_state(False) | ||||
|         keyblade_ability_pool_copy = self.keyblade_ability_pool.copy() | ||||
|         fill_restrictive(self.multiworld, state, keyblade_locations, keyblade_ability_pool_copy, True, True) | ||||
|  | ||||
|     def starting_invo_verify(self): | ||||
|         """ | ||||
|         Making sure the player doesn't put too many abilities in their starting inventory. | ||||
|         """ | ||||
|         for item, value in self.multiworld.start_inventory[self.player].value.items(): | ||||
|             if item in ActionAbility_Table \ | ||||
|                     or item in SupportAbility_Table or exclusionItem_table["StatUps"] \ | ||||
|                     or item in SupportAbility_Table or exclusion_item_table["StatUps"] \ | ||||
|                     or item in DonaldAbility_Table or item in GoofyAbility_Table: | ||||
|                 # cannot have more than the quantity for abilties | ||||
|                 if value > item_dictionary_table[item].quantity: | ||||
| @@ -324,78 +437,100 @@ class KH2World(World): | ||||
|                 self.item_quantity_dict[item] -= value | ||||
|  | ||||
|     def emblem_verify(self): | ||||
|         if self.luckyemblemamount < self.luckyemblemrequired: | ||||
|         """ | ||||
|         Making sure lucky emblems have amount>=required. | ||||
|         """ | ||||
|         if self.lucky_emblem_amount < self.lucky_emblem_required: | ||||
|             logging.info( | ||||
|                     f"Lucky Emblem Amount {self.multiworld.LuckyEmblemsAmount[self.player].value} is less than required " | ||||
|                     f"{self.multiworld.LuckyEmblemsRequired[self.player].value} for player {self.multiworld.get_file_safe_player_name(self.player)}." | ||||
|                     f" Setting amount to {self.multiworld.LuckyEmblemsRequired[self.player].value}") | ||||
|             luckyemblemamount = max(self.luckyemblemamount, self.luckyemblemrequired) | ||||
|             self.multiworld.LuckyEmblemsAmount[self.player].value = luckyemblemamount | ||||
|                     f"Lucky Emblem Amount {self.options.LuckyEmblemsAmount.value} is less than required " | ||||
|                     f"{self.options.LuckyEmblemsRequired.value} for player {self.multiworld.get_file_safe_player_name(self.player)}." | ||||
|                     f" Setting amount to {self.options.LuckyEmblemsRequired.value}") | ||||
|             luckyemblemamount = max(self.lucky_emblem_amount, self.lucky_emblem_required) | ||||
|             self.options.LuckyEmblemsAmount.value = luckyemblemamount | ||||
|  | ||||
|         self.item_quantity_dict[ItemName.LuckyEmblem] = self.multiworld.LuckyEmblemsAmount[self.player].value | ||||
|         self.item_quantity_dict[ItemName.LuckyEmblem] = self.options.LuckyEmblemsAmount.value | ||||
|         # give this proof to unlock the final door once the player has the amount of lucky emblem required | ||||
|         self.item_quantity_dict[ItemName.ProofofNonexistence] = 0 | ||||
|         if ItemName.ProofofNonexistence in self.item_quantity_dict: | ||||
|             del self.item_quantity_dict[ItemName.ProofofNonexistence] | ||||
|  | ||||
|     def hitlist_verify(self): | ||||
|         """ | ||||
|         Making sure hitlist have amount>=required. | ||||
|         """ | ||||
|         for location in self.multiworld.exclude_locations[self.player].value: | ||||
|             if location in self.RandomSuperBoss: | ||||
|                 self.RandomSuperBoss.remove(location) | ||||
|             if location in self.random_super_boss_list: | ||||
|                 self.random_super_boss_list.remove(location) | ||||
|  | ||||
|         if not self.options.SummonLevelLocationToggle: | ||||
|             self.random_super_boss_list.remove(LocationName.Summonlvl7) | ||||
|  | ||||
|         #  Testing if the player has the right amount of Bounties for Completion. | ||||
|         if len(self.RandomSuperBoss) < self.BountiesAmount: | ||||
|         if len(self.random_super_boss_list) < self.bounties_amount: | ||||
|             logging.info( | ||||
|                     f"{self.multiworld.get_file_safe_player_name(self.player)} has more bounties than bosses." | ||||
|                     f" Setting total bounties to {len(self.RandomSuperBoss)}") | ||||
|             self.BountiesAmount = len(self.RandomSuperBoss) | ||||
|             self.multiworld.BountyAmount[self.player].value = self.BountiesAmount | ||||
|                     f" Setting total bounties to {len(self.random_super_boss_list)}") | ||||
|             self.bounties_amount = len(self.random_super_boss_list) | ||||
|             self.options.BountyAmount.value = self.bounties_amount | ||||
|  | ||||
|         if len(self.RandomSuperBoss) < self.BountiesRequired: | ||||
|         if len(self.random_super_boss_list) < self.bounties_required: | ||||
|             logging.info(f"{self.multiworld.get_file_safe_player_name(self.player)} has too many required bounties." | ||||
|                          f" Setting required bounties to {len(self.RandomSuperBoss)}") | ||||
|             self.BountiesRequired = len(self.RandomSuperBoss) | ||||
|             self.multiworld.BountyRequired[self.player].value = self.BountiesRequired | ||||
|                          f" Setting required bounties to {len(self.random_super_boss_list)}") | ||||
|             self.bounties_required = len(self.random_super_boss_list) | ||||
|             self.options.BountyRequired.value = self.bounties_required | ||||
|  | ||||
|         if self.BountiesAmount < self.BountiesRequired: | ||||
|             logging.info(f"Bounties Amount {self.multiworld.BountyAmount[self.player].value} is less than required " | ||||
|                          f"{self.multiworld.BountyRequired[self.player].value} for player {self.multiworld.get_file_safe_player_name(self.player)}." | ||||
|                          f" Setting amount to {self.multiworld.BountyRequired[self.player].value}") | ||||
|             self.BountiesAmount = max(self.BountiesAmount, self.BountiesRequired) | ||||
|             self.multiworld.BountyAmount[self.player].value = self.BountiesAmount | ||||
|         if self.bounties_amount < self.bounties_required: | ||||
|             logging.info( | ||||
|                     f"Bounties Amount is less than required for player {self.multiworld.get_file_safe_player_name(self.player)}." | ||||
|                     f" Swapping Amount and Required") | ||||
|             temp = self.options.BountyRequired.value | ||||
|             self.options.BountyRequired.value = self.options.BountyAmount.value | ||||
|             self.options.BountyAmount.value = temp | ||||
|  | ||||
|         self.multiworld.start_hints[self.player].value.add(ItemName.Bounty) | ||||
|         self.item_quantity_dict[ItemName.ProofofNonexistence] = 0 | ||||
|         if self.options.BountyStartingHintToggle: | ||||
|             self.multiworld.start_hints[self.player].value.add(ItemName.Bounty) | ||||
|  | ||||
|         if ItemName.ProofofNonexistence in self.item_quantity_dict: | ||||
|             del self.item_quantity_dict[ItemName.ProofofNonexistence] | ||||
|  | ||||
|     def set_excluded_locations(self): | ||||
|         """ | ||||
|         Fills excluded_locations from player's settings. | ||||
|         """ | ||||
|         # Option to turn off all superbosses. Can do this individually but its like 20+ checks | ||||
|         if not self.multiworld.SuperBosses[self.player] and not self.multiworld.Goal[self.player] == "hitlist": | ||||
|             for superboss in exclusion_table["Datas"]: | ||||
|                 self.multiworld.exclude_locations[self.player].value.add(superboss) | ||||
|         if not self.options.SuperBosses: | ||||
|             for superboss in exclusion_table["SuperBosses"]: | ||||
|                 self.multiworld.exclude_locations[self.player].value.add(superboss) | ||||
|  | ||||
|         # Option to turn off Olympus Colosseum Cups. | ||||
|         if self.multiworld.Cups[self.player] == "no_cups": | ||||
|         if self.options.Cups == "no_cups": | ||||
|             for cup in exclusion_table["Cups"]: | ||||
|                 self.multiworld.exclude_locations[self.player].value.add(cup) | ||||
|         # exclude only hades paradox. If cups and hades paradox then nothing is excluded | ||||
|         elif self.multiworld.Cups[self.player] == "cups": | ||||
|         elif self.options.Cups == "cups": | ||||
|             self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups) | ||||
|  | ||||
|         if not self.options.AtlanticaToggle: | ||||
|             for loc in exclusion_table["Atlantica"]: | ||||
|                 self.multiworld.exclude_locations[self.player].value.add(loc) | ||||
|  | ||||
|     def level_subtraction(self): | ||||
|         # there are levels but level 1 is there for the yamls | ||||
|         if self.multiworld.LevelDepth[self.player] == "level_99_sanity": | ||||
|             # level 99 sanity | ||||
|             self.totalLocations -= 1 | ||||
|         elif self.multiworld.LevelDepth[self.player] == "level_50_sanity": | ||||
|         """ | ||||
|        Determine how many locations are on sora's levels. | ||||
|        """ | ||||
|         if self.options.LevelDepth == "level_50_sanity": | ||||
|             # level 50 sanity | ||||
|             self.totalLocations -= 50 | ||||
|         elif self.multiworld.LevelDepth[self.player] == "level_1": | ||||
|             return 49 | ||||
|         elif self.options.LevelDepth == "level_1": | ||||
|             # level 1. No checks on levels | ||||
|             self.totalLocations -= 99 | ||||
|             return 98 | ||||
|         elif self.options.LevelDepth in ["level_50", "level_99"]: | ||||
|             # could be if leveldepth!= 99 sanity but this reads better imo | ||||
|             return 75 | ||||
|         else: | ||||
|             # level 50/99 since they contain the same amount of levels | ||||
|             self.totalLocations -= 76 | ||||
|             return 0 | ||||
|  | ||||
|     def get_filler_item_name(self) -> str: | ||||
|         return self.multiworld.random.choice( | ||||
|                 [ItemName.PowerBoost, ItemName.MagicBoost, ItemName.DefenseBoost, ItemName.APBoost]) | ||||
|         """ | ||||
|         Returns random filler item name. | ||||
|         """ | ||||
|         return self.random.choice(filler_items) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 JaredWeakStrike
					JaredWeakStrike