Compare commits

..

No commits in common. "102891c8e841138b7368db43e8cc8fd98b1c8fb8" and "7dd3faac12b3b4b5cfda99f5dc034223107d980e" have entirely different histories.

11 changed files with 94 additions and 144 deletions

View File

@ -6,7 +6,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs ## TODOs
- [x] Server core - [ ] Server core
- [x] BeamMP System - [x] BeamMP System
- [x] Private access (Without key, Direct connect) - [x] Private access (Without key, Direct connect)
- [x] Public access (With key, listing in Launcher) - [x] Public access (With key, listing in Launcher)
@ -21,6 +21,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Packets handled (Recursive finding second packet) - [x] Packets handled (Recursive finding second packet)
- [x] Car synchronizations: - [x] Car synchronizations:
- [x] State packets - [x] State packets
- [ ] Debug (gear?)
- [x] Spawn cars - [x] Spawn cars
- [x] Delete cars - [x] Delete cars
- [x] Edit cars - [x] Edit cars
@ -28,9 +29,9 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] "ABG:" (compressed data) - [x] "ABG:" (compressed data)
- [x] Decompress data - [x] Decompress data
- [x] Compress data - [x] Compress data
- [x] UDP Server part: - [ ] UDP Server part:
- [x] Ping - [x] Ping
- [x] Position synchronizations - [ ] Position synchronizations _(Code: Zp)_
- [x] Additional: - [x] Additional:
- [ ] KuiToi System - [ ] KuiToi System
- [ ] Servers counter - [ ] Servers counter
@ -51,7 +52,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Async support - [x] Async support
- [x] Plugins support - [x] Plugins support
- [ ] KuiToi class - [ ] KuiToi class
- [ ] Client (Player) class - [ ] Client class
- [x] Load Python plugins - [x] Load Python plugins
- [x] Async support - [x] Async support
- [ ] Load Lua plugins (Original BeamMP compatibility) - [ ] Load Lua plugins (Original BeamMP compatibility)

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@ -103,75 +103,57 @@ class Client:
if type(data) == str: if type(data) == str:
data = bytes(data, "utf-8") data = bytes(data, "utf-8")
if writer is None:
writer = self.__writer
if to_all: if to_all:
code = chr(data[0]) code = chr(data[0])
for client in self.__Core.clients: for client in self.__Core.clients:
if not client or (client is self and not to_self): if not client or (client is self and not to_self):
continue continue
if not to_udp or code in ['V', 'W', 'Y', 'E']: if not to_udp or code in ['V', 'W', 'Y', 'E']:
await client._send(data) if code in ['O', 'T'] or len(data) > 1000:
await client._send(data)
else:
await client._send(data)
else: else:
await client._send(data, to_udp=to_udp) # TODO: UDP send
self.log.debug(f"UDP Part not ready: {code}")
return return
if not self.__alive:
return False
if writer is None:
writer = self.__writer
if len(data) > 400: if len(data) > 400:
data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION) data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION)
if to_udp:
udp_sock = self._udp_sock[0]
udp_addr = self._udp_sock[1]
# self.log.debug(f'[UDP] len: {len(data)}; send: {data!r}')
if udp_sock and udp_addr:
try:
if not udp_sock.is_closing():
# self.log.debug(f'[UDP] {data!r}')
udp_sock.sendto(data, udp_addr)
except OSError:
self.log.debug("[UDP] Error sending")
except Exception as e:
self.log.debug(f"[UDP] Error sending: {e}")
self.log.exception(e)
return
header = len(data).to_bytes(4, "little", signed=True) header = len(data).to_bytes(4, "little", signed=True)
self.log.debug(f'[TCP] {header + data!r}') self.log.debug(f'len: {len(data)}; send: {header + data!r}')
try: try:
writer.write(header + data) writer.write(header + data)
await writer.drain() await writer.drain()
return True
except ConnectionError: except ConnectionError:
self.log.debug('[TCP] Disconnected') self.log.debug('_send: Disconnected')
self.__alive = False self.__alive = False
await self._remove_me() await self._remove_me()
return False
# async def __handle_packet(self, data, int_header): async def __handle_packet(self, data, int_header):
# self.log.debug(f"int_header: {int_header}; data: {data};") self.log.debug(f"int_header: {int_header}; data: {data};")
# if len(data) != int_header: if len(data) != int_header:
# self.log.debug(f"WARN Expected to read {int_header} bytes, instead got {len(data)}") self.log.debug(f"WARN Expected to read {int_header} bytes, instead got {len(data)}")
#
# recv2 = data[int_header:] recv2 = data[int_header:]
# header2 = recv2[:4] header2 = recv2[:4]
# data2 = recv2[4:] data2 = recv2[4:]
# int_header2 = int.from_bytes(header2, byteorder='little', signed=True) int_header2 = int.from_bytes(header2, byteorder='little', signed=True)
# t = asyncio.create_task(self.__handle_packet(data2, int_header2)) t = asyncio.create_task(self.__handle_packet(data2, int_header2))
# self.__tasks.append(t) self.__tasks.append(t)
# data = data[:4 + int_header] data = data[:4 + int_header]
#
# abg = b"ABG:" abg = b"ABG:"
# if len(data) > len(abg) and data.startswith(abg): if len(data) > len(abg) and data.startswith(abg):
# data = zlib.decompress(data[len(abg):]) data = zlib.decompress(data[len(abg):])
# self.log.debug(f"ABG Packet: {len(data)}") self.log.debug(f"ABG Packet: {len(data)}")
#
# self.__packets_queue.append(data) self.__packets_queue.append(data)
# self.log.debug(f"Packets in queue: {len(self.__packets_queue)}") self.log.debug(f"Packets in queue: {len(self.__packets_queue)}")
async def _recv(self, one=False): async def _recv(self, one=False):
while self.__alive: while self.__alive:
@ -301,7 +283,7 @@ class Client:
else: else:
await self._send(bytes(mod_list, "utf-8")) await self._send(bytes(mod_list, "utf-8"))
elif data == b"Done": elif data == b"Done":
for c in range(int(config.Game['max_cars'] * 2.3)): for c in range(config.Game['max_cars']):
self._cars.append(None) self._cars.append(None)
await self._send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") await self._send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
break break
@ -328,7 +310,7 @@ class Client:
self.log.debug(f"Invalid packet: Could not parse pid/vid from packet: '{data}'") self.log.debug(f"Invalid packet: Could not parse pid/vid from packet: '{data}'")
return -1, -1 return -1, -1
async def _handle_car_codes(self, dta): async def _handle_vehicle_codes(self, dta):
if len(dta) < 6: if len(dta) < 6:
return return
sub_code = dta[1] sub_code = dta[1]
@ -354,9 +336,7 @@ class Client:
# TODO: Call event onCarSpawn # TODO: Call event onCarSpawn
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}" pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
unicycle = car_json.get("jbm") == "unicycle" unicycle = car_json.get("jbm") == "unicycle"
# FIXME: unicycle if (allow and (config.Game['max_cars'] > car_id or unicycle)) or over_spawn:
# if (allow and (config.Game['max_cars'] > car_id or unicycle)) or over_spawn:
if config.Game['max_cars'] > car_id and not unicycle:
self.log.debug(f"Car spawn accepted.") self.log.debug(f"Car spawn accepted.")
self._cars[car_id] = { self._cars[car_id] = {
"packet": pkt, "packet": pkt,
@ -436,10 +416,8 @@ class Client:
data = data.decode() data = data.decode()
except UnicodeDecodeError: except UnicodeDecodeError:
self.log.debug(f"UnicodeDecodeError: {data}") self.log.debug(f"UnicodeDecodeError: {data}")
return
code = data[0] code = data[0]
# Codes: p, Z in udp_server.py
match code: match code:
case "H": case "H":
# Client connected # Client connected
@ -459,10 +437,7 @@ class Client:
await self._send(car['packet']) await self._send(car['packet'])
case "C": # Chat handler case "C": # Chat handler
sup = data.find(":", 2) msg = data[4 + len(self.nick):]
if sup == -1:
await self._send("C:Server: Invalid message.")
msg = data[sup+2:]
if not msg: if not msg:
self.log.debug("Tried to send an empty event, ignoring") self.log.debug("Tried to send an empty event, ignoring")
return return
@ -493,7 +468,7 @@ class Client:
await self._send(data, to_all=True) await self._send(data, to_all=True)
case "O": # Cars handler case "O": # Cars handler
await self._handle_car_codes(data) await self._handle_vehicle_codes(data)
case "E": # Client events handler case "E": # Client events handler
# TODO: HandleEvent # TODO: HandleEvent

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@ -19,12 +19,12 @@ class Client:
self.__reader = reader self.__reader = reader
self.__writer = writer self.__writer = writer
self.__packets_queue = [] self.__packets_queue = []
self._udp_sock: Tuple[DatagramTransport | None, Tuple[str, int] | None] = (None, None) self._udp_sock: Tuple[DatagramTransport, tuple] | Tuple[None, None] = (None, None)
self._down_sock: Tuple[StreamReader | None, StreamWriter | None] = (None, None) self._down_sock: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
self._log = utils.get_logger("client(id: )") self._log = utils.get_logger("client(id: )")
self._addr: Tuple[str, int] = writer.get_extra_info("sockname") self._addr = writer.get_extra_info("sockname")
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self.__Core: Core = core self.__Core = core
self._cid: int = -1 self._cid: int = -1
self._key: str = None self._key: str = None
self.nick: str = None self.nick: str = None
@ -57,7 +57,7 @@ class Client:
async def _recv(self, one=False) -> bytes | None: ... async def _recv(self, one=False) -> bytes | None: ...
async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ... async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ...
async def _get_cid_vid(self, s: str) -> Tuple[int, int]: ... async def _get_cid_vid(self, s: str) -> Tuple[int, int]: ...
async def _handle_car_codes(self, data) -> None: ... async def _handle_vehicle_codes(self, data) -> None: ...
async def _handle_codes(self, data) -> None: ... async def _handle_codes(self, data) -> None: ...
async def _looper(self) -> None: ... async def _looper(self) -> None: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...

View File

@ -2,7 +2,7 @@
# File core.__init__.py # File core.__init__.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.3 # Version 1.3
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai) # Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
@ -10,8 +10,8 @@
__title__ = 'KuiToi-Server' __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.4.0' __version__ = '0.3.0'
__build__ = 1361 # Я это считаю лог файлами __build__ = 1257 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.py # File core.core.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version: 0.4.0 # Version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.pyi # File core.core.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 0.4.0 # Version 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.pyi # File core.tcp_server.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@ -11,7 +11,6 @@ from core import utils
class UDPServer(asyncio.DatagramTransport): class UDPServer(asyncio.DatagramTransport):
transport = None
def __init__(self, core, host=None, port=None): def __init__(self, core, host=None, port=None):
super().__init__() super().__init__()
@ -21,83 +20,60 @@ class UDPServer(asyncio.DatagramTransport):
self.host = host self.host = host
self.port = port self.port = port
self.run = False self.run = False
# self.transport = transport self.transport = None
def connection_made(self, transport): def connection_made(self, transport):
self.log.debug("set connection transport") self.transport = transport
# self.transport = self.transport()
async def handle_datagram(self, data, addr): def datagram_received(self, data, addr):
try: cid = data[0] - 1
cid = data[0] - 1 code = data[2:3].decode()
code = data[2:3].decode()
client = self.Core.get_client(cid=cid) client = self.Core.get_client(cid=cid)
if client: if client and client._udp_sock != (self.transport, addr):
if client._udp_sock != (self.transport, addr): client._udp_sock = (self.transport, addr)
client._udp_sock = (self.transport, addr) self.log.debug(f"Set UDP Sock for CID: {cid}")
self.log.debug(f"Set UDP Sock for CID: {cid}") else:
match code: self.log.debug(f"Client not found.")
case "p":
self.log.debug(f"[{cid}] Send ping")
# TODO: Call event onSentPing
self.transport.sendto(b"p", addr) # Send ping
case "Z":
# TODO: Call event onChangePosition
if client:
await client._send(data[2:], to_all=True, to_self=False, to_udp=True)
case _:
self.log.debug(f"[{cid}] Unknown code: {code}")
else:
self.log.debug(f"Client not found.")
except Exception as e: match code:
self.log.error(f"Error handle_datagram: {e}") case "p":
self.log.debug(f"[{cid}] Send ping")
def datagram_received(self, *args, **kwargs): # TODO: Call event onSentPing
self.loop.create_task(self.handle_datagram(*args, **kwargs)) self.transport.sendto(b"p", addr) # Send ping
case "Z":
# TODO: Positions synchronization
# TODO: Call event onChangePosition
pass
case _:
self.log.debug(f"[{cid}] Unknown code: {code}")
def connection_lost(self, exc): def connection_lost(self, exc):
if exc is not None and exc != KeyboardInterrupt: if exc is not None and exc != KeyboardInterrupt:
self.log.debug(f'Connection raised: {exc}') self.log.debug(f'Connection raised: {exc}')
self.log.debug(f'Disconnected.') self.log.debug(f'Disconnected.')
def error_received(self, exc):
self.log.debug(f'error_received: {exc}')
self.log.exception(exc)
self.connection_lost(exc)
self.transport.close() self.transport.close()
async def _start(self): async def _start(self):
self.log.debug("Starting UDP server.") self.log.debug("Starting UDP server.")
self.run = True
try: try:
while self.Core.run: self.transport, _ = await self.loop.create_datagram_endpoint(
await asyncio.sleep(0.2) lambda: UDPServer(self.Core),
local_addr=(self.host, self.port)
d = UDPServer )
self.transport, p = await self.loop.create_datagram_endpoint( self.log.debug(f"UDP server started on {self.transport.get_extra_info('sockname')}")
lambda: d(self.Core), return
local_addr=(self.host, self.port)
)
d.transport = self.transport
if not self.run:
self.log.debug(f"UDP server started on {self.transport.get_extra_info('sockname')}")
self.run = True
while not self.transport.is_closing():
await asyncio.sleep(0.2)
self.log.info("UDP сервер сдох 1")
except OSError as e: except OSError as e:
self.run = False
self.Core.run = False
self.log.error("Cannot bind port or other error") self.log.error("Cannot bind port or other error")
self.log.exception(e) self.log.exception(e)
except Exception as e: except Exception as e:
self.log.error(f"Error: {e}")
self.log.exception(e)
finally:
self.log.info("UDP сервер сдох 2")
self.run = False self.run = False
self.Core.run = False self.Core.run = False
self.log.error(f"Error: {e}")
self.log.exception(e)
def _stop(self): def _stop(self):
self.log.debug("Stopping UDP server") self.log.debug("Stopping UDP server")

View File

@ -1,30 +1,28 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
from asyncio import DatagramTransport from asyncio import DatagramTransport
from typing import Tuple, List from typing import Tuple
from core import utils from core import utils
from core.core import Core from core.core import Core
class UDPServer(asyncio.DatagramTransport): class UDPServer(asyncio.DatagramTransport):
transport: DatagramTransport = None
def __init__(self, core: Core, host=None, port=None, transport=None): def __init__(self, core: Core, host=None, port=None):
self.log = utils.get_logger("UDPServer") self.log = utils.get_logger("UDPServer")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.Core = core self.Core = core
self.host = host self.host = host
self.port = port self.port = port
self.run = False self.run = False
# self.transport: DatagramTransport = None self.transport: DatagramTransport = None
def connection_made(self, transport: DatagramTransport): ... def connection_made(self, transport: DatagramTransport): ...
async def handle_datagram(self, data: bytes, addr: Tuple[str, int]):
def datagram_received(self, data: bytes, addr: Tuple[str, int]): ... def datagram_received(self, data: bytes, addr: Tuple[str, int]): ...
async def _start(self) -> None: ... async def _start(self) -> None: ...
async def _stop(self) -> None: ... async def _stop(self) -> None: ...

View File

@ -2,7 +2,7 @@
# File core.utils.py # File core.utils.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.1 # Version 1.1
# Core version: 0.4.0 # Core version: 0.3.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import datetime import datetime