Launcher: dynamic Launcher
This commit is contained in:
		
							parent
							
								
									9ee37b0ec5
								
							
						
					
					
						commit
						958829d491
					
				
							
								
								
									
										105
									
								
								Launcher.py
								
								
								
								
							
							
						
						
									
										105
									
								
								Launcher.py
								
								
								
								
							| 
						 | 
				
			
			@ -14,10 +14,11 @@ import itertools
 | 
			
		|||
import shlex
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
from enum import Enum, auto
 | 
			
		||||
from os.path import isfile
 | 
			
		||||
from shutil import which
 | 
			
		||||
from typing import Iterable, Sequence, Callable, Union, Optional
 | 
			
		||||
from typing import Sequence, Union, Optional
 | 
			
		||||
 | 
			
		||||
from worlds.LauncherComponents import Component, components, Type, SuffixIdentifier
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    import ModuleUpdate
 | 
			
		||||
| 
						 | 
				
			
			@ -70,108 +71,12 @@ def browse_files():
 | 
			
		|||
        webbrowser.open(file)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# noinspection PyArgumentList
 | 
			
		||||
class Type(Enum):
 | 
			
		||||
    TOOL = auto()
 | 
			
		||||
    FUNC = auto()  # not a real component
 | 
			
		||||
    CLIENT = auto()
 | 
			
		||||
    ADJUSTER = auto()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SuffixIdentifier:
 | 
			
		||||
    suffixes: Iterable[str]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args: str):
 | 
			
		||||
        self.suffixes = args
 | 
			
		||||
 | 
			
		||||
    def __call__(self, path: str):
 | 
			
		||||
        if isinstance(path, str):
 | 
			
		||||
            for suffix in self.suffixes:
 | 
			
		||||
                if path.endswith(suffix):
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Component:
 | 
			
		||||
    display_name: str
 | 
			
		||||
    type: Optional[Type]
 | 
			
		||||
    script_name: Optional[str]
 | 
			
		||||
    frozen_name: Optional[str]
 | 
			
		||||
    icon: str  # just the name, no suffix
 | 
			
		||||
    cli: bool
 | 
			
		||||
    func: Optional[Callable]
 | 
			
		||||
    file_identifier: Optional[Callable[[str], bool]]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
 | 
			
		||||
                 cli: bool = False, icon: str = 'icon', component_type: Type = None, func: Optional[Callable] = None,
 | 
			
		||||
                 file_identifier: Optional[Callable[[str], bool]] = None):
 | 
			
		||||
        self.display_name = display_name
 | 
			
		||||
        self.script_name = script_name
 | 
			
		||||
        self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
 | 
			
		||||
        self.icon = icon
 | 
			
		||||
        self.cli = cli
 | 
			
		||||
        self.type = component_type or \
 | 
			
		||||
            None if not display_name else \
 | 
			
		||||
            Type.FUNC if func else \
 | 
			
		||||
            Type.CLIENT if 'Client' in display_name else \
 | 
			
		||||
            Type.ADJUSTER if 'Adjuster' in display_name else Type.TOOL
 | 
			
		||||
        self.func = func
 | 
			
		||||
        self.file_identifier = file_identifier
 | 
			
		||||
 | 
			
		||||
    def handles_file(self, path: str):
 | 
			
		||||
        return self.file_identifier(path) if self.file_identifier else False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
