Compare commits

...

7 Commits

Author SHA1 Message Date
9ae200d48a Fix reg and add call_lua_event();
Add call_lua_event("onChatMessage");
New MP.RegisterEvent();
Add GetPositionRaw();
2023-07-21 05:22:32 +03:00
3b5324d115 __gracefully_kick;
_last_position;
car position - client.cars[car_id]['pos'];
2023-07-21 04:55:25 +03:00
f181a82e0e Update warnings 2023-07-21 02:45:09 +03:00
b271c80e39 Update TODOs 2023-07-21 02:42:56 +03:00
ef9a55c407 MP Class +- ready.. 2023-07-21 02:42:04 +03:00
98b4878339 Add client.identifiers; 2023-07-21 02:41:45 +03:00
b345588c02 Prepare for lua events;
Add _get_player for info;
client.send_message();
2023-07-21 01:15:31 +03:00
11 changed files with 283 additions and 42 deletions

View File

@ -63,7 +63,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [ ] Client (Player) class
- [ ] Lua part: (Original BeamMP compatibility)
- [x] Load Lua plugins
- [ ] MP Class
- [x] MP Class
- [ ] Util class
- [ ] FS class
- [x] MultiLanguage (i18n support)

View File

@ -33,9 +33,16 @@ class Client:
self.roles = None
self._guest = True
self._ready = False
self._identifiers = []
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
self._snowman = {"id": -1, "packet": ""}
self._connect_time = 0
self._last_position = {}
ev.register_event("onServerStopped", self.__gracefully_kick)
async def __gracefully_kick(self, _):
await self.kick("Server shutdown!")
@property
def _writer(self):
@ -65,10 +72,18 @@ class Client:
def ready(self):
return self._ready
@property
def identifiers(self):
return self._identifiers
@property
def cars(self):
return self._cars
@property
def last_position(self):
return self._last_position
def _update_logger(self):
self._log = utils.get_logger(f"{self.nick}:{self.cid}")
self.log.debug(f"Update logger")
@ -94,7 +109,7 @@ class Client:
self.__alive = False
async def send_message(self, message, to_all=True):
pass
await self._send(f"C:{message}", to_all=to_all)
async def send_event(self, event_name, event_data):
pass
@ -373,7 +388,8 @@ class Client:
"json": car_json,
"json_ok": bool(car_json),
"snowman": snowman,
"over_spawn": (snowman and allow_snowman) or over_spawn
"over_spawn": (snowman and allow_snowman) or over_spawn,
"pos": {}
}
await self._send(pkt, to_all=True, to_self=True)
else:
@ -381,8 +397,13 @@ class Client:
des = f"Od:{self.cid}-{car_id}"
await self._send(des)
async def _delete_car(self, raw_data):
async def _delete_car(self, raw_data=None, car_id=None):
if not car_id and raw_data:
cid, car_id = self._get_cid_vid(raw_data)
else:
cid = self.cid
raw_data = f"Od:{self.cid}-{car_id}"
if car_id != -1 and self.cars[car_id]:
@ -515,6 +536,7 @@ class Client:
self.log.debug("Tried to send an empty event, ignoring")
return
to_ev = {"message": msg, "player": self}
ev.call_lua_event("onChatMessage", self.cid, self.nick, msg)
ev_data_list = ev.call_event("onChatReceive", **to_ev)
d2 = await ev.call_async_event("onChatReceive", **to_ev)
ev_data_list.extend(d2)

View File

@ -33,9 +33,11 @@ class Client:
self._guest = True
self.__alive = True
self._ready = False
self._identifiers = []
self._cars: List[Optional[Dict[str, int]]] = []
self._snowman: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
self._last_position = {}
async def __gracefully_kick(self): ...
@property
def _writer(self) -> StreamWriter: ...
@property
@ -51,7 +53,11 @@ class Client:
@property
def ready(self) -> bool: ...
@property
def identifiers(self) -> list: ...
@property
def cars(self) -> List[dict | None]: ...
@property
def last_position(self): ...
def is_disconnected(self) -> bool: ...
async def kick(self, reason: str) -> None: ...
async def send_message(self, message: str | bytes, to_all: bool = True) -> None:...
@ -62,7 +68,7 @@ class Client:
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 _delete_car(self, raw_data: str = None, car_id : int =None) -> 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: ...

