Files
dockipelago/worlds/khbbs/__init__.py
Jonathan Tinney 7971961166
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
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

185 lines
9.6 KiB
Python

from typing import List
from BaseClasses import Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import KHBBSItem, KHBBSItemData, event_item_table, get_items_by_category, item_table, item_name_groups
from .Locations import KHBBSLocation, location_table, get_locations_by_category, location_name_groups
from .Options import KHBBSOptions, khbbs_option_groups
from .Regions import create_regions
from .Rules import set_rules
from .OpenKH import patch_khbbs
from worlds.LauncherComponents import Component, components, Type, launch_subprocess
def launch_client():
from .Client import launch
launch_subprocess(launch, name="KHBBS Client")
components.append(Component("KHBBS Client", "KHBBSClient", func=launch_client, component_type=Type.CLIENT))
class KHBBSWeb(WebWorld):
theme = "ocean"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Kingdom Hearts BBS Randomizer software on your computer. This guide covers single-player, "
"multiworld, and related software.",
"English",
"setup_en.md",
"setup/en",
["Gicu"]
)]
option_groups = khbbs_option_groups
class KHBBSWorld(World):
"""
Kingdom Hearts Birth by Sleep is an action RPG following three friends,
Terra, Ventus, and Aqua on a journey through many worlds to defeat Xehanort.
"""
game = "Kingdom Hearts Birth by Sleep"
options_dataclass = KHBBSOptions
options: KHBBSOptions
topology_present = True
required_client_version = (0, 3, 5)
web = KHBBSWeb()
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.code for name, data in location_table.items()}
item_name_groups = item_name_groups
location_name_groups = location_name_groups
def create_items(self):
self.place_predetermined_items()
character_letters = ["V", "A", "T"]
# Handle starting worlds
starting_worlds = []
if self.options.starting_worlds > 0:
possible_starting_worlds = ["Dwarf Woodlands",
"Castle of Dreams", "Enchanted Dominion", "The Mysterious Tower",
"Radiant Garden", "Olympus Coliseum", "Deep Space",
"Never Land", "Disney Town"]
if self.options.mirage_arena or self.options.command_board or self.options.minigames:
possible_starting_worlds.append("Mirage Arena")
if self.options.character == 1 and self.options.realm_of_darkness:
possible_starting_worlds.append("Realm of Darkness")
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[KHBBSItem] = []
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
non_filler_item_categories = ["Movement Command", "Defense Command", "Reprisal Command", "Command Board",
"Shotlock Command", "Command Style", "Ability", "Key Item", "World", "Stat Up", "D-Link"]
for name, data in item_table.items():
quantity = data.max_quantity
if data.category not in non_filler_item_categories:
continue
if name in starting_worlds:
continue
if name == "Mirage Arena" and not (self.options.mirage_arena or self.options.command_board or self.options.minigames):
continue
if name == "Realm of Darkness" and not self.options.realm_of_darkness:
continue
if name == "HP Increase":
item_pool += [self.create_item(name) for _ in range(0, self.options.max_hp_increases.value)]
elif character_letters[self.options.character] in data.characters:
item_pool += [self.create_item(name) for _ in range(0, quantity)]
# These are magic commands (normally filler) but a few locations require them so guaranteeing some in the pool
if self.options.character != 0: #Ventus doesn't need Fire
item_pool += [self.create_item("Fire")]
if self.options.character != 1: #Aqua starts with Thunder
item_pool += [self.create_item("Thunder")]
# Fill any empty locations with filler items.
while len(item_pool) < total_locations:
item_pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += item_pool
def place_predetermined_items(self) -> None:
goal_locations = ["(V) The Keyblade Graveyard Defeat Final Vanitas",
"(A) The Keyblade Graveyard Defeat Ventus-Vanitas",
"(T) The Keyblade Graveyard Defeat Terra-Xehanort"]
if self.options.character == 1 and self.options.final_terra_xehanort_ii:
self.get_location("(A) Radiant Garden Defeat Final Terra-Xehanort II").place_locked_item(self.create_item("Victory"))
else:
self.get_location(goal_locations[self.options.character]).place_locked_item(self.create_item("Victory"))
def get_filler_item_name(self) -> str:
fillers = {}
exclude = []
characters = ["V","A","T"]
fillers.update(get_items_by_category("Attack Command", characters[self.options.character]))
fillers.update(get_items_by_category("Magic Command", characters[self.options.character]))
fillers.update(get_items_by_category("Item Command", characters[self.options.character]))
fillers.update(get_items_by_category("Friendship Command", characters[self.options.character]))
return self.random.choices([filler for filler in fillers.keys()])[0]
def fill_slot_data(self) -> dict:
slot_data = {"xpmult": int(self.options.exp_multiplier)/16,
"non_remote_location_ids": self.get_non_remote_location_ids(),
"character": int(self.options.character),
"mirage_arena": bool(self.options.mirage_arena),
"command_board": bool(self.options.command_board),
"minigames": bool(self.options.minigames),
"arena_medals": bool(self.options.arena_medals),
"superbosses": bool(self.options.super_bosses),
"arena_global": bool(self.options.arena_global_locations),
"advanced_logic": bool(self.options.advanced_logic),
"realm": bool(self.options.realm_of_darkness),
"final_terranort": bool(self.options.final_terra_xehanort_ii)}
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_mgc_bonus = min(self.options.keyblade_min_mgc.value, self.options.keyblade_max_mgc.value)
max_mgc_bonus = max(self.options.keyblade_min_mgc.value, self.options.keyblade_max_mgc.value)
self.options.keyblade_min_mgc.value = min_mgc_bonus
self.options.keyblade_max_mgc.value = max_mgc_bonus
slot_data["keyblade_stats"] = ""
for i in range(49):
str_bonus = int(self.random.randint(min_str_bonus, max_str_bonus))
mgc_bonus = int(self.random.randint(min_mgc_bonus, max_mgc_bonus))
slot_data["keyblade_stats"] = slot_data["keyblade_stats"] + str(str_bonus) + "," + str(mgc_bonus) + ","
slot_data["keyblade_stats"] = slot_data["keyblade_stats"][:-1]
return slot_data
def create_item(self, name: str) -> KHBBSItem:
data = item_table[name]
return KHBBSItem(name, data.classification, data.code, self.player)
def create_event(self, name: str) -> KHBBSItem:
data = event_item_table[name]
return KHBBSItem(name, data.classification, data.code, self.player)
def set_rules(self):
set_rules(self)
def create_regions(self):
create_regions(self.multiworld, self.player, self.options)
def generate_output(self, output_directory: str):
"""
Generates the .zip for OpenKH (The KH Mod Manager)
"""
patch_khbbs(self, output_directory, self.options.character)
def get_non_remote_location_ids(self):
non_remote_location_ids = []
for location in self.multiworld.get_filled_locations(self.player):
location_data = location_table[location.name]
if self.player == location.item.player:
item_data = item_table[location.item.name]
if location_data.type == "Chest":
if item_data.category in ["Attack Command", "Magic Command", "Item Command", "Friendship Command", "Movement Command", "Defense Command", "Reprisal Command", "Shotlock Command", "Key Item"] and not location_data.forced_remote and "Wayfinder" not in location.item.name:
non_remote_location_ids.append(location_data.code)
if location_data.type == "Sticker":
if item_data.category in ["Key Item"] and "Wayfinder" not in location.item.name:
non_remote_location_ids.append(location_data.code)
return non_remote_location_ids