components: Iterable[Component] = (
 | 
			
		||||
    # Launcher
 | 
			
		||||
    Component('', 'Launcher'),
 | 
			
		||||
    # Core
 | 
			
		||||
    Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
 | 
			
		||||
              file_identifier=SuffixIdentifier('.archipelago', '.zip')),
 | 
			
		||||
    Component('Generate', 'Generate', cli=True),
 | 
			
		||||
    Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'),
 | 
			
		||||
    # SNI
 | 
			
		||||
    Component('SNI Client', 'SNIClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3',
 | 
			
		||||
                                               '.apsmw', '.apl2ac')),
 | 
			
		||||
    Component('Links Awakening DX Client', 'LinksAwakeningClient', 
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apladx')),
 | 
			
		||||
    Component('LttP Adjuster', 'LttPAdjuster'),
 | 
			
		||||
    # Factorio
 | 
			
		||||
    Component('Factorio Client', 'FactorioClient'),
 | 
			
		||||
    # Minecraft
 | 
			
		||||
    Component('Minecraft Client', 'MinecraftClient', icon='mcicon', cli=True,
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apmc')),
 | 
			
		||||
    # Ocarina of Time
 | 
			
		||||
    Component('OoT Client', 'OoTClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apz5')),
 | 
			
		||||
    Component('OoT Adjuster', 'OoTAdjuster'),
 | 
			
		||||
    # FF1
 | 
			
		||||
    Component('FF1 Client', 'FF1Client'),
 | 
			
		||||
    # Pokémon
 | 
			
		||||
    Component('Pokemon Client', 'PokemonClient', file_identifier=SuffixIdentifier('.apred', '.apblue')),
 | 
			
		||||
    # TLoZ
 | 
			
		||||
    Component('Zelda 1 Client', 'Zelda1Client'),
 | 
			
		||||
    # ChecksFinder
 | 
			
		||||
    Component('ChecksFinder Client', 'ChecksFinderClient'),
 | 
			
		||||
    # Starcraft 2
 | 
			
		||||
    Component('Starcraft 2 Client', 'Starcraft2Client'),
 | 
			
		||||
    # Wargroove
 | 
			
		||||
    Component('Wargroove Client', 'WargrooveClient'),
 | 
			
		||||
    # Zillion
 | 
			
		||||
    Component('Zillion Client', 'ZillionClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apzl')),
 | 
			
		||||
    #Kingdom Hearts 2
 | 
			
		||||
    Component('KH2 Client', "KH2Client"),
 | 
			
		||||