View File

@ -47,12 +47,12 @@ class Core:
self.BeamMP_version = "3.1.1" # 20.07.2023
ev.register_event("_get_BeamMP_version", lambda x: tuple([int(i) for i in self.BeamMP_version.split(".")]))
ev.register_event("_get_player", self.get_client)
ev.register_event("_get_player", lambda x: self.get_client(**x['kwargs']))
def get_client(self, cid=None, nick=None, from_ev=None):
if from_ev is not None:
return self.get_client(*from_ev['args'], **from_ev['kwargs'])
def get_client(self, cid=None, nick=None):
if cid is not None:
if cid == -1:
return [i for i in self.clients if i is not None]
return self.clients_by_id.get(cid)
if nick:
return self.clients_by_nick.get(nick)

View File

@ -56,6 +56,7 @@ class TCPServer:
client.nick = res["username"]
client.roles = res["roles"]
client._guest = res["guest"]
client._identifiers = {k: v for s in res["identifiers"] for k, v in [s.split(':')]}
# noinspection PyProtectedMember
client._update_logger()
except Exception as e:

View File

@ -5,6 +5,7 @@
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
import json
from core import utils
@ -22,12 +23,14 @@ class UDPServer(asyncio.DatagramTransport):
self.port = port
self.run = False
def connection_made(self, transport): ...
def connection_made(self, transport):
...
async def handle_datagram(self, data, addr):
try:
cid = data[0] - 1
code = data[2:3].decode()
data = data[2:].decode()
client = self.Core.get_client(cid=cid)
if client:
@ -39,9 +42,19 @@ class UDPServer(asyncio.DatagramTransport):
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")
if client:
await client._send(data[2:], to_all=True, to_self=False, to_udp=True)
ev.call_event("onChangePosition", data=data)
sub = data.find("{", 1)
last_pos_data = data[sub:]
try:
last_pos = json.loads(last_pos_data)
client._last_position = last_pos
_, car_id = client._get_cid_vid(data)
client._cars[car_id]['pos'] = last_pos
except Exception as e:
self.log.debug(f"Cannot parse position packet: {e}")
self.log.debug(f"data: {data}, sup: {sub}")
self.log.debug(f"last_pos_data: {last_pos_data}")
await client._send(data, to_all=True, to_self=False, to_udp=True)
case _:
self.log.debug(f"[{cid}] Unknown code: {code}")
else:

View File

@ -46,12 +46,37 @@ class EventsSystem:
"onServerStopped": []
}
self.__lua_events = {
"onInit": [], # onServerStarted
"onShutdown": [], # onServerStopped
"onPlayerAuth": [], # onPlayerAuthenticated
"onPlayerConnecting": [], # TODO lua onPlayerConnecting
"onPlayerJoining": [], # TODO lua onPlayerJoining
"onPlayerJoin": [], # onPlayerJoin
"onPlayerDisconnect": [], # TODO lua onPlayerDisconnect
"onChatMessage": [], # onChatReceive
"onVehicleSpawn": [], # "onCarSpawn
"onVehicleEdited": [], # onCarEdited
"onVehicleDeleted": [], # onCarDelete
"onVehicleReset": [], # onCarReset
"onFileChanged": [], # TODO lua onFileChanged
}
def builtins_hook(self):
self.log.debug("used builtins_hook")
builtins.ev = self
def register_event(self, event_name, event_func, async_event=False):
self.log.debug(f"register_event({event_name}, {event_func}):")
def register_event(self, event_name, event_func, async_event=False, lua=None):
self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', "
f"async_event={async_event}, lua_event={lua}):")
if lua:
if event_name not in self.__lua_events:
self.__lua_events.update({str(event_name): [event_func]})
else:
self.__lua_events[event_name].append(event_func)
self.log.debug("Register ok")
return
if not callable(event_func):
# TODO: i18n
self.log.error(f"Cannot add event '{event_name}'. "
@ -62,11 +87,13 @@ class EventsSystem:
self.__async_events.update({str(event_name): [event_func]})
else:
self.__async_events[event_name].append(event_func)
self.log.debug("Register ok")
else:
if event_name not in self.__events:
self.__events.update({str(event_name): [event_func]})
else:
self.__events[event_name].append(event_func)
self.log.debug("Register ok")
async def call_async_event(self, event_name, *args, **kwargs):
self.log.debug(f"Calling async event: '{event_name}'")
@ -106,3 +133,24 @@ class EventsSystem:
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_async_event()?. Just skipping it...")
return funcs_data
def call_lua_event(self, event_name, *args):
self.log.debug(f"Calling lua event: '{event_name}'")
funcs_data = []
if event_name in self.__lua_events.keys():
for func in self.__lua_events[event_name]:
try:
funcs_data.append(func(*args))
except Exception as e:
# TODO: i18n
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
self.log.exception(e)
else:
# TODO: i18n
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
f"Just skipping it...")
return funcs_data

