forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
228 lines
11 KiB
Python
228 lines
11 KiB
Python
from typing import List, Dict, Any
|
|
|
|
from BaseClasses import Region, Entrance, Tutorial
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from .Items import KHDDDItem, item_data_table, item_table, get_items_by_category, get_items_by_character_category
|
|
from .Locations import KHDDDLocation, location_data_table, location_table, event_location_table, get_locations_by_region
|
|
from .Options import KHDDDOptions
|
|
from .Regions import region_data_table, create_regions
|
|
from .Rules import set_rules
|
|
from worlds.LauncherComponents import Component, components, Type, launch as launch_component
|
|
import random
|
|
|
|
def launch_client():
|
|
from .Client import launch
|
|
launch_component(launch, name="KHDDD Client")
|
|
|
|
components.append(Component("KHDDD Client", "KHDDD Client", func=launch_client, component_type=Type.CLIENT))
|
|
|
|
class KHDDDWebWorld(WebWorld):
|
|
theme = "partyTime"
|
|
setup = Tutorial(
|
|
tutorial_name = "Setup Guide",
|
|
description = "A guide to setting up the Kingdom Hearts Dream Drop Distance Archipelago Multiworld",
|
|
language = "English",
|
|
file_name = "setup.md",
|
|
link = "setup/en",
|
|
authors = ["Kwazila"]
|
|
)
|
|
tutorials = [setup]
|
|
|
|
class KHDDDWorld(World):
|
|
"""KHDDD is based"""
|
|
game = "Kingdom Hearts Dream Drop Distance"
|
|
options: KHDDDOptions
|
|
options_dataclass = KHDDDOptions
|
|
#location_name_to_id = event_location_table
|
|
location_name_to_id = {name: data.code for name, data in location_data_table.items()}
|
|
item_name_to_id = item_table
|
|
topology_present = True
|
|
origin_region_name = "World"
|
|
web = KHDDDWebWorld()
|
|
|
|
def create_item(self, name: str) -> KHDDDItem:
|
|
return KHDDDItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
|
|
|
|
def create_items(self) -> None:
|
|
self.place_predetermined_items()
|
|
|
|
#Starting Worlds
|
|
starting_worlds = []
|
|
if self.options.starting_worlds > 0:
|
|
possible_starting_worlds = []
|
|
if self.options.character < 2: #TODO: Include Traverse Town
|
|
possible_starting_worlds = possible_starting_worlds + ["La Cite des Cloches [Sora]", "Prankster's Paradise [Sora]",
|
|
"Country of the Musketeers [Sora]", "Symphony of Sorcery [Sora]"]
|
|
if self.options.character == 2 or self.options.character == 0:
|
|
possible_starting_worlds = possible_starting_worlds + [ "The Grid [Riku]",
|
|
"La Cite des Cloches [Riku]", "Prankster's Paradise [Riku]", "Country of the Musketeers [Riku]", "Symphony of Sorcery [Riku]"]
|
|
starting_worlds = self.random.sample(possible_starting_worlds, min(self.options.starting_worlds, len(possible_starting_worlds)))
|
|
for starting_world in starting_worlds:
|
|
self.multiworld.push_precollected(self.create_item(starting_world))
|
|
|
|
item_pool: List[KHDDDItem] = []
|
|
|
|
# Pre-fill level up locations if applicable
|
|
filler_stat_names = ["Strength Increase [Sora]", "Magic Increase [Sora]", "Defense Increase [Sora]",
|
|
"Strength Increase [Riku]", "Magic Increase [Riku]", "Defense Increase [Riku]"]
|
|
|
|
if not self.options.stats_on_levels:
|
|
for _ in range(self.options.strength_in_pool):
|
|
if self.options.character == 0 or self.options.character == 1:
|
|
item_pool += [self.create_item("Strength Increase [Sora]")]
|
|
if self.options.character == 0 or self.options.character == 2:
|
|
item_pool += [self.create_item("Strength Increase [Riku]")]
|
|
for _ in range(self.options.magic_in_pool):
|
|
if self.options.character == 0 or self.options.character == 1:
|
|
item_pool += [self.create_item("Magic Increase [Sora]")]
|
|
if self.options.character == 0 or self.options.character == 2:
|
|
item_pool += [self.create_item("Magic Increase [Riku]")]
|
|
for _ in range(self.options.defense_in_pool):
|
|
if self.options.character == 0 or self.options.character == 1:
|
|
item_pool += [self.create_item("Defense Increase [Sora]")]
|
|
if self.options.character == 0 or self.options.character == 2:
|
|
item_pool += [self.create_item("Defense Increase [Riku]")]
|
|
|
|
if self.options.stats_on_levels: # Place Stats on levels
|
|
possible_level_up_item_pool = []
|
|
|
|
for name, data in get_items_by_category("Stat").items():
|
|
quantity = data.qty
|
|
if data.category == "Stat":
|
|
if name in filler_stat_names:
|
|
if self.options.character == 0 or self.options.character == 1 and "Sora" in name or self.options.character == 2 and "Riku" in name:
|
|
for _ in range(quantity):
|
|
possible_level_up_item_pool.append(name)
|
|
|
|
self.random.shuffle(possible_level_up_item_pool)
|
|
|
|
level_up_item_pool = possible_level_up_item_pool
|
|
level_up_locations = list(get_locations_by_region("Levels").keys())
|
|
|
|
current_level_for_placing_stats = 1
|
|
|
|
if self.options.character == 2:
|
|
current_level_for_placing_stats = 50 # Riku's levels start here
|
|
|
|
level_range = 99
|
|
if self.options.character == 1:
|
|
level_range = 50
|
|
|
|
while len(level_up_item_pool) > 0 and current_level_for_placing_stats < level_range:
|
|
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
|
|
|
|
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
|
|
|
|
#Add correct flowmotion to the item pool
|
|
if int(self.options.single_flowmotion) == 1: #Flowmotion is a single item
|
|
item_pool += [self.create_item("Flowmotion")]
|
|
else:
|
|
for name, data in get_items_by_category("Flowmotion").items(): #Each flowmotion is an individual item
|
|
if name != "Flowmotion":
|
|
item_pool += [self.create_item(name)]
|
|
|
|
#Add recipes to the item pool
|
|
recipe_count = max(int(self.options.recipes_in_pool-2), int(self.options.recipe_reqs-2))
|
|
recipes = []
|
|
for name, data in get_items_by_category("Recipe").items():
|
|
if name != "Meow Wow Recipe" and name != "Komory Bat Recipe":
|
|
recipes.append(name)
|
|
|
|
#Shuffle recipes and add to item pool based on reqs
|
|
random.shuffle(recipes)
|
|
for x in range(recipe_count):
|
|
item_pool += [self.create_item(recipes[x])]
|
|
|
|
#Always have Meow Wow and Komory Bat in the item pool
|
|
item_pool += [self.create_item("Meow Wow Recipe")]
|
|
item_pool += [self.create_item("Komory Bat Recipe")]
|
|
|
|
non_filler_categories = ["Stat", "World", "Keyblade", "Movement", "Defense", "Ability", "Special"]
|
|
|
|
for name, data in item_data_table.items():
|
|
quantity = data.qty
|
|
if data.category in non_filler_categories:
|
|
#Prevent starting worlds and str/mag/def increases from being placed in pool again
|
|
if name in starting_worlds or name in filler_stat_names:
|
|
continue
|
|
|
|
#Omit any items not for the selected character
|
|
if data.character > 0 and int(self.options.character) > 0 and data.character != self.options.character:
|
|
continue
|
|
|
|
item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
|
|
|
#if name!="Victory":
|
|
# item_pool += [self.create_item(name)]
|
|
|
|
#Fill empty locations with filler
|
|
while len(item_pool) < total_locations:
|
|
item_pool.append(self.create_item(self.get_filler_item_name()))
|
|
|
|
self.multiworld.itempool += item_pool
|
|
|
|
|
|
def place_predetermined_items(self) -> None:
|
|
if self.options.goal == 1: #Place Superboss Goal Item
|
|
self.get_location("All Superbosses Defeated [Sora] [Riku]").place_locked_item(self.create_item("Victory"))
|
|
else:
|
|
#Place Goal item based on who the final boss actually is
|
|
if self.options.character == 0 or self.options.character == 2: #Either both characters or riku
|
|
if self.options.armored_ventus_nightmare: #Are we fighting avn?
|
|
self.get_location("Armored Ventus Nightmare Defeated [Riku]").place_locked_item(self.create_item("Victory"))
|
|
else: #We are fighting YX
|
|
self.get_location("The World That Never Was Young Xehanort Defeated [Riku]").place_locked_item(self.create_item("Victory"))
|
|
else: #We are fighting Xemnas
|
|
self.get_location("The World That Never Was Xemnas Bonus Slot 1 [Sora]").place_locked_item(self.create_item("Victory"))
|
|
|
|
def create_regions(self) -> None:
|
|
create_regions(self.multiworld, self.player, self.options)
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
if int(self.options.instant_drop_trap_chance) > 0: #Check to see if a trap was rolled
|
|
if int(self.random.randint(0, 99) < int(self.options.instant_drop_trap_chance)):
|
|
return "Instant Drop"
|
|
fillers = {}
|
|
fillers.update(get_items_by_character_category(int(self.options.character), "Command"))
|
|
fillers.update(get_items_by_character_category(int(self.options.character), "Item"))
|
|
return self.random.choices([filler for filler in fillers.keys()])[0]
|
|
|
|
|
|
def set_rules(self):
|
|
set_rules(self)
|
|
|
|
def fill_slot_data(self) -> Dict[str, Any]:
|
|
slot_data = {"character": int(self.options.character)}
|
|
#Roll random keyblade stats
|
|
if self.options.randomize_keyblade_stats:
|
|
slot_data["keyblade_stats"] = ""
|
|
min_str_bonus = self.options.keyblade_min_str.value
|
|
max_str_bonus = self.options.keyblade_max_str.value
|
|
min_mag_bonus = self.options.keyblade_min_mag.value
|
|
max_mag_bonus = self.options.keyblade_max_mag.value
|
|
for i in range(30):
|
|
str_bonus = int(self.random.randint(min_str_bonus, max_str_bonus))
|
|
mag_bonus = int(self.random.randint(min_mag_bonus, max_mag_bonus))
|
|
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + str(str_bonus) + "," + str(mag_bonus) + ","
|
|
slot_data["keyblade_stats"] = slot_data["keyblade_stats"][:-1]
|
|
|
|
if int(self.options.character) < 2:
|
|
slot_data["play_destiny_islands"] = str(self.options.play_destiny_islands.value)
|
|
else:
|
|
slot_data["play_destiny_islands"] = "0"
|
|
|
|
slot_data["skip_light_cycle"] = str(self.options.skip_light_cycle.value)
|
|
slot_data["fast_go_mode"] = str(self.options.fast_go_mode.value)
|
|
slot_data["exp_multiplier"] = int(self.options.exp_multiplier.value)
|
|
slot_data["stat_bonus"] = int(self.options.stat_bonus.value)
|
|
|
|
slot_data["recipe_reqs"] = int(self.options.recipe_reqs.value)
|
|
slot_data["win_con"] = int(self.options.goal.value)
|
|
|
|
slot_data["lord_kyroo"] = str(self.options.lord_kyroo.value)
|
|
slot_data["local_item_notifs"] = str(self.options.received_notifications.value)
|
|
slot_data["remote_item_notifs"] = str(self.options.sent_notifications.value)
|
|
|
|
return slot_data |