components.extend([
 | 
			
		||||
    # Functions
 | 
			
		||||
    Component('Open host.yaml', func=open_host_yaml),
 | 
			
		||||
    Component('Open Patch', func=open_patch),
 | 
			
		||||
    Component('Browse Files', func=browse_files),
 | 
			
		||||
)
 | 
			
		||||
icon_paths = {
 | 
			
		||||
    'icon': local_path('data', 'icon.ico' if is_windows else 'icon.png'),
 | 
			
		||||
    'mcicon': local_path('data', 'mcicon.ico')
 | 
			
		||||
}
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def identify(path: Union[None, str]):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,6 +105,7 @@ Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, E
 | 
			
		|||
Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni
 | 
			
		||||
Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp
 | 
			
		||||
 | 
			
		||||
Source: "{#source_path}\ArchipelagoLauncher.exe"; DestDir: "{app}"; Flags: ignoreversion;
 | 
			
		||||
Source: "{#source_path}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator
 | 
			
		||||
Source: "{#source_path}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server
 | 
			
		||||
Source: "{#source_path}\ArchipelagoFactorioClient.exe";  DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										2
									
								
								setup.py
								
								
								
								
							| 
						 | 
				
			
			@ -40,7 +40,7 @@ if __name__ == "__main__":
 | 
			
		|||
    ModuleUpdate.update(yes="--yes" in sys.argv or "-y" in sys.argv)
 | 
			
		||||
    ModuleUpdate.update_ran = False  # restore for later
 | 
			
		||||
 | 
			
		||||
from Launcher import components, icon_paths
 | 
			
		||||
from worlds.LauncherComponents import components, icon_paths
 | 
			
		||||
from Utils import version_tuple, is_windows, is_linux
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
from enum import Enum, auto
 | 
			
		||||
from typing import Optional, Callable, List, Iterable
 | 
			
		||||
 | 
			
		||||
from Utils import local_path, is_windows
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Type(Enum):
 | 
			
		||||
    TOOL = auto()
 | 
			
		||||
    FUNC = auto()  # not a real component
 | 
			
		||||
    CLIENT = auto()
 | 
			
		||||
    ADJUSTER = auto()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Component:
 | 
			
		||||
    display_name: str
 | 
			
		||||
    type: Optional[Type]
 | 
			
		||||
    script_name: Optional[str]
 | 
			
		||||
    frozen_name: Optional[str]
 | 
			
		||||
    icon: str  # just the name, no suffix
 | 
			
		||||
    cli: bool
 | 
			
		||||
    func: Optional[Callable]
 | 
			
		||||
    file_identifier: Optional[Callable[[str], bool]]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
 | 
			
		||||
                 cli: bool = False, icon: str = 'icon', component_type: Type = None, func: Optional[Callable] = None,
 | 
			
		||||
                 file_identifier: Optional[Callable[[str], bool]] = None):
 | 
			
		||||
        self.display_name = display_name
 | 
			
		||||
        self.script_name = script_name
 | 
			
		||||
        self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
 | 
			
		||||
        self.icon = icon
 | 
			
		||||
        self.cli = cli
 | 
			
		||||
        self.type = component_type or \
 | 
			
		||||
            None if not display_name else \
 | 
			
		||||
            Type.FUNC if func else \
 | 
			
		||||
            Type.CLIENT if 'Client' in display_name else \
 | 
			
		||||
            Type.ADJUSTER if 'Adjuster' in display_name else Type.TOOL
 | 
			
		||||
        self.func = func
 | 
			
		||||
        self.file_identifier = file_identifier
 | 
			
		||||
 | 
			
		||||
    def handles_file(self, path: str):
 | 
			
		||||
        return self.file_identifier(path) if self.file_identifier else False
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f"{self.__class__.__name__}({self.display_name})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SuffixIdentifier:
 | 
			
		||||
    suffixes: Iterable[str]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args: str):
 | 
			
		||||
        self.suffixes = args
 | 
			
		||||
 | 
			
		||||
    def __call__(self, path: str):
 | 
			
		||||
        if isinstance(path, str):
 | 
			
		||||
            for suffix in self.suffixes:
 | 
			
		||||
                if path.endswith(suffix):
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
components: List[Component] = [
 | 
			
		||||
    # Launcher
 | 
			
		||||
    Component('', 'Launcher'),
 | 
			
		||||
    # Core
 | 
			
		||||
    Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
 | 
			
		||||
              file_identifier=SuffixIdentifier('.archipelago', '.zip')),
 | 
			
		||||
    Component('Generate', 'Generate', cli=True),
 | 
			
		||||
    Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'),
 | 
			
		||||
    # SNI
 | 
			
		||||
    Component('SNI Client', 'SNIClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3',
 | 
			
		||||
                                               '.apsmw', '.apl2ac')),
 | 
			
		||||
    Component('Links Awakening DX Client', 'LinksAwakeningClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apladx')),
 | 
			
		||||
    Component('LttP Adjuster', 'LttPAdjuster'),
 | 
			
		||||
    # Minecraft
 | 
			
		||||
    Component('Minecraft Client', 'MinecraftClient', icon='mcicon', cli=True,
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apmc')),
 | 
			
		||||
    # Ocarina of Time
 | 
			
		||||
    Component('OoT Client', 'OoTClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apz5')),
 | 
			
		||||
    Component('OoT Adjuster', 'OoTAdjuster'),
 | 
			
		||||
    # FF1
 | 
			
		||||
    Component('FF1 Client', 'FF1Client'),
 | 
			
		||||
    # Pokémon
 | 
			
		||||
    Component('Pokemon Client', 'PokemonClient', file_identifier=SuffixIdentifier('.apred', '.apblue')),
 | 
			
		||||
    # TLoZ
 | 
			
		||||
    Component('Zelda 1 Client', 'Zelda1Client'),
 | 
			
		||||
    # ChecksFinder
 | 
			
		||||
    Component('ChecksFinder Client', 'ChecksFinderClient'),
 | 
			
		||||
    # Starcraft 2
 | 
			
		||||
    Component('Starcraft 2 Client', 'Starcraft2Client'),
 | 
			
		||||
    # Wargroove
 | 
			
		||||
    Component('Wargroove Client', 'WargrooveClient'),
 | 
			
		||||
    # Zillion
 | 
			
		||||
    Component('Zillion Client', 'ZillionClient',
 | 
			
		||||
              file_identifier=SuffixIdentifier('.apzl')),
 | 
			
		||||
    #Kingdom Hearts 2
 | 
			
		||||
    Component('KH2 Client', "KH2Client"),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
