22 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
7dd3faac12 Update TODOs 2023-07-18 22:43:49 +03:00
ef69df10d6 Compress DATA 2023-07-18 22:40:35 +03:00
a226b17612 Compress DATA 2023-07-18 22:40:27 +03:00
69348e9339 Some fixes in onChatReceive 2023-07-18 22:33:49 +03:00
31d8cf7842 try to check_alive 2023-07-18 22:33:15 +03:00
45d45a820c Update TODOs 2023-07-18 22:31:41 +03:00
aa440a1e3d Update TODOs 2023-07-18 22:31:14 +03:00
63c9515e86 Add UDP part 2023-07-18 22:08:00 +03:00
cfeb2e9823 heartbeat fix 2023-07-18 19:53:02 +03:00
85b85114b5 heartbeat fix 2023-07-18 19:52:44 +03:00
792884d7b0 Disable __handle_packet...;
Fix "Invalid packet - header negative";
Fix _get_cid_vid;
Add dta to _handle_vehicle_codes;
Fix disconnected message.
2023-07-18 05:35:34 +03:00
a5b087f8b4 Disable __handle_packet...;
Fix "Invalid packet - header negative";
Fix _get_cid_vid;
Add dta to _handle_vehicle_codes;
Fix disconnected message.
2023-07-18 05:35:17 +03:00
a01567c89a Auto removing cars after disconnect 2023-07-18 04:14:46 +03:00
041883644c UDP... 2023-07-18 04:10:56 +03:00
3d33eec5fd Minor fix 2023-07-18 04:10:42 +03:00
3f2c5b24f9 Add Disconnected message 2023-07-17 22:26:42 +03:00
11 changed files with 247 additions and 151 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)
@@ -27,10 +27,10 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Reset cars - [x] Reset cars
- [x] "ABG:" (compressed data) - [x] "ABG:" (compressed data)
- [x] Decompress data - [x] Decompress data
- [ ] Compress data - [x] Compress data
- [ ] UDP Server part: - [x] UDP Server part:
- [ ] Players synchronizations _(Code: Zp)_ - [x] Ping
- [ ] Ping _(Code: p)_ - [x] Position synchronizations
- [x] Additional: - [x] Additional:
- [ ] KuiToi System - [ ] KuiToi System
- [ ] Servers counter - [ ] Servers counter
@@ -51,6 +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 (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)
@@ -58,7 +59,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Core - [x] Core
- [x] Console - [x] Console
- [x] WebAPI - [x] WebAPI
- [x] HTTP API Server (fastapi) - [ ] HTTP API Server (fastapi)
- [x] Stop and Start with core - [x] Stop and Start with core
- [x] Configure FastAPI logger - [x] Configure FastAPI logger
- [ ] Sync with event system - [ ] Sync with event system

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
@@ -21,9 +21,10 @@ class Client:
self.__alive = True self.__alive = True
self.__packets_queue = [] self.__packets_queue = []
self.__tasks = [] self.__tasks = []
self._down_rw = (None, None) self._down_sock = (None, None)
self._udp_sock = (None, None)
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self._log = utils.get_logger("client(None:0)") self._log = utils.get_logger("player(None:0)")
self._addr = writer.get_extra_info("sockname") self._addr = writer.get_extra_info("sockname")
self._cid = -1 self._cid = -1
self._key = None self._key = None
@@ -102,58 +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)
if len(data) > 400:
# TODO: Compress data
await client._send(data)
else:
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
if not self.__alive:
return False
if writer is None:
writer = self.__writer
if len(data) > 400:
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 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:
@@ -166,6 +184,10 @@ class Client:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
self.is_disconnected() self.is_disconnected()
if self.__alive: if self.__alive:
if header == b"":
self.__packets_queue.append(None)
self.__alive = False
continue
self.log.debug(f"Header: {header}") self.log.debug(f"Header: {header}")
await self.kick("Invalid packet - header negative") await self.kick("Invalid packet - header negative")
self.__packets_queue.append(None) self.__packets_queue.append(None)
@@ -173,18 +195,28 @@ class Client:
if int_header > 100 * MB: if int_header > 100 * MB:
await self.kick("Header size limit exceeded") await self.kick("Header size limit exceeded")
self.log.warning(f"Client {self.nick}:{self.cid} sent header of >100MB - " self.log.warning("Client sent header of >100MB - "
f"assuming malicious intent and disconnecting the client.") "assuming malicious intent and disconnecting the client.")
self.__packets_queue.append(None) self.__packets_queue.append(None)
self.log.debug(f"Last recv: {await self.__reader.read(100 * MB)}")
continue continue
data = await self.__reader.read(100 * MB) data = await self.__reader.read(int_header)
self.log.debug(f"int_header: {int_header}; data: `{data}`;")
abg = b"ABG:"
if len(data) > len(abg) and data.startswith(abg):
data = zlib.decompress(data[len(abg):])
self.log.debug(f"ABG Packet: {len(data)}")
if one: if one:
self.log.debug(f"int_header: `{int_header}`; data: `{data}`;") # self.log.debug(f"int_header: `{int_header}`; data: `{data}`;")
return data return data
else: # FIXME
t = asyncio.create_task(self.__handle_packet(data, int_header)) # else:
self.__tasks.append(t) # t = asyncio.create_task(self.__handle_packet(data, int_header))
# self.__tasks.append(t)
self.__packets_queue.append(data)
except ConnectionError: except ConnectionError:
self.__alive = False self.__alive = False
@@ -193,7 +225,7 @@ class Client:
async def _split_load(self, start, end, d_sock, filename): async def _split_load(self, start, end, d_sock, filename):
# TODO: Speed limiter # TODO: Speed limiter
real_size = end - start real_size = end - start
writer = self._down_rw[1] if d_sock else self.__writer writer = self._down_sock[1] if d_sock else self.__writer
who = 'dwn' if d_sock else 'srv' who = 'dwn' if d_sock else 'srv'
if config.Server["debug"]: if config.Server["debug"]:
self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}") self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}")
@@ -232,7 +264,7 @@ class Client:
return return
await self._send(b"AG") await self._send(b"AG")
t = 0 t = 0
while not self._down_rw[0]: while not self._down_sock[0]:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
t += 1 t += 1
if t > 50: if t > 50:
@@ -269,14 +301,15 @@ 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
return return
def _get_cid_vid(self, data: str): def _get_cid_vid(self, data: str):
s = data[:data.find(":", 1)] sep = data.find(":", 1) + 1
s = data[sep:sep + 3]
id_sep = s.find('-') id_sep = s.find('-')
if id_sep == -1: if id_sep == -1:
self.log.debug(f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}'") self.log.debug(f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}'")
@@ -295,11 +328,11 @@ 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, data): async def _handle_car_codes(self, dta):
if len(data) < 6: if len(dta) < 6:
return return
sub_code = data[1] sub_code = dta[1]
data = data[3:] data = dta[3:]
match sub_code: match sub_code:
case "s": # Spawn car case "s": # Spawn car
self.log.debug("Trying to spawn car") self.log.debug("Trying to spawn car")
@@ -321,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,
@@ -337,10 +372,10 @@ class Client:
await self._send(des) await self._send(des)
case "d": # Delete car case "d": # Delete car
self.log.debug("Trying to delete car") self.log.debug("Trying to delete car")
cid, car_id = self._get_cid_vid(data) cid, car_id = self._get_cid_vid(dta)
if car_id != -1 and cid == self.cid: if car_id != -1 and cid == self.cid:
# TODO: Call event onCarDelete # TODO: Call event onCarDelete
await self._send(data, to_all=True, to_self=True) await self._send(dta, to_all=True, to_self=True)
try: try:
car = self.cars[car_id] car = self.cars[car_id]
if car['unicycle']: if car['unicycle']:
@@ -355,7 +390,7 @@ class Client:
self.log.debug("Trying to edit car") self.log.debug("Trying to edit car")
allow = True allow = True
# TODO: Call event onCarEdited # TODO: Call event onCarEdited
cid, car_id = self._get_cid_vid(data) cid, car_id = self._get_cid_vid(dta)
if car_id != -1 and cid == self.cid: if car_id != -1 and cid == self.cid:
try: try:
car = self.cars[car_id] car = self.cars[car_id]
@@ -363,7 +398,7 @@ class Client:
self._cars.pop(car_id) self._cars.pop(car_id)
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True) await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
elif allow: elif allow:
await self._send(data, to_all=True, to_self=False) await self._send(dta, to_all=True, to_self=False)
if car['json_ok']: if car['json_ok']:
old_car_json = car['json'] old_car_json = car['json']
try: try:
@@ -378,16 +413,16 @@ class Client:
self.log.debug(f"Unknown car: car_id={car_id}") self.log.debug(f"Unknown car: car_id={car_id}")
case "r": # Reset car case "r": # Reset car
self.log.debug("Trying to reset car") self.log.debug("Trying to reset car")
cid, car_id = self._get_cid_vid(data) cid, car_id = self._get_cid_vid(dta)
if car_id != -1 and cid == self.cid: if car_id != -1 and cid == self.cid:
# TODO: Call event onCarReset # TODO: Call event onCarReset
await self._send(data, to_all=True, to_self=False) await self._send(dta, to_all=True, to_self=False)
self.log.debug(f"Car reset: car_id={car_id}") self.log.debug(f"Car reset: car_id={car_id}")
case "t": case "t":
self.log.debug(f"Received 'Ot' packet: {data}") self.log.debug(f"Received 'Ot' packet: {dta}")
await self._send(data, to_all=True, to_self=False) await self._send(dta, to_all=True, to_self=False)
case "m": case "m":
await self._send(data, to_all=True, to_self=True) await self._send(dta, to_all=True, to_self=True)
async def _handle_codes(self, data): async def _handle_codes(self, data):
if not data: if not data:
@@ -401,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
@@ -422,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
@@ -437,12 +477,7 @@ class Client:
message = ev_data["message"] message = ev_data["message"]
to_all = ev_data.get("to_all") to_all = ev_data.get("to_all")
if to_all is None: if to_all is None:
if need_send:
need_send = False
to_all = True to_all = True
if to_all:
if need_send:
need_send = False
to_self = ev_data.get("to_self") to_self = ev_data.get("to_self")
if to_self is None: if to_self is None:
to_self = True to_self = True
@@ -451,13 +486,14 @@ class Client:
if to_client: if to_client:
writer = to_client._writer writer = to_client._writer
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer) await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
need_send = False
except KeyError | AttributeError: except KeyError | AttributeError:
self.log.error(f"Returns invalid data: {ev_data}") self.log.error(f"Returns invalid data: {ev_data}")
if need_send: if need_send:
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
@@ -488,9 +524,14 @@ class Client:
self.__alive = False self.__alive = False
if (self.cid > 0 or self.nick is not None) and \ if (self.cid > 0 or self.nick is not None) and \
self.__Core.clients_by_nick.get(self.nick): self.__Core.clients_by_nick.get(self.nick):
# if self.ready: for i, car in enumerate(self.cars):
# await self.tcp_send(b"", to_all=True) # I'm disconnected. if not car:
self.log.debug(f"Removing client {self.nick}:{self.cid}") continue
self.log.debug(f"Removing car: car_id={i}")
await self._send(f"Od:{self.cid}-{i}", to_all=True, to_self=False)
if self.ready:
await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False) # I'm disconnected.
self.log.debug(f"Removing client")
# TODO: i18n # TODO: i18n
self.log.info("Disconnected") self.log.info("Disconnected")
self.__Core.clients[self.cid] = None self.__Core.clients[self.cid] = None
@@ -504,7 +545,7 @@ class Client:
except Exception as e: except Exception as e:
self.log.debug(f"Error while closing writer: {e}") self.log.debug(f"Error while closing writer: {e}")
try: try:
_, down_w = self._down_rw _, down_w = self._down_sock
if down_w and not down_w.is_closing(): if down_w and not down_w.is_closing():
down_w.close() down_w.close()
except Exception as e: except Exception as e:

View File

@@ -1,11 +1,11 @@
# 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
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter, DatagramTransport
from logging import Logger from logging import Logger
from typing import Tuple, List, Dict from typing import Tuple, List, Dict
@@ -19,11 +19,12 @@ class Client:
self.__reader = reader self.__reader = reader
self.__writer = writer self.__writer = writer
self.__packets_queue = [] self.__packets_queue = []
self._down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None) self._udp_sock: Tuple[DatagramTransport | None, Tuple[str, int] | 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
@@ -56,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
@@ -95,16 +95,20 @@ class Core:
async def check_alive(self): async def check_alive(self):
maxp = config.Game['players'] maxp = config.Game['players']
while self.run: try:
await asyncio.sleep(1) while self.run:
ca = f"Ss{len(self.clients_by_id)}/{maxp}:{self.get_clients_list()}" await asyncio.sleep(1)
for client in self.clients: ca = f"Ss{len(self.clients_by_id)}/{maxp}:{self.get_clients_list()}"
if not client: for client in self.clients:
continue if not client:
if not client.ready: continue
client.is_disconnected() if not client.ready:
continue client.is_disconnected()
await client._send(bytes(ca, "utf-8")) continue
await client._send(bytes(ca, "utf-8"))
except Exception as e:
self.log.error("Error in check_alive.")
self.log.exception(e)
@staticmethod @staticmethod
def start_web(): def start_web():
@@ -141,7 +145,7 @@ class Core:
modstotal = len(self.mods_list) - 1 modstotal = len(self.mods_list) - 1
while self.run: while self.run:
try: try:
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"], data = {"uuid": config.Auth["key"], "players": len(self.clients_by_id), "maxplayers": config.Game["players"],
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json", "port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
"private": config.Auth['private'], "version": self.BeamMP_version, "private": config.Auth['private'], "version": self.BeamMP_version,
"clientversion": self.client_major_version, "clientversion": self.client_major_version,
@@ -188,13 +192,12 @@ class Core:
self.direct = True self.direct = True
else: else:
self.direct = True self.direct = True
if test:
self.log.error("Cannot auth...")
if not config.Auth['private']:
raise KeyboardInterrupt
if test: if test:
# TODO: i18n # TODO: i18n
self.log.error("Cannot authenticate server.")
self.log.info(f"Server still runnig, but only in Direct connect mode.") self.log.info(f"Server still runnig, but only in Direct connect mode.")
# if not config.Auth['private']:
# raise KeyboardInterrupt
if test: if test:
return ok return ok
@@ -251,7 +254,7 @@ class Core:
self.clients.append(None) self.clients.append(None)
tasks = [] tasks = []
# self.udp.start, # self.udp.start,
f_tasks = [self.tcp.start, console.start, self.stop_me, self.heartbeat, self.check_alive] f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
for task in f_tasks: for task in f_tasks:
tasks.append(asyncio.create_task(task())) tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
@@ -271,7 +274,7 @@ class Core:
finally: finally:
self.run = False self.run = False
self.tcp.stop() self.tcp.stop()
# self.udp.stop() self.udp._stop()
await self.stop() await self.stop()
def start(self): def start(self):

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
@@ -89,7 +89,7 @@ class TCPServer:
cid = (await reader.read(1))[0] cid = (await reader.read(1))[0]
client = self.Core.get_client(cid=cid) client = self.Core.get_client(cid=cid)
if client: if client:
client._down_rw = (reader, writer) client._down_sock = (reader, writer)
self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!") self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!")
else: else:
writer.close() writer.close()
@@ -143,7 +143,7 @@ class TCPServer:
self.run = True self.run = True
try: try:
server = await asyncio.start_server(self.handle_client, self.host, self.port, server = await asyncio.start_server(self.handle_client, self.host, self.port,
backlog=int(config.Game["players"] * 1.3)) backlog=int(config.Game["players"] * 2.3))
self.log.debug(f"TCP server started on {server.sockets[0].getsockname()!r}") self.log.debug(f"TCP server started on {server.sockets[0].getsockname()!r}")
while True: while True:
async with server: async with server:

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
@@ -10,51 +10,95 @@ import traceback
from core import utils from core import utils
class UDPServer: class UDPServer(asyncio.DatagramTransport):
transport = None
def __init__(self, core, host, port): def __init__(self, core, host=None, port=None):
super().__init__()
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 = transport
async def handle_client(self, reader, writer): def connection_made(self, transport):
while True: self.log.debug("set connection transport")
try: # self.transport = self.transport()
data = await reader.read(1)
if not data:
break
code = data.decode()
self.log.debug(f"Received {code!r} from {writer.get_extra_info('sockname')!r}")
# await self.handle_code(code, reader, writer)
# task = asyncio.create_task(self.handle_code(code, reader, writer))
# await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION)
if not writer.is_closing():
writer.close()
self.log.debug("Disconnected.")
break
except Exception as e:
self.log.error("Error while connecting..")
self.log.error(f"Error: {e}")
traceback.print_exc()
break
async def start(self): async def handle_datagram(self, data, addr):
self.log.debug("Starting UDP server.")
self.run = True
try: try:
pass cid = data[0] - 1
code = data[2:3].decode()
client = self.Core.get_client(cid=cid)
if client:
if client._udp_sock != (self.transport, addr):
client._udp_sock = (self.transport, addr)
self.log.debug(f"Set UDP Sock for CID: {cid}")
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.")
except Exception as e:
self.log.error(f"Error handle_datagram: {e}")
def datagram_received(self, *args, **kwargs):
self.loop.create_task(self.handle_datagram(*args, **kwargs))
def connection_lost(self, exc):
if exc is not None and exc != KeyboardInterrupt:
self.log.debug(f'Connection raised: {exc}')
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()
async def _start(self):
self.log.debug("Starting UDP server.")
try:
while self.Core.run:
await asyncio.sleep(0.2)
d = UDPServer
self.transport, p = await self.loop.create_datagram_endpoint(
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.log.error("Cannot bind port or other error") self.log.error("Cannot bind port or other error")
raise e self.log.exception(e)
except BaseException as e: except Exception as e:
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
raise e self.log.exception(e)
finally: finally:
self.log.info("UDP сервер сдох 2")
self.run = False self.run = False
self.Core.run = False self.Core.run = False
def stop(self): def _stop(self):
self.log.debug("Stopping UDP server") self.log.debug("Stopping UDP server")
self.transport.close()

View File

@@ -1,24 +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 typing import Tuple, List
from core import utils from core import utils
from core.core import Core
class UDPServer: class UDPServer(asyncio.DatagramTransport):
transport: DatagramTransport = None
def __init__(self, core, host, port): 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
async def handle_client(self, srv_sock) -> None: ... # self.transport: DatagramTransport = None
async def start(self) -> None: ... def connection_made(self, transport: DatagramTransport): ...
async def handle_datagram(self, data: bytes, addr: Tuple[str, int]):
async def stop(self) -> None: ... def datagram_received(self, data: bytes, addr: Tuple[str, int]): ...
async def _start(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