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
- [ ] KuiToi class
- [ ] Client (Player) class
- [ ] Lua part: (Original BeamMP compatibility)
- [x] Lua part: (Original BeamMP compatibility)
- [x] Load Lua plugins
- [x] MP Class (Excluding CreateEventTimer, CreateEventTimer, TriggerLocalEvent, TriggerGlobalEvent, TriggerClientEvent, TriggerClientEventJson)
- [ ] Util class
- [x] MP Class (Excluding CreateEventTimer, CreateEventTimer)
- [x] Util class
- [x] FS class
- [x] MultiLanguage (i18n support)
- [ ] Core

View File

@ -111,8 +111,8 @@ class Client:
async def send_message(self, message, to_all=True):
await self._send(f"C:{message}", to_all=to_all)
async def send_event(self, event_name, event_data):
pass
async def send_event(self, event_name, event_data, to_all=True):
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):

View File

@ -225,7 +225,7 @@ class Core:
pl = PluginsLoader(pl_dir)
await pl.load()
lpl = LuaPluginsLoader(pl_dir)
await lpl.load()
lpl.load()
try:
# WebApi Start
@ -267,7 +267,7 @@ 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")
# await ev.call_async_event("_lua_plugins_start")
self.run = True
self.log.info(i18n.start)
@ -289,10 +289,11 @@ class Core:
asyncio.run(self.main())
async def stop(self):
ev.call_lua_event("onShutdown")
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")
ev.call_event("_lua_plugins_unload")
self.run = False
self.log.info(i18n.stop)
if config.WebAPI["enabled"]:

View File

@ -1,8 +1,10 @@
import asyncio
import json
import os
import platform
import random
import shutil
from threading import Thread
from lupa.lua53 import LuaRuntime
@ -22,6 +24,11 @@ class MP:
self.name = name
self.tasks = []
self._lua = lua
self._local_events = {
"onInit": [], "onShutdown": [], "onPlayerAuth": [], "onPlayerConnecting": [], "onPlayerJoining": [],
"onPlayerJoin": [], "onPlayerDisconnect": [], "onChatMessage": [], "onVehicleSpawn": [],
"onVehicleEdited": [], "onVehicleDeleted": [], "onVehicleReset": [], "onFileChanged": []
}
def _print(self, *args):
s = " ".join(map(str, args))
@ -40,7 +47,13 @@ class MP:
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)
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):
self.log.debug("request CreateEventTimer()")
@ -48,17 +61,31 @@ class MP:
def CancelEventTimer(self, event_name: str):
self.log.debug("request CancelEventTimer()")
# TODO: CreateEventTimer
# TODO: CancelEventTimer
def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()")
# TODO: TriggerLocalEvent
return self._lua.table()
self.log.debug(f"Calling lcoal lua event: '{event_name}'")
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):
self.log.debug("request TriggerGlobalEvent()")
# TODO: TriggerGlobalEvent
return self._lua.table(IsDone=lambda: True, GetResults=lambda: "somedata")
return self._lua.table(
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):
self.log.debug("request SendChatMessage()")
@ -72,12 +99,25 @@ class MP:
self.tasks.append(t)
def TriggerClientEvent(self, player_id, event_name, data):
# TODO: 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):
# TODO: TriggerClientEventJson
self.log.debug("request TriggerClientEventJson()")
data = self._lua.globals().Util.JsonEncode(data)
self.TriggerClientEvent(player_id, event_name, data)
def GetPlayerCount(self):
self.log.debug("request GetPlayerCount()")
@ -85,6 +125,8 @@ class MP:
def GetPositionRaw(self, player_id, car_id):
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]
if client:
car = client.cars[car_id]
@ -95,17 +137,23 @@ class MP:
def IsPlayerConnected(self, player_id):
self.log.debug("request IsPlayerConnected()")
if player_id < 0:
return False
return bool(ev.call_event("_get_player", cid=player_id)[0])
def GetPlayerName(self, player_id):
self.log.debug("request GetPlayerName()")
if player_id < 0:
return None
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()")
self.log.debug("request RemoveVehicle()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client._delete_car(car_id=vehicle_id))
@ -113,6 +161,8 @@ class MP:
def GetPlayerVehicles(self, player_id):
self.log.debug("request GetPlayerVehicles()")
if player_id < 0:
return self._lua.table()
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from(
@ -126,6 +176,8 @@ class MP:
def IsPlayerGuest(self, player_id) -> bool:
self.log.debug("request IsPlayerGuest()")
if player_id < 0:
return True
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return client.guest
@ -133,6 +185,8 @@ class MP:
def DropPlayer(self, player_id, reason="Kicked"):
self.log.debug("request DropPlayer()")
if player_id < 0:
return
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
t = self.loop.create_task(client.kick(reason))
@ -170,39 +224,158 @@ class Util:
self.name = name
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):
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):
self.log.debug("requesting JsonDecode()")
return self._lua.table_from(json.loads(string))
def JsonPrettify(self, string):
self.log.debug("requesting JsonPrettify()")
data = json.loads(string)
return json.dumps(data, indent=4, sort_keys=True)
def JsonMinify(self, string):
self.log.debug("requesting JsonMinify()")
data = json.loads(string)
return json.dumps(data, separators=(',', ':'))
def JsonFlatten(self, string):
self.log.debug("requesting JsonFlatten()")
def JsonFlatten(self, json_str):
self.log.debug("request JsonFlatten()")
json_obj = json.loads(json_str)
flat_obj = {}
def JsonUnflatten(self, string):
self.log.debug("requesting JsonUnflatten()")
def flatten(obj, path=''):
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()")
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()")
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()")
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()")
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()")
return random.uniform(min_v, max_v)
# noinspection PyPep8Naming
@ -337,12 +510,23 @@ class LuaPluginsLoader:
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])
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...")
py_folders = ev.call_event("_plugins_get")[0]
for obj in os.listdir(self.plugins_dir):
@ -363,24 +547,16 @@ class LuaPluginsLoader:
lua.globals().print = mp._print
lua.globals().Util = Util(obj, lua)
lua.globals().FP = FP(obj, lua)
code = f'package.path = package.path.."' \
f';{self.plugins_dir}/{obj}/?.lua' \
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, "
pa = os.path.abspath(self.plugins_dir)
p0 = os.path.join(pa, obj, "?.lua")
p1 = os.path.join(pa, obj, "lua", "?.lua")
lua.globals().package.path += f';{p0};{p1}'
# with open("modules/PluginsLoader/add_in.lua", "r") as f:
# code += f.read()
self.lua_plugins.update({obj: {"mp": mp, "lua": lua, "thread": None, "ok": False}})
th = Thread(target=self._start, args=(obj, lua, "main.lua"), name=f"lua_plugin_{obj}-Thread")
th.start()
self.lua_plugins[obj]['thread'] = th
async def start(self, _):
...
async def unload(self, _):
def unload(self, _):
...