mirror of
https://github.com/solero/houdini.git
synced 2025-10-18 05:18:13 +00:00
Initial commit
This commit is contained in:
15
Houdini/Handlers/Login/Login.py
Normal file
15
Houdini/Handlers/Login/Login.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from Houdini import Handlers
|
||||
from Houdini.Handlers import XTPacket, XMLPacket
|
||||
from Houdini.Converters import CredentialsConverter, CommaConverter
|
||||
|
||||
|
||||
@Handlers.handler(XMLPacket('login'))
|
||||
async def handle_login(p, credentials: CredentialsConverter):
|
||||
username, password = credentials
|
||||
p.logger.info('{}:{} is logging in!'.format(username, password))
|
||||
|
||||
|
||||
@Handlers.handler(XTPacket('t', 'c'), pre_login=True)
|
||||
@Handlers.player_in_room(100)
|
||||
async def handle_test(p, numbers: CommaConverter):
|
||||
print(list(numbers))
|
0
Houdini/Handlers/Login/__init__.py
Normal file
0
Houdini/Handlers/Login/__init__.py
Normal file
12
Houdini/Handlers/Play/Navigation.py
Normal file
12
Houdini/Handlers/Play/Navigation.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from Houdini.Handlers import Handlers, XTPacket
|
||||
from Houdini.Converters import RoomConverter
|
||||
|
||||
|
||||
@Handlers.handler(XTPacket('j', 'js'))
|
||||
async def handle_join_world(p, is_moderator: bool, is_mascot: bool, is_member: bool):
|
||||
print(p, is_moderator, is_mascot, is_member)
|
||||
|
||||
|
||||
@Handlers.handler(XTPacket('j', 'jr'))
|
||||
async def handle_join_room(p, room: RoomConverter):
|
||||
print(room)
|
0
Houdini/Handlers/Play/__init__.py
Normal file
0
Houdini/Handlers/Play/__init__.py
Normal file
353
Houdini/Handlers/__init__.py
Normal file
353
Houdini/Handlers/__init__.py
Normal file
@@ -0,0 +1,353 @@
|
||||
import inspect
|
||||
import time
|
||||
import enum
|
||||
import os
|
||||
import asyncio
|
||||
from types import FunctionType
|
||||
|
||||
from Houdini.Converters import IConverter
|
||||
|
||||
|
||||
def get_relative_function_path(function_obj):
|
||||
abs_function_file = inspect.getfile(function_obj)
|
||||
rel_function_file = os.path.relpath(abs_function_file)
|
||||
|
||||
return rel_function_file
|
||||
|
||||
|
||||
def get_converter(component):
|
||||
if component.annotation is component.empty:
|
||||
return str
|
||||
return component.annotation
|
||||
|
||||
|
||||
async def do_conversion(converter, p, component_data):
|
||||
if IConverter.implementedBy(converter):
|
||||
converter_instance = converter(p, component_data)
|
||||
if asyncio.iscoroutinefunction(converter_instance.convert):
|
||||
return await converter_instance.convert()
|
||||
return converter_instance.convert()
|
||||
return converter(component_data)
|
||||
|
||||
|
||||
class _Packet:
|
||||
__slots__ = ['id']
|
||||
|
||||
def __init__(self):
|
||||
self.id = None
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
class XTPacket(_Packet):
|
||||
def __init__(self, *packet_id):
|
||||
super().__init__()
|
||||
self.id = '#'.join(packet_id)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
class XMLPacket(_Packet):
|
||||
def __init__(self, packet_id):
|
||||
super().__init__()
|
||||
self.id = packet_id
|
||||
|
||||
|
||||
class Priority(enum.Enum):
|
||||
Override = 3
|
||||
High = 2
|
||||
Low = 1
|
||||
|
||||
|
||||
class BucketType(enum.Enum):
|
||||
Default = 1
|
||||
Penguin = 1
|
||||
Server = 2
|
||||
|
||||
|
||||
class _Cooldown:
|
||||
|
||||
__slots__ = ['rate', 'per', 'bucket_type', 'last',
|
||||
'_window', '_tokens']
|
||||
|
||||
def __init__(self, per, rate, bucket_type):
|
||||
self.per = float(per)
|
||||
self.rate = int(rate)
|
||||
self.bucket_type = bucket_type
|
||||
self.last = 0.0
|
||||
|
||||
self._window = 0.0
|
||||
self._tokens = self.rate
|
||||
|
||||
@property
|
||||
def is_cooling(self):
|
||||
current = time.time()
|
||||
self.last = current
|
||||
|
||||
if self._tokens == self.rate:
|
||||
self._window = current
|
||||
|
||||
if current > self._window + self.per:
|
||||
self._tokens = self.rate
|
||||
self._window = current
|
||||
|
||||
if self._tokens == 0:
|
||||
return self.per - (current - self._window)
|
||||
|
||||
self._tokens -= 1
|
||||
if self._tokens == 0:
|
||||
self._window = current
|
||||
|
||||
def reset(self):
|
||||
self._tokens = self.rate
|
||||
self.last = 0.0
|
||||
|
||||
def copy(self):
|
||||
return _Cooldown(self.per, self.rate, self.bucket_type)
|
||||
|
||||
|
||||
class _CooldownMapping:
|
||||
|
||||
__slots__ = ['_cooldown', '_cache']
|
||||
|
||||
def __init__(self, cooldown_object):
|
||||
self._cooldown = cooldown_object
|
||||
|
||||
self._cache = {}
|
||||
|
||||
def _get_bucket_key(self, p):
|
||||
if self._cooldown.bucket_type == BucketType.Default:
|
||||
return p
|
||||
return p.server
|
||||
|
||||
def _verify_cache_integrity(self):
|
||||
current = time.time()
|
||||
self._cache = {cache_key: bucket for cache_key, bucket in
|
||||
self._cache.items() if current < bucket.last + bucket.per}
|
||||
|
||||
def get_bucket(self, p):
|
||||
self._verify_cache_integrity()
|
||||
cache_key = self._get_bucket_key(p)
|
||||
if cache_key not in self._cache:
|
||||
bucket = self._cooldown.copy()
|
||||
self._cache[cache_key] = bucket
|
||||
else:
|
||||
bucket = self._cache[cache_key]
|
||||
return bucket
|
||||
|
||||
|
||||
class _Listener:
|
||||
|
||||
__slots__ = ['packet', 'components', 'handler', 'priority',
|
||||
'cooldown', 'pass_packet', 'handler_file',
|
||||
'overrides', 'pre_login', 'checklist', 'instance']
|
||||
|
||||
def __init__(self, packet, components, handler_function, **kwargs):
|
||||
self.packet = packet
|
||||
self.components = components
|
||||
self.handler = handler_function
|
||||
|
||||
self.priority = kwargs.get('priority', Priority.Low)
|
||||
self.overrides = kwargs.get('overrides', [])
|
||||
self.cooldown = kwargs.get('cooldown')
|
||||
self.pass_packet = kwargs.get('pass_packet', False)
|
||||
self.checklist = kwargs.get('checklist', [])
|
||||
|
||||
self.instance = None
|
||||
|
||||
if type(self.overrides) is not list:
|
||||
self.overrides = [self.overrides]
|
||||
|
||||
self.handler_file = get_relative_function_path(handler_function)
|
||||
|
||||
def _can_run(self, p):
|
||||
return True if not self.checklist else all(predicate(self.packet, p) for predicate in self.checklist)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__name__())
|
||||
|
||||
def __name__(self):
|
||||
return "{}.{}".format(self.handler.__module__, self.handler.__name__)
|
||||
|
||||
def __call__(self, p, packet_data):
|
||||
if self.cooldown is not None:
|
||||
bucket = self.cooldown.get_bucket(p)
|
||||
if bucket.is_cooling:
|
||||
raise RuntimeError('{} sent packet during cooldown'.format(p.peer_name))
|
||||
|
||||
if not self._can_run(p):
|
||||
raise RuntimeError('Could not handle packet due to checklist failure')
|
||||
|
||||
|
||||
class _XTListener(_Listener):
|
||||
|
||||
__slots__ = ['pre_login']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.pre_login = kwargs.get('pre_login')
|
||||
|
||||
async def __call__(self, p, packet_data):
|
||||
if not self.pre_login and not p.joined_world:
|
||||
p.logger.warn('{} tried sending XT packet before authentication!'.format(p.peer_name))
|
||||
await p.close()
|
||||
return
|
||||
|
||||
super().__call__(p, packet_data)
|
||||
|
||||
handler_call_arguments = [self.instance] if self.instance is not None else []
|
||||
handler_call_arguments += [self.packet, p] if self.pass_packet else [p]
|
||||
|
||||
arguments = iter(packet_data)
|
||||
for index, component in enumerate(self.components):
|
||||
if component.default is not component.empty:
|
||||
handler_call_arguments.append(component.default)
|
||||
next(arguments)
|
||||
elif component.kind == component.POSITIONAL_OR_KEYWORD:
|
||||
component_data = next(arguments)
|
||||
converter = get_converter(component)
|
||||
handler_call_arguments.append(await do_conversion(converter, p, component_data))
|
||||
elif component.kind == component.VAR_POSITIONAL:
|
||||
for component_data in arguments:
|
||||
converter = get_converter(component)
|
||||
handler_call_arguments.append(await do_conversion(converter, p, component_data))
|
||||
break
|
||||
return await self.handler(*handler_call_arguments)
|
||||
|
||||
|
||||
class _XMLListener(_Listener):
|
||||
__slots__ = ['pre_login']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def __call__(self, p, packet_data):
|
||||
super().__call__(p, packet_data)
|
||||
|
||||
handler_call_arguments = [self.instance] if self.instance is not None else []
|
||||
handler_call_arguments += [self.packet, p] if self.pass_packet else [p]
|
||||
|
||||
for index, component in enumerate(self.components):
|
||||
if component.default is not component.empty:
|
||||
handler_call_arguments.append(component.default)
|
||||
elif component.kind == component.POSITIONAL_OR_KEYWORD:
|
||||
converter = get_converter(component)
|
||||
handler_call_arguments.append(await do_conversion(converter, p, packet_data))
|
||||
return await self.handler(*handler_call_arguments)
|
||||
|
||||
|
||||
def handler(packet, **kwargs):
|
||||
def decorator(handler_function):
|
||||
if not asyncio.iscoroutinefunction(handler_function):
|
||||
raise TypeError('All handlers must be a coroutine.')
|
||||
|
||||
components = list(inspect.signature(handler_function).parameters.values())[1:]
|
||||
|
||||
if not issubclass(type(packet), _Packet):
|
||||
raise TypeError('All handlers can only listen for either XMLPacket or XTPacket.')
|
||||
|
||||
listener_class = _XTListener if isinstance(packet, XTPacket) else _XMLListener
|
||||
|
||||
try:
|
||||
cooldown_object = handler_function.cooldown
|
||||
del handler_function.cooldown
|
||||
except AttributeError:
|
||||
cooldown_object = None
|
||||
|
||||
try:
|
||||
checklist = handler_function.checks
|
||||
del handler_function.checks
|
||||
except AttributeError:
|
||||
checklist = []
|
||||
|
||||
listener_object = listener_class(packet, components, handler_function,
|
||||
cooldown=cooldown_object, checklist=checklist,
|
||||
**kwargs)
|
||||
return listener_object
|
||||
return decorator
|
||||
|
||||
|
||||
def listener_exists(xt_listeners, xml_listeners, packet):
|
||||
listener_collection = xt_listeners if isinstance(packet, XTPacket) else xml_listeners
|
||||
return packet in listener_collection
|
||||
|
||||
|
||||
def is_listener(listener):
|
||||
return issubclass(type(listener), _Listener)
|
||||
|
||||
|
||||
def listeners_from_module(xt_listeners, xml_listeners, module):
|
||||
listener_objects = inspect.getmembers(module, is_listener)
|
||||
for listener_name, listener_object in listener_objects:
|
||||
listener_collection = xt_listeners if type(listener_object) == _XTListener else xml_listeners
|
||||
if listener_object.packet not in listener_collection:
|
||||
listener_collection[listener_object.packet] = []
|
||||
|
||||
if listener_object not in listener_collection[listener_object.packet]:
|
||||
if listener_object.priority == Priority.High:
|
||||
listener_collection[listener_object.packet].insert(0, listener_object)
|
||||
elif listener_object.priority == Priority.Override:
|
||||
listener_collection[listener_object.packet] = [listener_object]
|
||||
else:
|
||||
listener_collection[listener_object.packet].append(listener_object)
|
||||
|
||||
for listener_name, listener_object in listener_objects:
|
||||
listener_collection = xt_listeners if type(listener_object) == _XTListener else xml_listeners
|
||||
for override in listener_object.overrides:
|
||||
listener_collection[override.packet].remove(override)
|
||||
|
||||
|
||||
def cooldown(per=1.0, rate=1, bucket_type=BucketType.Default):
|
||||
def decorator(handler_function):
|
||||
handler_function.cooldown = _CooldownMapping(_Cooldown(per, rate, bucket_type))
|
||||
return handler_function
|
||||
return decorator
|
||||
|
||||
|
||||
def check(predicate):
|
||||
def decorator(handler_function):
|
||||
if not hasattr(handler_function, 'checks'):
|
||||
handler_function.checks = []
|
||||
|
||||
if not type(predicate) == FunctionType:
|
||||
raise TypeError('All handler checks must be a function')
|
||||
|
||||
handler_function.checks.append(predicate)
|
||||
return handler_function
|
||||
return decorator
|
||||
|
||||
|
||||
def allow_once():
|
||||
def check_for_packet(packet, p):
|
||||
return packet not in p.received_packets
|
||||
return check(check_for_packet)
|
||||
|
||||
|
||||
def player_attribute(**attrs):
|
||||
def check_for_attributes(_, p):
|
||||
for attr, value in attrs.items():
|
||||
if not getattr(p, attr) == value:
|
||||
return False
|
||||
return True
|
||||
return check(check_for_attributes)
|
||||
|
||||
|
||||
def player_data_attribute(**attrs):
|
||||
def check_for_attributes(_, p):
|
||||
for attr, value in attrs.items():
|
||||
if not getattr(p.data, attr) == value:
|
||||
return False
|
||||
return True
|
||||
return check(check_for_attributes)
|
||||
|
||||
|
||||
def player_in_room(*room_ids):
|
||||
def check_room_id(_, p):
|
||||
return p.room.ID in room_ids
|
||||
return check(check_room_id)
|
Reference in New Issue
Block a user