 c9625e1b35
			
		
	
	c9625e1b35
	
	
	
		
			
			* 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>
		
			
				
	
	
		
			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
 |