Archipelago/worlds/saving_princess/__init__.py

175 lines
6.7 KiB
Python
Raw Normal View History

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>
2024-12-07 10:29:27 +00:00
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