Compare commits

...

20 Commits

Author SHA1 Message Date
bf0a3f3feb Update TODOs; 2023-07-22 07:56:55 +03:00
5e071c5705 EventTimer Ready;
Unload lua plugins;
2023-07-22 07:56:23 +03:00
132beb0dd6 Add MP.CreateTimer(), MP.CreateEventTimer(), MP.CancelEventTimer() 2023-07-22 07:48:35 +03:00
33f2d2ba72 Add call_lua_events() 2023-07-22 06:28:08 +03:00
90113179d7 Add ServerConfig.toml generation; 2023-07-22 06:00:48 +03:00
cd178b815a Add log_chat; 2023-07-22 05:49:52 +03:00
c1f3983856 Minor 2023-07-22 05:23:56 +03:00
96cc4b08db Add warnings to lua;
Add MP.Settings;
2023-07-22 05:23:02 +03:00
d13a319f39 Remove LuaPluginsLoader from imports;
Add use_lua to config;
2023-07-22 05:05:15 +03:00
f24ae23eac Some fixes; 2023-07-22 05:01:11 +03:00
b31b01d137 Update build 2023-07-22 05:00:44 +03:00
e7be3c88be Configs 2023-07-22 04:44:23 +03:00
2dd8b5f5eb _event_waiters;
Recreate loading lua plugins;
2023-07-22 04:16:16 +03:00
84c45d321a Minor update; 2023-07-22 03:51:58 +03:00
b1162af681 Update TODOs 2023-07-22 02:53:28 +03:00
91c9cd8454 CreateTimer 2023-07-22 02:52:57 +03:00
b8326ecdf8 Refactored for pretty view error; 2023-07-22 02:49:13 +03:00
c068629c83 Add calling lua events 2023-07-22 02:48:42 +03:00
905c0a361d Add GetPlayerIDByName (Non docs...); 2023-07-22 02:48:23 +03:00
d7073d9124 New print (Now print tables as json);
Fix _recursive_dict_encode;
Fix lua.globals().onInit();
2023-07-22 01:48:55 +03:00
14 changed files with 350 additions and 111 deletions

2
.gitignore vendored
View File

@ -138,3 +138,5 @@ dmypy.json
/test/ /test/
*test.py *test.py
logs/ logs/
*.yml
*.toml

View File

@ -19,7 +19,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Chat - [x] Chat
- [x] Players online counter - [x] Players online counter
- [x] Packets handled (Recursive finding second packet) - [x] Packets handled (Recursive finding second packet)
- [ ] Client events - [x] Client events
- [x] Car synchronizations: - [x] Car synchronizations:
- [x] State packets - [x] State packets
- [x] Spawn cars - [x] Spawn cars
@ -63,7 +63,7 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [ ] Client (Player) class - [ ] Client (Player) class
- [x] 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) - [x] MP Class
- [x] Util class - [x] Util class
- [x] FS class - [x] FS class
- [x] MultiLanguage (i18n support) - [x] MultiLanguage (i18n support)

View File

@ -7,3 +7,4 @@ starlette~=0.27.0
pydantic~=2.0.2 pydantic~=2.0.2
click~=8.1.4 click~=8.1.4
lupa~=2.0 lupa~=2.0
toml~=0.10.2

View File