View File

@ -3,9 +3,11 @@ from typing import Any
class EventsSystem:
@staticmethod
def register_event(event_name, event_func): ...
def register_event(event_name, event_func, async_event: bool = False, lua: bool | object = None): ...
@staticmethod
async def call_async_event(event_name, *args, **kwargs) -> list[Any]: ...
@staticmethod
def call_event(event_name, *data, **kwargs) -> list[Any]: ...
@staticmethod
def call_lua_event(event_name, *data) -> list[Any]: ...
class ev(EventsSystem): ...

View File

@ -1,4 +1,26 @@
function MP:GetServerVersion()
ver = MP:_GetServerVersion()
return ver[0], ver[1], ver[2]
package.path = package.path..";modules/PluginsLoader/lua_libs/?.lua"
MP.Timer = {}
function MP.CreateTimer()
local newObj = {
startTime = os.clock()
}
setmetatable(newObj, { __index = MP.Timer })
return newObj
end
function MP.Timer:GetCurrent()
return os.clock() - self.startTime
end
function MP.Timer:Start()
self.startTime = os.clock()
end
function MP.Sleep(time_ms)
local start = getTickCount()
while getTickCount() - start < time_ms do end
end
MP.CallStrategy = {
BestEffort = 0,
Precise = 1
}

View File

