KH2: init cleanup and random visit locking fix and docs update. (#1652)

Random Visit Locking wasn't copying correctly
init cleanup and moved itempool population to create_items
Updated docs due to a lot of people having issues setting it up.
This commit is contained in:
JaredWeakStrike
2023-04-09 22:12:23 -04:00
committed by GitHub
parent 6059b5ef66
commit 81411a191c
6 changed files with 320 additions and 286 deletions

View File

@@ -1,4 +1,3 @@
from BaseClasses import Tutorial, ItemClassification
import logging
@@ -41,6 +40,10 @@ class KH2World(World):
def __init__(self, multiworld: "MultiWorld", player: int):
super().__init__(multiworld, player)
self.visitlocking_dict = None
self.plando_locations = None
self.luckyemblemamount = None
self.luckyemblemrequired = None
self.BountiesRequired = None
self.BountiesAmount = None
self.hitlist = None
@@ -57,15 +60,14 @@ class KH2World(World):
self.growth_list = list()
for x in range(4):
self.growth_list.extend(Movement_Table.keys())
self.visitlocking_dict = Progression_Dicts["AllVisitLocking"]
def fill_slot_data(self) -> dict:
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}
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}
def create_item(self, name: str, ) -> Item:
data = item_dictionary_table[name]
@@ -78,185 +80,10 @@ class KH2World(World):
return created_item
def generate_early(self) -> None:
# Item Quantity dict because Abilities can be a problem for KH2's Software.
for item, data in item_dictionary_table.items():
self.item_quantity_dict[item] = data.quantity
if self.multiworld.KeybladeAbilities[self.player] == "support":
self.sora_keyblade_ability_pool.extend(SupportAbility_Table.keys())
elif self.multiworld.KeybladeAbilities[self.player] == "action":
self.sora_keyblade_ability_pool.extend(ActionAbility_Table.keys())
else:
# both action and support on keyblades.
# TODO: make option to just exclude scom
self.sora_keyblade_ability_pool.extend(ActionAbility_Table.keys())
self.sora_keyblade_ability_pool.extend(SupportAbility_Table.keys())
for item, value in self.multiworld.start_inventory[self.player].value.items():
if item in ActionAbility_Table.keys() or item in SupportAbility_Table.keys() or exclusionItem_table["StatUps"]:
# cannot have more than the quantity for abilties
if value > item_dictionary_table[item].quantity:
logging.info(f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}"
f"Changing the amount to the max amount")
value = item_dictionary_table[item].quantity
self.item_quantity_dict[item] -= value
# Option to turn off Promise Charm Item
if not self.multiworld.Promise_Charm[self.player]:
self.item_quantity_dict[ItemName.PromiseCharm] = 0
for ability in self.multiworld.BlacklistKeyblade[self.player].value:
if ability in self.sora_keyblade_ability_pool:
self.sora_keyblade_ability_pool.remove(ability)
# 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)
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":
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":
self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups)
if self.multiworld.Goal[self.player] == "lucky_emblem_hunt":
luckyemblemamount = self.multiworld.LuckyEmblemsAmount[self.player].value
luckyemblemrequired = self.multiworld.LuckyEmblemsRequired[self.player].value
if luckyemblemamount < luckyemblemrequired:
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(luckyemblemamount, luckyemblemrequired)
self.multiworld.LuckyEmblemsAmount[self.player].value = luckyemblemamount
self.item_quantity_dict[ItemName.LuckyEmblem] = item_dictionary_table[ItemName.LuckyEmblem].quantity + luckyemblemamount
# 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
# 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
for location in self.multiworld.exclude_locations[self.player].value:
if location in self.RandomSuperBoss:
self.RandomSuperBoss.remove(location)
# Testing if the player has the right amount of Bounties for Completion.
if len(self.RandomSuperBoss) < self.BountiesAmount:
logging.info(f"{self.multiworld.get_file_safe_player_name(self.player)} has too many bounties than bosses."
f" Setting total bounties to {len(self.RandomSuperBoss)}")
self.BountiesAmount = len(self.RandomSuperBoss)
self.multiworld.BountyAmount[self.player].value = self.BountiesAmount
if len(self.RandomSuperBoss) < self.BountiesRequired:
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
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
self.multiworld.start_hints[self.player].value.add(ItemName.Bounty)
self.item_quantity_dict[ItemName.ProofofNonexistence] = 0
while len(self.sora_keyblade_ability_pool) < len(self.keyblade_slot_copy):
self.sora_keyblade_ability_pool.append(
self.multiworld.per_slot_randoms[self.player].choice(list(SupportAbility_Table.keys())))
for item in DonaldAbility_Table.keys():
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))
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))
def generate_basic(self):
itempool: typing.List[KH2Item] = []
self.hitlist = list()
self.filler_items.extend(item_groups["Filler"])
if self.multiworld.FinalXemnas[self.player]:
self.multiworld.get_location(LocationName.FinalXemnas, self.player).place_locked_item(
self.create_item(ItemName.Victory))
else:
self.multiworld.get_location(LocationName.FinalXemnas, self.player).place_locked_item(
self.create_item(self.multiworld.per_slot_randoms[self.player].choice(self.filler_items)))
self.totalLocations -= 1
if self.multiworld.Goal[self.player] == "hitlist":
for bounty in range(self.BountiesAmount):
randomBoss = self.multiworld.per_slot_randoms[self.player].choice(self.RandomSuperBoss)
self.multiworld.get_location(randomBoss, self.player).place_locked_item(
self.create_item(ItemName.Bounty))
self.hitlist.append(self.location_name_to_id[randomBoss])
self.RandomSuperBoss.remove(randomBoss)
self.totalLocations -= 1
# 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.sora_keyblade_ability_pool)
while random_ability == ItemName.NoExperience:
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.sora_keyblade_ability_pool)
self.multiworld.get_location(LocationName.KingdomKeySlot, self.player).place_locked_item(self.create_item(random_ability))
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool.remove(random_ability)
self.totalLocations -= 1
# 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.sora_keyblade_ability_pool)
self.multiworld.get_location(keyblade, self.player).place_locked_item(self.create_item(random_ability))
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool.remove(random_ability)
self.totalLocations -= 1
# 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.multiworld.get_location(donaldLocation, self.player).place_locked_item(
self.create_item(random_ability))
self.totalLocations -= 1
self.donald_ability_pool.remove(random_ability)
# 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.multiworld.get_location(goofyLocation, self.player).place_locked_item(self.create_item(random_ability))
self.totalLocations -= 1
self.goofy_ability_pool.remove(random_ability)
# 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.multiworld.per_slot_randoms[self.player].choice(self.filler_items)
self.multiworld.get_location(LocationName.JunkChampionBelt, self.player).place_locked_item(
self.create_item(random_stt_item))
self.multiworld.get_location(LocationName.JunkMedal, self.player).place_locked_item(
self.create_item(random_stt_item))
self.totalLocations -= 2
def create_items(self) -> None:
itempool: typing.List[Item] = []
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,
@@ -273,17 +100,15 @@ class KH2World(World):
self.growth_list.remove(random_growth)
self.multiworld.push_precollected(self.create_item(random_growth))
# no visit locking
if self.multiworld.Visitlocking[self.player] == "no_visit_locking":
for item, amount in Progression_Dicts["AllVisitLocking"].items():
for _ in range(amount):
self.multiworld.push_precollected(self.create_item(item))
self.item_quantity_dict[item] -= 1
self.visitlocking_dict[item] -= 1
if self.visitlocking_dict[item] == 0:
self.visitlocking_dict.pop(item)
self.visitlocking_dict[item] -= 1
# first and second visit locking
elif self.multiworld.Visitlocking[self.player] == "second_visit_locking":
for item in Progression_Dicts["2VisitLocking"]:
self.item_quantity_dict[item] -= 1
@@ -303,7 +128,227 @@ class KH2World(World):
self.visitlocking_dict.pop(item)
self.multiworld.push_precollected(self.create_item(item))
# there are levels but level 1 is there to keep code clean
for item in item_dictionary_table:
data = self.item_quantity_dict[item]
itempool += [self.create_item(item) for _ in range(data)]
# Creating filler for unfilled locations
itempool += [self.create_filler()
for _ in range(self.totalLocations-len(itempool))]
self.multiworld.itempool += itempool
def generate_early(self) -> None:
# Item Quantity dict because Abilities can be a problem for KH2's Software.
for item, data in item_dictionary_table.items():
self.item_quantity_dict[item] = data.quantity
# Dictionary to mark locations with their plandoed item
# Example. Final Xemnas: Victory
self.plando_locations = dict()
self.hitlist = []
self.starting_invo_verify()
# Option to turn off Promise Charm Item
if not self.multiworld.Promise_Charm[self.player]:
self.item_quantity_dict[ItemName.PromiseCharm] = 0
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
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
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
self.donald_fill()
self.goofy_fill()
self.keyblade_fill()
if self.multiworld.FinalXemnas[self.player]:
self.plando_locations[LocationName.FinalXemnas] = ItemName.Victory
else:
self.plando_locations[LocationName.FinalXemnas] = self.create_filler().name
# 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
def pre_fill(self):
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)
def set_rules(self):
set_rules(self.multiworld, self.player)
def generate_output(self, output_directory: str):
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.extend(SupportAbility_Table.keys())
elif self.multiworld.KeybladeAbilities[self.player] == "action":
self.sora_keyblade_ability_pool.extend(ActionAbility_Table.keys())
else:
# both action and support on keyblades.
# TODO: make option to just exclude scom
self.sora_keyblade_ability_pool.extend(ActionAbility_Table.keys())
self.sora_keyblade_ability_pool.extend(SupportAbility_Table.keys())
for ability in self.multiworld.BlacklistKeyblade[self.player].value:
if ability in self.sora_keyblade_ability_pool:
self.sora_keyblade_ability_pool.remove(ability)
while len(self.sora_keyblade_ability_pool) < len(self.keyblade_slot_copy):
self.sora_keyblade_ability_pool.append(
self.multiworld.per_slot_randoms[self.player].choice(list(SupportAbility_Table.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.sora_keyblade_ability_pool)
while random_ability == ItemName.NoExperience:
random_ability = self.multiworld.per_slot_randoms[self.player].choice(self.sora_keyblade_ability_pool)
self.plando_locations[LocationName.KingdomKeySlot] = random_ability
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool.remove(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.sora_keyblade_ability_pool)
self.plando_locations[keyblade] = random_ability
self.item_quantity_dict[random_ability] -= 1
self.sora_keyblade_ability_pool.remove(random_ability)
self.totalLocations -= 1
def starting_invo_verify(self):
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"]:
# cannot have more than the quantity for abilties
if value > item_dictionary_table[item].quantity:
logging.info(
f"{self.multiworld.get_file_safe_player_name(self.player)} cannot have more than {item_dictionary_table[item].quantity} of {item}"
f"Changing the amount to the max amount")
value = item_dictionary_table[item].quantity
self.item_quantity_dict[item] -= value
def emblem_verify(self):
if self.luckyemblemamount < self.luckyemblemrequired:
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
self.item_quantity_dict[ItemName.LuckyEmblem] = item_dictionary_table[
ItemName.LuckyEmblem].quantity + self.luckyemblemamount
# 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
def hitlist_verify(self):
for location in self.multiworld.exclude_locations[self.player].value:
if location in self.RandomSuperBoss:
self.RandomSuperBoss.remove(location)
# Testing if the player has the right amount of Bounties for Completion.
if len(self.RandomSuperBoss) < self.BountiesAmount:
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
if len(self.RandomSuperBoss) < self.BountiesRequired:
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
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
self.multiworld.start_hints[self.player].value.add(ItemName.Bounty)
self.item_quantity_dict[ItemName.ProofofNonexistence] = 0
def set_excluded_locations(self):
# 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)
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":
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":
self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups)
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
@@ -317,24 +362,5 @@ class KH2World(World):
# level 50/99 since they contain the same amount of levels
self.totalLocations -= 76
for item in item_dictionary_table:
data = self.item_quantity_dict[item]
for _ in range(data):
itempool.append(self.create_item(item))
# Creating filler for unfilled locations
while len(itempool) < self.totalLocations:
item = self.multiworld.per_slot_randoms[self.player].choice(self.filler_items)
itempool += [self.create_item(item)]
self.multiworld.itempool += itempool
def create_regions(self):
location_table = setup_locations()
create_regions(self.multiworld, self.player, location_table)
connect_regions(self.multiworld, self.player)
def set_rules(self):
set_rules(self.multiworld, self.player)
def generate_output(self, output_directory: str):
patch_kh2(self, output_directory)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice([ItemName.PowerBoost, ItemName.MagicBoost, ItemName.DefenseBoost, ItemName.APBoost])