@ -364,6 +364,9 @@ class Client:
allow = True allow = True
allow_snowman = True allow_snowman = True
over_spawn = False over_spawn = False
lua_data = ev.call_lua_event("onVehicleSpawn", self.cid, car_id, car_data[car_data.find("{"):])
if 1 in lua_data:
allow = False
ev_data_list = ev.call_event("onCarSpawn", car=car_json, car_id=car_id, player=self) ev_data_list = ev.call_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarSpawn", car=car_json, car_id=car_id, player=self) d2 = await ev.call_async_event("onCarSpawn", car=car_json, car_id=car_id, player=self)
ev_data_list.extend(d2) ev_data_list.extend(d2)
@ -407,6 +410,8 @@ class Client:
if car_id != -1 and self.cars[car_id]: if car_id != -1 and self.cars[car_id]:
ev.call_lua_event("onVehicleDeleted", self.cid, car_id)
admin_allow = False # Delete from admin, for example... admin_allow = False # Delete from admin, for example...
ev_data_list = ev.call_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self) ev_data_list = ev.call_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self) d2 = await ev.call_async_event("onCarDelete", car=self.cars[car_id], car_id=car_id, player=self)
@ -444,6 +449,9 @@ class Client:
allow = False allow = False
admin_allow = False admin_allow = False
lua_data = ev.call_lua_event("onVehicleEdited", self.cid, car_id, data[data.find("{"):])
if 1 in lua_data:
allow = False
ev_data_list = ev.call_event("onCarEdited", car=new_car_json, car_id=car_id, player=self) ev_data_list = ev.call_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
d2 = await ev.call_async_event("onCarEdited", car=new_car_json, car_id=car_id, player=self) d2 = await ev.call_async_event("onCarEdited", car=new_car_json, car_id=car_id, player=self)
ev_data_list.extend(d2) ev_data_list.extend(d2)
@ -472,8 +480,14 @@ class Client:
cid, car_id = self._get_cid_vid(raw_data) cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and cid == self.cid and self.cars[car_id]: if car_id != -1 and cid == self.cid and self.cars[car_id]:
await self._send(raw_data, to_all=True, to_self=False) await self._send(raw_data, to_all=True, to_self=False)
ev.call_event("onCarReset", car=self.cars[car_id], car_id=car_id, player=self) ev.call_lua_event("onVehicleReset", self.cid, car_id, raw_data[raw_data.find("{"):])
await ev.call_async_event("onCarReset", car=self.cars[car_id], car_id=car_id, player=self) car_json = {}
try:
car_json = json.loads(raw_data[raw_data.find("{"):])
except Exception as e:
self.log.debug(f"Invalid new_car_json: Error: {e}; Data: {raw_data}")
ev.call_event("onCarReset", car=car_json, car_id=car_id, player=self)
await ev.call_async_event("onCarReset", car=car_json, car_id=car_id, player=self)
self.log.debug(f"Car reset: car_id={car_id}") self.log.debug(f"Car reset: car_id={car_id}")
else: else:
self.log.debug(f"Invalid car: car_id={car_id}") self.log.debug(f"Invalid car: car_id={car_id}")
@ -536,7 +550,11 @@ class Client:
self.log.debug("Tried to send an empty event, ignoring") self.log.debug("Tried to send an empty event, ignoring")
return return
to_ev = {"message": msg, "player": self} to_ev = {"message": msg, "player": self}
ev.call_lua_event("onChatMessage", self.cid, self.nick, msg) lua_data = ev.call_lua_event("onChatMessage", self.cid, self.nick, msg)
if 1 in lua_data:
if config.Options['log_chat']:
self.log.info(f"{self.nick}: {msg}")
return
ev_data_list = ev.call_event("onChatReceive", **to_ev) ev_data_list = ev.call_event("onChatReceive", **to_ev)
d2 = await ev.call_async_event("onChatReceive", **to_ev) d2 = await ev.call_async_event("onChatReceive", **to_ev)
ev_data_list.extend(d2) ev_data_list.extend(d2)
@ -555,12 +573,14 @@ class Client:
if to_client: if to_client:
# noinspection PyProtectedMember # noinspection PyProtectedMember
writer = to_client._writer writer = to_client._writer
if config.Options['log_chat']:
self.log.info(f"{message}" if to_all else f"{self.nick}: {msg}") self.log.info(f"{message}" if to_all else f"{self.nick}: {msg}")
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer) await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
need_send = False need_send = False
except KeyError | AttributeError: except KeyError | AttributeError:
self.log.error(f"Returns invalid data: {ev_data}") self.log.error(f"Returns invalid data: {ev_data}")
if need_send: if need_send:
if config.Options['log_chat']:
self.log.info(f"{self.nick}: {msg}") self.log.info(f"{self.nick}: {msg}")
await self._send(data, to_all=True) await self._send(data, to_all=True)
@ -599,9 +619,11 @@ class Client:
await self._send(data, to_all=True, to_self=False) await self._send(data, to_all=True, to_self=False)
async def _looper(self): async def _looper(self):
ev.call_lua_event("onPlayerConnecting", self.cid)
self._connect_time = time.monotonic() self._connect_time = time.monotonic()
await self._send(f"P{self.cid}") # Send clientID await self._send(f"P{self.cid}") # Send clientID
await self._sync_resources() await self._sync_resources()
ev.call_lua_event("onPlayerJoining", self.cid)
tasks = self.__tasks tasks = self.__tasks
recv = asyncio.create_task(self._recv()) recv = asyncio.create_task(self._recv())
tasks.append(recv) tasks.append(recv)
@ -629,6 +651,10 @@ class Client:
if self.ready: if self.ready:
await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False) # I'm disconnected. await self._send(f"J{self.nick} disconnected!", to_all=True, to_self=False) # I'm disconnected.
self.log.debug(f"Removing client") self.log.debug(f"Removing client")
ev.call_lua_event("onPlayerDisconnect", self.cid)
ev.call_event("onPlayerDisconnect", player=self)
await ev.call_async_event("onPlayerDisconnect", player=self)
# TODO: i18n # TODO: i18n
self.log.info(f"Disconnected, online time: {round((time.monotonic() - self._connect_time) / 60, 2)}min.") self.log.info(f"Disconnected, online time: {round((time.monotonic() - self._connect_time) / 60, 2)}min.")
self.__Core.clients[self.cid] = None self.__Core.clients[self.cid] = None

