Saving Princess: implement new game (#3238)
* Saving Princess: initial commit * settings -> options Co-authored-by: Scipio Wright <scipiowright@gmail.com> * settings -> options Co-authored-by: Scipio Wright <scipiowright@gmail.com> * replace RegionData class with List[str] RegionData was only wrapping a List[str], so we can directly use List[str] * world: MultiWorld -> multiworld: MultiWorld * use world's random instead of multiworld's * use state's has_any and has_all where applicable * remove unused StartInventory import * reorder PerGameCommonOptions * fix relative AutoWorld import Co-authored-by: Scipio Wright <scipiowright@gmail.com> * clean up double spaces * local commands -> Local Commands Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * remove redundant which items section Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * game info rework * clean up item count redundancy * add game to readme and codeowners * fix get_region_entrance return type * world.multiworld.get -> world.get * add more events added events for the boss kills that open the gate, as well as for system power being restored these only apply if expanded pool is not selected * add client/autoupdater to launcher * reorder commands in game info * update docs with automated installation info * add quick links to doc * Update setup_en.md * remove standalone saving princess client * doc fixes * code improvements and redundant default removal as suggested by @Exempt-Medic this includes the removal of events from the item/location name to id, as well as checking for the player name being ASCII * add option to change launch coammnd the LaunchCommand option is filled to either the executable or wine with the necessary arguments based on Utils.is_windows * simplify valid install check * mod installer improvements now deletes possible existing files before installing the mod * add option groups and presets * add required client version * update docs about cheat items pop-ups items sent directly by the server (such as with starting inventory) now have pop-ups just like any other item * add Steam Input issue to faq * Saving Princess: BRAINOS requires all weapons * Saving Princess: Download dll and patch together Previously, gm-apclientpp.dll was downloaded from its own repo With this update, the dll is instead extracted from the same zip as the game's patch * Saving Princess: Add URI launch support * Saving Princess: goal also requires all weapons given it's past brainos * Saving Princess: update docs automatic connection support was added, docs now reflect this * Saving Princess: extend([item]) -> append(item) * Saving Princess: automatic connection validation also parses the slot, password and host:port into parameters for the game * Saving Princess: change subprocess .run to .Popen This keeps the game from freezing the launcher while it is running --------- Co-authored-by: Scipio Wright <scipiowright@gmail.com> Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										174
									
								
								worlds/saving_princess/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								worlds/saving_princess/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| 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 | ||||
		Reference in New Issue
	
	Block a user
	 LeonarthCG
					LeonarthCG