diff --git a/aiocraft/server.py b/aiocraft/server.py index 3a95d1b..a4cb261 100644 --- a/aiocraft/server.py +++ b/aiocraft/server.py @@ -2,7 +2,8 @@ import asyncio import logging import uuid -from asyncio import Task, StreamReader, StreamWriter, Server +from asyncio import Task, StreamReader, StreamWriter +from asyncio.base_events import Server # just for typing from enum import Enum from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator @@ -13,7 +14,7 @@ from .mc.token import Token, AuthException from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState from .mc.proto.handshaking.serverbound import PacketSetProtocol from .mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse -from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect +from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect, PacketPosition, PacketLogin from .mc.proto.login.serverbound import PacketLoginStart, PacketEncryptionBegin as PacketEncryptionResponse from .mc.proto.login.clientbound import ( PacketCompress, PacketDisconnect, PacketEncryptionBegin, PacketLoginPluginRequest, PacketSuccess @@ -22,7 +23,7 @@ from .util import encryption LOGGER = logging.getLogger(__name__) -class Server: +class MinecraftServer: host:str port:int options:dict @@ -45,6 +46,7 @@ class Server: self.port = port self.options = options or { + "poll-timeout" : 1, "online-mode" : False, } @@ -63,11 +65,11 @@ class Server: loop.run_until_complete(self.start()) async def idle(): - while self._processing: # TODO don't busywait even if it doesn't matter much + while True: # TODO don't busywait even if it doesn't matter much await asyncio.sleep(self.options["poll-timeout"]) try: - loop.run_forever(idle()) + loop.run_until_complete(idle()) except KeyboardInterrupt: self._logger.info("Received SIGINT, stopping...") try: @@ -76,26 +78,19 @@ class Server: self._logger.info("Received SIGINT, stopping for real") loop.run_until_complete(self.stop(wait_tasks=False)) - async def start(self, block=False): + async def start(self): self._server = await asyncio.start_server( self._server_worker, self.host, self.port ) - addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets) - print(f'Serving on {addrs}') - self._processing = True - async with self._server: - self._logger.info("Minecraft server started") - if block: - await self._server.serve_forever() - else: - await self._server.start_serving() + await self._server.start_serving() + self._logger.info("Minecraft server started") async def stop(self, block=True, wait_tasks=True): self._processing = False - if self.dispatcher.connected: - await self.dispatcher.disconnect(block=block) + # if self.dispatcher.connected: + # await self.dispatcher.disconnect(block=block) self._server.close() if block: await self._server.wait_closed() @@ -109,7 +104,16 @@ class Server: await dispatcher.write(PacketKickDisconnect(dispatcher.proto, reason="Connection terminated")) async def _server_worker(self, reader:StreamReader, writer:StreamWriter): - dispatcher = Dispatcher() + if not self._processing: + if not writer.is_closing() and writer.can_write_eof(): + try: + writer.write_eof() + except OSError as e: + self._logger.error("Failed to write EOF to connecting client : %s", str(e)) + writer.close() + return await writer.wait_closed() + + dispatcher = Dispatcher(server=True) self._logger.debug("Starting dispatcher for client") await dispatcher.connect( @@ -131,23 +135,29 @@ class Server: await dispatcher.disconnect() async def _handshake(self, dispatcher:Dispatcher) -> bool: # TODO make this fancier! poll for version and status first + self._logger.info("Awaiting handshake") async for packet in dispatcher.packets(): if isinstance(packet, PacketSetProtocol): + self._logger.info("Received set protocol packet") dispatcher.proto = packet.protocolVersion - if packet.nextState == ConnectionState.STATUS: + if packet.nextState == 1: + self._logger.debug("Changing state to STATUS") dispatcher.state = ConnectionState.STATUS return True - elif packet.nextState == ConnectionState.LOGIN: + elif packet.nextState == 2: + self._logger.debug("Changing state to LOGIN") dispatcher.state = ConnectionState.LOGIN return True return False async def _status(self, dispatcher:Dispatcher) -> bool: + self._logger.info("Answering ping") async for packet in dispatcher.packets(): pass # TODO handle status! return False async def _login(self, dispatcher:Dispatcher) -> bool: + self._logger.info("Logging in player") async for packet in dispatcher.packets(): if isinstance(packet, PacketLoginStart): if self.options["online-mode"]: @@ -164,7 +174,7 @@ class Server: await dispatcher.write( PacketSuccess( dispatcher.proto, - uuid=packet.username, + uuid=str(uuid.uuid4()), username=packet.username, ) ) @@ -177,7 +187,47 @@ class Server: return False async def _play(self, dispatcher:Dispatcher) -> bool: + self._logger.info("Player connected") + + await dispatcher.write( + PacketLogin( + dispatcher.proto, + gameMode=3, + isFlat=False, + worldNames=b'', + worldName='aiocraft', + previousGameMode=3, + entityId=1, + isHardcore=False, + difficulty=0, + isDebug=True, + enableRespawnScreen=False, + maxPlayers=1, + dimension=1, + levelType='aiocraft', + reducedDebugInfo=False, + hashedSeed=1234, + viewDistance=4 + ) + ) + + await dispatcher.write( + PacketPosition( + dispatcher.proto, + dismountVehicle=True, + x=0, + y=120, + flags=0, + yaw=0.0, + onGround=False, + teleportId=1, + pitch=0.0, + z=0, + ) + ) + async for packet in dispatcher.packets(): pass # TODO handle play + return False