View File

@ -11,7 +11,7 @@ __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.4.1' __version__ = '0.4.1'
__build__ = 1486 # Я это считаю лог файлами __build__ = 1925 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"

View File

@ -16,7 +16,7 @@ from core import utils
from core.Client import Client from core.Client import Client
from core.tcp_server import TCPServer from core.tcp_server import TCPServer
from core.udp_server import UDPServer from core.udp_server import UDPServer
from modules import PluginsLoader, LuaPluginsLoader from modules import PluginsLoader
from modules.WebAPISystem import app as webapp from modules.WebAPISystem import app as webapp
@ -224,6 +224,8 @@ class Core:
os.mkdir(pl_dir) os.mkdir(pl_dir)
pl = PluginsLoader(pl_dir) pl = PluginsLoader(pl_dir)
await pl.load() await pl.load()
if config.Options['use_lua']:
from modules.PluginsLoader.lua_plugins_loader import LuaPluginsLoader
lpl = LuaPluginsLoader(pl_dir) lpl = LuaPluginsLoader(pl_dir)
lpl.load() lpl.load()

View File

@ -73,6 +73,20 @@ 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
allow = True
reason = "You are not allowed on the server!"
lua_data = ev.call_lua_event("onPlayerAuth", client.nick, client.roles, client.guest, client.identifiers)
for data in lua_data:
if 1 == data:
allow = True
elif isinstance(data, str):
allow = True
reason = data
if not allow:
await client.kick(reason)
return False, client
ev.call_event("onPlayerAuthenticated", 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"]:

View File

@ -10,6 +10,7 @@ import secrets
import yaml import yaml
class Config: class Config:
def __init__(self, auth=None, game=None, server=None, options=None, web=None): def __init__(self, auth=None, game=None, server=None, options=None, web=None):
self.Auth = auth or {"key": None, "private": True} self.Auth = auth or {"key": None, "private": True}
@ -17,7 +18,7 @@ class Config:
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
"server_ip": "0.0.0.0", "server_port": 30814} "server_ip": "0.0.0.0", "server_port": 30814}
self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False, self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False,
"debug": False} "debug": False, "use_lua": False, "log_chat": True}
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433, self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"secret_key": secrets.token_hex(16)} "secret_key": secrets.token_hex(16)}

View File

@ -33,6 +33,7 @@ class EventsSystem:
"onCarReset": [], "onCarReset": [],
"onSentPing": [], # Only sync "onSentPing": [], # Only sync
"onChangePosition": [], # Only sync "onChangePosition": [], # Only sync
"onPlayerDisconnect": [],
"onServerStopped": [], "onServerStopped": [],
} }
self.__async_events = { self.__async_events = {
@ -43,6 +44,7 @@ class EventsSystem:
"onCarDelete": [], "onCarDelete": [],
"onCarEdited": [], "onCarEdited": [],
"onCarReset": [], "onCarReset": [],
"onPlayerDisconnect": [],
"onServerStopped": [] "onServerStopped": []
} }
@ -71,9 +73,9 @@ class EventsSystem:
f"async_event={async_event}, lua_event={lua}):") f"async_event={async_event}, lua_event={lua}):")
if lua: if lua:
if event_name not in self.__lua_events: if event_name not in self.__lua_events:
self.__lua_events.update({str(event_name): [event_func]}) self.__lua_events.update({str(event_name): [{"func": event_func, "name": lua}]})
else: else:
self.__lua_events[event_name].append(event_func) self.__lua_events[event_name].append({"func": event_func, "name": lua})
self.log.debug("Register ok") self.log.debug("Register ok")
return return
@ -138,12 +140,14 @@ class EventsSystem:
self.log.debug(f"Calling lua event: '{event_name}'") self.log.debug(f"Calling lua event: '{event_name}'")
funcs_data = [] funcs_data = []
if event_name in self.__lua_events.keys(): if event_name in self.__lua_events.keys():
for func in self.__lua_events[event_name]: for data in self.__lua_events[event_name]:
func = data['func']
try: try:
funcs_data.append(func(*args)) fd = func(*args)
funcs_data.append(fd)
except Exception as e: except Exception as e:
# TODO: i18n # TODO: i18n
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"') self.log.error(f'Error while calling lua event "{event_name}"; In function: "{data["name"]}"')
self.log.exception(e) self.log.exception(e)
else: else:
# TODO: i18n # TODO: i18n

