175 lines
6.7 KiB
Python
175 lines
6.7 KiB
Python
from typing import ClassVar, Dict, Any, Type, List, Union
|
|
|
|
import Utils
|
|
from BaseClasses import Tutorial, ItemClassification as ItemClass
|
|
from Options import PerGameCommonOptions, OptionError
|
|
from settings import Group, UserFilePath, LocalFolderPath, Bool
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from worlds.LauncherComponents import components, Component, launch_subprocess, Type as ComponentType
|
|
from . import Options, Items, Locations
|
|
from .Constants import *
|
|
|
|
|
|
def launch_client(*args: str):
|
|
from .Client import launch
|
|
launch_subprocess(launch(*args), name=CLIENT_NAME)
|
|
|
|
|
|
components.append(
|
|
Component(f"{GAME_NAME} Client", game_name=GAME_NAME, func=launch_client, component_type=ComponentType.CLIENT, supports_uri=True)
|
|
)
|
|
|
|
|
|
class SavingPrincessSettings(Group):
|
|
class GamePath(UserFilePath):
|
|
"""Path to the game executable from which files are extracted"""
|
|
description = "the Saving Princess game executable"
|
|
is_exe = True
|
|
md5s = [GAME_HASH]
|
|
|
|
class InstallFolder(LocalFolderPath):
|
|
"""Path to the mod installation folder"""
|
|
description = "the folder to install Saving Princess Archipelago to"
|
|
|
|
class LaunchGame(Bool):
|
|
"""Set this to false to never autostart the game"""
|
|
|
|
class LaunchCommand(str):
|
|
"""
|
|
The console command that will be used to launch the game
|
|
The command will be executed with the installation folder as the current directory
|
|
"""
|
|
|
|
exe_path: GamePath = GamePath("Saving Princess.exe")
|
|
install_folder: InstallFolder = InstallFolder("Saving Princess")
|
|
launch_game: Union[LaunchGame, bool] = True
|
|
launch_command: LaunchCommand = LaunchCommand('"Saving Princess v0_8.exe"' if Utils.is_windows
|
|
else 'wine "Saving Princess v0_8.exe"')
|
|
|
|
|
|
class SavingPrincessWeb(WebWorld):
|
|
theme = "partyTime"
|
|
bug_report_page = "https://github.com/LeonarthCG/saving-princess-archipelago/issues"
|
|
setup_en = Tutorial(
|
|
"Multiworld Setup Guide",
|
|
"A guide to setting up Saving Princess for Archipelago multiworld.",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["LeonarthCG"]
|
|
)
|
|
tutorials = [setup_en]
|
|
options_presets = Options.presets
|
|
option_groups = Options.groups
|
|
|
|
|
|
class SavingPrincessWorld(World):
|
|
"""
|
|
Explore a space station crawling with rogue machines and even rival bounty hunters
|
|
with the same objective as you - but with far, far different intentions!
|
|
|
|
Expand your arsenal as you collect upgrades to your trusty arm cannon and armor!
|
|
""" # Excerpt from itch
|
|
game = GAME_NAME
|
|
web = SavingPrincessWeb()
|
|
required_client_version = (0, 5, 0)
|
|
|
|
topology_present = False
|
|
|
|
item_name_to_id = {
|
|
key: value.code for key, value in (Items.item_dict.items() - Items.item_dict_events.items())
|
|
}
|
|
location_name_to_id = {
|
|
key: value.code for key, value in (Locations.location_dict.items() - Locations.location_dict_events.items())
|
|
}
|
|
|
|
item_name_groups = {
|
|
"Weapons": {key for key in Items.item_dict_weapons.keys()},
|
|
"Upgrades": {key for key in Items.item_dict_upgrades.keys()},
|
|
"Keys": {key for key in Items.item_dict_keys.keys()},
|
|
"Filler": {key for key in Items.item_dict_filler.keys()},
|
|
"Traps": {key for key in Items.item_dict_traps.keys()},
|
|
}
|
|
|
|
options_dataclass: ClassVar[Type[PerGameCommonOptions]] = Options.SavingPrincessOptions
|
|
options: Options.SavingPrincessOptions
|
|
settings_key = "saving_princess_settings"
|
|
settings: ClassVar[SavingPrincessSettings]
|
|
|
|
is_pool_expanded: bool = False
|
|
music_table: List[int] = list(range(16))
|
|
|
|
def generate_early(self) -> None:
|
|
if not self.player_name.isascii():
|
|
raise OptionError(f"{self.player_name}'s name must be only ASCII.")
|
|
self.is_pool_expanded = self.options.expanded_pool > 0
|
|
if self.options.music_shuffle:
|
|
self.random.shuffle(self.music_table)
|
|
# find zzz and purple and swap them back to their original positions
|
|
for song_id in [9, 13]:
|
|
song_index = self.music_table.index(song_id)
|
|
t = self.music_table[song_id]
|
|
self.music_table[song_id] = song_id
|
|
self.music_table[song_index] = t
|
|
|
|
def create_regions(self) -> None:
|
|
from .Regions import create_regions
|
|
create_regions(self.multiworld, self.player, self.is_pool_expanded)
|
|
|
|
def create_items(self) -> None:
|
|
items_made: int = 0
|
|
|
|
# now, for each item
|
|
item_dict = Items.item_dict_expanded if self.is_pool_expanded else Items.item_dict_base
|
|
for item_name, item_data in item_dict.items():
|
|
# create count copies of the item
|
|
for i in range(item_data.count):
|
|
self.multiworld.itempool.append(self.create_item(item_name))
|
|
items_made += item_data.count
|
|
# and create count_extra useful copies of the item
|
|
original_item_class: ItemClass = item_data.item_class
|
|
item_data.item_class = ItemClass.useful
|
|
for i in range(item_data.count_extra):
|
|
self.multiworld.itempool.append(self.create_item(item_name))
|
|
item_data.item_class = original_item_class
|
|
items_made += item_data.count_extra
|
|
|
|
# get the number of unfilled locations, that is, locations for items - items generated
|
|
location_count = len(Locations.location_dict_base)
|
|
if self.is_pool_expanded:
|
|
location_count = len(Locations.location_dict_expanded)
|
|
junk_count: int = location_count - items_made
|
|
|
|
# and generate as many junk items as unfilled locations
|
|
for i in range(junk_count):
|
|
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
|
|
|
|
def create_item(self, name: str) -> Items.SavingPrincessItem:
|
|
return Items.item_dict[name].create_item(self.player)
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
filler_list = list(Items.item_dict_filler.keys())
|
|
# check if this is going to be a trap
|
|
if self.random.randint(0, 99) < self.options.trap_chance:
|
|
filler_list = list(Items.item_dict_traps.keys())
|
|
# and return one of the names at random
|
|
return self.random.choice(filler_list)
|
|
|
|
def set_rules(self):
|
|
from .Rules import set_rules
|
|
set_rules(self)
|
|
|
|
def fill_slot_data(self) -> Dict[str, Any]:
|
|
slot_data = self.options.as_dict(
|
|
"death_link",
|
|
"expanded_pool",
|
|
"instant_saving",
|
|
"sprint_availability",
|
|
"cliff_weapon_upgrade",
|
|
"ace_weapon_upgrade",
|
|
"shake_intensity",
|
|
"iframes_duration",
|
|
)
|
|
slot_data["music_table"] = self.music_table
|
|
return slot_data
|