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
							 |