View File

@ -1,2 +1 @@
from .plugins_loader import PluginsLoader from .plugins_loader import PluginsLoader
from .lua_plugins_loader import LuaPluginsLoader

View File

@ -1,26 +1,100 @@
package.path = package.path..";modules/PluginsLoader/lua_libs/?.lua" 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 = { MP.CallStrategy = {
BestEffort = 0, BestEffort = 0,
Precise = 1 Precise = 1
} }
MP.Settings = {
Debug = 0,
Private = 1,
MaxCars = 2,
MaxPlayers = 3,
Map = 4,
Name = 5,
Description = 6
}
function MP.CreateTimer()
MP.log.debug("request MP.CreateTimer()")
local timer = {}
timer.start_time = os.clock()
function timer:GetCurrent()
return os.clock() - self.start_time
end
function timer:Start()
self.start_time = os.clock()
end
return timer
end
----Timer object for event timers
--local TimedEvent = {}
--TimedEvent.__index = TimedEvent
--
--function TimedEvent:new(interval_ms, event_name, strategy)
-- local o = {}
-- setmetatable(o, self)
-- o.interval = interval_ms
-- o.event_name = event_name
-- o.strategy = strategy or MP.CallStrategy.BestEffort
-- o.last_trigger_time = 0
-- o.timer = MP.CreateTimer()
-- return o
--end
--
--function TimedEvent:trigger()
-- MP.TriggerLocalEvent(self.event_name)
-- self.last_trigger_time = self.timer:GetCurrent()
--end
--
--function TimedEvent:is_ready()
-- local elapsed_time = self.timer:GetCurrent() - self.last_trigger_time
-- return elapsed_time * 1000 >= self.interval
--end
--
---- Event timer management functions
--MP.event_timers = {}
--MP.event_timers_mutex = {}
--
--function MP.CreateEventTimer(event_name, interval_ms, strategy)
-- MP.log.debug("request MP.CreateEventTimer()")
-- strategy = strategy or MP.CallStrategy.BestEffort
-- local timer = TimedEvent:new(interval_ms, event_name, strategy)
-- table.insert(MP.event_timers, timer)
-- MP.log.debug("created event timer for \"" .. event_name .. "\" with " .. interval_ms .. "ms interval")
--end
--
--function MP.CancelEventTimer(event_name)
-- MP.log.debug("request MP.CancelEventTimer()")
-- for i, timer in ipairs(MP.event_timers) do
-- if timer.event_name == event_name then
-- table.remove(MP.event_timers, i)
-- end
-- end
-- MP.log.debug("cancelled event timer for \"" .. event_name .. "\"")
--end
--
--function MP.run_event_timers()
-- MP.log.debug("request MP.run_event_timers()")
-- while true do
-- -- Wait for some time before checking timers
-- MP.Sleep(100)
--
-- -- Check each timer and trigger events as necessary
-- for _, timer in ipairs(MP.event_timers) do
-- if timer:is_ready() then
-- if timer.strategy == MP.CallStrategy.Precise then
-- while timer:is_ready() do
-- timer:trigger()
-- end
-- else
-- timer:trigger()
-- end
-- end
-- end
-- end
--end

View File

