cleaned and improved server implementation
This commit is contained in:
parent
801231a868
commit
4ba6b67d75
1 changed files with 74 additions and 63 deletions
|
@ -1,3 +1,5 @@
|
|||
import re
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
|
@ -10,10 +12,11 @@ from enum import Enum
|
|||
from typing import Dict, List, Callable, Coroutine, Type, Optional, Tuple, AsyncIterator
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .traits import CallbacksHolder, Runnable
|
||||
from .mc.packet import Packet
|
||||
from .mc.token import Token, AuthException
|
||||
from .mc.auth import AuthException, AuthInterface
|
||||
from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState
|
||||
from .mc.proto.status.serverbound import PacketPing, PacketPingStart
|
||||
from .mc.proto.status.clientbound import PacketServerInfo, PacketPing as PacketPong
|
||||
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, PacketPosition, PacketLogin
|
||||
|
@ -23,6 +26,7 @@ from .mc.proto.login.clientbound import (
|
|||
)
|
||||
from .util import encryption
|
||||
|
||||
REMOVE_COLOR_FORMATS = re.compile(r"§[0-9a-z]")
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
|
@ -30,12 +34,10 @@ class ServerOptions:
|
|||
online_mode : bool
|
||||
spawn_player : bool
|
||||
poll_interval : float
|
||||
motd : str
|
||||
max_players : int
|
||||
|
||||
class ServerEvent(Enum):
|
||||
CLIENT_CONNECTED = 0
|
||||
CLIENT_DISCONNECTED = 1
|
||||
|
||||
class MinecraftServer(CallbacksHolder, Runnable):
|
||||
class MinecraftServer:
|
||||
host:str
|
||||
port:int
|
||||
options:ServerOptions
|
||||
|
@ -45,15 +47,17 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
_server : Server
|
||||
_worker : Task
|
||||
|
||||
_logger : logging.Logger
|
||||
logger : logging.Logger
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host:str,
|
||||
host:str = "127.0.0.1",
|
||||
port:int = 25565,
|
||||
online_mode:bool = False,
|
||||
spawn_player:bool = True,
|
||||
poll_interval:float = 1.0,
|
||||
motd:str = "aiocraft server",
|
||||
max_players:int = 10,
|
||||
):
|
||||
super().__init__()
|
||||
self.host = host
|
||||
|
@ -63,12 +67,14 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
online_mode=online_mode,
|
||||
spawn_player=spawn_player,
|
||||
poll_interval=poll_interval,
|
||||
motd=motd,
|
||||
max_players=max_players,
|
||||
)
|
||||
|
||||
self._dispatcher_pool = []
|
||||
self._processing = False
|
||||
|
||||
self._logger = LOGGER.getChild(f"@({self.host}:{self.port})")
|
||||
self.logger = LOGGER.getChild(f"@({self.host}:{self.port})")
|
||||
|
||||
@property
|
||||
def started(self) -> bool:
|
||||
|
@ -78,24 +84,6 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
def connected(self) -> int:
|
||||
return len(self._dispatcher_pool)
|
||||
|
||||
def on_connect(self):
|
||||
def wrapper(fun):
|
||||
self.register(ServerEvent.CLIENT_CONNECTED, fun)
|
||||
return fun
|
||||
return wrapper
|
||||
|
||||
def on_disconnect(self):
|
||||
def wrapper(fun):
|
||||
self.register(ServerEvent.CLIENT_DISCONNECTED, fun)
|
||||
return fun
|
||||
return wrapper
|
||||
|
||||
def on_packet(self, packet:Type[Packet]):
|
||||
def wrapper(fun):
|
||||
self.register(packet, fun)
|
||||
return fun
|
||||
return wrapper
|
||||
|
||||
async def start(self):
|
||||
if self.started:
|
||||
return
|
||||
|
@ -105,35 +93,21 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
|
||||
self._processing = True
|
||||
await self._server.start_serving()
|
||||
self._logger.info("Minecraft server started")
|
||||
self.logger.info("Minecraft server started")
|
||||
|
||||
async def stop(self, force:bool = False):
|
||||
self._processing = False
|
||||
self._server.close()
|
||||
await asyncio.gather(*[d.disconnect(block=not force) for d in self._dispatcher_pool])
|
||||
if not force:
|
||||
await asyncio.gather(
|
||||
self._server.wait_closed(),
|
||||
self.join_callbacks(),
|
||||
)
|
||||
|
||||
async def _disconnect_client(self, dispatcher):
|
||||
if dispatcher.state == ConnectionState.LOGIN:
|
||||
await dispatcher.write(PacketDisconnect(dispatcher.proto, reason="Connection terminated"))
|
||||
else:
|
||||
await dispatcher.write(PacketKickDisconnect(dispatcher.proto, reason="Connection terminated"))
|
||||
await self._server.wait_closed()
|
||||
|
||||
async def _server_worker(self, reader:StreamReader, writer:StreamWriter):
|
||||
dispatcher = Dispatcher(server=True)
|
||||
dispatcher = Dispatcher(server=True).set_host(self.host, self.port)
|
||||
self._dispatcher_pool.append(dispatcher)
|
||||
|
||||
self._logger.debug("Starting dispatcher for client")
|
||||
await dispatcher.connect(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
reader=reader,
|
||||
writer=writer,
|
||||
)
|
||||
self.logger.debug("Starting dispatcher for client")
|
||||
await dispatcher.connect(reader=reader, writer=writer)
|
||||
|
||||
await self._handshake(dispatcher)
|
||||
if dispatcher.state == ConnectionState.STATUS:
|
||||
|
@ -143,33 +117,75 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
await self._play(dispatcher)
|
||||
|
||||
if dispatcher.connected:
|
||||
await self._disconnect_client(dispatcher)
|
||||
await dispatcher.write(
|
||||
PacketKickDisconnect(dispatcher.proto, reason="Connection terminated")
|
||||
if dispatcher.state == ConnectionState.PLAY else
|
||||
PacketDisconnect(dispatcher.proto, reason="Connection terminated")
|
||||
)
|
||||
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 def _handshake(self, dispatcher:Dispatcher) -> bool:
|
||||
self.logger.info("Awaiting handshake")
|
||||
async for packet in dispatcher.packets():
|
||||
if isinstance(packet, PacketSetProtocol):
|
||||
self._logger.info("Received set protocol packet")
|
||||
self.logger.info("Received set protocol packet")
|
||||
dispatcher.proto = packet.protocolVersion
|
||||
if packet.nextState == 1:
|
||||
self._logger.debug("Changing state to STATUS")
|
||||
self.logger.debug("Changing state to STATUS")
|
||||
dispatcher.state = ConnectionState.STATUS
|
||||
return True
|
||||
elif packet.nextState == 2:
|
||||
self._logger.debug("Changing state to LOGIN")
|
||||
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")
|
||||
self.logger.info("Answering ping")
|
||||
async for packet in dispatcher.packets():
|
||||
pass # TODO handle status!
|
||||
if isinstance(packet, PacketPingStart):
|
||||
await dispatcher.write(
|
||||
PacketServerInfo(
|
||||
dispatcher.proto,
|
||||
response=json.dumps({
|
||||
"online": True,
|
||||
"ip": self.host,
|
||||
"port": self.port,
|
||||
"motd": {
|
||||
"raw": self.options.motd.split("\n"),
|
||||
"clean": REMOVE_COLOR_FORMATS.sub("", self.options.motd).split("\n"),
|
||||
"html": self.options.motd.replace("\n", "<br/>"),
|
||||
},
|
||||
"players": {
|
||||
"online": len(self._dispatcher_pool),
|
||||
"max": self.options.max_players,
|
||||
# "list": [
|
||||
# "notch",
|
||||
# ],
|
||||
# "uuid": {
|
||||
# "notch": "xxx-xxx...",
|
||||
# }
|
||||
},
|
||||
"version": "many", # TODO proto number to string
|
||||
"protocol": dispatcher.proto,
|
||||
# "hostname": "server.mymcserver.tld", # TODO get from config?
|
||||
# "icon": "data:image\/png;base64,iVBORw0KGgoAAAANSUhEU...dSk6AAAAAElFTkSuQmCC",
|
||||
"software": "aiocraft",
|
||||
"map": "null",
|
||||
# "info": { # TODO overrules default player names list
|
||||
# "raw": [],
|
||||
# "clean": [],
|
||||
# "html": []
|
||||
# }
|
||||
})
|
||||
)
|
||||
)
|
||||
elif isinstance(packet, PacketPing):
|
||||
await dispatcher.write(PacketPong(dispatcher.proto, packet.time))
|
||||
return False
|
||||
|
||||
async def _login(self, dispatcher:Dispatcher) -> bool:
|
||||
self._logger.info("Logging in player")
|
||||
self.logger.info("Logging in player")
|
||||
async for packet in dispatcher.packets():
|
||||
if isinstance(packet, PacketLoginStart):
|
||||
if self.options.online_mode:
|
||||
|
@ -199,7 +215,7 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
return False
|
||||
|
||||
async def _play(self, dispatcher:Dispatcher) -> bool:
|
||||
self._logger.info("Player connected")
|
||||
self.logger.info("Player connected")
|
||||
|
||||
if self.options.spawn_player:
|
||||
await dispatcher.write(
|
||||
|
@ -239,14 +255,9 @@ class MinecraftServer(CallbacksHolder, Runnable):
|
|||
)
|
||||
)
|
||||
|
||||
self.run_callbacks(ServerEvent.CLIENT_CONNECTED)
|
||||
|
||||
async for packet in dispatcher.packets():
|
||||
# TODO handle play
|
||||
self.run_callbacks(Packet, packet)
|
||||
self.run_callbacks(type(packet), packet)
|
||||
|
||||
self.run_callbacks(ServerEvent.CLIENT_DISCONNECTED)
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
|
Loading…
Reference in a new issue