@ -1,10 +1,7 @@
import asyncio
import os
import platform
from typing import Tuple, List, Any
# noinspection PyUnresolvedReferences
import lupa.lua53
from lupa.lua53 import LuaRuntime
from core import get_logger
@ -13,24 +10,147 @@ from core import get_logger
# noinspection PyPep8Naming
class MP:
def __init__(self, name):
# In ./in_lua.lua
# MP.CreateTimer
# MP.Sleep
def __init__(self, name: str, lua: LuaRuntime):
self.loop = asyncio.get_event_loop()
self.tasks = []
self.name = name
self.log = get_logger(f"LuaPlugin | {name}")
self._lua = lua
def _print_log(self, *args):
s = ""
for i in args:
s += f" {i}"
def _print(self, *args):
s = " ".join(map(str, args))
self.log.info(s)
def CreateTimer(self): ...
def GetOSName(self) -> str:
return platform.system()
self.log.debug("request MP.GetOSName()")
pl = platform.system()
if pl in ["Linux", "Windows"]:
return pl
return "Other"
def _GetServerVersion(self) -> tuple[int, int, int]:
major, minor, patch = ev.call_event("_get_BeamMP_version")[0]
return major, minor, patch
def GetServerVersion(self) -> tuple[int, int, int]:
self.log.debug("request MP.GetServerVersion()")
return ev.call_event("_get_BeamMP_version")[0]
def RegisterEvent(self, event_name: str, function_name: str) -> None:
self.log.debug("request MP.RegisterEvent()")
ev.register_event(event_name, self._lua.globals()[function_name], lua=True)
def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()")
# TODO: TriggerLocalEvent
return self._lua.table()
def TriggerGlobalEvent(self, event_name, *args):
self.log.debug("request TriggerGlobalEvent()")
# TODO: TriggerGlobalEvent
return self._lua.table(IsDone=lambda: True, GetResults=lambda: "somedata")
def SendChatMessage(self, player_id, message):
self.log.debug("request SendChatMessage()")
client = ev.call_event("_get_player", cid=player_id)[0]
to_all = False
if player_id < 0:
to_all = True
client = client[0]
if client and message:
t = self.loop.create_task(client.send_message(f"Server: {message}", to_all=to_all))
self.tasks.append(t)
def TriggerClientEvent(self, player_id, event_name, data):
# TODO: TriggerClientEvent
self.log.debug("request TriggerClientEvent()")
def TriggerClientEventJson(self, player_id, event_name, data):
# TODO: TriggerClientEventJson
self.log.debug("request TriggerClientEventJson()")
def GetPlayerCount(self):
self.log.debug("request GetPlayerCount()")
return len(ev.call_event("_get_player", cid=-1)[0])
def GetPositionRaw(self, player_id, car_id):
self.log.debug("request GetPositionRaw()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
car = client.cars[car_id]
if car:
return self._lua.table_from(car['pos'])
return self._lua.table(), "Vehicle not found"
return self._lua.table(), "Client expired"
def IsPlayerConnected(self, player_id):
self.log.debug("request IsPlayerConnected()")
return bool(ev.call_event("_get_player", cid=player_id)[0])
def GetPlayerName(self, player_id):
self.log.debug("request GetPlayerName()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return client.nick
return
def RemoveVehicle(self, player_id, vehicle_id):
self.log.debug("request GetPlayerName()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client._delete_car(car_id=vehicle_id))
self.tasks.append(t)
def GetPlayerVehicles(self, player_id):
self.log.debug("request GetPlayerVehicles()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from(
{i: f'{v["json"]}' for i, d in enumerate([i for i in client.cars if i is not None]) for k, v in
d.items() if k == "json"})
def GetPlayers(self):
self.log.debug("request GetPlayers()")
clients = ev.call_event("_get_players", cid=-1)
return self._lua.table_from({i: n for i, n in enumerate(clients)})
def IsPlayerGuest(self, player_id) -> bool:
self.log.debug("request IsPlayerGuest()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return client.guest
return False
def DropPlayer(self, player_id, reason="Kicked"):
self.log.debug("request DropPlayer()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client.kick(reason))
self.tasks.append(t)
def GetStateMemoryUsage(self):
self.log.debug("request GetStateMemoryUsage()")
return self._lua.get_memory_used()
def GetLuaMemoryUsage(self):
self.log.debug("request GetStateMemoryUsage()")
lua_plugins = ev.call_event("_lua_plugins_get")[0]
return sum(pl['lua'].get_memory_used() for pls in lua_plugins.values() for pl in pls.values())
def GetPlayerIdentifiers(self, player_id):
self.log.debug("request GetStateMemoryUsage()")
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from(client.identifiers)
return self._lua.table()
def Set(self, *args):
self.log.debug("request Set")
self.log.warning("KuiToi cannot support this: MP.Set()")
def Settings(self, *args):
self.log.debug("request Set")
self.log.warning("KuiToi cannot support this: MP.Settings()")
class LuaPluginsLoader:
@ -43,6 +163,7 @@ class LuaPluginsLoader:
self.lua_dirs = []
self.log = get_logger("LuaPluginsLoader")
self.loaded_str = "Lua plugins: "
ev.register_event("_lua_plugins_get", lambda x: self.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])
@ -61,21 +182,27 @@ class LuaPluginsLoader:
for path, obj in self.lua_dirs:
# noinspection PyArgumentList
lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc)
mp = MP(obj)
lua.globals().MP = mp
lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc, unpack_returned_tuples=True)
lua.globals().printRaw = lua.globals().print
lua.globals().print = mp._print_log
lua.globals().exit = lambda x: self.log.info(f"{obj}: You can't disable server..")
mp = MP(obj, lua)
lua.globals().MP = mp
lua.globals().print = mp._print
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"\n'
f';{self.plugins_dir}/{obj}/lua/?.lua"\n'
with open("modules/PluginsLoader/add_in.lua", "r") as f:
code += f.read()
with open(os.path.join(path, "main.lua"), 'r', encoding=config.enc) as f:
code += f.read()
try:
lua.execute(code)
self.loaded_str += f"{obj}:ok, "
self.lua_plugins.update({obj: {"mp": mp, "lua": lua}})
except Exception as e:
self.log.error(f"Cannot load lua plugin from `{obj}/main.lua`")
self.log.exception(e)
self.loaded_str += f"{obj}:no, "
async def start(self, _):
...