@ -4,21 +4,53 @@ import os
import platform import platform
import random import random
import shutil import shutil
import threading
import time
from threading import Thread from threading import Thread
import toml
from lupa.lua53 import LuaRuntime from lupa.lua53 import LuaRuntime
from core import get_logger from core import get_logger
class EventTimer:
def __init__(self, event_name, interval_ms, mp, strategy=None):
self.log = get_logger(f"EventTimer | {mp.name}")
self.mp = mp
self.event_name = event_name
self.interval_ms = interval_ms
self.strategy = strategy
self.timer = None
self.stopped = False
def start(self):
def callback():
if not self.stopped:
self.start()
self.trigger_event()
self.timer = threading.Timer(self.interval_ms / 1000.0, callback)
self.timer.start()
def stop(self):
self.stopped = True
if self.timer is not None:
self.timer.cancel()
def trigger_event(self):
self.log.debug(f"Event '{self.event_name}' triggered")
self.mp.TriggerLocalEvent(self.event_name)
# noinspection PyPep8Naming # noinspection PyPep8Naming
class MP: class MP:
# In ./in_lua.lua # In ./in_lua.lua
# MP.CreateTimer
# MP.Sleep
def __init__(self, name: str, lua: LuaRuntime): def __init__(self, name: str, lua: LuaRuntime):
self.loaded = False
self._event_waiters = []
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.log = get_logger(f"LuaPlugin | {name}") self.log = get_logger(f"LuaPlugin | {name}")
self.name = name self.name = name
@ -29,8 +61,13 @@ class MP:
"onPlayerJoin": [], "onPlayerDisconnect": [], "onChatMessage": [], "onVehicleSpawn": [], "onPlayerJoin": [], "onPlayerDisconnect": [], "onChatMessage": [], "onVehicleSpawn": [],
"onVehicleEdited": [], "onVehicleDeleted": [], "onVehicleReset": [], "onFileChanged": [] "onVehicleEdited": [], "onVehicleDeleted": [], "onVehicleReset": [], "onFileChanged": []
} }
self._event_timers = {}
def _print(self, *args): def _print(self, *args):
args = list(args)
for i, arg in enumerate(args):
if "LuaTable" in str(type(arg)):
args[i] = self._lua.globals().Util.JsonEncode(arg)
s = " ".join(map(str, args)) s = " ".join(map(str, args))
self.log.info(s) self.log.info(s)
@ -45,10 +82,21 @@ class MP:
self.log.debug("request MP.GetServerVersion()") self.log.debug("request MP.GetServerVersion()")
return ev.call_event("_get_BeamMP_version")[0] return ev.call_event("_get_BeamMP_version")[0]
def _reg_ev(self):
for event in self._event_waiters:
self.RegisterEvent(*event)
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()")
if not self.loaded:
self.log.debug("MP.RegisterEvent: plugin not loaded, waiting...")
self._event_waiters.append([event_name, function_name])
return
event_func = self._lua.globals()[function_name] event_func = self._lua.globals()[function_name]
ev.register_event(event_name, event_func, lua=True) if not event_func:
self.log.warning(f"Can't register '{event_name}': not found function: '{function_name}'")
return
ev.register_event(event_name, event_func, lua=function_name)
if event_name not in self._local_events: if event_name not in self._local_events:
self._local_events.update({str(event_name): [event_func]}) self._local_events.update({str(event_name): [event_func]})
else: else:
@ -57,36 +105,45 @@ class MP:
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()")
# TODO: CreateEventTimer event_timer = EventTimer(event_name, interval_ms, self, strategy)
self._event_timers[event_name] = event_timer
event_timer.start()
def CancelEventTimer(self, event_name: str): def CancelEventTimer(self, event_name: str):
self.log.debug("request CancelEventTimer()") self.log.debug("request CancelEventTimer()")
# TODO: CancelEventTimer if event_name in self._event_timers:
event_timer = self._event_timers[event_name]
event_timer.stop()
del self._event_timers[event_name]
def TriggerLocalEvent(self, event_name, *args): def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()") self.log.debug("request TriggerLocalEvent()")
self.log.debug(f"Calling lcoal lua event: '{event_name}'") self.log.debug(f"Calling local lua event: '{event_name}'")
funcs_data = [] funcs_data = []
if event_name in self._local_events.keys(): if event_name in self._local_events.keys():
for func in self._local_events[event_name]: for func in self._local_events[event_name]:
try: try:
funcs_data.append(func(*args)) funcs_data.append(func(*args))
except Exception as e: except Exception as e:
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"') self.log.error(f'Error while calling "{event_name}"; In function: "{func}"')
self.log.exception(e) self.log.exception(e)
else: else:
self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. " self.log.warning(f"Event {event_name} does not exist, maybe ev.call_lua_event() or MP.Trigger<>Event()?. "
f"Just skipping it...") f"Just skipping it...")
return self._lua.table_from({i: v for i, v in enumerate(funcs_data)}) return self._lua.table_from(funcs_data)
def TriggerGlobalEvent(self, event_name, *args): def TriggerGlobalEvent(self, event_name, *args):
self.log.debug("request TriggerGlobalEvent()") self.log.debug("request TriggerGlobalEvent()")
return self._lua.table( return self._lua.table(
IsDone=lambda: True, IsDone=lambda: True,
GetResults=lambda: self._lua.table_from({i: v for i, v in enumerate(ev.call_lua_event(event_name, *args))}) GetResults=lambda: self._lua.table_from(ev.call_lua_event(event_name, *args))
) )
def Sleep(self, time_ms):
self.log.debug(f"request Sleep(); Thread: {threading.current_thread().name}")
time.sleep(time_ms * 0.001)
def SendChatMessage(self, player_id, message): def SendChatMessage(self, player_id, message):
self.log.debug("request SendChatMessage()") self.log.debug("request SendChatMessage()")
client = ev.call_event("_get_player", cid=player_id)[0] client = ev.call_event("_get_player", cid=player_id)[0]
@ -150,6 +207,15 @@ class MP:
return client.nick return client.nick
return return
def GetPlayerIDByName(self, player_name):
self.log.debug("request GetPlayerIDByName()")
if not isinstance(player_name, str):
return None
client = ev.call_event("_get_player", nick=player_name)[0]
if client:
return client.cid
return
def RemoveVehicle(self, player_id, vehicle_id): def RemoveVehicle(self, player_id, vehicle_id):
self.log.debug("request RemoveVehicle()") self.log.debug("request RemoveVehicle()")
if player_id < 0: if player_id < 0:
@ -165,14 +231,13 @@ class MP:
return self._lua.table() 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([f'{v["json"]}' for d in [i for i in client.cars if i is not None]
{i: f'{v["json"]}' for i, d in enumerate([i for i in client.cars if i is not None]) for k, v in for k, v in d.items() if k == "json"])
d.items() if k == "json"})
def GetPlayers(self): def GetPlayers(self):
self.log.debug("request GetPlayers()") self.log.debug("request GetPlayers()")
clients = ev.call_event("_get_players", cid=-1) clients = ev.call_event("_get_players", cid=-1)
return self._lua.table_from({i: n for i, n in enumerate(clients)}) return self._lua.table_from(clients)
def IsPlayerGuest(self, player_id) -> bool: def IsPlayerGuest(self, player_id) -> bool:
self.log.debug("request IsPlayerGuest()") self.log.debug("request IsPlayerGuest()")
@ -212,10 +277,6 @@ class MP:
self.log.debug("request Set") self.log.debug("request Set")
self.log.warning("KuiToi cannot support this: MP.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()")
# noinspection PyPep8Naming # noinspection PyPep8Naming
class Util: class Util:
@ -240,18 +301,19 @@ class Util:
return [i for i in new_list if i is not None] return [i for i in new_list if i is not None]
def _recursive_dict_encode(self, table): def _recursive_dict_encode(self, table):
new_dict = dict(table)
for k, v in table.items(): for k, v in table.items():
if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)): if not isinstance(v, (int, float, bool, str, dict, list)) and "LuaTable" not in str(type(v)):
table[k] = None new_dict[k] = None
continue continue
if "LuaTable" in str(type(v)): if "LuaTable" in str(type(v)):
d = dict(v) d = dict(v)
if all(isinstance(i, int) for i in d.keys()): if all(isinstance(i, int) for i in d.keys()):
table[k] = self._recursive_list_encode(d) new_dict[k] = self._recursive_list_encode(d)
continue continue
else: else:
table[k] = self._recursive_dict_encode(d) new_dict[k] = self._recursive_dict_encode(d)
return {k: v for k, v in dict(table).items() if v is not None} return {k: v for k, v in new_dict.items() if v is not None}
def JsonEncode(self, table): def JsonEncode(self, table):
self.log.debug("requesting JsonEncode()") self.log.debug("requesting JsonEncode()")
@ -379,7 +441,7 @@ class Util:
# noinspection PyPep8Naming # noinspection PyPep8Naming
class FP: class FS:
def __init__(self, name: str, lua: LuaRuntime): def __init__(self, name: str, lua: LuaRuntime):
self.log = get_logger(f"LuaPlugin | FP | {name}") self.log = get_logger(f"LuaPlugin | FP | {name}")
@ -391,6 +453,8 @@ class FP:
try: try:
os.makedirs(path) os.makedirs(path)
return True, None return True, None
except FileExistsError:
return True, None
except FileNotFoundError | NotADirectoryError as e: except FileNotFoundError | NotADirectoryError as e:
return False, f"{e}" return False, f"{e}"
except PermissionError as e: except PermissionError as e:
@ -483,7 +547,7 @@ class FP:
item_path = os.path.join(path, item) item_path = os.path.join(path, item)
if os.path.isdir(item_path): if os.path.isdir(item_path):
directories.append(item) directories.append(item)
return self._lua.table_from({i: v for i, v in enumerate(directories)}) return self._lua.table_from(directories)
def ListFiles(self, path): def ListFiles(self, path):
self.log.debug("requesting ListFiles()") self.log.debug("requesting ListFiles()")
@ -492,7 +556,7 @@ class FP:
item_path = os.path.join(path, item) item_path = os.path.join(path, item)
if os.path.isfile(item_path): if os.path.isfile(item_path):
files.append(item) files.append(item)
return self._lua.table_from({i: v for i, v in enumerate(files)}) return self._lua.table_from(files)
def ConcatPaths(self, *args): def ConcatPaths(self, *args):
self.log.debug("requesting ConcatPaths()") self.log.debug("requesting ConcatPaths()")
@ -506,7 +570,7 @@ class LuaPluginsLoader:
self.plugins_dir = plugins_dir self.plugins_dir = plugins_dir
self.lua_plugins = {} self.lua_plugins = {}
self.lua_plugins_tasks = [] self.lua_plugins_tasks = []
self.lua_dirs = [] self.lua_dirs = set()
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)
@ -514,49 +578,103 @@ class LuaPluginsLoader:
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])
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): def load(self):
self.log.debug("Loading Lua plugins...") self.log.debug("Loading Lua plugins...")
# TODO: i18n
self.log.info("You have enabled support for Lua plugins.")
self.log.warning("There are some nuances to working with KuiToi. "
"If you have a proposal for their solution, and it is related to KuiToi, "
"please contact the developer.")
self.log.warning("Some BeamMP plugins require a correctly configured ServerConfig.toml file to function.")
self.log.info("Creating it.")
data = {
"info": "ServerConfig.toml is created solely for backward compatibility support. "
"This file will be updated every time the program is launched.",
"General": {
"Name": config.Server['name'],
"Port": config.Server['server_port'],
"AuthKey": config.Auth['key'],
"LogChat": config.Options['log_chat'],
"Debug": config.Options['debug'],
"Private": config.Auth['private'],
"MaxCars": config.Game['max_cars'],
"MaxPlayers": config.Game['players'],
"Map": f"/levels/{config.Game['map']}/info.json",
"Description": config.Server['description'],
"ResourceFolder": "plugins/"
},
"Misc": {
"ImScaredOfUpdates": False,
"SendErrorsShowMessage": False,
"SendErrors": False
},
"HTTP": {
"HTTPServerIP": config.WebAPI['server_ip'],
"HTTPServerPort": config.WebAPI['server_port'],
"SSLKeyPath": None,
"SSLCertPath": None,
"UseSSL": False,
"HTTPServerEnabled": config.WebAPI['enabled'],
}
}
with open("ServerConfig.toml", "w") as f:
toml.dump(data, f)
self.log.warning("KuiToi will not support at all: MP.Set()")
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 name in os.listdir(self.plugins_dir):
path = os.path.join(self.plugins_dir, obj) path = os.path.join(self.plugins_dir, name)
if os.path.isdir(path) and obj not in py_folders and obj not in "__pycache__": if os.path.isdir(path) and name not in py_folders and name not in "__pycache__":
if os.path.isfile(os.path.join(path, "main.lua")): plugin_path = os.path.join(self.plugins_dir, name)
self.lua_dirs.append([path, obj]) for file in os.listdir(plugin_path):
path = f"plugins/{name}/{file}"
if os.path.isfile(path) and path.endswith(".lua"):
self.lua_dirs.add(name)
self.log.debug(f"py_folders {py_folders}, lua_dirs {self.lua_dirs}") self.log.debug(f"py_folders {py_folders}, lua_dirs {self.lua_dirs}")
for path, obj in self.lua_dirs: for name in self.lua_dirs:
# noinspection PyArgumentList # noinspection PyArgumentList
lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc, unpack_returned_tuples=True) lua = LuaRuntime(encoding=config.enc, source_encoding=config.enc, unpack_returned_tuples=True)
lua.globals().printRaw = lua.globals().print lua_globals = lua.globals()
lua.globals().exit = lambda x: self.log.info(f"{obj}: You can't disable server..") lua_globals.printRaw = lua.globals().print
mp = MP(obj, lua) lua_globals.exit = lambda x: self.log.info(f"{name}: You can't disable server..")
lua.globals().MP = mp mp = MP(name, lua)
lua.globals().print = mp._print lua_globals.MP = mp
lua.globals().Util = Util(obj, lua) lua_globals.print = mp._print
lua.globals().FP = FP(obj, lua) lua_globals.Util = Util(name, lua)
lua_globals.FS = FS(name, lua)
pa = os.path.abspath(self.plugins_dir) pa = os.path.abspath(self.plugins_dir)
p0 = os.path.join(pa, obj, "?.lua") p0 = os.path.join(pa, name, "?.lua")
p1 = os.path.join(pa, obj, "lua", "?.lua") p1 = os.path.join(pa, name, "lua", "?.lua")
lua.globals().package.path += f';{p0};{p1}' lua_globals.package.path += f';{p0};{p1}'
# with open("modules/PluginsLoader/add_in.lua", "r") as f: with open("modules/PluginsLoader/add_in.lua", "r") as f:
# code += f.read() lua.execute(f.read())
self.lua_plugins.update({obj: {"mp": mp, "lua": lua, "thread": None, "ok": False}}) self.lua_plugins.update({name: {"lua": lua, "ok": False, "th": None, "stop_th": None}})
th = Thread(target=self._start, args=(obj, lua, "main.lua"), name=f"lua_plugin_{obj}-Thread") plugin_path = os.path.join(self.plugins_dir, name)
th.start() for file in os.listdir(plugin_path):
self.lua_plugins[obj]['thread'] = th path = f"plugins/{name}/{file}"
if os.path.isfile(path) and path.endswith(".lua"):
try:
lua_globals.loadfile(path)()
except Exception as e:
self.loaded_str += f"{name}:no, "
self.log.error(f"Cannot load lua plugin from `{path}`: {e}")
try:
lua_globals.MP.loaded = True
lua_globals.MP._reg_ev()
lua_globals.MP.TriggerLocalEvent("onInit")
lua_globals.onInit()
self.lua_plugins[name]['ok'] = True
self.loaded_str += f"{name}:ok, "
except Exception as e:
self.loaded_str += f"{name}:no, "
self.log.error(f"Exception onInit from `{name}`: {e}")
self.log.exception(e)
def unload(self, _): def unload(self, _):
... self.log.debug("Unloading lua plugins")
for k, data in self.lua_plugins.items():
if data['ok']:
self.log.debug(f"Unloading lua plugin: {k}")
for k, v in data['lua'].globals().MP._event_timers.items():
v.stop()

