Kingdom Hearts: Adding a bunch of new features (#5078)
* Change vanilla_emblem_pieces to randomize_emblem_pieces * Add jungle slider and starting tools options * Update option name and add preset * GICU changes * unnecessary * Update Options.py * Fix has_all * Update Options.py * Update Options.py * Some potenitial logic changes * Oops * Oops 2 * Cups choice options * typos * Logic tweaks * Ice Titan and Superboss changes * Suggested change and one more * Updating some other option descriptions for clarity/typos * Update Locations.py * commit * SYNTHESIS * commit * commit * commit * Add command to change communication path I'm not a python programmer, so do excuse the code etiquette. This aims to allow Linux users to communicate to their proton directory. * commit * commit * commit * commit * commit * commit * commit * commit * Update Client.py * Update Locations.py * Update Regions.py * commit * commit * commit * Update Rules.py * commit * commit * commit * commit logic changes and linux fix from other branch * commit * commit * Update __init__.py * Update Rules.py * commit * commit * commit * commit * add starting accessory setting * fix starting accessories bug * Update Locations.py * commit * add ap cost rando * fix some problem locations * add raft materials * Update Client.py * OK WORK THIS TIME PLEASE * Corrected typos * setting up for logic difficulty * commit 1 * commit 2 * commit 3 * minor error fix * some logic changes and fixed some typos * tweaks * commit * SYNTHESIS * commit * commit * commit * commit * commit * commit * commit * commit * commit * commit * commit * Update Client.py * Update Locations.py * Update Regions.py * commit * commit * commit * Update Rules.py * commit * commit * commit * commit logic changes and linux fix from other branch * commit * commit * Update __init__.py * Update Rules.py * commit * commit * commit * commit * add starting accessory setting * fix starting accessories bug * Update Locations.py * commit * add ap cost rando * fix some problem locations * add raft materials * Update Client.py * cleanup * commit 4 * tweaks 2 * tweaks 3 * Reset * Update __init__.py * Change vanilla_emblem_pieces to randomize_emblem_pieces * Add jungle slider and starting tools options * unnecessary * Vanilla Puppies Part 1 The easy part * Update __init__.py I'm not certain this is the exact right chest for Tea Party Garden, Waterfall Cavern, HT Cemetery, or Neverland Hold but logically it's the same. Will do a test run later and fix if need be * Vanilla Puppies Part 3 Wrong toggle cause I just copied over Emblem Pieces oops * Vanilla Puppies Part 4 Forgor commented out code * Vanilla Puppies Part 5 I now realize how this works and that what I had before was redundant * Update __init__.py Learning much about strings * cleanup * Update __init__.py Only missed one! * Update option name and add preset * GICU changes * Update Options.py * Fix has_all * Update Options.py * Update Options.py * Cups choice options * typos * Ice Titan and Superboss changes * Some potenitial logic changes * Oops * Oops 2 * Logic tweaks * Suggested change and one more * Updating some other option descriptions for clarity/typos * Update Locations.py * Add command to change communication path I'm not a python programmer, so do excuse the code etiquette. This aims to allow Linux users to communicate to their proton directory. * Moving over changes from REVAMP * whoops * Fix patch files on the website * Update test_goal.py * commit * Update worlds/kh1/__init__.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * change some default options * Missed a condition * let's try that * Update Options.py * unnecessary sub check * Some more cleanup * tuples * add icon * merge cleanup * merge cleanup 2 * merge clean up 3 * Update Data.py * Fix cups option * commit * Update Rules.py * Update Rules.py * phantom tweak * review commit * minor fixes * review 2 * minor typo fix * minor logic tweak * Update Client.py * Update __init__.py * Update Rules.py * Olympus Cup fixes * Update Options.py * even MORE tweaks * commit * Update Options.py * Update has_x_worlds * Update Rules.py * commit * Update Options.py * Update Options.py * Update Options.py * tweak 5 * Add Stacking Key Items and Halloween Town Key Item Bundle * Update worlds/kh1/Rules.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Update Rules.py * commit * Update worlds/kh1/__init__.py Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Update __init__.py * Update __init__.py * whoops * Update Rules.py * Update Rules.py * Fix documentation styling * Clean up option help text * Reordering options so they're consistent and fixing a logic bug when EOTW Unlock is item but door is emblems * Make have x world logic consider if the player has HAW on or not * Fix Atlantica beginner logic things, vanilla keyblade stats being broken, and some behind boss locations * Fix vanilla puppy option * hotfix for crabclaw logic * Fix defaults and some boss locations * Fix server spam * Remove 3 High Jump Item Workshop Logic, small client changes * Updates for PR --------- Co-authored-by: esutley <ecsutley@gmail.com> Co-authored-by: Goblin God <37878138+esutley@users.noreply.github.com> Co-authored-by: River Buizel <4911928+rocket0634@users.noreply.github.com> Co-authored-by: omnises <OmnisGamers@gmail.com> Co-authored-by: Omnises Nihilis <38057571+Omnises@users.noreply.github.com> Co-authored-by: Scipio Wright <scipiowright@gmail.com>
This commit is contained in:
@@ -1,23 +1,29 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import List
|
||||
from math import ceil
|
||||
|
||||
from BaseClasses import Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Items import KH1Item, KH1ItemData, event_item_table, get_items_by_category, item_table, item_name_groups
|
||||
from .Locations import KH1Location, location_table, get_locations_by_category, location_name_groups
|
||||
from .Locations import KH1Location, location_table, get_locations_by_type, location_name_groups
|
||||
from .Options import KH1Options, kh1_option_groups
|
||||
from .Regions import connect_entrances, create_regions
|
||||
from .Rules import set_rules
|
||||
from .Presets import kh1_option_presets
|
||||
from worlds.LauncherComponents import Component, components, Type, launch as launch_component
|
||||
|
||||
from worlds.LauncherComponents import Component, components, Type, launch as launch_component, icon_paths
|
||||
from .GenerateJSON import generate_json
|
||||
from .Data import VANILLA_KEYBLADE_STATS, VANILLA_PUPPY_LOCATIONS, CHAR_TO_KH, VANILLA_ABILITY_AP_COSTS, WORLD_KEY_ITEMS
|
||||
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
|
||||
|
||||
def launch_client():
|
||||
from .Client import launch
|
||||
launch_component(launch, name="KH1 Client")
|
||||
|
||||
|
||||
components.append(Component("KH1 Client", "KH1Client", func=launch_client, component_type=Type.CLIENT))
|
||||
components.append(Component("KH1 Client", "KH1Client", func=launch_client, component_type=Type.CLIENT, icon="kh1_heart"))
|
||||
|
||||
icon_paths["kh1_heart"] = f"ap:{__name__}/icons/kh1_heart.png"
|
||||
|
||||
|
||||
class KH1Web(WebWorld):
|
||||
@@ -54,6 +60,19 @@ class KH1World(World):
|
||||
fillers.update(get_items_by_category("Item"))
|
||||
fillers.update(get_items_by_category("Camping"))
|
||||
fillers.update(get_items_by_category("Stat Ups"))
|
||||
slot_2_levels: list[int]
|
||||
keyblade_stats: list[dict[str, int]]
|
||||
starting_accessory_locations: list[str]
|
||||
starting_accessories: list[str]
|
||||
ap_costs: list[dict[str, str | int | bool]]
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super(KH1World, self).__init__(multiworld, player)
|
||||
self.slot_2_levels = None
|
||||
self.keyblade_stats = None
|
||||
self.starting_accessory_locations = None
|
||||
self.starting_accessories = None
|
||||
self.ap_costs = None
|
||||
|
||||
def create_items(self):
|
||||
self.place_predetermined_items()
|
||||
@@ -63,12 +82,29 @@ class KH1World(World):
|
||||
possible_starting_worlds = ["Wonderland", "Olympus Coliseum", "Deep Jungle", "Agrabah", "Monstro", "Halloween Town", "Neverland", "Hollow Bastion"]
|
||||
if self.options.atlantica:
|
||||
possible_starting_worlds.append("Atlantica")
|
||||
if self.options.destiny_islands:
|
||||
possible_starting_worlds.append("Destiny Islands")
|
||||
if self.options.end_of_the_world_unlock == "item":
|
||||
possible_starting_worlds.append("End of the World")
|
||||
starting_worlds = self.random.sample(possible_starting_worlds, min(self.options.starting_worlds.value, len(possible_starting_worlds)))
|
||||
for starting_world in starting_worlds:
|
||||
self.multiworld.push_precollected(self.create_item(starting_world))
|
||||
|
||||
# Handle starting tools
|
||||
starting_tools = []
|
||||
if self.options.starting_tools:
|
||||
starting_tools = ["Scan", "Dodge Roll"]
|
||||
self.multiworld.push_precollected(self.create_item("Scan"))
|
||||
self.multiworld.push_precollected(self.create_item("Dodge Roll"))
|
||||
|
||||
# Handle starting party member accessories
|
||||
starting_party_member_accessories = []
|
||||
starting_party_member_locations = []
|
||||
starting_party_member_locations = self.get_starting_accessory_locations()
|
||||
starting_party_member_accessories = self.get_starting_accessories()
|
||||
for i in range(len(starting_party_member_locations)):
|
||||
self.get_location(self.starting_accessory_locations[i]).place_locked_item(self.create_item(self.starting_accessories[i]))
|
||||
|
||||
item_pool: List[KH1Item] = []
|
||||
possible_level_up_item_pool = []
|
||||
level_up_item_pool = []
|
||||
@@ -94,19 +130,26 @@ class KH1World(World):
|
||||
|
||||
# Fill remaining pool with items from other pool
|
||||
self.random.shuffle(possible_level_up_item_pool)
|
||||
level_up_item_pool = level_up_item_pool + possible_level_up_item_pool[:(100 - len(level_up_item_pool))]
|
||||
|
||||
level_up_locations = list(get_locations_by_category("Levels").keys())
|
||||
level_up_item_pool = level_up_item_pool + possible_level_up_item_pool[:(99 - len(level_up_item_pool))]
|
||||
|
||||
level_up_locations = list(get_locations_by_type("Level Slot 1").keys())
|
||||
self.random.shuffle(level_up_item_pool)
|
||||
current_level_for_placing_stats = self.options.force_stats_on_levels.value
|
||||
while len(level_up_item_pool) > 0 and current_level_for_placing_stats <= self.options.level_checks:
|
||||
self.get_location(level_up_locations[current_level_for_placing_stats - 1]).place_locked_item(self.create_item(level_up_item_pool.pop()))
|
||||
current_level_for_placing_stats += 1
|
||||
current_level_index_for_placing_stats = self.options.force_stats_on_levels.value - 2 # Level 2 is index 0, Level 3 is index 1, etc
|
||||
if self.options.remote_items.current_key == "off" and self.options.force_stats_on_levels.value != 2:
|
||||
logging.info(f"{self.player_name}'s value {self.options.force_stats_on_levels.value} for force_stats_on_levels was changed\n"
|
||||
f"Set to 2 as remote_items if \"off\"")
|
||||
self.options.force_stats_on_levels.value = 2
|
||||
current_level_index_for_placing_stats = 0
|
||||
while len(level_up_item_pool) > 0 and current_level_index_for_placing_stats < self.options.level_checks: # With all levels in location pool, 99 level ups so need to go index 0-98
|
||||
self.get_location(level_up_locations[current_level_index_for_placing_stats]).place_locked_item(self.create_item(level_up_item_pool.pop()))
|
||||
current_level_index_for_placing_stats += 1
|
||||
|
||||
|
||||
|
||||
# Calculate prefilled locations and items
|
||||
prefilled_items = []
|
||||
if self.options.vanilla_emblem_pieces:
|
||||
prefilled_items = prefilled_items + ["Emblem Piece (Flame)", "Emblem Piece (Chest)", "Emblem Piece (Fountain)", "Emblem Piece (Statue)"]
|
||||
exclude_items = ["Final Door Key", "Lucky Emblem"]
|
||||
if not self.options.randomize_emblem_pieces:
|
||||
exclude_items = exclude_items + ["Emblem Piece (Flame)", "Emblem Piece (Chest)", "Emblem Piece (Fountain)", "Emblem Piece (Statue)"]
|
||||
|
||||
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
|
||||
|
||||
@@ -117,27 +160,29 @@ class KH1World(World):
|
||||
quantity = data.max_quantity
|
||||
if data.category not in non_filler_item_categories:
|
||||
continue
|
||||
if name in starting_worlds:
|
||||
if name in starting_worlds or name in starting_tools or name in starting_party_member_accessories:
|
||||
continue
|
||||
if data.category == "Puppies":
|
||||
if self.options.puppies == "triplets" and "-" in name:
|
||||
item_pool += [self.create_item(name) for _ in range(quantity)]
|
||||
if self.options.puppies == "individual" and "Puppy" in name:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
if self.options.puppies == "full" and name == "All Puppies":
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
if self.options.stacking_world_items and name in WORLD_KEY_ITEMS.keys() and name not in ("Crystal Trident", "Jack-In-The-Box"): # Handling these special cases separately
|
||||
item_pool += [self.create_item(WORLD_KEY_ITEMS[name]) for _ in range(0, 1)]
|
||||
elif self.options.halloween_town_key_item_bundle and name == "Jack-In-The-Box":
|
||||
continue
|
||||
elif name == "Puppy":
|
||||
if self.options.randomize_puppies:
|
||||
item_pool += [self.create_item(name) for _ in range(ceil(99/self.options.puppy_value.value))]
|
||||
elif name == "Atlantica":
|
||||
if self.options.atlantica:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Mermaid Kick":
|
||||
if self.options.atlantica:
|
||||
if self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 2)]
|
||||
else:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
if self.options.atlantica and self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 2)]
|
||||
else:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Crystal Trident":
|
||||
if self.options.atlantica:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
if self.options.stacking_world_items:
|
||||
item_pool += [self.create_item(WORLD_KEY_ITEMS[name]) for _ in range(0, 1)]
|
||||
else:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "High Jump":
|
||||
if self.options.extra_shared_abilities:
|
||||
item_pool += [self.create_item(name) for _ in range(0, 3)]
|
||||
@@ -154,11 +199,26 @@ class KH1World(World):
|
||||
elif name == "EXP Zero":
|
||||
if self.options.exp_zero_in_pool:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name not in prefilled_items:
|
||||
elif name == "Postcard":
|
||||
if self.options.randomize_postcards.current_key == "chests":
|
||||
item_pool += [self.create_item(name) for _ in range(0, 3)]
|
||||
if self.options.randomize_postcards.current_key == "all":
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Orichalcum":
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.orichalcum_in_pool.value)]
|
||||
elif name == "Mythril":
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.mythril_in_pool.value)]
|
||||
elif name == "Destiny Islands":
|
||||
if self.options.destiny_islands:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
elif name == "Raft Materials":
|
||||
if self.options.destiny_islands:
|
||||
item_pool += [self.create_item(name) for _ in range(0, self.options.materials_in_pool.value)]
|
||||
elif name not in exclude_items:
|
||||
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
|
||||
for i in range(self.determine_reports_in_pool()):
|
||||
item_pool += [self.create_item("Ansem's Report " + str(i+1))]
|
||||
for i in range(self.determine_lucky_emblems_in_pool()):
|
||||
item_pool += [self.create_item("Lucky Emblem")]
|
||||
|
||||
while len(item_pool) < total_locations and len(level_up_item_pool) > 0:
|
||||
item_pool += [self.create_item(level_up_item_pool.pop())]
|
||||
@@ -170,63 +230,117 @@ class KH1World(World):
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def place_predetermined_items(self) -> None:
|
||||
goal_dict = {
|
||||
"sephiroth": "Olympus Coliseum Defeat Sephiroth Ansem's Report 12",
|
||||
"unknown": "Hollow Bastion Defeat Unknown Ansem's Report 13",
|
||||
"postcards": "Traverse Town Mail Postcard 10 Event",
|
||||
"final_ansem": "Final Ansem",
|
||||
"puppies": "Traverse Town Piano Room Return 99 Puppies Reward 2",
|
||||
"final_rest": "End of the World Final Rest Chest"
|
||||
}
|
||||
self.get_location(goal_dict[self.options.goal.current_key]).place_locked_item(self.create_item("Victory"))
|
||||
if self.options.vanilla_emblem_pieces:
|
||||
if self.options.final_rest_door_key.current_key not in ["puppies", "postcards", "lucky_emblems"]:
|
||||
goal_dict = {
|
||||
"sephiroth": "Olympus Coliseum Defeat Sephiroth Ansem's Report 12",
|
||||
"unknown": "Hollow Bastion Defeat Unknown Ansem's Report 13",
|
||||
"final_rest": "End of the World Final Rest Chest"
|
||||
}
|
||||
goal_location_name = goal_dict[self.options.final_rest_door_key.current_key]
|
||||
elif self.options.final_rest_door_key.current_key == "postcards":
|
||||
lpad_number = str(self.options.required_postcards).rjust(2, "0")
|
||||
goal_location_name = "Traverse Town Mail Postcard " + lpad_number + " Event"
|
||||
elif self.options.final_rest_door_key.current_key == "puppies":
|
||||
required_puppies = self.options.required_puppies.value
|
||||
goal_location_name = "Traverse Town Piano Room Return " + str(required_puppies) + " Puppies"
|
||||
if required_puppies == 50 or required_puppies == 99:
|
||||
goal_location_name = goal_location_name + " Reward 2"
|
||||
if self.options.final_rest_door_key.current_key != "lucky_emblems":
|
||||
self.get_location(goal_location_name).place_locked_item(self.create_item("Final Door Key"))
|
||||
self.get_location("Final Ansem").place_locked_item(self.create_event("Victory"))
|
||||
|
||||
if not self.options.randomize_emblem_pieces:
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Flame)").place_locked_item(self.create_item("Emblem Piece (Flame)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Statue)").place_locked_item(self.create_item("Emblem Piece (Statue)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Fountain)").place_locked_item(self.create_item("Emblem Piece (Fountain)"))
|
||||
self.get_location("Hollow Bastion Entrance Hall Emblem Piece (Chest)").place_locked_item(self.create_item("Emblem Piece (Chest)"))
|
||||
if self.options.randomize_postcards != "all":
|
||||
self.get_location("Traverse Town Item Shop Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 1st District Safe Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Gizmo Shop Postcard 1").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Gizmo Shop Postcard 2").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Item Workshop Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 3rd District Balcony Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town Geppetto's House Postcard").place_locked_item(self.create_item("Postcard"))
|
||||
if self.options.randomize_postcards.current_key == "vanilla":
|
||||
self.get_location("Traverse Town 1st District Accessory Shop Roof Chest").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 2nd District Boots and Shoes Awning Chest").place_locked_item(self.create_item("Postcard"))
|
||||
self.get_location("Traverse Town 1st District Blue Trinity Balcony Chest").place_locked_item(self.create_item("Postcard"))
|
||||
if not self.options.randomize_puppies:
|
||||
if self.options.puppy_value.value != 3:
|
||||
self.options.puppy_value.value = 3
|
||||
logging.info(f"{self.player_name}'s value of {self.options.puppy_value.value} for puppy value was changed to 3 as Randomize Puppies is OFF")
|
||||
for i, location in enumerate(VANILLA_PUPPY_LOCATIONS):
|
||||
self.get_location(location).place_locked_item(self.create_item("Puppy"))
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
weights = [data.weight for data in self.fillers.values()]
|
||||
return self.random.choices([filler for filler in self.fillers.keys()], weights)[0]
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = {"xpmult": int(self.options.exp_multiplier)/16,
|
||||
"required_reports_eotw": self.determine_reports_required_to_open_end_of_the_world(),
|
||||
"required_reports_door": self.determine_reports_required_to_open_final_rest_door(),
|
||||
"door": self.options.final_rest_door.current_key,
|
||||
"seed": self.multiworld.seed_name,
|
||||
"advanced_logic": bool(self.options.advanced_logic),
|
||||
"hundred_acre_wood": bool(self.options.hundred_acre_wood),
|
||||
slot_data = {
|
||||
"atlantica": bool(self.options.atlantica),
|
||||
"goal": str(self.options.goal.current_key)}
|
||||
if self.options.randomize_keyblade_stats:
|
||||
min_str_bonus = min(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
max_str_bonus = max(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
self.options.keyblade_min_str.value = min_str_bonus
|
||||
self.options.keyblade_max_str.value = max_str_bonus
|
||||
min_mp_bonus = min(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
max_mp_bonus = max(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
self.options.keyblade_min_mp.value = min_mp_bonus
|
||||
self.options.keyblade_max_mp.value = max_mp_bonus
|
||||
slot_data["keyblade_stats"] = ""
|
||||
for i in range(22):
|
||||
if i < 4 and self.options.bad_starting_weapons:
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + "1,0,"
|
||||
else:
|
||||
str_bonus = int(self.random.randint(min_str_bonus, max_str_bonus))
|
||||
mp_bonus = int(self.random.randint(min_mp_bonus, max_mp_bonus))
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + str(str_bonus) + "," + str(mp_bonus) + ","
|
||||
slot_data["keyblade_stats"] = slot_data["keyblade_stats"][:-1]
|
||||
if self.options.donald_death_link:
|
||||
slot_data["donalddl"] = ""
|
||||
if self.options.goofy_death_link:
|
||||
slot_data["goofydl"] = ""
|
||||
if self.options.keyblades_unlock_chests:
|
||||
slot_data["chestslocked"] = ""
|
||||
else:
|
||||
slot_data["chestsunlocked"] = ""
|
||||
if self.options.interact_in_battle:
|
||||
slot_data["interactinbattle"] = ""
|
||||
"auto_attack": bool(self.options.auto_attack),
|
||||
"auto_save": bool(self.options.auto_save),
|
||||
"bad_starting_weapons": bool(self.options.bad_starting_weapons),
|
||||
"beep_hack": bool(self.options.beep_hack),
|
||||
"consistent_finishers": bool(self.options.consistent_finishers),
|
||||
"cups": str(self.options.cups.current_key),
|
||||
"day_2_materials": int(self.options.day_2_materials.value),
|
||||
"death_link": str(self.options.death_link.current_key),
|
||||
"destiny_islands": bool(self.options.destiny_islands),
|
||||
"donald_death_link": bool(self.options.donald_death_link),
|
||||
"early_skip": bool(self.options.early_skip),
|
||||
"end_of_the_world_unlock": str(self.options.end_of_the_world_unlock.current_key),
|
||||
"exp_multiplier": int(self.options.exp_multiplier.value)/16,
|
||||
"exp_zero_in_pool": bool(self.options.exp_zero_in_pool),
|
||||
"extra_shared_abilities": bool(self.options.extra_shared_abilities),
|
||||
"fast_camera": bool(self.options.fast_camera),
|
||||
"faster_animations": bool(self.options.faster_animations),
|
||||
"final_rest_door_key": str(self.options.final_rest_door_key.current_key),
|
||||
"force_stats_on_levels": int(self.options.force_stats_on_levels.value),
|
||||
"four_by_three": bool(self.options.four_by_three),
|
||||
"goofy_death_link": bool(self.options.goofy_death_link),
|
||||
"halloween_town_key_item_bundle": bool(self.options.halloween_town_key_item_bundle),
|
||||
"homecoming_materials": int(self.options.homecoming_materials.value),
|
||||
"hundred_acre_wood": bool(self.options.hundred_acre_wood),
|
||||
"interact_in_battle": bool(self.options.interact_in_battle),
|
||||
"jungle_slider": bool(self.options.jungle_slider),
|
||||
"keyblades_unlock_chests": bool(self.options.keyblades_unlock_chests),
|
||||
"level_checks": int(self.options.level_checks.value),
|
||||
"logic_difficulty": str(self.options.logic_difficulty.current_key),
|
||||
"materials_in_pool": int(self.options.materials_in_pool.value),
|
||||
"max_ap_cost": int(self.options.max_ap_cost.value),
|
||||
"min_ap_cost": int(self.options.min_ap_cost.value),
|
||||
"mythril_in_pool": int(self.options.mythril_in_pool.value),
|
||||
"mythril_price": int(self.options.mythril_price.value),
|
||||
"one_hp": bool(self.options.one_hp),
|
||||
"orichalcum_in_pool": int(self.options.orichalcum_in_pool.value),
|
||||
"orichalcum_price": int(self.options.orichalcum_price.value),
|
||||
"puppy_value": int(self.options.puppy_value.value),
|
||||
"randomize_ap_costs": str(self.options.randomize_ap_costs.current_key),
|
||||
"randomize_emblem_pieces": bool(self.options.exp_zero_in_pool),
|
||||
"randomize_party_member_starting_accessories": bool(self.options.randomize_party_member_starting_accessories),
|
||||
"randomize_postcards": str(self.options.randomize_postcards.current_key),
|
||||
"randomize_puppies": str(self.options.randomize_puppies.current_key),
|
||||
"remote_items": str(self.options.remote_items.current_key),
|
||||
"remote_location_ids": self.get_remote_location_ids(),
|
||||
"required_lucky_emblems_door": self.determine_lucky_emblems_required_to_open_final_rest_door(),
|
||||
"required_lucky_emblems_eotw": self.determine_lucky_emblems_required_to_open_end_of_the_world(),
|
||||
"required_postcards": int(self.options.required_postcards.value),
|
||||
"required_puppies": int(self.options.required_puppies.value),
|
||||
"seed": self.multiworld.seed_name,
|
||||
"shorten_go_mode": bool(self.options.shorten_go_mode),
|
||||
"slot_2_level_checks": int(self.options.slot_2_level_checks.value),
|
||||
"stacking_world_items": bool(self.options.stacking_world_items),
|
||||
"starting_items": [item.code for item in self.multiworld.precollected_items[self.player]],
|
||||
"starting_tools": bool(self.options.starting_tools),
|
||||
"super_bosses": bool(self.options.super_bosses),
|
||||
"synthesis_item_name_byte_arrays": self.get_synthesis_item_name_byte_arrays(),
|
||||
"unlock_0_volume": bool(self.options.unlock_0_volume),
|
||||
"unskippable": bool(self.options.unskippable),
|
||||
"warp_anywhere": bool(self.options.warp_anywhere)
|
||||
}
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> KH1Item:
|
||||
@@ -241,45 +355,260 @@ class KH1World(World):
|
||||
set_rules(self)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player, self.options)
|
||||
|
||||
create_regions(self)
|
||||
|
||||
def connect_entrances(self):
|
||||
connect_entrances(self.multiworld, self.player)
|
||||
connect_entrances(self)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
"""
|
||||
Generates the json file for use with mod generator.
|
||||
"""
|
||||
generate_json(self, output_directory)
|
||||
|
||||
def generate_early(self):
|
||||
value_names = ["Reports to Open End of the World", "Reports to Open Final Rest Door", "Reports in Pool"]
|
||||
initial_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
|
||||
self.change_numbers_of_reports_to_consider()
|
||||
new_report_settings = [self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value]
|
||||
self.determine_level_checks()
|
||||
|
||||
value_names = ["Lucky Emblems to Open End of the World", "Lucky Emblems to Open Final Rest Door", "Lucky Emblems in Pool"]
|
||||
initial_lucky_emblem_settings = [self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value]
|
||||
self.change_numbers_of_lucky_emblems_to_consider()
|
||||
new_lucky_emblem_settings = [self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value]
|
||||
for i in range(3):
|
||||
if initial_report_settings[i] != new_report_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_report_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_report_settings[i]}")
|
||||
if initial_lucky_emblem_settings[i] != new_lucky_emblem_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_lucky_emblem_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_lucky_emblem_settings[i]}")
|
||||
|
||||
value_names = ["Day 2 Materials", "Homecoming Materials", "Materials in Pool"]
|
||||
initial_materials_settings = [self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value]
|
||||
self.change_numbers_of_materials_to_consider()
|
||||
new_materials_settings = [self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value]
|
||||
for i in range(3):
|
||||
if initial_materials_settings[i] != new_materials_settings[i]:
|
||||
logging.info(f"{self.player_name}'s value {initial_materials_settings[i]} for \"{value_names[i]}\" was invalid\n"
|
||||
f"Setting \"{value_names[i]}\" value to {new_materials_settings[i]}")
|
||||
|
||||
if self.options.stacking_world_items.value and not self.options.halloween_town_key_item_bundle.value:
|
||||
logging.info(f"{self.player_name}'s value {self.options.halloween_town_key_item_bundle.value} for Halloween Town Key Item Bundle must be TRUE when Stacking World Items is on. Setting to TRUE")
|
||||
self.options.halloween_town_key_item_bundle.value = True
|
||||
|
||||
def change_numbers_of_reports_to_consider(self) -> None:
|
||||
if self.options.end_of_the_world_unlock == "reports" and self.options.final_rest_door == "reports":
|
||||
self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_eotw.value, self.options.required_reports_door.value, self.options.reports_in_pool.value])
|
||||
def change_numbers_of_lucky_emblems_to_consider(self) -> None:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems" and self.options.final_rest_door_key == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_eotw.value, self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value])
|
||||
|
||||
elif self.options.end_of_the_world_unlock == "reports":
|
||||
self.options.required_reports_eotw.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_eotw.value, self.options.reports_in_pool.value])
|
||||
elif self.options.end_of_the_world_unlock == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_eotw.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_eotw.value, self.options.lucky_emblems_in_pool.value])
|
||||
|
||||
elif self.options.final_rest_door == "reports":
|
||||
self.options.required_reports_door.value, self.options.reports_in_pool.value = sorted(
|
||||
[self.options.required_reports_door.value, self.options.reports_in_pool.value])
|
||||
elif self.options.final_rest_door_key == "lucky_emblems":
|
||||
self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value = sorted(
|
||||
[self.options.required_lucky_emblems_door.value, self.options.lucky_emblems_in_pool.value])
|
||||
|
||||
def determine_reports_in_pool(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "reports" or self.options.final_rest_door == "reports":
|
||||
return self.options.reports_in_pool.value
|
||||
def determine_lucky_emblems_in_pool(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems" or self.options.final_rest_door_key == "lucky_emblems":
|
||||
return self.options.lucky_emblems_in_pool.value
|
||||
return 0
|
||||
|
||||
def determine_reports_required_to_open_end_of_the_world(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "reports":
|
||||
return self.options.required_reports_eotw.value
|
||||
return 14
|
||||
def determine_lucky_emblems_required_to_open_end_of_the_world(self) -> int:
|
||||
if self.options.end_of_the_world_unlock == "lucky_emblems":
|
||||
return self.options.required_lucky_emblems_eotw.value
|
||||
return -1
|
||||
|
||||
def determine_reports_required_to_open_final_rest_door(self) -> int:
|
||||
if self.options.final_rest_door == "reports":
|
||||
return self.options.required_reports_door.value
|
||||
return 14
|
||||
def determine_lucky_emblems_required_to_open_final_rest_door(self) -> int:
|
||||
if self.options.final_rest_door_key == "lucky_emblems":
|
||||
return self.options.required_lucky_emblems_door.value
|
||||
return -1
|
||||
|
||||
def change_numbers_of_materials_to_consider(self) -> None:
|
||||
if self.options.destiny_islands:
|
||||
self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value = sorted(
|
||||
[self.options.day_2_materials.value, self.options.homecoming_materials.value, self.options.materials_in_pool.value])
|
||||
|
||||
def get_remote_location_ids(self):
|
||||
remote_location_ids = []
|
||||
for location in self.multiworld.get_filled_locations(self.player):
|
||||
if location.name != "Final Ansem":
|
||||
location_data = location_table[location.name]
|
||||
if self.options.remote_items.current_key == "full":
|
||||
if location_data.type != "Starting Accessory":
|
||||
remote_location_ids.append(location_data.code)
|
||||
elif self.player == location.item.player and location.item.name != "Victory":
|
||||
item_data = item_table[location.item.name]
|
||||
if location_data.type == "Chest":
|
||||
if item_data.type in ["Stats"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Reward":
|
||||
if item_data.type in ["Stats"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Static":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Level Slot 1":
|
||||
if item_data.category not in ["Level Up", "Limited Level Up"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Level Slot 2":
|
||||
if item_data.category not in ["Level Up", "Limited Level Up", "Abilities"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Synth":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
if location_data.type == "Prize":
|
||||
if item_data.type not in ["Item"]:
|
||||
remote_location_ids.append(location_data.code)
|
||||
return remote_location_ids
|
||||
|
||||
def get_slot_2_levels(self):
|
||||
if self.slot_2_levels is None:
|
||||
self.slot_2_levels = []
|
||||
if self.options.max_level_for_slot_2_level_checks - 1 > self.options.level_checks.value:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.max_level_for_slot_2_level_checks.value} for max level for slot 2 level checks is invalid as it exceeds their value of {self.options.level_checks.value} for Level Checks\n"
|
||||
f"Setting max level for slot 2 level checks's value to {self.options.level_checks.value + 1}")
|
||||
self.options.max_level_for_slot_2_level_checks.value = self.options.level_checks.value + 1
|
||||
if self.options.slot_2_level_checks.value > self.options.level_checks.value:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.slot_2_level_checks.value} for slot 2 level checks is invalid as it exceeds their value of {self.options.level_checks.value} for Level Checks\n"
|
||||
f"Setting slot 2 level check's value to {self.options.level_checks.value}")
|
||||
self.options.slot_2_level_checks.value = self.options.level_checks.value
|
||||
if self.options.slot_2_level_checks > self.options.max_level_for_slot_2_level_checks - 1:
|
||||
logging.info(f"{self.player_name}'s value of {self.options.slot_2_level_checks.value} for slot 2 level checks is invalid as it exceeds their value of {self.options.max_level_for_slot_2_level_checks.value} for Max Level for Slot 2 Level Checks\n"
|
||||
f"Setting slot 2 level check's value to {self.options.max_level_for_slot_2_level_checks.value - 1}")
|
||||
self.options.slot_2_level_checks.value = self.options.max_level_for_slot_2_level_checks.value - 1
|
||||
# Range is exclusive of the top, so if max_level_for_slot_2_level_checks is 2 then the top end of the range needs to be 3 as the only level it can choose is 2.
|
||||
self.slot_2_levels = self.random.sample(range(2,self.options.max_level_for_slot_2_level_checks.value + 1), self.options.slot_2_level_checks.value)
|
||||
return self.slot_2_levels
|
||||
|
||||
def get_keyblade_stats(self):
|
||||
# Create keyblade stat array from vanilla
|
||||
keyblade_stats = [x.copy() for x in VANILLA_KEYBLADE_STATS]
|
||||
# Handle shuffling keyblade stats
|
||||
if self.options.keyblade_stats != "vanilla":
|
||||
if self.options.keyblade_stats == "randomize":
|
||||
# Fix any minimum and max values from settings
|
||||
min_str_bonus = min(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
max_str_bonus = max(self.options.keyblade_min_str.value, self.options.keyblade_max_str.value)
|
||||
self.options.keyblade_min_str.value = min_str_bonus
|
||||
self.options.keyblade_max_str.value = max_str_bonus
|
||||
min_crit_rate = min(self.options.keyblade_min_crit_rate.value, self.options.keyblade_max_crit_rate.value)
|
||||
max_crit_rate = max(self.options.keyblade_min_crit_rate.value, self.options.keyblade_max_crit_rate.value)
|
||||
self.options.keyblade_min_crit_rate.value = min_crit_rate
|
||||
self.options.keyblade_max_crit_rate.value = max_crit_rate
|
||||
min_crit_str = min(self.options.keyblade_min_crit_str.value, self.options.keyblade_max_crit_str.value)
|
||||
max_crit_str = max(self.options.keyblade_min_crit_str.value, self.options.keyblade_max_crit_str.value)
|
||||
self.options.keyblade_min_crit_str.value = min_crit_str
|
||||
self.options.keyblade_max_crit_str.value = max_crit_str
|
||||
min_recoil = min(self.options.keyblade_min_recoil.value, self.options.keyblade_max_recoil.value)
|
||||
max_recoil = max(self.options.keyblade_min_recoil.value, self.options.keyblade_max_recoil.value)
|
||||
self.options.keyblade_min_recoil.value = min_recoil
|
||||
self.options.keyblade_max_recoil.value = max_recoil
|
||||
min_mp_bonus = min(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
max_mp_bonus = max(self.options.keyblade_min_mp.value, self.options.keyblade_max_mp.value)
|
||||
self.options.keyblade_min_mp.value = min_mp_bonus
|
||||
self.options.keyblade_max_mp.value = max_mp_bonus
|
||||
if self.options.bad_starting_weapons:
|
||||
starting_weapons = keyblade_stats[:4]
|
||||
other_weapons = keyblade_stats[4:]
|
||||
else:
|
||||
starting_weapons = []
|
||||
other_weapons = keyblade_stats
|
||||
for keyblade in other_weapons:
|
||||
keyblade["STR"] = self.random.randint(min_str_bonus, max_str_bonus)
|
||||
keyblade["CRR"] = self.random.randint(min_crit_rate, max_crit_rate)
|
||||
keyblade["CRB"] = self.random.randint(min_crit_str, max_crit_str)
|
||||
keyblade["REC"] = self.random.randint(min_recoil, max_recoil)
|
||||
keyblade["MP"] = self.random.randint(min_mp_bonus, max_mp_bonus)
|
||||
keyblade_stats = starting_weapons + other_weapons
|
||||
elif self.options.keyblade_stats == "shuffle":
|
||||
if self.options.bad_starting_weapons:
|
||||
starting_weapons = keyblade_stats[:4]
|
||||
other_weapons = keyblade_stats[4:]
|
||||
self.random.shuffle(other_weapons)
|
||||
keyblade_stats = starting_weapons + other_weapons
|
||||
else:
|
||||
self.random.shuffle(keyblade_stats)
|
||||
return keyblade_stats
|
||||
|
||||
def determine_level_checks(self):
|
||||
# Handle if remote_items is off and level_checks > number of stats items
|
||||
total_level_up_items = min(99,
|
||||
self.options.strength_increase.value +\
|
||||
self.options.defense_increase.value +\
|
||||
self.options.hp_increase.value +\
|
||||
self.options.mp_increase.value +\
|
||||
self.options.ap_increase.value +\
|
||||
self.options.accessory_slot_increase.value +\
|
||||
self.options.item_slot_increase.value)
|
||||
if self.options.level_checks.value > total_level_up_items and self.options.remote_items.current_key == "off":
|
||||
logging.info(f"{self.player_name}'s value {self.options.level_checks.value} for level_checks was changed.\n"
|
||||
f"This value cannot be more than the number of stat items in the pool when \"remote_items\" is \"off\".\n"
|
||||
f"Set to be equal to number of stat items in pool, {total_level_up_items}.")
|
||||
self.options.level_checks.value = total_level_up_items
|
||||
|
||||
def get_synthesis_item_name_byte_arrays(self):
|
||||
# Get synth item names to show in synthesis menu
|
||||
synthesis_byte_arrays = []
|
||||
for location in self.multiworld.get_filled_locations(self.player):
|
||||
if location.name != "Final Ansem":
|
||||
location_data = location_table[location.name]
|
||||
if location_data.type == "Synth":
|
||||
item_name = re.sub('[^A-Za-z0-9 ]+', '',str(location.item.name.replace("Progressive", "Prog")))[:14]
|
||||
byte_array = []
|
||||
for character in item_name:
|
||||
byte_array.append(CHAR_TO_KH[character])
|
||||
synthesis_byte_arrays.append(byte_array)
|
||||
return synthesis_byte_arrays
|
||||
|
||||
def get_starting_accessory_locations(self):
|
||||
if self.starting_accessory_locations is None:
|
||||
if self.options.randomize_party_member_starting_accessories:
|
||||
self.starting_accessory_locations = list(get_locations_by_type("Starting Accessory").keys())
|
||||
if not self.options.atlantica:
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 1")
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 2")
|
||||
self.starting_accessory_locations.remove("Ariel Starting Accessory 3")
|
||||
self.starting_accessory_locations = self.random.sample(self.starting_accessory_locations, 10)
|
||||
else:
|
||||
self.starting_accessory_locations = []
|
||||
return self.starting_accessory_locations
|
||||
|
||||
def get_starting_accessories(self):
|
||||
if self.starting_accessories is None:
|
||||
if self.options.randomize_party_member_starting_accessories:
|
||||
self.starting_accessories = list(get_items_by_category("Accessory").keys())
|
||||
self.starting_accessories = self.random.sample(self.starting_accessories, 10)
|
||||
else:
|
||||
self.starting_accessories = []
|
||||
return self.starting_accessories
|
||||
|
||||
def get_ap_costs(self):
|
||||
if self.ap_costs is None:
|
||||
ap_costs = VANILLA_ABILITY_AP_COSTS.copy()
|
||||
if self.options.randomize_ap_costs.current_key == "shuffle":
|
||||
possible_costs = []
|
||||
for ap_cost in VANILLA_ABILITY_AP_COSTS:
|
||||
if ap_cost["Randomize"]:
|
||||
possible_costs.append(ap_cost["AP Cost"])
|
||||
self.random.shuffle(possible_costs)
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
ap_cost["AP Cost"] = possible_costs.pop(0)
|
||||
elif self.options.randomize_ap_costs.current_key == "randomize":
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
ap_cost["AP Cost"] = self.random.randint(self.options.min_ap_cost.value, self.options.max_ap_cost.value)
|
||||
elif self.options.randomize_ap_costs.current_key == "distribute":
|
||||
total_ap_value = 0
|
||||
for ap_cost in VANILLA_ABILITY_AP_COSTS:
|
||||
if ap_cost["Randomize"]:
|
||||
total_ap_value = total_ap_value + ap_cost["AP Cost"]
|
||||
for ap_cost in ap_costs:
|
||||
if ap_cost["Randomize"]:
|
||||
total_ap_value = total_ap_value - self.options.min_ap_cost.value
|
||||
ap_cost["AP Cost"] = self.options.min_ap_cost.value
|
||||
while total_ap_value > 0:
|
||||
ap_cost = self.random.choice(ap_costs)
|
||||
if ap_cost["Randomize"]:
|
||||
if ap_cost["AP Cost"] < self.options.max_ap_cost.value:
|
||||
amount_to_add = self.random.randint(1, min(self.options.max_ap_cost.value - ap_cost["AP Cost"], total_ap_value))
|
||||
ap_cost["AP Cost"] = ap_cost["AP Cost"] + amount_to_add
|
||||
total_ap_value = total_ap_value - amount_to_add
|
||||
self.ap_costs = ap_costs
|
||||
return self.ap_costs
|
||||
|
||||
Reference in New Issue
Block a user