mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 16:25:36 +00:00
Compare commits
13 Commits
4f7e83a00f
...
1b5ddbdd45
Author | SHA1 | Date | |
---|---|---|---|
1b5ddbdd45 | |||
0d3699bfee | |||
b3dffe74ec | |||
2992c9cbab | |||
43518ac57c | |||
a96e8111e3 | |||
ecf06bf1c9 | |||
8d57db4a23 | |||
3d9e08d05d | |||
ac5f5ee894 | |||
92880a94df | |||
dcc1f14b17 | |||
f9f4df7438 |
@ -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
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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"]:
|
||||
|
@ -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, _):
|
||||
...
|
||||
|
Loading…
x
Reference in New Issue
Block a user