super basic server impl

This commit is contained in:
əlemi 2021-11-21 23:57:22 +01:00
parent 3cee338ca5
commit f513801132

View file

@ -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