Compare commits

..

13 Commits

Author SHA1 Message Date
1b5ddbdd45 Refactor package.path;
Fix: Now .lua run from loadfrom(file);
2023-07-22 00:20:24 +03:00
0d3699bfee Fix lpl.load(); 2023-07-21 22:55:56 +03:00
b3dffe74ec Lau plugins now run in thread;
Remove unused event;
Add event from to Lua;
2023-07-21 22:51:22 +03:00
2992c9cbab Update TODOs 2023-07-21 22:34:06 +03:00
43518ac57c Update TODOs 2023-07-21 22:33:57 +03:00
a96e8111e3 Add TriggerClientEventJson,JsonDecode, JsonPrettify, JsonMinify, JsonFlatten, JsonUnflatten, JsonDiff; 2023-07-21 22:33:44 +03:00
ecf06bf1c9 Fix Util.JsonEncode 2023-07-21 20:11:46 +03:00
8d57db4a23 Add Util.JsonEncode 2023-07-21 20:06:38 +03:00
3d9e08d05d Util.Random* 2023-07-21 18:37:42 +03:00
ac5f5ee894 Update TODOs 2023-07-21 18:28:27 +03:00
92880a94df TriggerClientEvent 2023-07-21 18:28:00 +03:00
dcc1f14b17 Update TODOs 2023-07-21 17:42:19 +03:00
f9f4df7438 Add TriggerLocalEvent, TriggerGlobalEvent 2023-07-21 17:42:00 +03:00
4 changed files with 225 additions and 48 deletions

View File

@ -61,10 +61,10 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [ ] Load JavaScript plugins - [ ] Load JavaScript plugins
- [ ] KuiToi class - [ ] KuiToi class
- [ ] Client (Player) class - [ ] Client (Player) class
- [ ] Lua part: (Original BeamMP compatibility) - [x] Lua part: (Original BeamMP compatibility)
- [x] Load Lua plugins - [x] Load Lua plugins
- [x] MP Class (Excluding CreateEventTimer, CreateEventTimer, TriggerLocalEvent, TriggerGlobalEvent, TriggerClientEvent, TriggerClientEventJson) - [x] MP Class (Excluding CreateEventTimer, CreateEventTimer)
- [ ] Util class - [x] Util class
- [x] FS class - [x] FS class
- [x] MultiLanguage (i18n support) - [x] MultiLanguage (i18n support)
- [ ] Core - [ ] Core

View File

@ -111,8 +111,8 @@ class Client:
async def send_message(self, message, to_all=True): async def send_message(self, message, to_all=True):
await self._send(f"C:{message}", to_all=to_all) await self._send(f"C:{message}", to_all=to_all)
async def send_event(self, event_name, event_data): async def send_event(self, event_name, event_data, to_all=True):
pass await self._send(f"E:{event_name}:{event_data}", to_all=to_all)
async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None): async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None):

View File

@ -225,7 +225,7 @@ class Core:
pl = PluginsLoader(pl_dir) pl = PluginsLoader(pl_dir)
await pl.load() await pl.load()
lpl = LuaPluginsLoader(pl_dir) lpl = LuaPluginsLoader(pl_dir)
await lpl.load() lpl.load()
try: try:
# WebApi Start # WebApi Start
@ -267,7 +267,7 @@ class Core:
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
await ev.call_async_event("_plugins_start") await ev.call_async_event("_plugins_start")
await ev.call_async_event("_lua_plugins_start") # await ev.call_async_event("_lua_plugins_start")
self.run = True self.run = True
self.log.info(i18n.start) self.log.info(i18n.start)
@ -289,10 +289,11 @@ class Core:
asyncio.run(self.main()) asyncio.run(self.main())
async def stop(self): async def stop(self):
ev.call_lua_event("onShutdown")
ev.call_event("onServerStopped") ev.call_event("onServerStopped")
await ev.call_async_event("onServerStopped") await ev.call_async_event("onServerStopped")
await ev.call_async_event("_plugins_unload") await ev.call_async_event("_plugins_unload")
await ev.call_async_event("_lua_plugins_unload") ev.call_event("_lua_plugins_unload")
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:

