Compare commits

..

No commits in common. "b80d519c8da7d11665bab01b0559962a8563c7ac" and "a5a7a5dfc980f322b178c5cbafa11557ec47a1f5" have entirely different histories.

17 changed files with 252 additions and 361 deletions

View File

@ -7,7 +7,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs
- [x] Server core
- [x] BeamMP System:
- [x] BeamMP System
- [x] Private access (Without key, Direct connect)
- [x] Public access (With key, listing in Launcher)
- [X] Player authentication
@ -19,7 +19,6 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Chat
- [x] Players online counter
- [x] Packets handled (Recursive finding second packet)
- [ ] Client events
- [x] Car synchronizations:
- [x] State packets
- [x] Spawn cars
@ -50,24 +49,14 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Create custom events
- [x] Return from events
- [x] Async support
- [ ] Add all events
- [x] Plugins support
- [ ] Python part:
- [x] Load Python plugins
- [x] Async support
- [ ] KuiToi class
- [ ] Client (Player) class
- [ ] JavaScript part:
- [ ] Load JavaScript plugins
- [ ] KuiToi class
- [ ] Client (Player) class
- [ ] Lua part: (Original BeamMP compatibility)
- [x] Load Lua plugins
- [ ] MP Class
- [ ] Util class
- [ ] FS class
- [ ] KuiToi class
- [ ] Client (Player) class
- [x] Load Python plugins
- [x] Async support
- [ ] Load Lua plugins (Original BeamMP compatibility)
- [x] MultiLanguage (i18n support)
- [ ] Core
- [x] Core
- [x] Console
- [x] WebAPI
- [ ] HTTP API Server (fastapi)

View File

@ -5,5 +5,4 @@ uvicorn~=0.22.0
fastapi~=0.100.0
starlette~=0.27.0
pydantic~=2.0.2
click~=8.1.4
lupa~=2.0
click~=8.1.4

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev
# File core.tcp_server.py
# Written by: SantaSpeen
# Core version: 0.4.1
# Core version: 0.4.0
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
@ -33,9 +33,8 @@ class Client:
self.roles = None
self._guest = True
self._ready = False
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
self._snowman = {"id": -1, "packet": ""}
self._connect_time = 0
self._cars = []
self._connect_time: float = 0
@property
def _writer(self):
@ -76,11 +75,13 @@ class Client:
def is_disconnected(self):
if not self.__alive:
return True
if self.__writer.is_closing():
self.log.debug(f"is_d: Disconnected.")
res = self.__writer.is_closing()
if res:
self.log.debug(f"Disconnected.")
self.__alive = False
return True
else:
self.log.debug(f"Alive.")
self.__alive = True
return False
@ -147,7 +148,7 @@ class Client:
return
header = len(data).to_bytes(4, "little", signed=True)
# self.log.debug(f'[TCP] {header + data!r}')
self.log.debug(f'[TCP] {header + data!r}')
try:
writer.write(header + data)
await writer.drain()
@ -159,6 +160,27 @@ class Client:
await self._remove_me()
return False
# async def __handle_packet(self, data, int_header):
# self.log.debug(f"int_header: {int_header}; data: {data};")
# if len(data) != int_header:
# self.log.debug(f"WARN Expected to read {int_header} bytes, instead got {len(data)}")
#
# recv2 = data[int_header:]
# header2 = recv2[:4]
# data2 = recv2[4:]
# int_header2 = int.from_bytes(header2, byteorder='little', signed=True)
# t = asyncio.create_task(self.__handle_packet(data2, int_header2))
# self.__tasks.append(t)
# data = data[:4 + int_header]
#
# 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)}")
#
# self.__packets_queue.append(data)
# self.log.debug(f"Packets in queue: {len(self.__packets_queue)}")
async def _recv(self, one=False):
while self.__alive:
try:
@ -174,7 +196,7 @@ class Client:
self.__packets_queue.append(None)
self.__alive = False
continue
self.log.error(f"Header: {header}")
self.log.debug(f"Header: {header}")
await self.kick("Invalid packet - header negative")
self.__packets_queue.append(None)
continue
@ -183,20 +205,25 @@ class Client:
await self.kick("Header size limit exceeded")
self.log.warning("Client sent header of >100MB - "
"assuming malicious intent and disconnecting the client.")
self.log.error(f"Last recv: {await self.__reader.read(100 * MB)}")
self.__packets_queue.append(None)
self.log.error(f"Last recv: {await self.__reader.read(100 * MB)}")
continue
data = await self.__reader.read(int_header)
# self.log.debug(f"int_header: {int_header}; data: `{data}`;")
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)}")
self.log.debug(f"ABG Packet: {len(data)}")
if one:
# self.log.debug(f"int_header: `{int_header}`; data: `{data}`;")
return data
# FIXME
# else:
# t = asyncio.create_task(self.__handle_packet(data, int_header))
# self.__tasks.append(t)
self.__packets_queue.append(data)
except ConnectionError:
@ -308,6 +335,8 @@ class Client:
else:
await self._send(mod_list)
elif data == b"Done":
for c in range(int(config.Game['max_cars'] * 2.3)):
self._cars.append(None)
await self._send(f"M/levels/{config.Game['map']}/info.json")
break
return
@ -334,213 +363,116 @@ class Client:
self.log.debug(f"Invalid packet: Could not parse pid/vid from packet: '{data}'")
return -1, -1
async def _spawn_car(self, data):
car_data = data[2:]
car_id = next((i for i, car in enumerate(self.cars) if car is None), len(self.cars))
cars_count = len(self.cars) - self.cars.count(None)
if self._snowman['id'] != -1:
cars_count -= 1 # -1 for unicycle
self.log.debug(f"car_id={car_id}, cars_count={cars_count}")
car_json = {}
try:
car_json = json.loads(car_data[car_data.find("{"):])
except Exception as e:
self.log.debug(f"Invalid car_json: Error: {e}; Data: {car_data}")
allow = True
allow_snowman = True
over_spawn = False
ev_data_list = ev.call_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
ev_data_list.extend(d2)
for ev_data in ev_data_list:
# TODO: handle event onCarSpawn
pass
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
snowman = car_json.get("jbm") == "unicycle"
if allow and config.Game['max_cars'] > cars_count or (snowman and allow_snowman) or over_spawn:
if snowman:
unicycle_id = self._snowman['id']
if unicycle_id != -1:
self.log.debug(f"Delete old unicycle: unicycle_id={unicycle_id}")
self._cars[unicycle_id] = None
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
self._snowman = {"id": car_id, "packet": pkt}
self.log.debug(f"Unicycle spawn accepted: car_id={car_id}")
else:
self.log.debug(f"Car spawn accepted: car_id={car_id}")
self._cars[car_id] = {
"packet": pkt,
"json": car_json,
"json_ok": bool(car_json),
"snowman": snowman,
"over_spawn": (snowman and allow_snowman) or over_spawn
}
await self._send(pkt, to_all=True, to_self=True)
else:
await self._send(pkt)
des = f"Od:{self.cid}-{car_id}"
await self._send(des)
async def _delete_car(self, raw_data):
cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and self.cars[car_id]:
admin_allow = False # Delete from admin, for example...
ev_data_list = ev.call_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self)
ev_data_list.extend(d2)
for ev_data in ev_data_list:
# TODO: handle event onCarDelete
pass
if cid == self.cid or admin_allow:
await self._send(raw_data, to_all=True, to_self=True)
car = self.cars[car_id]
if car['snowman']:
self.log.debug(f"Snowman found")
unicycle_id = self._snowman['id']
self._snowman['id'] = -1
self._cars[unicycle_id] = None
self._cars[car_id] = None
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
self.log.debug(f"Deleted car: car_id={car_id}")
else:
self.log.debug(f"Invalid car: car_id={car_id}")
async def _edit_car(self, raw_data, data):
cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and self.cars[car_id]:
client = self.__Core.get_client(cid=cid)
if client:
car = client.cars[car_id]
new_car_json = {}
try:
new_car_json = json.loads(data[data.find("{"):])
except Exception as e:
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {data}")
allow = False
admin_allow = False
ev_data_list = ev.call_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
ev_data_list.extend(d2)
for ev_data in ev_data_list:
# TODO: handle event onCarEdited
pass
if cid == self.cid or allow or admin_allow:
if car['snowman']:
unicycle_id = self._snowman['id']
self._snowman['id'] = -1
self.log.debug(f"Delete snowman")
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
self._cars[unicycle_id] = None
else:
await self._send(raw_data, to_all=True, to_self=False)
if car['json_ok']:
old_car_json = car['json']
old_car_json.update(new_car_json)
car['json'] = old_car_json
self.log.debug(f"Updated car: car_id={car_id}")
else:
self.log.debug(f"Invalid car: car_id={car_id}")
async def _reset_car(self, raw_data):
cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and cid == self.cid and self.cars[car_id]:
await self._send(raw_data, to_all=True, to_self=False)
ev.call_event("onCarReset", car=self.cars[car_id], car_id=car_id, player=self)
await ev.call_async_event("onCarReset", car=self.cars[car_id], car_id=car_id, player=self)
self.log.debug(f"Car reset: car_id={car_id}")
else:
self.log.debug(f"Invalid car: car_id={car_id}")
async def _handle_car_codes(self, raw_data):
if len(raw_data) < 6:
async def _handle_car_codes(self, dta):
if len(dta) < 6:
return
sub_code = raw_data[1]
data = raw_data[3:]
sub_code = dta[1]
data = dta[3:]
match sub_code:
case "s": # Spawn car
self.log.debug("Trying to spawn car")
if data[0] == "0":
await self._spawn_car(data)
car_id = 0
for c in self.cars:
if c is None:
break
car_id += 1
self.log.debug(f"Created a car: car_id={car_id}")
car_data = data[2:]
car_json = {}
try:
car_json = json.loads(data[data.find("{"):])
except Exception as e:
self.log.debug(f"Invalid car_json: Error: {e}; Data: {car_data}")
# allow = True
over_spawn = False
ev_data = ev.call_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
ev_data.extend(d2)
for d in ev_data:
# TODO: handle event onCarSpawn
pass
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
unicycle = car_json.get("jbm") == "unicycle"
# 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._cars[car_id] = {
"packet": pkt,
"json": car_json,
"json_ok": bool(car_json),
"unicycle": unicycle,
"over_spawn": over_spawn or unicycle
}
await self._send(pkt, to_all=True, to_self=True)
else:
await self._send(pkt)
des = f"Od:{self.cid}-{car_id}"
await self._send(des)
case "d": # Delete car
self.log.debug("Trying to delete car")
await self._delete_car(raw_data)
cid, car_id = self._get_cid_vid(dta)
if car_id != -1 and cid == self.cid:
# TODO: Call event onCarDelete
await self._send(dta, to_all=True, to_self=True)
try:
car = self.cars[car_id]
if car['unicycle']:
self._cars.pop(car_id)
else:
self._cars[car_id] = None
await self._send(f"Od:{self.cid}-{car_id}")
self.log.debug(f"Deleted car: car_id={car_id}")
except IndexError:
self.log.debug(f"Unknown car: car_id={car_id}")
case "c": # Edit car
self.log.debug("Trying to edit car")
await self._edit_car(raw_data, data)
cid, car_id = self._get_cid_vid(dta)
try:
client = self.__Core.get_client(cid=cid)
if client:
car = client.cars[car_id]
new_car_json = {}
try:
new_car_json = json.loads(data[data.find("{"):])
except Exception as e:
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {data}")
allow = False
ev_data = ev.call_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
ev_data.extend(d2)
for d in ev_data:
# TODO: handle event onCarEdited
pass
if car_id != -1 and cid == self.cid or allow:
if car['unicycle']:
self._cars.pop(car_id)
await self._send(f"Od:{cid}-{car_id}", to_all=True, to_self=True)
else:
await self._send(dta, to_all=True, to_self=False)
if car['json_ok']:
old_car_json = car['json']
old_car_json.update(new_car_json)
car['json'] = old_car_json
self.log.debug(f"Updated car: car_id={car_id}")
except IndexError:
self.log.debug(f"Unknown car: car_id={car_id}")
case "r": # Reset car
self.log.debug("Trying to reset car")
await self._reset_car(raw_data)
case "t": # Broken details
self.log.debug(f"Something changed/broken: {raw_data}")
await self._send(raw_data, to_all=True, to_self=False)
case "m": # Move focus cat
self.log.debug(f"Move focus to: {raw_data}")
await self._send(raw_data, to_all=True, to_self=True)
async def _connected_handler(self):
self.log.info(f"Syncing time: {round(time.monotonic() - self._connect_time, 2)}s")
# Client connected
ev.call_event("onPlayerJoin", player=self)
await ev.call_async_event("onPlayerJoin", player=self)
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
self._ready = True
for client in self.__Core.clients:
if not client:
continue
for car in client.cars:
if not car:
continue
await self._send(car['packet'])
async def _chat_handler(self, data):
sup = data.find(":", 2)
if sup == -1:
await self._send("C:Server: Invalid message.")
msg = data[sup + 2:]
if not msg:
self.log.debug("Tried to send an empty event, ignoring")
return
to_ev = {"message": msg, "player": self}
ev_data_list = ev.call_event("onChatReceive", **to_ev)
d2 = await ev.call_async_event("onChatReceive", **to_ev)
ev_data_list.extend(d2)
need_send = True
for ev_data in ev_data_list:
try:
message = ev_data["message"]
to_all = ev_data.get("to_all")
if to_all is None:
to_all = True
to_self = ev_data.get("to_self")
if to_self is None:
to_self = True
to_client = ev_data.get("to_client")
writer = None
if to_client:
# noinspection PyProtectedMember
writer = to_client._writer
self.log.info(f"{message}" if to_all else f"{self.nick}: {msg}")
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
need_send = False
except KeyError | AttributeError:
self.log.error(f"Returns invalid data: {ev_data}")
if need_send:
self.log.info(f"{self.nick}: {msg}")
await self._send(data, to_all=True)
cid, car_id = self._get_cid_vid(dta)
if car_id != -1 and cid == self.cid:
# TODO: Call event onCarReset
await self._send(dta, to_all=True, to_self=False)
self.log.debug(f"Car reset: car_id={car_id}")
case "t":
self.log.debug(f"Received 'Ot' packet: {dta}")
await self._send(dta, to_all=True, to_self=False)
case "m":
await self._send(dta, to_all=True, to_self=True)
async def _handle_codes(self, data):
if not data:
@ -550,27 +482,73 @@ class Client:
# Codes: V W X Y
if 89 >= data[0] >= 86:
await self._send(data, to_all=True, to_self=False)
return
try:
data = data.decode()
except UnicodeDecodeError:
self.log.error(f"UnicodeDecodeError: {data}")
self.log.debug(f"UnicodeDecodeError: {data}")
return
code = data[0]
# Codes: p, Z in udp_server.py
match data[0]: # At data[0] code
case "H": # Map load, client ready
await self._connected_handler()
match code:
case "H":
self.log.info(f"Syncing time: {round(time.monotonic() - self._connect_time, 2)}s")
# Client connected
ev.call_event("onPlayerJoin", player=self)
await ev.call_async_event("onPlayerJoin", player=self)
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
self._ready = True
for client in self.__Core.clients:
if not client:
continue
for car in client.cars:
if not car:
continue
await self._send(car['packet'])
case "C": # Chat handler
await self._chat_handler(data)
sup = data.find(":", 2)
if sup == -1:
await self._send("C:Server: Invalid message.")
msg = data[sup + 2:]
if not msg:
self.log.debug("Tried to send an empty event, ignoring")
return
self.log.info(f"{self.nick}: {msg}")
to_ev = {"message": msg, "player": self}
ev_data_list = ev.call_event("onChatReceive", **to_ev)
d2 = await ev.call_async_event("onChatReceive", **to_ev)
ev_data_list.extend(d2)
need_send = True
for ev_data in ev_data_list:
try:
message = ev_data["message"]
to_all = ev_data.get("to_all")
if to_all is None:
to_all = True
to_self = ev_data.get("to_self")
if to_self is None:
to_self = True
to_client = ev_data.get("to_client")
writer = None
if to_client:
# noinspection PyProtectedMember
writer = to_client._writer
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
need_send = False
except KeyError | AttributeError:
self.log.error(f"Returns invalid data: {ev_data}")
if need_send:
await self._send(data, to_all=True)
case "O": # Cars handler
await self._handle_car_codes(data)
case "E": # Client events handler
# TODO: Handle events from client
# TODO: HandleEvent
pass
case "N":
@ -586,7 +564,7 @@ class Client:
while self.__alive:
if len(self.__packets_queue) > 0:
for index, packet in enumerate(self.__packets_queue):
# self.log.debug(f"Packet: {packet}")
self.log.debug(f"Packet: {packet}")
del self.__packets_queue[index]
task = self._loop.create_task(self._handle_codes(packet))
tasks.append(task)

View File

@ -1,13 +1,13 @@
# Developed by KuiToi Dev
# File core.tcp_server.py
# Written by: SantaSpeen
# Core version: 0.4.1
# Core version: 0.4.0
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
from asyncio import StreamReader, StreamWriter, DatagramTransport
from logging import Logger
from typing import Tuple, List, Dict, Optional, Union
from typing import Tuple, List, Dict
from core import Core, utils
@ -15,7 +15,7 @@ from core import Core, utils
class Client:
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
self._connect_time: float = 0.0
self._connect_time: float = None
self.__tasks = []
self.__reader = reader
self.__writer = writer
@ -33,9 +33,7 @@ class Client:
self._guest = True
self.__alive = True
self._ready = False
self._cars: List[Optional[Dict[str, int]]] = []
self._snowman: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
self._cars: List[dict | None] = []
@property
def _writer(self) -> StreamWriter: ...
@property
@ -58,17 +56,12 @@ class Client:
async def send_event(self, event_name: str, event_data: str) -> None: ...
async def _send(self, data: bytes | str, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ...
async def _sync_resources(self) -> None: ...
# async def __handle_packet(self, data, int_header): ...
async def _recv(self, one=False) -> bytes | None: ...
async def _split_load(self, start: int, end: int, d_sock: bool, filename: str, sl: float) -> None: ...
async def _get_cid_vid(self, s: str) -> Tuple[int, int]: ...
async def _spawn_car(self, data: str) -> None: ...
async def _delete_car(self, raw_data: str) -> None: ...
async def _edit_car(self, raw_data: str, data: str) -> None: ...
async def _reset_car(self, raw_data: str) -> None: ...
async def _handle_car_codes(self, data: str) -> None: ...
async def _connected_handler(self) -> None: ...
async def _chat_handler(self, data: str) -> None: ...
async def _handle_codes(self, data: bytes) -> None: ...
async def _handle_car_codes(self, data) -> None: ...
async def _handle_codes(self, data) -> None: ...
async def _looper(self) -> None: ...
def _update_logger(self) -> None: ...
async def _remove_me(self) -> None: ...

View File

@ -2,7 +2,7 @@
# File core.__init__.py
# Written by: SantaSpeen
# Version 1.3
# Core version: 0.4.1
# Core version: 0.4.0
# Licence: FPA
# (c) kuitoi.su 2023
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
@ -10,8 +10,8 @@
__title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.4.1'
__build__ = 1486 # Я это считаю лог файлами
__version__ = '0.4.0'
__build__ = 1361 # Я это считаю лог файлами
__author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su'
__license__ = "FPA"
@ -46,13 +46,13 @@ if args.config:
config_provider = ConfigProvider(config_path)
config = config_provider.open_config()
builtins.config = config
config.enc = config.Options['encoding']
if config.Options['debug'] is True:
utils.set_debug_status()
log.info("Debug enabled!")
log = get_logger("core.init")
log.debug("Debug mode enabled!")
log.debug(f"Server config: {config}")
config.enc = config.Options['encoding']
# i18n init
log.debug("Initializing i18n...")
ml = MultiLanguage()

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev
# File core.core.py
# Written by: SantaSpeen
# Version: 0.4.1
# Version: 0.4.0
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
@ -16,7 +16,7 @@ from core import utils
from core.Client import Client
from core.tcp_server import TCPServer
from core.udp_server import UDPServer
from modules import PluginsLoader, LuaPluginsLoader
from modules import PluginsLoader
from modules.WebAPISystem import app as webapp
@ -44,7 +44,7 @@ class Core:
self.lock_upload = False
self.client_major_version = "2.0"
self.BeamMP_version = "3.1.1" # 20.07.2023
self.BeamMP_version = "3.2.0"
ev.register_event("get_player", self.get_client)
@ -217,14 +217,11 @@ class Core:
lambda x: f"Players list: {self.get_clients_list(True)}"
)
pl_dir = "plugins"
self.log.debug("Initializing PluginsLoaders...")
if not os.path.exists(pl_dir):
os.mkdir(pl_dir)
pl = PluginsLoader(pl_dir)
self.log.debug("Initializing PluginsLoader...")
if not os.path.exists("plugins"):
os.mkdir("plugins")
pl = PluginsLoader("plugins")
await pl.load()
lpl = LuaPluginsLoader(pl_dir)
await lpl.load()
try:
# WebApi Start
@ -266,7 +263,6 @@ class Core:
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
await ev.call_async_event("_plugins_start")
await ev.call_async_event("_lua_plugins_start")
self.run = True
self.log.info(i18n.start)
@ -291,7 +287,6 @@ class Core:
ev.call_event("onServerStopped")
await ev.call_async_event("onServerStopped")
await ev.call_async_event("_plugins_unload")
await ev.call_async_event("_lua_plugins_unload")
self.run = False
self.log.info(i18n.stop)
if config.WebAPI["enabled"]:

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# Developed by KuiToi Dev
# File core.udp_server.py
# Written by: SantaSpeen
# Core version: 0.4.1
# Core version: 0.4.0
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
@ -9,7 +9,6 @@ import asyncio
from core import utils
# noinspection PyProtectedMember
class UDPServer(asyncio.DatagramTransport):
transport = None
@ -21,8 +20,11 @@ class UDPServer(asyncio.DatagramTransport):
self.host = host
self.port = port
self.run = False
# self.transport = transport
def connection_made(self, transport): ...
def connection_made(self, transport):
self.log.debug("set connection transport")
# self.transport = self.transport()
async def handle_datagram(self, data, addr):
try:
@ -31,15 +33,16 @@ class UDPServer(asyncio.DatagramTransport):
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": # Ping packet
ev.call_event("onSentPing")
self.transport.sendto(b"p", addr)
case "Z": # Position packet
if client._udp_sock != (self.transport, addr):
client._udp_sock = (self.transport, addr)
self.log.debug(f"Set UDP Sock for CID: {cid}")
ev.call_event("onChangePosition")
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 _:
@ -83,6 +86,7 @@ class UDPServer(asyncio.DatagramTransport):
self.run = True
while not self.transport.is_closing():
await asyncio.sleep(0.2)
self.log.info("UDP сервер сдох 1")
except OSError as e:
self.log.error("Cannot bind port or other error")
self.log.exception(e)

View File

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

View File

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

View File

@ -28,11 +28,7 @@ class EventsSystem:
"onPlayerJoin": [],
"onChatReceive": [],
"onCarSpawn": [],
"onCarDelete": [],
"onCarEdited": [],
"onCarReset": [],
"onSentPing": [], # Only sync
"onChangePosition": [], # Only sync
"onServerStopped": [],
}
self.__async_events = {
@ -40,9 +36,7 @@ class EventsSystem:
"onPlayerJoin": [],
"onChatReceive": [],
"onCarSpawn": [],
"onCarDelete": [],
"onCarEdited": [],
"onCarReset": [],
"onServerStopped": []
}
@ -88,8 +82,7 @@ class EventsSystem:
return funcs_data
def call_event(self, event_name, *args, **kwargs):
if event_name not in ["onChangePosition", "onSentPing"]: # UDP events
self.log.debug(f"Calling sync event: '{event_name}'")
self.log.debug(f"Calling sync event: '{event_name}'")
funcs_data = []
if event_name in self.__events.keys():

View File

@ -1,2 +1 @@
from .plugins_loader import PluginsLoader
from .lua_plugins_loader import LuaPluginsLoader

View File

@ -1,67 +0,0 @@
import asyncio
import os
# noinspection PyUnresolvedReferences
import lupa.lua53
from lupa.lua53 import LuaRuntime
from core import get_logger
class MP:
def __init__(self, name):
self.name = name
self.log = get_logger(f"LuaPlugin | {name}")
def log_info(self, *args):
s = ""
for i in args:
s += i
self.log.info(s)
class LuaPluginsLoader:
def __init__(self, plugins_dir):
self.loop = asyncio.get_event_loop()
self.plugins_dir = plugins_dir
self.lua_plugins = {}
self.lua_plugins_tasks = []
self.lua_dirs = []
self.log = get_logger("LuaPluginsLoader")
self.loaded_str = "Lua plugins: "
ev.register_event("_lua_plugins_start", self.start)
ev.register_event("_lua_plugins_unload", self.unload)
console.add_command("lua_plugins", lambda x: self.loaded_str[:-2])
console.add_command("lua_pl", lambda x: self.loaded_str[:-2])
async def load(self):
self.log.debug("Loading Lua plugins...")
py_folders = ev.call_event("_plugins_get")[0]
for obj in os.listdir(self.plugins_dir):
path = os.path.join(self.plugins_dir, obj)
if os.path.isdir(path) and obj not in py_folders and obj not in "__pycache__":
if os.path.isfile(os.path.join(path, "main.lua")):
self.lua_dirs.append([path, obj])
self.log.debug(f"py_folders {py_folders}, lua_dirs {self.lua_dirs}")
for path, obj in self.lua_dirs:
lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc)
mp = MP(obj)
lua.globals().MP = mp
lua.globals().print = mp.log_info
code = f'package.path = package.path.."' \
f';{self.plugins_dir}/{obj}/?.lua' \
f';{self.plugins_dir}/{obj}/lua/?.lua' \
f';modules/PluginsLoader/lua_libs/?.lua"'
with open(os.path.join(path, "main.lua"), 'r', encoding=config.enc) as f:
code += f.read()
lua.execute(code)
async def start(self, _):
...
async def unload(self, _):
...

View File

@ -33,10 +33,20 @@ class KuiToi:
def name(self):
return self.__name
@name.setter
def name(self, value):
# You chell not pass
pass
@property
def dir(self):
return self.__dir
@dir.setter
def dir(self, value):
# You chell not pass
pass
@contextmanager
def open(self, file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None):
path = os.path.join(self.__dir, file)
@ -73,7 +83,6 @@ class PluginsLoader:
self.loaded_str = "Plugins: "
ev.register_event("_plugins_start", self.start)
ev.register_event("_plugins_unload", self.unload)
ev.register_event("_plugins_get", lambda x: list(self.plugins.keys()))
console.add_command("plugins", lambda x: self.loaded_str[:-2])
console.add_command("pl", lambda x: self.loaded_str[:-2])
@ -81,14 +90,14 @@ class PluginsLoader:
self.log.debug("Loading plugins...")
files = os.listdir(self.plugins_dir)
for file in files:
file_path = os.path.join(self.plugins_dir, file)
if os.path.isfile(file_path) and file.endswith(".py"):
if file.endswith(".py"):
try:
self.log.debug(f"Loading plugin: {file[:-3]}")
plugin = types.ModuleType(file[:-3])
plugin.KuiToi = KuiToi
plugin.KuiToi._plugins_dir = self.plugins_dir
plugin.print = print
file_path = os.path.join(self.plugins_dir, file)
plugin.__file__ = file_path
with open(f'{file_path}', 'r', encoding=config.enc) as f:
code = f.read()

View File

@ -11,6 +11,5 @@ from .ConfigProvider import ConfigProvider, Config
from .i18n import MultiLanguage
from .EventsSystem import EventsSystem
from .PluginsLoader import PluginsLoader
from .PluginsLoader import LuaPluginsLoader
from .WebAPISystem import web_app
from .WebAPISystem import _stop as stop_web