102 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			102 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
"""
 | 
						|
A module containing the BizHawkClient base class and metaclass
 | 
						|
"""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import abc
 | 
						|
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
 | 
						|
 | 
						|
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
 | 
						|
 | 
						|
if TYPE_CHECKING:
 | 
						|
    from .context import BizHawkClientContext
 | 
						|
 | 
						|
 | 
						|
def launch_client(*args) -> None:
 | 
						|
    from .context import launch
 | 
						|
    launch_subprocess(launch, name="BizHawkClient")
 | 
						|
 | 
						|
 | 
						|
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
 | 
						|
                      file_identifier=SuffixIdentifier())
 | 
						|
components.append(component)
 | 
						|
 | 
						|
 | 
						|
class AutoBizHawkClientRegister(abc.ABCMeta):
 | 
						|
    game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
 | 
						|
 | 
						|
    def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
 | 
						|
        new_class = super().__new__(cls, name, bases, namespace)
 | 
						|
 | 
						|
        # Register handler
 | 
						|
        if "system" in namespace:
 | 
						|
            systems = (namespace["system"],) if type(namespace["system"]) is str else tuple(sorted(namespace["system"]))
 | 
						|
            if systems not in AutoBizHawkClientRegister.game_handlers:
 | 
						|
                AutoBizHawkClientRegister.game_handlers[systems] = {}
 | 
						|
 | 
						|
            if "game" in namespace:
 | 
						|
                AutoBizHawkClientRegister.game_handlers[systems][namespace["game"]] = new_class()
 | 
						|
 | 
						|
        # Update launcher component's suffixes
 | 
						|
        if "patch_suffix" in namespace:
 | 
						|
            if namespace["patch_suffix"] is not None:
 | 
						|
                existing_identifier: SuffixIdentifier = component.file_identifier
 | 
						|
                new_suffixes = [*existing_identifier.suffixes]
 | 
						|
 | 
						|
                if type(namespace["patch_suffix"]) is str:
 | 
						|
                    new_suffixes.append(namespace["patch_suffix"])
 | 
						|
                else:
 | 
						|
                    new_suffixes.extend(namespace["patch_suffix"])
 | 
						|
 | 
						|
                component.file_identifier = SuffixIdentifier(*new_suffixes)
 | 
						|
 | 
						|
        return new_class
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]:
 | 
						|
        for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
 | 
						|
            if system in systems:
 | 
						|
                for handler in handlers.values():
 | 
						|
                    if await handler.validate_rom(ctx):
 | 
						|
                        return handler
 | 
						|
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
 | 
						|
    system: ClassVar[Union[str, Tuple[str, ...]]]
 | 
						|
    """The system(s) that the game this client is for runs on"""
 | 
						|
 | 
						|
    game: ClassVar[str]
 | 
						|
    """The game this client is for"""
 | 
						|
 | 
						|
    patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
 | 
						|
    """The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
 | 
						|
 | 
						|
    @abc.abstractmethod
 | 
						|
    async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
 | 
						|
        """Should return whether the currently loaded ROM should be handled by this client. You might read the game name
 | 
						|
        from the ROM header, for example. This function will only be asked to validate ROMs from the system set by the
 | 
						|
        client class, so you do not need to check the system yourself.
 | 
						|
 | 
						|
        Once this function has determined that the ROM should be handled by this client, it should also modify `ctx`
 | 
						|
        as necessary (such as setting `ctx.game = self.game`, modifying `ctx.items_handling`, etc...)."""
 | 
						|
        ...
 | 
						|
 | 
						|
    async def set_auth(self, ctx: "BizHawkClientContext") -> None:
 | 
						|
        """Should set ctx.auth in anticipation of sending a `Connected` packet. You may override this if you store slot
 | 
						|
        name in your patched ROM. If ctx.auth is not set after calling, the player will be prompted to enter their
 | 
						|
        username."""
 | 
						|
        pass
 | 
						|
 | 
						|
    @abc.abstractmethod
 | 
						|
    async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
 | 
						|
        """Runs on a loop with the approximate interval `ctx.watcher_timeout`. The currently loaded ROM is guaranteed
 | 
						|
        to have passed your validator when this function is called, and the emulator is very likely to be connected."""
 | 
						|
        ...
 | 
						|
 | 
						|
    def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
 | 
						|
        """For handling packages from the server. Called from `BizHawkClientContext.on_package`."""
 | 
						|
        pass
 |