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 |