icon_paths = {
 | 
			
		||||
    'icon': local_path('data', 'icon.ico' if is_windows else 'icon.png'),
 | 
			
		||||
    'mcicon': local_path('data', 'mcicon.ico')
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +40,9 @@ class WorldSource(typing.NamedTuple):
 | 
			
		|||
    path: str  # typically relative path from this module
 | 
			
		||||
    is_zip: bool = False
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# find potential world containers, currently folders and zip-importable .apworld's
 | 
			
		||||
world_sources: typing.List[WorldSource] = []
 | 
			
		||||
| 
						 | 
				
			
			@ -55,24 +58,35 @@ for file in os.scandir(folder):
 | 
			
		|||
# import all submodules to trigger AutoWorldRegister
 | 
			
		||||
world_sources.sort()
 | 
			
		||||
for world_source in world_sources:
 | 
			
		||||
    if world_source.is_zip:
 | 
			
		||||
        importer = zipimport.zipimporter(os.path.join(folder, world_source.path))
 | 
			
		||||
        if hasattr(importer, "find_spec"):  # new in Python 3.10
 | 
			
		||||
            spec = importer.find_spec(world_source.path.split(".", 1)[0])
 | 
			
		||||
            mod = importlib.util.module_from_spec(spec)
 | 
			
		||||
        else:  # TODO: remove with 3.8 support
 | 
			
		||||
            mod = importer.load_module(world_source.path.split(".", 1)[0])
 | 
			
		||||
    try:
 | 
			
		||||
        if world_source.is_zip:
 | 
			
		||||
            importer = zipimport.zipimporter(os.path.join(folder, world_source.path))
 | 
			
		||||
            if hasattr(importer, "find_spec"):  # new in Python 3.10
 | 
			
		||||
                spec = importer.find_spec(world_source.path.split(".", 1)[0])
 | 
			
		||||
                mod = importlib.util.module_from_spec(spec)
 | 
			
		||||
            else:  # TODO: remove with 3.8 support
 | 
			
		||||
                mod = importer.load_module(world_source.path.split(".", 1)[0])
 | 
			
		||||
 | 
			
		||||
        mod.__package__ = f"worlds.{mod.__package__}"
 | 
			
		||||
        mod.__name__ = f"worlds.{mod.__name__}"
 | 
			
		||||
        sys.modules[mod.__name__] = mod
 | 
			
		||||
        with warnings.catch_warnings():
 | 
			
		||||
            warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
 | 
			
		||||
            # Found no equivalent for < 3.10
 | 
			
		||||
            if hasattr(importer, "exec_module"):
 | 
			
		||||
                importer.exec_module(mod)
 | 
			
		||||
    else:
 | 
			
		||||
        importlib.import_module(f".{world_source.path}", "worlds")
 | 
			
		||||
            mod.__package__ = f"worlds.{mod.__package__}"
 | 
			
		||||
            mod.__name__ = f"worlds.{mod.__name__}"
 | 
			
		||||
            sys.modules[mod.__name__] = mod
 | 
			
		||||
            with warnings.catch_warnings():
 | 
			
		||||
                warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
 | 
			
		||||
                # Found no equivalent for < 3.10
 | 
			
		||||
                if hasattr(importer, "exec_module"):
 | 
			
		||||
                    importer.exec_module(mod)
 | 
			
		||||
        else:
 | 
			
		||||
            importlib.import_module(f".{world_source.path}", "worlds")
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        # A single world failing can still mean enough is working for the user, log and carry on
 | 
			
		||||
        import traceback
 | 
			
		||||
        import io
 | 
			
		||||
        file_like = io.StringIO()
 | 
			
		||||
        print(f"Could not load world {world_source}:", file=file_like)
 | 
			
		||||
        traceback.print_exc(file=file_like)
 | 
			
		||||
        file_like.seek(0)
 | 
			
		||||
        import logging
 | 
			
		||||
        logging.exception(file_like.read())
 | 
			
		||||
 | 
			
		||||
lookup_any_item_id_to_name = {}
 | 
			
		||||
lookup_any_location_id_to_name = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,9 @@ from .Technologies import base_tech_table, recipe_sources, base_technology_table
 | 
			
		|||
    get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \
 | 
			
		||||
    fluids, stacking_items, valid_ingredients, progressive_rows
 | 
			
		||||
from .Locations import location_pools, location_table
 | 
			
		||||
from worlds.LauncherComponents import Component, components
 | 
			
		||||
 | 
			
		||||
components.append(Component("Factorio Client", "FactorioClient"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorioWeb(WebWorld):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue