Compare commits

...

4 Commits

Author SHA1 Message Date
acdb32d900 Update TODOs 2023-07-17 14:25:07 +03:00
50b1e7b176 Recreate cars system;
Move code "O" to client._handle_vehicle_codes();
2023-07-17 14:18:52 +03:00
c9e6a0a9cd Change evens naming semantic 2023-07-16 16:14:16 +03:00
cd098571d9 Change evens naming semantic 2023-07-16 16:14:00 +03:00
6 changed files with 197 additions and 139 deletions

View File

@ -18,9 +18,11 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Connecting to the world - [x] Connecting to the world
- [x] Chat - [x] Chat
- [x] Players online counter - [x] Players online counter
- [ ] Packets handled (One or more)
- [ ] Car synchronizations: - [ ] Car synchronizations:
- [x] State packets
- [x] Spawn cars - [x] Spawn cars
- [ ] Edit cars - [x] Edit cars
- [ ] Delete cars - [ ] Delete cars
- [ ] Reset cars - [ ] Reset cars
- [ ] "ABG:" (compressed data) - [ ] "ABG:" (compressed data)

View File

@ -110,10 +110,13 @@ class Client:
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: if code in ['O', 'T'] or len(data) > 1000:
if len(data) > 400:
# TODO: Compress data # TODO: Compress data
await client._send(data) await client._send(data)
else: else:
await client._send(data) await client._send(data)
else:
await client._send(data)
else: else:
# TODO: UDP send # TODO: UDP send
self.log.debug(f"UDP Part not ready: {code}") self.log.debug(f"UDP Part not ready: {code}")
@ -159,7 +162,7 @@ class Client:
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: {data}") self.log.debug(f"ABG Packet: {len(data)}")
return data return data
return data return data
except ConnectionError: except ConnectionError:
@ -247,32 +250,117 @@ class Client:
await self._send(bytes(mod_list, "utf-8")) await self._send(bytes(mod_list, "utf-8"))
elif data == b"Done": elif data == b"Done":
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")
for c in range(config.Game['max_cars']):
self._cars.append(None)
break break
return return
async def _looper(self): def _get_cid_vid(self, data: str):
await self._send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID s = data[data.find(":", 1)+1:]
await self._sync_resources() id_sep = s.find('-')
while self.__alive: if id_sep == -1:
data = await self._recv() self.log.debug(f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{s}'")
return -1, -1
cid = s[:id_sep]
vid = s[id_sep + 1:]
if cid.isdigit() and vid.isdigit():
try:
cid = int(cid)
vid = int(vid)
return cid, vid
except ValueError:
self.log.debug(f"Invalid packet: Could not parse cid/vid from packet, as one or both are not valid "
f"numbers: '{s}'")
return -1, -1
self.log.debug(f"Invalid packet: Could not parse pid/vid from packet: '{s}'")
return -1, -1
async def _handle_vehicle_codes(self, data):
if len(data) < 6:
return
sub_code = data[1]
data = data[3:]
match sub_code:
case "s": # Spawn car
self.log.debug("Trying to spawn car")
if data[0] == "0":
car_id = 0
for c in self.cars:
if c is None:
break
car_id += 1
self.log.debug(f"Created a car with ID {car_id}")
car_data = data[2:]
car_json = {}
try:
car_json = json.loads(data[5:])
except Exception as e:
self.log.debug(f"Invalid car_json: Error: {e}; Data: {car_data}")
# TODO: Call event onCarSpawn
allow_spawn = True
over_spawn = False
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
unicycle = car_json.get("jbm") == "unicycle"
if (allow_spawn and (config.Game['max_cars'] > car_id or unicycle)) or over_spawn:
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)
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")
cid, car_id = self._get_cid_vid(data)
if car_id != -1 and cid == self.cid:
# TODO: Call event onCarDelete
await self._send(data, to_all=True, to_self=True)
try:
self._cars[car_id] = None
await self._send(f"Od:{self.cid}-{car_id}")
self.log.debug(f"Deleted car with 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")
# TODO: edit car
cid, car_id = self._get_cid_vid(data)
if car_id != -1 and cid == self.cid:
car = self.cars[car_id]
if car['unicycle']:
pass
case "r": # Reset car
self.log.debug("Trying to reset car")
# TODO: reset car
cid, car_id = self._get_cid_vid(data)
if car_id != -1 and cid == self.cid:
pass
case "t" | "m":
pass
async def _handle_codes(self, data):
if not data: if not data:
self.__alive = False self.__alive = False
break return
# Codes: V W X Y # Codes: V W X Y
if 89 >= data[0] >= 86: if 89 >= data[0] >= 86:
await self._send(data, to_all=True, to_self=False) await self._send(data, to_all=True, to_self=False)
data = data.decode('utf-8') data = data.decode()
code = data[0] code = data[0]
self.log.debug(f"Received code: {code}, data: {data}")
match code: match code:
case "H": case "H":
# Client connected # Client connected
ev.call_event("onPlayerJoin", player=self)
ev.call_event("player_join", player=self) await ev.call_async_event("onPlayerJoin", player=self)
await ev.call_async_event("player_join", player=self)
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it 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 await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
@ -282,18 +370,17 @@ class Client:
if not client: if not client:
continue continue
for car in client.cars: for car in client.cars:
await self._send(car) await self._send(car['packet'])
case "C": # Chat handler case "C": # Chat handler
msg = data[4 + len(self.nick):] msg = data[4 + len(self.nick):]
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")
continue return
self.log.info(f"Received message: {msg}") self.log.info(f"{self.nick}: {msg}")
# TODO: Handle chat event
to_ev = {"message": msg, "player": self} to_ev = {"message": msg, "player": self}
ev_data_list = ev.call_event("chat_receive", **to_ev) ev_data_list = ev.call_event("onChatReceive", **to_ev)
d2 = await ev.call_async_event("chat_receive", **to_ev) d2 = await ev.call_async_event("onChatReceive", **to_ev)
ev_data_list.extend(d2) ev_data_list.extend(d2)
need_send = True need_send = True
for ev_data in ev_data_list: for ev_data in ev_data_list:
@ -320,54 +407,22 @@ class Client:
if need_send: if need_send:
await self._send(data, to_all=True) await self._send(data, to_all=True)
case "O": # Vehicle info handler case "O": # Cars handler
if len(data) < 6: await self._handle_vehicle_codes(data)
continue
sub_code = data[1]
data = data[3:]
vid = -1
pid = -1
match sub_code:
case "s": # Spawn car
if data[0] == "0":
car_id = len(self._cars)
self.log.debug(f"Created a car with ID {car_id}")
car_data = data[2:]
car_json = {}
try:
car_json = json.loads(data[5:])
except Exception as e:
self.log.debug(f"Invalid car_json: Error: {e}; Data: {car_data}")
# TODO: Call event onVehicleSpawn
spawn = True
pkt = f"Os:{self.roles}:{self.nick}:{self.cid}-{car_id}:{car_data}"
if spawn and (config.Game['max_cars'] > car_id or car_json.get("jbm") == "unicycle"):
self.log.debug(f"Car spawn accepted.")
self._cars.append(car_data)
await self._send(pkt, to_all=True)
else:
await self._send(pkt)
des = f"Od:{self.cid}-{car_id}"
await self._send(des)
case "c": # Edit car
# TODO: edit car
pass
case "d": # Delete car
# TODO: delete car
pass
case "r": # Reset car
# TODO: reset car
pass
case "t" | "m":
pass
case "E": # Client events handler case "E": # Client events handler
# TODO: HandleEvent # TODO: HandleEvent
pass pass
case "N": case "N":
# TODO: N await self._send(data, to_all=True, to_self=False)
pass
async def _looper(self):
await self._send(f"P{self.cid}") # Send clientID
await self._sync_resources()
while self.__alive:
data = await self._recv()
self._loop.create_task(self._handle_codes(data))
async def _remove_me(self): async def _remove_me(self):
await asyncio.sleep(0.3) await asyncio.sleep(0.3)

View File

@ -7,7 +7,7 @@
import asyncio import asyncio
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from logging import Logger from logging import Logger
from typing import Tuple from typing import Tuple, List, Dict
from core import Core, utils from core import Core, utils
@ -29,7 +29,7 @@ class Client:
self._guest = True self._guest = True
self.__alive = True self.__alive = True
self._ready = False self._ready = False
self._cars = [] self._cars: List[dict | None] = []
@property @property
def _writer(self) -> StreamWriter: ... def _writer(self) -> StreamWriter: ...
@property @property
@ -45,13 +45,16 @@ class Client:
@property @property
def ready(self) -> bool: ... def ready(self) -> bool: ...
@property @property
def cars(self) -> list: ... def cars(self) -> List[dict | None]: ...
def is_disconnected(self) -> bool: ... def is_disconnected(self) -> bool: ...
async def kick(self, reason: str) -> None: ... async def kick(self, reason: 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 _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 _sync_resources(self) -> None: ...
async def _recv(self) -> bytes: ... async def _recv(self) -> bytes: ...
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 _handle_vehicle_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: ...
async def _remove_me(self) -> None: ... async def _remove_me(self) -> None: ...

View File

@ -260,8 +260,8 @@ class Core:
self.run = True self.run = True
self.log.info(i18n.start) self.log.info(i18n.start)
ev.call_event("server_started") ev.call_event("onServerStarted")
await ev.call_async_event("server_started") await ev.call_async_event("onServerStarted")
await t # Wait end. await t # Wait end.
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
@ -278,8 +278,8 @@ class Core:
asyncio.run(self.main()) asyncio.run(self.main())
async def stop(self): async def stop(self):
ev.call_event("server_stopped") ev.call_event("onServerStopped")
await ev.call_async_event("server_stopped") await ev.call_async_event("onServerStopped")
await ev.call_async_event("_plugins_unload") await ev.call_async_event("_plugins_unload")
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)

View File

@ -41,7 +41,7 @@ class TCPServer:
await client.kick("Invalid Key (too long)!") await client.kick("Invalid Key (too long)!")
return False, client return False, client
client._key = data.decode("utf-8") client._key = data.decode("utf-8")
ev.call_event("auth_sent_key", player=client) ev.call_event("onPlayerSentKey", player=client)
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
url = 'https://auth.beammp.com/pkToUser' url = 'https://auth.beammp.com/pkToUser'
@ -71,7 +71,7 @@ class TCPServer:
await client.kick('Stale Client (replaced by new client)') await client.kick('Stale Client (replaced by new client)')
return False, client return False, client
ev.call_event("auth_ok", player=client) ev.call_event("onPlayerAuthenticated", player=client)
if len(self.Core.clients_by_id) > config.Game["players"]: if len(self.Core.clients_by_id) > config.Game["players"]:
# TODO: i18n # TODO: i18n

View File

@ -22,20 +22,18 @@ class EventsSystem:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.as_tasks = [] self.as_tasks = []
self.__events = { self.__events = {
"server_started": [], "onServerStarted": [],
"auth_sent_key": [], # Only sync "onPlayerSentKey": [], # Only sync
"auth_ok": [], # Only sync "onPlayerAuthenticated": [], # Only sync
"player_join": [], "onPlayerJoin": [],
"chat_receive": [], "onChatReceive": [],
"server_stopped": [], "onServerStopped": [],
} }
self.__async_events = { self.__async_events = {
"server_started": [], "onServerStarted": [],
"_plugins_start": [], "onPlayerJoin": [],
"_plugins_unload": [], "onChatReceive": [],
"player_join": [], "onServerStopped": []
"chat_receive": [],
"server_stopped": []
} }
def builtins_hook(self): def builtins_hook(self):