Compare commits

..

6 Commits

Author SHA1 Message Date
102891c8e8 0.3.0 -> 0.4.0 2023-07-19 03:53:27 +03:00
46b0419340 Update TODOs 2023-07-19 03:50:33 +03:00
47cca3a0d8 Add UDP send;
Remove rights to spawn unicycle (For now);
Minor updates;
2023-07-19 03:48:57 +03:00
77ee76c0c0 UDP Part ready! 2023-07-19 03:45:02 +03:00
852e977a75 Fix set udp sock 2023-07-18 22:59:12 +03:00
407127ec97 Typing update 2023-07-18 22:58:34 +03:00
11 changed files with 144 additions and 94 deletions

View File

@ -6,7 +6,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs ## TODOs
- [ ] Server core - [x] 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,7 +21,6 @@ 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
@ -29,9 +28,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
- [ ] UDP Server part: - [x] UDP Server part:
- [x] Ping - [x] Ping
- [ ] Position synchronizations _(Code: Zp)_ - [x] Position synchronizations
- [x] Additional: - [x] Additional:
- [ ] KuiToi System - [ ] KuiToi System
- [ ] Servers counter - [ ] Servers counter
@ -52,7 +51,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 class - [ ] Client (Player) 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.3.0 # Core version: 0.4.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@ -103,57 +103,75 @@ 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']:
if code in ['O', 'T'] or len(data) > 1000: await client._send(data)
await client._send(data)
else:
await client._send(data)
else: else:
# TODO: UDP send await client._send(data, to_udp=to_udp)
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'len: {len(data)}; send: {header + data!r}') self.log.debug(f'[TCP] {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('_send: Disconnected') self.log.debug('[TCP] 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:
@ -283,7 +301,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(config.Game['max_cars']): for c in range(int(config.Game['max_cars'] * 2.3)):
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
@ -310,7 +328,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_vehicle_codes(self, dta): async def _handle_car_codes(self, dta):
if len(dta) < 6: if len(dta) < 6:
return return
sub_code = dta[1] sub_code = dta[1]
@ -336,7 +354,9 @@ 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"
if (allow and (config.Game['max_cars'] > car_id or unicycle)) or over_spawn: # FIXME: unicycle
# 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,
@ -416,8 +436,10 @@ 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
@ -437,7 +459,10 @@ class Client:
await self._send(car['packet']) await self._send(car['packet'])
case "C": # Chat handler case "C": # Chat handler
msg = data[4 + len(self.nick):] sup = data.find(":", 2)
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
@ -468,7 +493,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_vehicle_codes(data) await self._handle_car_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.3.0 # Core version: 0.4.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, tuple] | Tuple[None, None] = (None, None) self._udp_sock: Tuple[DatagramTransport | None, Tuple[str, int] | None] = (None, None)
self._down_sock: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None) self._down_sock: Tuple[StreamReader | None, StreamWriter | None] = (None, None)
self._log = utils.get_logger("client(id: )") self._log = utils.get_logger("client(id: )")
self._addr = writer.get_extra_info("sockname") self._addr: Tuple[str, int] = writer.get_extra_info("sockname")
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self.__Core = core self.__Core: 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_vehicle_codes(self, data) -> None: ... async def _handle_car_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.3.0 # Core version: 0.4.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.3.0' __version__ = '0.4.0'
__build__ = 1257 # Я это считаю лог файлами __build__ = 1361 # Я это считаю лог файлами
__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.3.0 # Version: 0.4.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.3.0 # Version 0.4.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.3.0 # Core version: 0.4.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.3.0 # Core version: 0.4.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.3.0 # Core version: 0.4.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@ -11,6 +11,7 @@ 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__()
@ -20,60 +21,83 @@ class UDPServer(asyncio.DatagramTransport):
self.host = host self.host = host
self.port = port self.port = port
self.run = False self.run = False
self.transport = None # self.transport = transport
def connection_made(self, transport): def connection_made(self, transport):
self.transport = transport self.log.debug("set connection transport")
# self.transport = self.transport()
def datagram_received(self, data, addr): async def handle_datagram(self, data, addr):
cid = data[0] - 1 try:
code = data[2:3].decode() cid = data[0] - 1
code = data[2:3].decode()
client = self.Core.get_client(cid=cid) client = self.Core.get_client(cid=cid)
if client and client._udp_sock != (self.transport, addr): if client:
client._udp_sock = (self.transport, addr) if client._udp_sock != (self.transport, addr):
self.log.debug(f"Set UDP Sock for CID: {cid}") client._udp_sock = (self.transport, addr)
else: self.log.debug(f"Set UDP Sock for CID: {cid}")
self.log.debug(f"Client not found.") match code:
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.")
match code: except Exception as e:
case "p": self.log.error(f"Error handle_datagram: {e}")
self.log.debug(f"[{cid}] Send ping")
# TODO: Call event onSentPing def datagram_received(self, *args, **kwargs):
self.transport.sendto(b"p", addr) # Send ping self.loop.create_task(self.handle_datagram(*args, **kwargs))
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:
self.transport, _ = await self.loop.create_datagram_endpoint( while self.Core.run:
lambda: UDPServer(self.Core), await asyncio.sleep(0.2)
local_addr=(self.host, self.port)
) d = UDPServer
self.log.debug(f"UDP server started on {self.transport.get_extra_info('sockname')}") self.transport, p = await self.loop.create_datagram_endpoint(
return lambda: d(self.Core),
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.run = False
self.Core.run = False
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
self.log.exception(e) self.log.exception(e)
finally:
self.log.info("UDP сервер сдох 2")
self.run = False
self.Core.run = False
def _stop(self): def _stop(self):
self.log.debug("Stopping UDP server") self.log.debug("Stopping UDP server")

View File

@ -1,28 +1,30 @@
# 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.3.0 # Core version: 0.4.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 from typing import Tuple, List
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): def __init__(self, core: Core, host=None, port=None, transport=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.3.0 # Core version: 0.4.0
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import datetime import datetime