View File

@ -79,8 +79,7 @@ class PluginsLoader:
async def load(self): async def load(self):
self.log.debug("Loading plugins...") self.log.debug("Loading plugins...")
files = os.listdir(self.plugins_dir) for file in os.listdir(self.plugins_dir):
for file in files:
file_path = os.path.join(self.plugins_dir, file) file_path = os.path.join(self.plugins_dir, file)
if os.path.isfile(file_path) and file.endswith(".py"): if os.path.isfile(file_path) and file.endswith(".py"):
try: try:
@ -96,14 +95,14 @@ class PluginsLoader:
ok = True ok = True
try: try:
isfunc = inspect.isfunction is_func = inspect.isfunction
if not isfunc(plugin.load): if not is_func(plugin.load):
self.log.error('Function "def load():" not found.') self.log.error('Function "def load():" not found.')
ok = False ok = False
if not isfunc(plugin.start): if not is_func(plugin.start):
self.log.error('Function "def start():" not found.') self.log.error('Function "def start():" not found.')
ok = False ok = False
if not isfunc(plugin.unload): if not is_func(plugin.unload):
self.log.error('Function "def unload():" not found.') self.log.error('Function "def unload():" not found.')
ok = False ok = False
if type(plugin.kt) != KuiToi: if type(plugin.kt) != KuiToi:
@ -121,22 +120,22 @@ class PluginsLoader:
f'Plugin name: "{pl_name}"; Plugin file "{file_path}"') f'Plugin name: "{pl_name}"; Plugin file "{file_path}"')
plugin.open = plugin.kt.open plugin.open = plugin.kt.open
iscorfunc = inspect.iscoroutinefunction is_coro_func = inspect.iscoroutinefunction
self.plugins.update( self.plugins.update(
{ {
pl_name: { pl_name: {
"plugin": plugin, "plugin": plugin,
"load": { "load": {
"func": plugin.load, "func": plugin.load,
"async": iscorfunc(plugin.load) "async": is_coro_func(plugin.load)
}, },
"start": { "start": {
"func": plugin.start, "func": plugin.start,
"async": iscorfunc(plugin.start) "async": is_coro_func(plugin.start)
}, },
"unload": { "unload": {
"func": plugin.unload, "func": plugin.unload,
"async": iscorfunc(plugin.unload) "async": is_coro_func(plugin.unload)
} }
} }
} }

View File

@ -11,6 +11,5 @@ from .ConfigProvider import ConfigProvider, Config
from .i18n import MultiLanguage from .i18n import MultiLanguage
from .EventsSystem import EventsSystem from .EventsSystem import EventsSystem
from .PluginsLoader import PluginsLoader from .PluginsLoader import PluginsLoader
from .PluginsLoader import LuaPluginsLoader
from .WebAPISystem import web_app from .WebAPISystem import web_app
from .WebAPISystem import _stop as stop_web from .WebAPISystem import _stop as stop_web