View File

@ -1,8 +1,10 @@
import asyncio import asyncio
import json
import os import os
import platform import platform
import random import random
import shutil import shutil
from threading import Thread
from lupa.lua53 import LuaRuntime from lupa.lua53 import LuaRuntime
@ -22,6 +24,11 @@ class MP:
self.name = name self.name = name
self.tasks = [] self.tasks = []
self._lua = lua self._lua = lua
self._local_events = {
"onInit": [], "onShutdown": [], "onPlayerAuth": [], "onPlayerConnecting": [], "onPlayerJoining": [],
"onPlayerJoin": [], "onPlayerDisconnect": [], "onChatMessage": [], "onVehicleSpawn": [],
"onVehicleEdited": [], "onVehicleDeleted": [], "onVehicleReset": [], "onFileChanged": []
}
def _print(self, *args): def _print(self, *args):
s = " ".join(map(str, args)) s = " ".join(map(str, args))
@ -40,7 +47,13 @@ class MP:
def RegisterEvent(self, event_name: str, function_name: str) -> None: def RegisterEvent(self, event_name: str, function_name: str) -> None:
self.log.debug("request MP.RegisterEvent()") self.log.debug("request MP.RegisterEvent()")
ev.register_event(event_name, self._lua.globals()[function_name], lua=True) event_func = self._lua.globals()[function_name]
ev.register_event(event_name, event_func, lua=True)
if event_name not in self._local_events:
self._local_events.update({str(event_name): [event_func]})
else:
self._local_events[event_name].append(event_func)
self.log.debug("Register ok (local)")
def CreateEventTimer(self, event_name: str, interval_ms: int, strategy: int = None): def CreateEventTimer(self, event_name: str, interval_ms: int, strategy: int = None):
self.log.debug("request CreateEventTimer()") self.log.debug("request CreateEventTimer()")
@ -48,17 +61,31 @@ class MP:
def CancelEventTimer(self, event_name: str): def CancelEventTimer(self, event_name: str):
self.log.debug("request CancelEventTimer()") self.log.debug("request CancelEventTimer()")
# TODO: CreateEventTimer # TODO: CancelEventTimer
def TriggerLocalEvent(self, event_name, *args): def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()") self.log.debug("request TriggerLocalEvent()")
# TODO: TriggerLocalEvent self.log.debug(f"Calling lcoal lua event: '{event_name}'")
return self._lua.table() funcs_data = []
if event_name in self._local_events.keys():
for func in self._local_events[event_name]:
try:
funcs_data.append(func(*args))
except Exception as e:
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
self.log.exception(e)
else:
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
f"Just skipping it...")
return self._lua.table_from({i: v for i, v in enumerate(funcs_data)})
def TriggerGlobalEvent(self, event_name, *args): def TriggerGlobalEvent(self, event_name, *args):
self.log.debug("request TriggerGlobalEvent()") self.log.debug("request TriggerGlobalEvent()")
# TODO: TriggerGlobalEvent return self._lua.table(
return self._lua.table(IsDone=lambda: True, GetResults=lambda: "somedata") IsDone=lambda: True,
GetResults=lambda: self._lua.table_from({i: v for i, v in enumerate(ev.call_lua_event(event_name, *args))})
)
def SendChatMessage(self, player_id, message): def SendChatMessage(self, player_id, message):
self.log.debug("request SendChatMessage()") self.log.debug("request SendChatMessage()")
@ -72,12 +99,25 @@ class MP:
self.tasks.append(t) self.tasks.append(t)
def TriggerClientEvent(self, player_id, event_name, data): def TriggerClientEvent(self, player_id, event_name, data):
# TODO: TriggerClientEvent
self.log.debug("request TriggerClientEvent()") self.log.debug("request TriggerClientEvent()")
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 event_name and data:
t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all))
self.tasks.append(t)
return True, None
elif not client:
return False, "Client expired"
else:
return False, "Can't found event_name or data"
def TriggerClientEventJson(self, player_id, event_name, data): def TriggerClientEventJson(self, player_id, event_name, data):
# TODO: TriggerClientEventJson
self.log.debug("request TriggerClientEventJson()") self.log.debug("request TriggerClientEventJson()")
data = self._lua.globals().Util.JsonEncode(data)
self.TriggerClientEvent(player_id, event_name, data)
def GetPlayerCount(self): def GetPlayerCount(self):
self.log.debug("request GetPlayerCount()") self.log.debug("request GetPlayerCount()")
@ -85,6 +125,8 @@ class MP:
def GetPositionRaw(self, player_id, car_id): def GetPositionRaw(self, player_id, car_id):
self.log.debug("request GetPositionRaw()") self.log.debug("request GetPositionRaw()")
if player_id < 0:
return self._lua.table(), "Bad client"
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
car = client.cars[car_id] car = client.cars[car_id]
@ -95,17 +137,23 @@ class MP:
def IsPlayerConnected(self, player_id): def IsPlayerConnected(self, player_id):
self.log.debug("request IsPlayerConnected()") self.log.debug("request IsPlayerConnected()")
if player_id < 0:
return False
return bool(ev.call_event("_get_player", cid=player_id)[0]) return bool(ev.call_event("_get_player", cid=player_id)[0])
def GetPlayerName(self, player_id): def GetPlayerName(self, player_id):
self.log.debug("request GetPlayerName()") self.log.debug("request GetPlayerName()")
if player_id < 0:
return None
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
return client.nick return client.nick
return return
def RemoveVehicle(self, player_id, vehicle_id): def RemoveVehicle(self, player_id, vehicle_id):
self.log.debug("request GetPlayerName()") self.log.debug("request RemoveVehicle()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
t = self.loop.create_task(client._delete_car(car_id=vehicle_id)) t = self.loop.create_task(client._delete_car(car_id=vehicle_id))
@ -113,6 +161,8 @@ class MP:
def GetPlayerVehicles(self, player_id): def GetPlayerVehicles(self, player_id):
self.log.debug("request GetPlayerVehicles()") self.log.debug("request GetPlayerVehicles()")
if player_id < 0:
return self._lua.table()
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
return self._lua.table_from( return self._lua.table_from(
@ -126,6 +176,8 @@ class MP:
def IsPlayerGuest(self, player_id) -> bool: def IsPlayerGuest(self, player_id) -> bool:
self.log.debug("request IsPlayerGuest()") self.log.debug("request IsPlayerGuest()")
if player_id < 0:
return True
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
return client.guest return client.guest
@ -133,6 +185,8 @@ class MP:
def DropPlayer(self, player_id, reason="Kicked"): def DropPlayer(self, player_id, reason="Kicked"):
self.log.debug("request DropPlayer()") self.log.debug("request DropPlayer()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
if client: if client:
t = self.loop.create_task(client.kick(reason)) t = self.loop.create_task(client.kick(reason))
@ -170,39 +224,158 @@ class Util:
self.name = name self.name = name
self._lua = lua self._lua = lua
def _recursive_list_encode(self, table):
new_list = list(table.values())
for i, v in enumerate(list(table.values())):
if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)):
new_list[i] = None
continue
if "LuaTable" in str(type(v)):
d = dict(v)
if all(isinstance(ii, int) for ii in d.keys()):
new_list[i] = self._recursive_list_encode(d)
continue
else:
new_list[i] = self._recursive_dict_encode(d)
return [i for i in new_list if i is not None]
def _recursive_dict_encode(self, table):
for k, v in table.items():
if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)):
table[k] = None
continue
if "LuaTable" in str(type(v)):
d = dict(v)
if all(isinstance(i, int) for i in d.keys()):
table[k] = self._recursive_list_encode(d)
continue
else:
table[k] = self._recursive_dict_encode(d)
return {k: v for k, v in dict(table).items() if v is not None}
def JsonEncode(self, table): def JsonEncode(self, table):
self.log.debug("requesting JsonEncode()") self.log.debug("requesting JsonEncode()")
if all(isinstance(k, int) for k in table.keys()):
data = self._recursive_list_encode(table)
else:
data = self._recursive_dict_encode(table)
return json.dumps(data)
def JsonDecode(self, string): def JsonDecode(self, string):
self.log.debug("requesting JsonDecode()") self.log.debug("requesting JsonDecode()")
return self._lua.table_from(json.loads(string))
def JsonPrettify(self, string): def JsonPrettify(self, string):
self.log.debug("requesting JsonPrettify()") self.log.debug("requesting JsonPrettify()")
data = json.loads(string)
return json.dumps(data, indent=4, sort_keys=True)
def JsonMinify(self, string): def JsonMinify(self, string):
self.log.debug("requesting JsonMinify()") self.log.debug("requesting JsonMinify()")
data = json.loads(string)
return json.dumps(data, separators=(',', ':'))
def JsonFlatten(self, string): def JsonFlatten(self, json_str):
self.log.debug("requesting JsonFlatten()") self.log.debug("request JsonFlatten()")
json_obj = json.loads(json_str)
flat_obj = {}
def JsonUnflatten(self, string): def flatten(obj, path=''):
self.log.debug("requesting JsonUnflatten()") if isinstance(obj, dict):
for key in obj:
flatten(obj[key], path + '/' + key)
elif isinstance(obj, list):
for i in range(len(obj)):
flatten(obj[i], path + '/' + str(i))
else:
flat_obj[path] = obj
def JsonDiff(self, a, b): flatten(json_obj)
flat_json = json.dumps(flat_obj)
return flat_json
def JsonUnflatten(self, flat_json):
self.log.debug("request JsonUnflatten")
flat_obj = json.loads(flat_json)
def unflatten(obj):
result = {}
for key in obj:
parts = key.split('/')
d = result
for part in parts[:-1]:
if part not in d:
# create a new node in the dictionary
# if the path doesn't exist
d[part] = {}
d = d[part]
# assign the value to the last part of the path
d[parts[-1]] = obj[key]
return result
json_obj = unflatten(flat_obj)
return json.dumps(json_obj)
def JsonDiff(self, a: str, b: str) -> str:
self.log.debug("requesting JsonDiff()") self.log.debug("requesting JsonDiff()")
a_obj = json.loads(a)
b_obj = json.loads(b)
diff = []
for k, v in b_obj.items():
if k not in a_obj:
diff.append({"op": "add", "path": "/" + k, "value": v})
elif a_obj[k] != v:
diff.append({"op": "replace", "path": "/" + k, "value": v})
for k in a_obj.keys() - b_obj.keys():
diff.append({"op": "remove", "path": "/" + k})
return json.dumps(diff)
def JsonDiffApply(self, base, diff): @staticmethod
def _apply_patch(base_obj, patch_obj):
for patch in patch_obj:
op = patch['op']
path = patch['path']
value = patch.get('value', None)
tokens = path.strip('/').split('/')
obj = base_obj
for i, token in enumerate(tokens):
if isinstance(obj, list):
token = int(token)
if i == len(tokens) - 1:
if op == 'add':
if isinstance(obj, list):
obj.insert(int(token), value)
else:
obj[token] = value
elif op == 'replace':
obj[token] = value
elif op == 'remove':
if isinstance(obj, list):
obj.pop(int(token))
else:
del obj[token]
else:
obj = obj[token]
return base_obj
def JsonDiffApply(self, base: str, diff: str) -> str:
self.log.debug("requesting JsonDiffApply()") self.log.debug("requesting JsonDiffApply()")
base_obj = json.loads(base)
diff_obj = json.loads(diff)
result = self._apply_patch(base_obj, diff_obj)
return json.dumps(result)
def Random(self) -> int: def Random(self) -> float:
self.log.debug("requesting Random()") self.log.debug("requesting Random()")
return random.randint(0, 1) return random.random()
def RandomIntRange(self, min_v, max_v): def RandomIntRange(self, min_v, max_v) -> int:
self.log.debug("requesting RandomIntRange()") self.log.debug("requesting RandomIntRange()")
return random.randint(min_v, max_v)
def RandomRange(self, min_v, max_v): def RandomRange(self, min_v, max_v) -> float:
self.log.debug("requesting RandomRange()") self.log.debug("requesting RandomRange()")
return random.uniform(min_v, max_v)
# noinspection PyPep8Naming # noinspection PyPep8Naming
@ -337,12 +510,23 @@ class LuaPluginsLoader:
self.log = get_logger("LuaPluginsLoader") self.log = get_logger("LuaPluginsLoader")
self.loaded_str = "Lua plugins: " self.loaded_str = "Lua plugins: "
ev.register_event("_lua_plugins_get", lambda x: self.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) ev.register_event("_lua_plugins_unload", self.unload)
console.add_command("lua_plugins", lambda x: self.loaded_str[:-2]) console.add_command("lua_plugins", lambda x: self.loaded_str[:-2])
console.add_command("lua_pl", lambda x: self.loaded_str[:-2]) console.add_command("lua_pl", lambda x: self.loaded_str[:-2])
async def load(self): def _start(self, obj, lua, file):
try:
f = lua.globals().loadfile(os.path.abspath(f"plugins/{obj}/{file}"))
f()
self.lua_plugins[obj]['ok'] = True
self.loaded_str += f"{obj}:ok, "
lua.globals().MP.TriggerLocalEvent("onInit")
except Exception as e:
self.loaded_str += f"{obj}:no, "
self.log.error(f"Cannot load lua plugin from `{obj}/main.lua`\n{e}")
# self.log.exception(e)
def load(self):
self.log.debug("Loading Lua plugins...") self.log.debug("Loading Lua plugins...")
py_folders = ev.call_event("_plugins_get")[0] py_folders = ev.call_event("_plugins_get")[0]
for obj in os.listdir(self.plugins_dir): for obj in os.listdir(self.plugins_dir):
@ -363,24 +547,16 @@ class LuaPluginsLoader:
lua.globals().print = mp._print lua.globals().print = mp._print
lua.globals().Util = Util(obj, lua) lua.globals().Util = Util(obj, lua)
lua.globals().FP = FP(obj, lua) lua.globals().FP = FP(obj, lua)
code = f'package.path = package.path.."' \ pa = os.path.abspath(self.plugins_dir)
f';{self.plugins_dir}/{obj}/?.lua' \ p0 = os.path.join(pa, obj, "?.lua")
f';{self.plugins_dir}/{obj}/lua/?.lua"\n' p1 = os.path.join(pa, obj, "lua", "?.lua")
with open("modules/PluginsLoader/add_in.lua", "r") as f: lua.globals().package.path += f';{p0};{p1}'
code += f.read() # with open("modules/PluginsLoader/add_in.lua", "r") as f:
with open(os.path.join(path, "main.lua"), 'r', encoding=config.enc) as f: # code += f.read()
code += f.read() self.lua_plugins.update({obj: {"mp": mp, "lua": lua, "thread": None, "ok": False}})
try: th = Thread(target=self._start, args=(obj, lua, "main.lua"), name=f"lua_plugin_{obj}-Thread")
lua.execute(code) th.start()
self.loaded_str += f"{obj}:ok, " self.lua_plugins[obj]['thread'] = th
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, _): def unload(self, _):
...
async def unload(self, _):
... ...