Files
dockipelago/worlds/hades/__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

314 lines
14 KiB
Python

import string
import typing
import settings
from BaseClasses import Entrance, Item, MultiWorld, Region, Tutorial
from .Items import event_item_pairs_weapon_mode, item_table, item_table_pacts, HadesItem, event_item_pairs, \
create_pact_pool_amount, item_table_keepsake, item_table_weapons, \
item_table_store, item_table_hidden_aspects, create_trap_pool, item_name_groups
from .Locations import setup_location_table_with_settings, give_all_locations_table, HadesLocation, \
location_table_fates_events, location_name_groups
from .Options import hades_option_presets, hades_option_groups, HadesOptions
from .Regions import create_regions
from .Rules import set_rules
from worlds.AutoWorld import WebWorld, World
from worlds.LauncherComponents import icon_paths, Component, components, Type, launch_subprocess
from Utils import local_path
def launch_client():
from .Client import launch
launch_subprocess(launch, "HadesClient")
icon_paths['hades_icon'] = local_path('data', 'hades_icon.png')
components.append(Component("Hades Client", "HadesClient",
func=launch_client, component_type=Type.CLIENT, icon='hades_icon'))
class HadesSettings(settings.Group):
class StyxScribePath(settings.UserFilePath):
"""Path to the StyxScribe install"""
styx_scribe_path: StyxScribePath = StyxScribePath(
"C:/Program Files/Steam/steamapps/common/Hades/StyxScribe.py")
class HadesWeb(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up Hades for Archipelago. "
"This guide covers single-player, multiworld, and related software.",
"English",
"Hades.md",
"Hades/en",
["Naix"]
)]
options_presets = hades_option_presets
option_groups = hades_option_groups
class HadesWorld(World):
"""
Hades is a rogue-like dungeon crawler in which you defy the god of the dead as you hack and slash
your way out of the Underworld of Greek myth.
"""
options: HadesOptions
options_dataclass = HadesOptions
game = "Hades"
topology_present = False
settings: typing.ClassVar[HadesSettings]
web = HadesWeb()
required_client_version = (0, 6, 4)
polycosmos_version = "0.15"
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = give_all_locations_table()
item_name_groups = item_name_groups
location_name_groups = location_name_groups
def create_items(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
pool = []
# Fill pact items
item_pool_pacts = create_pact_pool_amount(self.options)
# Fill pact items
if self.options.heat_system == "reverse_heat":
for name, data in item_table_pacts.items():
for amount in range(item_pool_pacts.get(name, 1)):
item = HadesItem(name, self.player)
pool.append(item)
# Fill keepsake items
if self.options.keepsakesanity:
for name, data in item_table_keepsake.items():
item = HadesItem(name, self.player)
pool.append(item)
# Fill weapons items
if self.options.weaponsanity:
for name, data in item_table_weapons.items():
if self.should_ignore_weapon(name):
continue
item = HadesItem(name, self.player)
pool.append(item)
# Fill store items
if self.options.storesanity:
for name, data in item_table_store.items():
item = HadesItem(name, self.player)
pool.append(item)
if self.options.hidden_aspectsanity:
for name, date in item_table_hidden_aspects.items():
item = HadesItem(name, self.player)
pool.append(item)
# Pair up our event locations with our event items
if self.options.location_system == "room_weapon_based":
for event, item in event_item_pairs_weapon_mode.items():
event_item = HadesItem(item, self.player)
self.multiworld.get_location(
event, self.player).place_locked_item(event_item)
else:
for event, item in event_item_pairs.items():
event_item = HadesItem(item, self.player)
self.multiworld.get_location(
event, self.player).place_locked_item(event_item)
# Fill filler items uniformly. Maybe later we can tweak this.
index = 0
total_fillers_needed = len(local_location_table)-len(pool)-len(location_table_fates_events)
if self.options.location_system == "room_weapon_based":
# Substract the 4 bosses for each of the 6 weapons = 24
total_fillers_needed = total_fillers_needed - 24
else:
# Substract the 4 bosses
total_fillers_needed = total_fillers_needed - 4
#Get the percentagesa and compute then compute the quantity of each one
darkness_percentage = self.options.darkness_pack_percentage
keys_pack_percentage = self.options.keys_pack_percentage
gemstones_pack_percentage = self.options.gemstones_pack_percentage
diamonds_pack_percentage = self.options.diamonds_pack_percentage
titan_blood_pack_percentage = self.options.titan_blood_pack_percentage
nectar_pack_percentage = self.options.nectar_pack_percentage
ambrosia_pack_percentage = self.options.ambrosia_pack_percentage
helper_percentage = self.options.filler_helper_percentage
trap_percentage = self.options.filler_trap_percentage
total_percentage = darkness_percentage + keys_pack_percentage + gemstones_pack_percentage \
+ diamonds_pack_percentage + titan_blood_pack_percentage + nectar_pack_percentage \
+ ambrosia_pack_percentage + helper_percentage + trap_percentage
if total_percentage == 0:
darkness_percentage = 100
correction = 100/total_percentage
traps_needed = int(total_fillers_needed * trap_percentage * correction / 100)
helpers_needed = int(total_fillers_needed * helper_percentage * correction / 100)
ambrosia_needed = int(total_fillers_needed * ambrosia_pack_percentage * correction / 100)
nectar_needed = int(total_fillers_needed * nectar_pack_percentage * correction / 100)
titan_blood_needed = int(total_fillers_needed * titan_blood_pack_percentage * correction / 100)
diamonds_needed = int(total_fillers_needed * diamonds_pack_percentage * correction / 100)
gemstones_needed = int(total_fillers_needed * gemstones_pack_percentage * correction / 100)
keys_needed = int(total_fillers_needed * keys_pack_percentage * correction / 100)
darkness_needed = total_fillers_needed - keys_needed - gemstones_needed - diamonds_needed \
- titan_blood_needed - nectar_needed - ambrosia_needed - helpers_needed - traps_needed
trap_pool = create_trap_pool()
#Fill the standard fillers
for amount in range(0, darkness_needed):
item = HadesItem("Darkness", self.player)
pool.append(item)
for amount in range(0, keys_needed):
item = HadesItem("Keys", self.player)
pool.append(item)
for amount in range(0, gemstones_needed):
item = HadesItem("Gemstones", self.player)
pool.append(item)
for amount in range(0, diamonds_needed):
item = HadesItem("Diamonds", self.player)
pool.append(item)
for amount in range(0, titan_blood_needed):
item = HadesItem("TitanBlood", self.player)
pool.append(item)
for amount in range(0, nectar_needed):
item = HadesItem("Nectar", self.player)
pool.append(item)
for amount in range(0, ambrosia_needed):
item = HadesItem("Ambrosia", self.player)
pool.append(item)
# Fill helpers
health_helpers_needed = int(helpers_needed * self.options.max_health_helper_percentage / 100)
money_helpers_needed = int(helpers_needed * self.options.initial_money_helper_percentage / 100)
boon_helpers_needed = helpers_needed-health_helpers_needed - money_helpers_needed
for amount in range(0, health_helpers_needed):
item = HadesItem("Max Health Helper", self.player)
pool.append(item)
for amount in range(0, min(money_helpers_needed, helpers_needed - health_helpers_needed)):
item = HadesItem("Initial Money Helper", self.player)
pool.append(item)
for amount in range(0, max(boon_helpers_needed, 0)):
item = HadesItem("Boon Boost Helper", self.player)
pool.append(item)
index = 0
# Fill traps
for amount in range(0, traps_needed):
item_name = trap_pool[index]
item = HadesItem(item_name, self.player)
pool.append(item)
index = (index + 1) % len(trap_pool)
self.multiworld.itempool += pool
def should_ignore_weapon(self, name : str) -> bool:
if self.options.initial_weapon.value == 0 and name == "Sword Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 1 and name == "Bow Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 2 and name == "Spear Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 3 and name == "Shield Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 4 and name == "Fist Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 5 and name == "Gun Weapon Unlock Item":
return True
return False
def set_rules(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
set_rules(self.multiworld, self.player, self.calculate_number_of_pact_items(
), local_location_table, self.options)
def calculate_number_of_pact_items(self) -> int:
# Go thorugh every option and count what is the chosen level
total = int(self.options.hard_labor_pact_amount)
total += int(self.options.lasting_consequences_pact_amount)
total += int(self.options.convenience_fee_pact_amount)
total += int(self.options.jury_summons_pact_amount)
total += int(self.options.extreme_measures_pact_amount)
total += int(self.options.calisthenics_program_pact_amount)
total += int(self.options.benefits_package_pact_amount)
total += int(self.options.middle_management_pact_amount)
total += int(self.options.underworld_customs_pact_amount)
total += int(self.options.forced_overtime_pact_amount)
total += int(self.options.heightened_security_pact_amount)
total += int(self.options.routine_inspection_pact_amount)
total += int(self.options.damage_control_pact_amount)
total += int(self.options.approval_process_pact_amount)
total += int(self.options.tight_deadline_pact_amount)
total += int(self.options.personal_liability_pact_amount)
return total
def create_item(self, name: str) -> Item:
return HadesItem(name, self.player)
def create_regions(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
create_regions(self, local_location_table)
def fill_slot_data(self) -> dict:
slot_data = self.options.as_dict("initial_weapon", "location_system", "score_rewards_amount", "keepsakesanity",
"weaponsanity", "hidden_aspectsanity", "storesanity", "fatesanity",
"hades_defeats_needed", "weapons_clears_needed", "keepsakes_needed",
"fates_needed", "heat_system", "hard_labor_pact_amount",
"lasting_consequences_pact_amount", "convenience_fee_pact_amount",
"jury_summons_pact_amount", "extreme_measures_pact_amount",
"calisthenics_program_pact_amount", "benefits_package_pact_amount",
"middle_management_pact_amount", "underworld_customs_pact_amount",
"forced_overtime_pact_amount", "heightened_security_pact_amount",
"routine_inspection_pact_amount", "damage_control_pact_amount",
"approval_process_pact_amount", "tight_deadline_pact_amount",
"personal_liability_pact_amount", "darkness_pack_value", "keys_pack_value",
"gemstones_pack_value", "diamonds_pack_value", "titan_blood_pack_value",
"nectar_pack_value", "ambrosia_pack_value", "filler_helper_percentage",
"max_health_helper_percentage", "initial_money_helper_percentage",
"filler_trap_percentage", "reverse_order_em", "ignore_greece_deaths",
"store_give_hints", "automatic_rooms_finish_on_hades_defeat", "disable_late_styx", "death_link")
slot_data['seed'] = "".join(self.random.choice(string.ascii_letters) for i in range(16))
slot_data["version_check"] = self.polycosmos_version
return slot_data
def get_filler_item_name(self) -> str:
return "Darkness"
def create_region(multiworld: MultiWorld, player: int, location_database, name: str, locations=None, exits=None) -> Region:
ret = Region(name, player, multiworld)
if locations:
for location in locations:
loc_id = location_database.get(location, 0)
location = HadesLocation(player, location, loc_id, ret)
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret