86 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			86 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
 | 
						|
from __future__ import annotations
 | 
						|
import abc
 | 
						|
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union
 | 
						|
 | 
						|
from typing_extensions import TypeGuard
 | 
						|
 | 
						|
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components
 | 
						|
 | 
						|
if TYPE_CHECKING:
 | 
						|
    from SNIClient import SNIContext
 | 
						|
 | 
						|
component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe"))
 | 
						|
components.append(component)
 | 
						|
 | 
						|
 | 
						|
def valid_patch_suffix(obj: object) -> TypeGuard[Union[str, Iterable[str]]]:
 | 
						|
    """ make sure this is a valid value for the class variable `patch_suffix` """
 | 
						|
 | 
						|
    def valid_individual(one: object) -> TypeGuard[str]:
 | 
						|
        """ check an individual suffix """
 | 
						|
        # TODO: decide:                 len(one) > 3 and one.startswith(".ap") ?
 | 
						|
        # or keep it more general?
 | 
						|
        return isinstance(one, str) and len(one) > 1 and one.startswith(".")
 | 
						|
 | 
						|
    if isinstance(obj, str):
 | 
						|
        return valid_individual(obj)
 | 
						|
    if not isinstance(obj, Iterable):
 | 
						|
        return False
 | 
						|
    obj_it: Iterable[object] = obj
 | 
						|
    return all(valid_individual(each) for each in obj_it)
 | 
						|
 | 
						|
 | 
						|
class AutoSNIClientRegister(abc.ABCMeta):
 | 
						|
    game_handlers: ClassVar[Dict[str, SNIClient]] = {}
 | 
						|
 | 
						|
    def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoSNIClientRegister:
 | 
						|
        # construct class
 | 
						|
        new_class = super().__new__(cls, name, bases, dct)
 | 
						|
        if "game" in dct:
 | 
						|
            AutoSNIClientRegister.game_handlers[dct["game"]] = new_class()
 | 
						|
 | 
						|
        if "patch_suffix" in dct:
 | 
						|
            patch_suffix = dct["patch_suffix"]
 | 
						|
            assert valid_patch_suffix(patch_suffix), f"class {name} defining invalid {patch_suffix=}"
 | 
						|
 | 
						|
            existing_identifier = component.file_identifier
 | 
						|
            assert isinstance(existing_identifier, SuffixIdentifier), f"{existing_identifier=}"
 | 
						|
            new_suffixes = [*existing_identifier.suffixes]
 | 
						|
 | 
						|
            if isinstance(patch_suffix, str):
 | 
						|
                new_suffixes.append(patch_suffix)
 | 
						|
            else:
 | 
						|
                new_suffixes.extend(patch_suffix)
 | 
						|
 | 
						|
            component.file_identifier = SuffixIdentifier(*new_suffixes)
 | 
						|
 | 
						|
        return new_class
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    async def get_handler(ctx: SNIContext) -> Optional[SNIClient]:
 | 
						|
        for _game, handler in AutoSNIClientRegister.game_handlers.items():
 | 
						|
            if await handler.validate_rom(ctx):
 | 
						|
                return handler
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
 | 
						|
 | 
						|
    patch_suffix: ClassVar[Union[str, Iterable[str]]] = ()
 | 
						|
    """The file extension(s) this client is meant to open and patch (e.g. ".aplttp")"""
 | 
						|
 | 
						|
    @abc.abstractmethod
 | 
						|
    async def validate_rom(self, ctx: SNIContext) -> bool:
 | 
						|
        """ TODO: interface documentation here """
 | 
						|
        ...
 | 
						|
 | 
						|
    @abc.abstractmethod
 | 
						|
    async def game_watcher(self, ctx: SNIContext) -> None:
 | 
						|
        """ TODO: interface documentation here """
 | 
						|
        ...
 | 
						|
 | 
						|
    async def deathlink_kill_player(self, ctx: SNIContext) -> None:
 | 
						|
        """ override this with implementation to kill player """
 | 
						|
        pass
 |