Compare commits

..

24 Commits

Author SHA1 Message Date
ddcfa56467 cn.json 2023-07-23 00:26:07 +03:00
9e1017609c Add cn translations 2023-07-23 00:25:42 +03:00
d76262fc43 Fix 2023-07-23 00:17:32 +03:00
9253f24421 Update setup page 2023-07-23 00:10:07 +03:00
8435b00617 Add cn translation 2023-07-23 00:01:33 +03:00
847fe68417 Update en translation 2023-07-22 23:59:21 +03:00
5f32bf8423 Update TODOs 2023-07-22 23:31:24 +03:00
139143c517 Add логику 2023-07-22 23:28:41 +03:00
4bd2e28a21 md... 2023-07-22 23:28:05 +03:00
28203c5836 Заглушка 2023-07-22 23:26:58 +03:00
7de5837db4 Player class 2023-07-22 23:26:47 +03:00
cb00829ae7 client.cars -> client._cars;
Add some handlers;
2023-07-22 23:26:30 +03:00
59b0e58801 links fix 2023-07-22 23:14:47 +03:00
5b814efbb9 Minor update 2023-07-22 21:34:52 +03:00
17b8be1b9d Update KuiToi Class docs 2023-07-22 21:33:46 +03:00
25ac16c300 Add new Methods 2023-07-22 21:33:33 +03:00
1fac1d5ae9 Minor get_client update 2023-07-22 21:20:44 +03:00
d6021ddc2d Add pid (PlayerId) 2023-07-22 21:13:57 +03:00
8e8c66c3bf Optimize imports 2023-07-22 20:58:26 +03:00
4273571d97 call_lua_event 2023-07-22 20:45:36 +03:00
71df291391 Add KuiTio docs 2023-07-22 20:45:22 +03:00
cf0f397465 Move to examples 2023-07-22 18:15:54 +03:00
7b579d2916 Lua example 2023-07-22 18:14:57 +03:00
a3386339d0 Ньансы работы Lua 2023-07-22 17:59:29 +03:00
38 changed files with 1423 additions and 101 deletions

View File

@ -55,8 +55,8 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [ ] Python part:
- [x] Load Python plugins
- [x] Async support
- [ ] KuiToi class
- [ ] Client (Player) class
- [x] KuiToi class
- [x] Client (Player) class
- [ ] JavaScript part:
- [ ] Load JavaScript plugins
- [ ] KuiToi class

View File

@ -0,0 +1,48 @@
{
"": "基础阶段",
"hello": "来自KuiToi服务器的问候",
"config_path": "使用{}进行配置。",
"init_ok": "初始化完成。",
"start": "服务器已启动!",
"stop": "服务器已停止!",
"": "服务器认证",
"auth_need_key": "需要BeamMP密钥才能启动",
"auth_empty_key": "BeamMP密钥为空",
"auth_cannot_open_browser": "无法打开浏览器:{}",
"auth_use_link": "使用此链接:{}",
"": "GUI阶段",
"GUI_yes": "是",
"GUI_no": "否",
"GUI_ok": "确定",
"GUI_cancel": "取消",
"GUI_need_key_message": "需要BeamMP密钥才能启动\n是否在浏览器中打开链接以获取密钥",
"GUI_enter_key_message": "请输入密钥:",
"GUI_cannot_open_browser": "无法打开浏览器。\n请使用此链接{}",
"": "Web阶段",
"web_start": "WebAPI已启动{}CTRL+C停止",
"": "命令man",
"man_message_man": "man - 显示COMMAND的帮助页面。\n用法man COMMAND",
"help_message_man": "显示COMMAND的帮助页面。",
"man_for": "帮助页面",
"man_message_not_found": "man找不到帮助页面。",
"man_command_not_found": "man找不到\"{}\"命令!",
"": "命令help",
"man_message_help": "help - 显示命令的名称和简短描述。\n用法help [--raw]\n命令`help`列出所有可用的命令,并为每个命令提供简短描述。",
"help_message_help": "显示命令的名称和简短描述。",
"help_command": "命令",
"help_message": "文本",
"help_message_not_found": "无文本",
"": "命令stop",
"man_message_stop": "stop - 关闭服务器。\n用法stop",
"help_message_stop": "关闭服务器。",
"": "命令exit",
"man_message_exit": "exit - 关闭服务器。\n用法exit",
"help_message_exit": "关闭服务器。"
}

View File

@ -0,0 +1,3 @@
# MultiLanguage - i18n支持
在 [example.json](./example.json) 中是 [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json) 的副本。如果你想将其翻译成以前未翻译过的语言,或者更新现有的翻译,我将很高兴接受你的拉取请求。

174
docs/cn/plugins/classes.md Normal file
View File

@ -0,0 +1,174 @@
# 传递的类
## 值得一读
1. 什么是“*args”和“**kwargs” -> [habr上的文章 ↗](https://habr.com/ru/companies/ruvds/articles/482464/)
## KuiToi
_`kt = KuiToi("PluginName"")`_
### kt.log
_常量_\
返回预配置的记录器
### kt.name
_常量_\
返回插件名称
### kt.dir
_常量_\
返回插件文件夹
### kt.open()
_与open()参数相同_\
在kt.dir中打开文件
### kt.register_event(event_name: str, event_func: function)
_`event_name: str` -> 作为`event_func`调用的事件名称._\
_`event_func: function` -> 要调用的函数._
`event_func`可以传递普通函数或async - 不需要提前进行await。\
您也可以创建自己的事件,并使用自己的名称注册任意数量的事件。
### kt.call_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> 要调用的事件名称._\
_`*args, **kwargs` -> 要传递给函数的参数._
### **async** kt.call_async_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> 要调用的事件名称._\
_`*args, **kwargs` -> 要传递给函数的参数._\
_需要用`await`调用_
###### _建议阅读*args, **kwargs链接在开头_
所有事件的数据都以以下格式传递:`{"event_name": event_name, "args": args, "kwargs": kwargs}`\
`args: list` -> 表示传递到事件中的数据数组\
`kwargs: dict` -> 表示传递到事件中的数据字典
数据将以数组形式从所有成功的波动中返回。
### kt.call_lua_event(event_name: str, *args) -> list:
_`event_name: str` -> 要调用的事件名称._\
_`*args` -> 要传递给函数的参数._
添加用于向后兼容性。\
lua函数使用直接传递参数`lua_func(*args)`进行调用。
### kt.get_player([pid: int], [nick: str]) -> Player | None:
_`pid: int` -> Player ID - 玩家标识符._\
_`nick: str` -> Player Nick - 玩家昵称._
该方法通过其`pid``nick`返回玩家对象。\
如果无法找到玩家,则返回 `None`
### kt.get_players() -> List[Player] | list:
该方法返回所有玩家的数组。\
如果没有玩家,则数组将为空。
### kt.players_counter() -> int:
该方法返回在线的玩家数量。
### kt.is_player_connected([pid: int], [nick: str]) -> bool:
_`pid: int` -> Player ID - 玩家标识符._\
_`nick: str` -> Player Nick - 玩家昵称._
该方法通过其`pid``nick`返回玩家对象。
## Player (或 Client)
_`pl = kt.get_player()`_\
_`pl = event_data['kwargs']['player']`_
### pl.log -> Logger
_常量_\
返回预配置的记录器
### pl.addr -> str
_常量_\
返回玩家的 IP 地址
### pl.pid -> int
### pl.cid -> int
_常量_\
返回客户端的 ID _(pid: PlayerId = cid: ClientId)_
### pl.key -> str
_常量_\
返回在身份验证期间传递的密钥
### pl.nick -> str
_变量_\
从 BeamMP 服务器传递的昵称,可以更改,后果未知
### pl.roles -> str
_变量_\
从 BeamMP 服务器传递的角色,可以更改(如果设置了不正确的角色,可能会发生意外情况。)
### pl.guest -> bool
_常量_\
返回玩家是否为游客,从 BeamMP 服务器传递
### pl.identifiers -> dict
_常量_\
标识符,从 BeamMP 服务器传递。
### pl.ready -> bool
_常量由核心更改_\
返回布尔值,如果为 True-> 玩家已下载所有资源,在地图上加载
### pl.cars -> dict
_常量由核心更改_\
按类型返回汽车字典:
```python
{
1: {
"packet": car_packet,
"json": car_json,
"json_ok": bool(car_json),
"snowman": snowman,
"over_spawn": (snowman and allow_snowman) or over_spawn,
"pos": {
"pos":[0,0,0],
"rvel":[0,0,0],
"rot":[0,0,0],
"vel":[0,0,0],
"tim":0,
"ping":0
}
},
2: ...
}
```
其中 `1` - car_id\
其中 `pkt` - 未处理的从客户端收到的数据包(仅供非常有经验的用户使用)\
其中 `json` - 以 dict 形式存储的已处理的数据包\
其中 `json_ok` - 核心是否能够处理数据包\
其中 `snowman` - 车辆是否为雪人\
其中 `over_spawn` - 车辆是否超过了生成限制(通过插件允许)\
其中 `pos` - 车辆位置(通过 UDP 传递)
### pl.last_position -> dict
_常量由核心更改_
返回玩家的最后位置
### **async** pl.kick([reason: str = "Kicked!"]) -> None
_`reason: str` -> 踢出理由,参数可选,默认值为 `Kicked!`_
将玩家踢出服务器
### **async** pl.send_message(message: str, [to_all: bool = True]) -> None
_`message: str` -> 消息文本,不带 "Server:"_
_`to_all: bool` -> 是否向所有人发送此消息?参数可选,默认值为 `True`_
向玩家或所有人发送消息
### **async** pl.send_event(event_name: str, event_data: Any, [to_all: bool = True]) -> None
_`event_name: str` -> 要调用的事件名称_
_`event_data: Any` -> 发送到事件的数据。_
_`to_all: bool` -> 是否向所有人发送此消息?参数可选,默认值为 `True`_
将事件发送到客户端。\
如果 event_data 是 tuple、list、dict则核心会通过 json.dumps(event_data) 将其转换为 json然后再发送。\
否则,数据将是字符串,不受限制;
### **async** pl.delete_car(self, car_id: int) -> None
_`car_id: int` -> 要删除的车辆的 ID_
删除玩家的车辆

View File

@ -0,0 +1,3 @@
# 所有可用事件列表
大多数事件将包含`pl = data ['kwargs'] ['player']`,可以在[这里](./classes.md)找到描述。

View File

@ -0,0 +1,27 @@
print("example.lua")
--CreateTimer Testing
local mytimer = MP.CreateTimer()
--.--.--.--.--.--.--.
--GetOSName Testing
print("OS Name: "..MP.GetOSName())
--.--.--.--.--.--.-
--GetServerVersion Testing
local major, minor, patch = MP.GetServerVersion()
print("Server Version: "..major.."."..minor.."."..patch)
--.--.--.--.--.--.--.--.--
--Events Testing--
function handleChat(player_id, player_name, message)
print("Lua handleChat:", player_id, player_name, message, "; Uptime: "..mytimer:GetCurrent())
return 1
end
MP.RegisterEvent("onChatMessage", "handleChat")
--.--.--.--.--.--.
function onInit()
print("Initializing ready!")
end

View File

@ -0,0 +1,35 @@
# BeamMP Lua反馈支持
KiuToi几乎完全支持BeamMP的lua插件所有必要的方法都已经创建测试显示以下细节
在KiuToi中没有支持:`MP.Set()`
#### Economic Rework V2.0付费DiscordRU[Hlebushek](https://discordapp.com/users/449634697593749516)
1. 要获取`pluginPath`,需要:`debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")`,因为路径返回值中包含`@`,这破坏了插件。
#### Cobalt Essentials V1.7.5(免费,[github ↗](https://github.com/prestonelam2003/CobaltEssentials/)
1. 要获取`pluginPath`,需要:`debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")`,因为路径返回值中包含`@`,这破坏了插件。
2. 必须将所有的`require()`移动到`onInit`之后。
3. 在某些情况下,必须在函数声明之后注册`MP.RegisterEvent`,即:
```lua
--这样不正确,可能无法注册
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
function onPlayerAuth(name, role, isGuest)
-- Some plugin code
end
--这样就可以了
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
```
### 工作原理
插件加载经过几个阶段:
1. 扫描`plugins/`文件夹
2. 如果文件夹不在PyPlugins中并且文件夹中存在`*.lua`,则添加它,例如`plugins/LuaPlugin`
3. 然后从该文件夹中进行`lua.loadfile({filename})`这是lua中的标准方法
4. 最后调用事件和函数`onInit()`
5. 如果在执行`onInit()`期间没有发生错误,则可以通过`lua_plugins`命令看到这样的消息:`Lua plugins: LuaPlugin:ok`

97
docs/cn/plugins/readme.md Normal file
View File

@ -0,0 +1,97 @@
# 插件系统
### 事件:[这里](./events_list.md)
### 类:[这里](./classes.md)
## 使用带有“Dummy”的库
###### 这意味着它没有服务器无法工作但IDE将指导API
###### (库还在开发中)
* 使用pip\
`$ pip install KuiToi`
* 从源代码安装:\
`git clone https://github.com/KuiToi/KuiToi-PyLib`
## 示例
```python
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("ExamplePlugin")
log = kt.log
def my_event_handler(event_data):
log.info(f"{event_data}")
def load():
# 初始化插件
ev.register_event("my_event", my_event_handler)
log.info("插件已成功加载。")
def start():
# 启动插件进程
ev.call_event("my_event")
ev.call_event("my_event", "一些数据", data="一些数据也是")
log.info("插件已成功启动。")
def unload():
# 结束所有进程的代码
log.info("插件已成功卸载。")
```
您还可以在[example.py](examples/example.py)中找到更广泛的示例。
* 建议在`load()`后使用`open()`,否则应使用`kt.load()`-在`plugin/<plugin_name>/<filename>`文件夹中创建一个文件
* 创建自己的事件:`kt.register_event("my_event", my_event_function)`-
* 调用事件:`kt.call_event("my_event")`
* 使用数据调用事件:`kt.call_event("my_event", data, data2=data2)`
* 基本事件_稍后会写_
## 异步函数
支持async
```python
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# 初始化插件
ev.register_event("my_event", my_event_handler)
log.info("插件已成功加载。")
async def start():
# 启动插件进程
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "一些数据", data="一些数据也是")
log.info("插件已成功启动。")
async def unload():
# 结束所有进程的代码
log.info("插件已成功卸载。")
```
您还可以在[async_example.py](examples/async_example.py)中找到更广泛的示例。
* 创建自己的事件:`kt.register_event("my_event", my_event_function)`在register_event中检查函数
* 调用async事件`kt.call_async_event("my_event")`
* 使用数据调用async事件`kt.call_async_event("my_event", data, data2=data2)`
* 基本的async事件_稍后会写_

10
docs/cn/readme.md Normal file
View File

@ -0,0 +1,10 @@
# KuiToi服务器文档
### 文档尚未完善,但终有一天会完善
1. 服务器设置和启动 - [这里](./setup)
2. 插件和事件系统 - [这里](./plugins)
3. Lua的细微差别 - [这里](./plugins/lua)
4. 多语言支持 - [这里](./multilanguage)
5. KuiToi WebAPI - [这里](./web)
6. 将会有新的内容...

88
docs/cn/setup/readme.md Normal file
View File

@ -0,0 +1,88 @@
# 来自 KuiToi 服务器的问候
## 好的,让我们开始吧
###### _(这里是 Linux 的命令)_
* 运行它需要 **Python 3.10.x**只有这个版本才能运行Python 3.11 不支持...
* 您可以像这样检查 Python 版本(你要在这里笑):
```bash
python3 --version # Python 3.10.6
```
* 克隆存储库并导航到它
* 安装所有必需的内容
* 然后,使用我的“脚本”,删除所有不必要的文件并移动到核心源
```bash
git clone -b Stable https://github.com/kuitoi/KuiToi-Server.git && cd KuiToi-Server
pip install -r requirements.txt
mv ./src/ $HOME/ktsrc/ && rm -rf ./* && mv $HOME/ktsrc/* . && rm -rf $HOME/ktsrc
```
* 这是如何检查服务器信息并启动它的方法:
```bash
python3 main.py --help # 显示所有可用命令
python3 main.py # 启动服务器
```
## 配置
* 启动后,将创建 `kuitoi.yaml`
* 默认情况下,它如下所示:
```yaml
!!python/object:modules.ConfigProvider.config_provider.Config
Auth:
key: null
private: true
Game:
map: gridmap_v2
max_cars: 1
players: 8
Options:
debug: false
encoding: utf-8
language: en
log_chat: true
speed_limit: 0
use_lua: true
use_queue: false
Server:
description: Welcome to KuiToi Server!
name: KuiToi-Server
server_ip: 0.0.0.0
server_port: 30814
WebAPI:
enabled: false
secret_key: 3838ccb03c86cdb386b67fbfdcba62d0
server_ip: 127.0.0.1
server_port: 8433
```
### Auth
* 如果您将 `private: false` 并且不设置 `key`,服务器将请求一个 BeamMP 密钥,没有它无法启动。
* 输入 BeamMP 密钥后,服务器将出现在启动器列表中。
* 您可以在此处获取密钥:[https://beammp.com/k/keys ↗](https://beammp.com/k/keys)
### Game
* `map` 仅为地图名称,即打开具有地图的 mod 在 `map.zip/levels` - 地图名称将在那里,那就是我们插入的地方。
* `max_cars` - 每个玩家的最大汽车数量
* `players` - 最大玩家数
### Options
* `debug` - 是否输出调试消息(仅适用于有经验的用户,会略微降低性能)
* `encoding` - 使用哪种编码打开文件
* `language` - 服务器将使用哪种语言启动当前可用enru
* `log_chat` - 是否将聊天输出到控制台
* `speed_limit` - 下载 mod 的下载速度限制(以 MB/s 为单位)
* `use_lua` - 启用 lua 支持
* `use_queue` - 按队列下载 mod即一次只能下载一个客户端
### Server
* `description` - BeamMP 启动器的服务器描述
* `name` - BeamMP 启动器的服务器名称
* `server_ip` - 分配给服务器的 IP 地址(仅适用于有经验的用户,默认为 0.0.0.0
* `server_port` - 服务器将在哪个端口上工作
### WebAPI
##### _文档尚未准备好_

13
docs/cn/web/readme.md Normal file
View File

@ -0,0 +1,13 @@
# 服务器的 WebAPI
## 可用的端点
* `/stop`:
* 必需参数:
* `secret_key` - 在服务器配置中指定的密钥
* `/event.get`
* 这个端点还没有准备好
* 必需参数:
* `secret_key` - 在服务器配置中指定的密钥

298
docs/en/plugins/classes.md Normal file
View File

@ -0,0 +1,298 @@
Sure, here's a translation of the text:
# Passed Classes
## Worth looking at
1. What are `*args` and `**kwargs`? -> [Post on Habr (RU)](https://habr.com/ru/companies/ruvds/articles/482464/)
## KuiToi
_`kt = KuiToi("PluginName"")`_
### kt.log
_Constant_\
Returns a pre-configured logger
### kt.name
_Constant_\
Returns the name of the plugin
### kt.dir
_Constant_\
Returns the directory of the plugin
### kt.open()
_Parameters are the same as for open()_\
Opens a file in kt.dir
### kt.register_event(event_name: str, event_func: function)
_`event_name: str` -> The name of the event that `event_func` will be called on._\
_`event_func: function` -> The function that will be called._
In `event_func`, you can pass both regular functions and async functions - you don't need to make them async beforehand.\
You can also create your own events with your own names.\
You can register an unlimited number of events.
### kt.call_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args, **kwargs` -> Arguments to be passed to the function._
### **async** kt.call_async_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args, **kwargs` -> Arguments to be passed to the function._\
_Must be called with `await`_
###### _I recommend familiarizing yourself with *args, **kwargs_, there is a link at the beginning
Data is passed to all events in the form of: `{"event_name": event_name, "args": args, "kwargs": kwargs}`\
`args: list` -> Represents an array of data passed to the event\
`kwargs: dict` -> Represents a dictionary of data passed to the event
The data will be returned from all successful attempts in an array.
### kt.call_lua_event(event_name: str, *args) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args` -> Arguments to be passed to the function._
Added to support backward compatibility.\
The lua function is called with a direct transmission of arguments `lua_func(*args)`
### kt.get_player([pid: int], [nick: str]) -> Player | None:
_`pid: int` -> Player ID - The identifier of the player._\
_`nick: str` -> Player Nickname - The name of the player._
The method returns a player object by their `pid` or `nick`.\
If the player cannot be found, `None` will be returned.
### kt.get_players() -> List[Player] | list:
The method returns an array with all players.\
The array will be empty if there are no players.
### kt.players_counter() -> int:
The method returns the number of players currently online.
### kt.is_player_connected([pid: int], [nick: str]) -> bool:
_`pid: int` -> Player ID - The identifier of the player._\
_`nick: str` -> Player Nickname - The name of the player._
The method returns a player object by their `pid` or `nick`.
## Player (or Client)
_`pl = kt.get_player()`_\
_`pl = event_data['kwargs']['player']`_
### pl.log -> Logger
_Constant_\
Returns a pre-configured logger
### pl.addr -> str
_Constant_\
Returns the IP address of the player
### pl.pid -> int
### pl.cid -> int
_Constant_\
Returns the client ID _(pid: PlayerId = cid: ClientId)_
### pl.key -> str
_Constant_\
Returns the key passed during authentication
### pl.nick -> str
_Variable_\
The nickname passed during authentication from the BeamMP server, can be changed, consequences are untested
### pl.roles -> str
_Variable_\
The role passed during authentication from the BeamMP server, can be changed (if an incorrect role is set, unexpected things may happen.)
### pl.guest -> bool
_Constant_\
Returns whether the player is a guest, passed during authentication from the BeamMP server
### pl.identifiers -> dict
_Constant_\
Identifiers passed during authentication from the BeamMP server.
### pl.ready -> bool
_Constant, changed by the core_\
Returns a bool value, if True -> the player has downloaded all resources, loaded on the map
### pl.cars -> dict
_Constant, changed by the core_\
Returns a dictionary of cars like thisSure, here's the translation:
# Passed Classes
## Worth looking at
1. What are `*args` and `**kwargs`? -> [Post on Habr ↗](https://habr.com/ru/companies/ruvds/articles/482464/)
## KuiToi
_`kt = KuiToi("PluginName"")`_
### kt.log
_Constant_\
Returns a pre-configured logger
### kt.name
_Constant_\
Returns the name of the plugin
### kt.dir
_Constant_\
Returns the directory of the plugin
### kt.open()
_Parameters are the same as for open()_\
Opens a file in kt.dir
### kt.register_event(event_name: str, event_func: function)
_`event_name: str` -> The name of the event that `event_func` will be called on._\
_`event_func: function` -> The function that will be called._
In `event_func`, you can pass both regular functions and async functions - you don't need to make them async beforehand.\
You can also create your own events with your own names.\
You can register an unlimited number of events.
### kt.call_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args, **kwargs` -> Arguments to be passed to the function._
### **async** kt.call_async_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args, **kwargs` -> Arguments to be passed to the function._\
_Must be called with `await`_
###### _I recommend familiarizing yourself with *args, **kwargs_, there is a link at the beginning
Data is passed to all events in the form of: `{"event_name": event_name, "args": args, "kwargs": kwargs}`\
`args: list` -> Represents an array of data passed to the event\
`kwargs: dict` -> Represents a dictionary of data passed to the event
The data will be returned from all successful attempts in an array.
### kt.call_lua_event(event_name: str, *args) -> list:
_`event_name: str` -> The name of the event to call._\
_`*args` -> Arguments to be passed to the function._
Added to support backward compatibility.\
The lua function is called with a direct transmission of arguments `lua_func(*args)`
### kt.get_player([pid: int], [nick: str]) -> Player | None:
_`pid: int` -> Player ID - The identifier of the player._\
_`nick: str` -> Player Nickname - The name of the player._
The method returns a player object by their `pid` or `nick`.\
If the player cannot be found, `None` will be returned.
### kt.get_players() -> List[Player] | list:
The method returns an array with all players.\
The array will be empty if there are no players.
### kt.players_counter() -> int:
The method returns the number of players currently online.
### kt.is_player_connected([pid: int], [nick: str]) -> bool:
_`pid: int` -> Player ID - The identifier of the player._\
_`nick: str` -> Player Nickname - The name of the player._
The method returns a player object by their `pid` or `nick`.
## Player (or Client)
_`pl = kt.get_player()`_\
_`pl = event_data['kwargs']['player']`_
### pl.log -> Logger
_Constant_\
Returns a preconfigured logger.
### pl.addr -> str
_Constant_\
Returns the player's IP address.
### pl.pid -> int
### pl.cid -> int
_Constant_\
Returns the client ID _(pid: PlayerId = cid: ClientId)_.
### pl.key -> str
_Constant_\
Returns the key passed during authorization.
### pl.nick -> str
_Variable_\
Nickname passed during authorization from the BeamMP server, can be changed, consequences are not tested.
### pl.roles -> str
_Variable_\
Role passed during authorization from the BeamMP server, can be changed (If the wrong role is set, unexpected behavior may occur.)
### pl.guest -> bool
_Constant_\
Returns whether the player is a guest, passed during authorization from the BeamMP server.
### pl.identifiers -> dict
_Constant_\
Identifiers passed during authorization from the BeamMP server.
### pl.ready -> bool
_Constant, changed by the core_\
Returns a bool value, if True -> player has downloaded all resources and loaded on the map.
### pl.cars -> dict
_Constant, changed by the core_\
Returns a dictionary of cars by type:
```python
{
1: {
"packet": car_packet,
"json": car_json,
"json_ok": bool(car_json),
"snowman": snowman,
"over_spawn": (snowman and allow_snowman) or over_spawn,
"pos": {
"pos":[0,0,0],
"rvel":[0,0,0],
"rot":[0,0,0],
"vel":[0,0,0],
"tim":0,
"ping":0
}
},
2: ...
}
```
Where `1` - car_id\
Where `pkt` - Unprocessed packet that came from the client (For very experienced users)\
Where `json` - Processed packet stored as dict\
Where `json_ok` - Whether the core was able to process the packet\
Where `snowman` - Is the car a snowman\
Where `over_spawn` - Is the car spawned over the limit (Allowed through plugins)\
Where `pos` - Car position (Passed through UDP)
### pl.last_position -> dict
_Constant, changed by the core_\
Returns the player's last position
### **async** pl.kick([reason: str = "Kicked!"]) -> None
_`reason: str` -> Kick reason. Parameter is optional, by default: `Kicked!`_\
Kicks the player from the server.
### **async** pl.send_message(message: str, [to_all: bool = True]) -> None
_`message: str` -> Message text, sent without "Server:"_\
_`to_all: bool` -> Should this message be sent to everyone? Parameter is optional, by default: `True`_\
Sends a message to the player or everyone.
### **async** pl.send_event(event_name: str, event_data: Any, [to_all: bool = True]) -> None
_`event_name: str` -> Name of the event that will be called_\
_`event_data: Any` -> Data sent to the event._\
_`to_all: bool` -> Should this message be sent to everyone? Parameter is optional, by default: `True`_\
Sends an event to the client.\
If event_data is a tuple, list, dict, then before sending the core converts it to JSON via json.dumps(event_data)\
Otherwise, the data will be a string without regulation.
### **async** pl.delete_car(self, car_id: int) -> None
_`car_id: int` -> Car ID_\
Deletes the player's car.

View File

@ -0,0 +1,4 @@
# List of available events
Most events will receive `pl = data['kwargs']['player']`, you can find a description [here](./classes.md)

View File

@ -0,0 +1,27 @@
print("example.lua")
--CreateTimer Testing
local mytimer = MP.CreateTimer()
--.--.--.--.--.--.--.
--GetOSName Testing
print("OS Name: "..MP.GetOSName())
--.--.--.--.--.--.-
--GetServerVersion Testing
local major, minor, patch = MP.GetServerVersion()
print("Server Version: "..major.."."..minor.."."..patch)
--.--.--.--.--.--.--.--.--
--Events Testing--
function handleChat(player_id, player_name, message)
print("Lua handleChat:", player_id, player_name, message, "; Uptime: "..mytimer:GetCurrent())
return 1
end
MP.RegisterEvent("onChatMessage", "handleChat")
--.--.--.--.--.--.
function onInit()
print("Initializing ready!")
end

View File

@ -0,0 +1,35 @@
# Providing Backward Compatibility for BeamMP Lua
KiuToi provides almost full support for lua plugins with BeamMP. All necessary methods have been created, and testing has revealed the following nuances:
KiuToi does not support: `MP.Set()`
#### Economic Rework V2.0 (Paid, Discord (RU): [Hlebushek](https://discordapp.com/users/449634697593749516))
1. To obtain `pluginPath`, use: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` as the path returns with `@`, which broke the plugin.
#### Cobalt Essentials V1.7.5 (Free, [github](https://github.com/prestonelam2003/CobaltEssentials/))
1. To obtain `pluginPath`, use: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` as the path returns with `@`, which broke the plugin.
2. All `require()` statements had to be moved after `onInit`.
3. In some cases, `MP.RegisterEvent` had to be moved after the function declaration, i.e.:
```lua
--This is incorrect, registration may fail
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
function onPlayerAuth(name, role, isGuest)
-- Some plugin code
end
--This is the correct version
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
```
### A Little About How it Works
Plugin loading goes through several stages:
1. The `plugins/` folder is scanned.
2. If the folder is not in PyPlugins and there are `*.lua` files in the folder, then it is added as a plugin folder, let's say it will be `plugins/LuaPlugin`
3. Next, `lua.loadfile({filename})` is performed from this folder (this is the standard method in lua).
4. Finally, the `onInit()` function is called, and an event is triggered.
5. If no errors occur during the execution of `onInit()`, you can see the message `Lua plugins: LuaPlugin:ok` through the `lua_plugins` command.

View File

@ -1,12 +1,18 @@
# Plugin System
Это описание системы плагинов для KuiToi сервера на Python:
## Installing the Library with "Stubs"
###### (This means that it will not work without a server, but the IDE will suggest the API)
###### (The library is still under development)
## Events
### Events list: [here](./events_list.md)
## Classes
### Classes list: [here](./classes.md)
## Installing the library with "stubs"
###### (This means it won't work without the server, but your IDE will suggest the API)
###### (The library is still in development)
* Using pip:\
`$ pip install KuiToi`
* From source code:\
* From source:\
`git clone https://github.com/KuiToi/KuiToi-PyLib`
## Example
@ -17,7 +23,7 @@ try:
except ImportError:
pass
kt = KuiToi("Example")
kt = KuiToi("ExamplePlugin")
log = kt.log
def my_event_handler(event_data):
@ -25,14 +31,14 @@ def my_event_handler(event_data):
def load():
# Plugin initialization
ev.register_event("my_event", my_event_handler)
kt.register_event("my_event", my_event_handler)
log.info("Plugin loaded successfully.")
def start():
# Running plugin processes
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
# Starting plugin processes
kt.call_event("my_event")
kt.call_event("my_event", "Some data", data="some data too")
log.info("Plugin started successfully.")
@ -41,15 +47,17 @@ def unload():
log.info("Plugin unloaded successfully.")
```
* It is recommended to use `open()` after `load()`. Otherwise, use `kt.load()` - creates a file in the `plugin/<plugin_name>/<filename>` folder.
* Creating your own event: `kt.register_event("my_event", my_event_function)`
A more comprehensive example can also be found in [example.py](examples/example.py)
* It is recommended to use `open()` after `load()`, otherwise use `kt.load()` - It creates a file in the `plugin/<plugin_name>/<filename>` folder.
* Creating your own event: `kt.register_event("my_event", my_event_function)` -
* Calling an event: `kt.call_event("my_event")`
* Calling an event with data: `kt.call_event("my_event", data, data2=data2)`
* Basic events: _Will write later_
* Base events: _To be added later_
## Async Functions
## Async functions
Async support is available.
Async support is available
```python
try:
@ -67,14 +75,14 @@ async def my_event_handler(event_data):
async def load():
# Plugin initialization
ev.register_event("my_event", my_event_handler)
kt.register_event("my_event", my_event_handler)
log.info("Plugin loaded successfully.")
async def start():
# Running plugin processes
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
# Starting plugin processes
await kt.call_async_event("my_event")
await kt.call_async_event("my_event", "Some data", data="some data too")
log.info("Plugin started successfully.")
@ -84,9 +92,9 @@ async def unload():
```
A more extensive example can also be found in [async_example.py](./async_example.py).
A more comprehensive example can also be found in [async_example.py](examples/async_example.py)
* Creating your own event: `kt.register_event("my_event", my_event_function)` (register_event checks for function)
* Creating your own event: `kt.register_event("my_event", my_event_function)` (register_event has a function check)
* Calling an async event: `kt.call_async_event("my_event")`
* Calling an async event with data: `kt.call_async_event("my_event", data, data2=data2)`
* Basic async events: _Will write later_
* Base async events: _To be added later_

View File

@ -1,9 +1,10 @@
# Documentation for KuiToi Server
### The documentation is not perfect yet, but it will be one day
### The documentation is not yet perfect, but someday it will be.
1. Setup and Start server - [here](setup)
2. Plugins and Events system - [here](plugins)
3. MultiLanguage - [here](./multilanguage)
4. KuiToi WebAPI - [here](./web)
5. Something new...
1. Setup and Launching the Server - [here](./setup)
2. Plugins and Event System - [here](./plugins)
3. Nuances of Working with Lua - [here](./plugins/lua)
4. Multilanguage Support - [here](./multilanguage)
5. KuiToi WebAPI - [here](./web)
6. Something new will be added here soon...

View File

@ -1,31 +1,31 @@
# Greetings from KuiToi Server
## Well, let's begin
## Well, let's start
###### _(Here are the commands for Linux)_
* **Python 3.10.x** is required to run the server! It won't work on Python 3.11...
* You can check the version of your Python installation with the following command:
* **Python 3.10.x** is required to run it! Only this version works, it won't work on Python 3.11...
* You can check your Python version like this (you have to laugh here):
```bash
python3 --version # Python 3.10.6
```
* Clone the repository and navigate to it.
* Install everything that's needed.
* Then, using my "script", remove all unnecessary files and move to the core source code.
* Clone the repository and navigate to it
* Install everything necessary
* Then, using my "script", remove all unnecessary files and move to the core source
```bash
git clone -b Stable https://github.com/kuitoi/KuiToi-Server.git && cd KuiToi-Server
pip install -r requirements.txt
mv ./src/ $HOME/ktsrc/ && rm -rf ./* && mv $HOME/ktsrc/* . && rm -rf $HOME/ktsrc
```
* Here's how to view information about the server and start it:
* Here's how you can check server info and start it:
```bash
python3 main.py --help # Displays all available commands
python3 main.py --help # Shows all available commands
python3 main.py # Starts the server
```
## Configuration
* After starting the server, a `kuitoi.yaml` file will be created.
* After starting, `kuitoi.yaml` will be created
* By default, it looks like this:
```yaml
!!python/object:modules.ConfigProvider.config_provider.Config
@ -36,40 +36,53 @@ Game:
map: gridmap_v2
max_cars: 1
players: 8
Server:
Options:
debug: false
description: Welcome to KuiToi Server!
encoding: utf-8
language: en
log_chat: true
speed_limit: 0
use_lua: true
use_queue: false
Server:
description: Welcome to KuiToi Server!
name: KuiToi-Server
server_ip: 0.0.0.0
server_port: 30813
server_port: 30814
WebAPI:
enabled: false
secret_key: <random_key>
secret_key: 3838ccb03c86cdb386b67fbfdcba62d0
server_ip: 127.0.0.1
server_port: 8433
```
### Auth
* If you set `private: false` and do not set a `key`, the server will request a BeamMP key and will not start without it.
* By entering a BeamMP key, the server will appear in the launcher list.
* You can get a key here: [https://beammp.com/k/keys ↗](https://beammp.com/k/keys)
* If you set `private: false` and don't set a `key`, the server will request a BeamMP key and won't start without it.
* After entering a BeamMP key, the server will appear in the launcher list.
* You can get the key here: [https://beammp.com/k/keys ↗](https://beammp.com/k/keys)
### Game
* `map` specifies only the name of the map. That is, open the mod with the map in `map.zip/levels` - the name of the map will be there, and that's what you need to insert.
* `max_cars` - the maximum number of cars per player
* `players` - the maximum number of players
* `map` is only the name of the map, i.e. open the mod with the map in `map.zip/levels` - the name of the map will be there, that's what we insert.
* `max_cars` - Maximum number of cars per player
* `players` - Maximum number of players
### Options
* `debug` - Whether to output debug messages (for experienced users only, slightly reduces performance)
* `encoding` - Which encoding to use to open files
* `language` - Which language the server will start with (currently available: en, ru)
* `log_chat` - Whether to output chat to the console
* `speed_limit` - Download speed limit for mods (in MB/s)
* `use_lua` - Enable lua support
* `use_queue` - Download mods in queue, i.e. only 1 client can download at a time
### Server
* `debug` - should debug messages be displayed (for experienced users only; slightly affects performance)
* `description` - server description for the BeamMP launcher
* `language` - the language in which the server will run (currently available: en, ru)
* `name` - server name for the BeamMP launcher
* `server_ip` - the IP address to be used by the server (for experienced users only; defaults to 0.0.0.0)
* `server_port` - the port on which the server will run
* `description` - Server description for the BeamMP launcher
* `name` - Server name for the BeamMP launcher
* `server_ip` - IP address to assign to the server (for experienced users only, defaults to 0.0.0.0)
* `server_port` - On which port the server will work
### WebAPI
##### _Docs are not ready yet_
##### _Docs are not ready_

View File

@ -1,4 +1,5 @@
# Choose your language
### [English](./en)
### [Русский](./ru)
### [English](./en) (AI translation)
### [Chinese](./cn) (AI translation)

175
docs/ru/plugins/classes.md Normal file
View File

@ -0,0 +1,175 @@
# Передаваемые классы
## Стоит ознакомится
1. Что такое `*args` и `**kwargs`? -> [Пост на habr](https://habr.com/ru/companies/ruvds/articles/482464/)
## KuiToi
_`kt = KuiToi("PluginName"")`_
### kt.log
_Константа_\
Вернёт преднастроенный логгер
### kt.name
_Константа_\
Вернёт имя плагина
### kt.dir
_Константа_\
Вернёт папку плагина
### kt.open()
араметры как у open()_\
Открывает файл в kt.dir
### kt.register_event(event_name: str, event_func: function)
_`event_name: str` -> Имя ивента, по которому будет вызвана `event_func`._\
_`event_func: function` -> Функция, которая будет вызвана._
В `event_func` можно передавать как обычную функцию, так и async - await не нужно делать заранее.\
Ивенты можно создавать так же свои, со своим именем.\
Зарегистрировать можно не ограниченное кол-во ивентов.
### kt.call_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> Имя ивента, который будет вызван._\
_`*args, **kwargs` -> Аргументы, передаваемые во функции._
### **async** kt.call_async_event(event_name: str, *args, **kwargs) -> list:
_`event_name: str` -> Имя ивента, который будет вызван._\
_`*args, **kwargs` -> Аргументы, передаваемые во функции._\
_Необходимо вызывать с `await`_
###### _Советую ознакомиться с *args, **kwargs_, ссылка есть в начале
Данные во все ивенты приходят по типу: `{"event_name": event_name, "args": args, "kwargs": kwargs}`\
`args: list` -> Представляет из себя массив данных, которые переданы в ивент\
`kwargs: dict` -> Представляет из себя словарь данных, которые переданы в ивент
Данные вернутся от всех удачных волнений в массиве.
### kt.call_lua_event(event_name: str, *args) -> list:
_`event_name: str` -> Имя ивента, который будет вызван._\
_`*args` -> Аргументы, передаваемые во функции._
Добавлено для поддержки обратной совместимости.\
lua функция вызывается с прямой передачей аргументов `lua_func(*args)`
### kt.get_player([pid: int], [nick: str]) -> Player | None:
_`pid: int` -> Player ID - Идентификатор игрока._\
_`nick: str` -> Player Nick - Ник игрока._
Метод возвращает объект игрока по его `pid` или `nick`.\
Если не удалось найти игрока вернётся `None`.
### kt.get_players() -> List[Player] | list:
Метод возвращает массив со всеми игроками.\
Массив будет пустой, если игроков нет.
### kt.players_counter() -> int:
Метод возвращает количество игроков, которые сейчас онлайн.
### kt.is_player_connected([pid: int], [nick: str]) -> bool:
_`pid: int` -> Player ID - Идентификатор игрока._\
_`nick: str` -> Player Nick - Ник игрока._
Метод возвращает объект игрока по его `pid`, `nick`.
## Player (или Client)
_`pl = kt.get_player()`_\
_`pl = event_data['kwargs']['player']`_
### pl.log -> Logger
_Константа_\
Вернёт преднастроенный логгер
### pl.addr -> str
_Константа_\
Вернёт IP адрес игрока
### pl.pid -> int
### pl.cid -> int
_Константа_\
Вернёт id клиента _(pid: PlayerId = cid: ClientId)_
### pl.key -> str
_Константа_\
Вернёт ключ, переданный при авторизации
### pl.nick -> str
еременная_\
Ник, переданные при авторизации от сервера BeamMP, можно изменить, последствия не проверенны
### pl.roles -> str
еременная_\
Роль, переданная при авторизации от сервера BeamMP, можно изменить (Если установить не верную роль, могут происходить неожиданности.)
### pl.guest -> bool
_Константа_\
Вернёт является ли игрок гостем, передаётся при авторизации от сервера BeamMP
### pl.identifiers -> dict
_Константа_\
Идентификаторы, передаются при авторизации от сервера BeamMP.
### pl.ready -> bool
_Константа, меняется ядром_\
Вернёт bool значение, если True -> игрок скачал все ресурсы, прогрузился на карте
### pl.cars -> dict
_Константа, меняется ядром_\
Возвращает словарь автомобилей по типу:
```python
{
1: {
"packet": car_packet,
"json": car_json,
"json_ok": bool(car_json),
"snowman": snowman,
"over_spawn": (snowman and allow_snowman) or over_spawn,
"pos": {
"pos":[0,0,0],
"rvel":[0,0,0],
"rot":[0,0,0],
"vel":[0,0,0],
"tim":0,
"ping":0
}
},
2: ...
}
```
Где `1` - car_id\
Где `pkt` - Необработанный пакет который пришел от клиента (Для очень опытных пользователй) \
Где `json` - Обработанный пакет, хранящийся в виде dict\
Где `json_ok` - Смогло ли ядро обработать пакет\
Где `snowman` - Снеговик ли машина\
Где `over_spawn` - Заспавнена ли машина сверх лимита (Разрешается через плагины)\
Где `pos` - Позиция машины (Передаётся через udp)
### pl.last_position -> dict
_Константа, меняется ядром_
Возвращает последнюю позицию игрока
### **async** pl.kick([reason: str = "Kicked!"]) -> None
_`reason: str` -> Причина кика. Параметр не обязателен, по дефолту: `Kicked!`_
Кикает игрока с сервера
### **async** pl.send_message(message: str, [to_all: bool = True]) -> None
_`message: str` -> Текст сообщения, отправляется без "Server:"_
_`to_all: bool` -> Нужно ли отправить это сообщение всем? Параметр не обязателен, по дефолту: `True`_
Отправляет сообщение игроку или всем сразу
### **async** pl.send_event(event_name: str, event_data: Any, [to_all: bool = True]) -> None
_`event_name: str` -> Имя ивента, который будет вызван_
_`event_data: Any` -> Отправляемые данные в ивент._
_`to_all: bool` -> Нужно ли отправить это сообщение всем? Параметр не обязателен, по дефолту: `True`_
Отправляет ивент на клиент.\
Если event_data будет tuple, list, dict, то перед отправкой ядро конвертирует в json через json.dumps(event_data)
Иначе данные будут строкой, без регуляции;
### **async** pl.delete_car(self, car_id: int) -> None
_`car_id: int` -> Идентификатор машины_
Удалят машину у игрока

View File

@ -0,0 +1,5 @@
# Список ивентов
В большинство ивентов будет приходить `pl = data['kwargs']['player']`, описание можно найти [тут](./classes.md)
###

View File

@ -0,0 +1,37 @@
import json
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
async def my_event_handler(event_data):
log.info(f"{event_data}")
async def load():
# Инициализация плагина
with open(cfg_file, 'w') as f:
json.dump(config, f)
cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
async def start():
# Запуск процессов плагина
await ev.call_async_event("my_event")
await ev.call_async_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
async def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")

View File

@ -0,0 +1,37 @@
import json
try:
import KuiToi
except ImportError:
pass
kt = KuiToi("Example")
log = kt.log
config = {"config_version": 0.1, "sql": {"enabled": False, "host": "127.0.0.1", "port": 3363, "database": "fucklua"}}
cfg_file = "config.json"
def my_event_handler(event_data):
log.info(f"{event_data}")
def load():
# Инициализация плагина
with open(cfg_file, 'w') as f:
json.dump(config, f)
cgf = config
log.info(cgf)
ev.register_event("my_event", my_event_handler)
log.info("Плагин загружен успешно.")
def start():
# Запуск процессов плагина
ev.call_event("my_event")
ev.call_event("my_event", "Some data", data="some data too")
log.info("Плагин запустился успешно.")
def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")

View File

@ -0,0 +1,27 @@
print("example.lua")
--CreateTimer Testing
local mytimer = MP.CreateTimer()
--.--.--.--.--.--.--.
--GetOSName Testing
print("OS Name: "..MP.GetOSName())
--.--.--.--.--.--.-
--GetServerVersion Testing
local major, minor, patch = MP.GetServerVersion()
print("Server Version: "..major.."."..minor.."."..patch)
--.--.--.--.--.--.--.--.--
--Events Testing--
function handleChat(player_id, player_name, message)
print("Lua handleChat:", player_id, player_name, message, "; Uptime: "..mytimer:GetCurrent())
return 1
end
MP.RegisterEvent("onChatMessage", "handleChat")
--.--.--.--.--.--.
function onInit()
print("Initializing ready!")
end

View File

@ -0,0 +1,35 @@
# Обеспечение обратной поддержки BeamMP Lua
В KiuToi есть практически полная поддержка lua плагинов с BeamMP, все необходимые методы созданы, тестирование показало следующие нюансы:
В KiuToi не будет поддержки: `MP.Set()`
#### Economic Rework V2.0 (Платный, Discord (RU): [Hlebushek](https://discordapp.com/users/449634697593749516))
1. Для получения `pluginPath` нужно: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` так как пусть возвращается с `@`, что сломало плагин.
#### Cobalt Essentials V1.7.5 (Бесплатный, [github](https://github.com/prestonelam2003/CobaltEssentials/))
1. Для получения `pluginPath` нужно: `debug.getinfo(1).source:gsub("\\","/")` => `debug.getinfo(1).source:gsub("\\","/"):gsub("@", "")` так как пусть возвращается с `@`, что сломало плагин.
2. Пришлось перенести все `require()` за `onInit`
3. В некоторых моментах пришлось перенести `MP.RegisterEvent` уже после объявления функции, т.е.:
```lua
--Вот так не правильно, может не пройти регистрация
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
function onPlayerAuth(name, role, isGuest)
-- Some plugin code
end
--Вот такой вариант пройдёт
MP.RegisterEvent("onPlayerAuth","onPlayerAuth")
```
### Немного о принципе работы
Загрузка плагина проходит в несколько этапов:
1. Сканируется папка `plugins/`
2. Если папки нет в PyPlugins и в папке есть `*.lua`, то она добавляется, допустим это будет `plugins/LuaPlugin`
3. Далее из этой папки проходит `lua.loadfile({filename})` (Это стандартный метод в lua)
4. И в конце вызывается ивент и функция `onInit()`
5. Если во время выполнения `onInit()` не произошло ошибок, можно будет увидеть через команду `lua_plugins` такое сообщение: `Lua plugins: LuaPlugin:ok`

View File

@ -1,5 +1,8 @@
# Система плагинов
### Ивенты: [тут](./events_list.md)
### Классы: [тут](./classes.md)
## Установка библиотеки с "Заглушками"
###### (Это значит, что не будет работать без сервера, но IDE подскажет API)
###### (Библиотека ещё в разработке)
@ -17,7 +20,7 @@ try:
except ImportError:
pass
kt = KuiToi("Example")
kt = KuiToi("ExamplePlugin")
log = kt.log
def my_event_handler(event_data):
@ -40,6 +43,7 @@ def unload():
# Код завершающий все процессы
log.info("Плагин выгружен успешно.")
```
Так же более обширный пример можно найти в [example.py](examples/example.py)
* Рекомендуется использовать `open()` после `load()`, иначе стоит использовать `kt.load()` - Создаёт файл в папке `plugin/<plugin_name>/<filename>`
* Создание своего ивента : `kt.register_event("my_event", my_event_function)` -
@ -84,7 +88,7 @@ async def unload():
```
Так же более обширный пример можно найти в [async_example.py](./async_example.py)
Так же более обширный пример можно найти в [async_example.py](examples/async_example.py)
* Создание своего ивента: `kt.register_event("my_event", my_event_function)` (в register_event стоит проверка на функцию)
* Вызов async ивента: `kt.call_async_event("my_event")`

View File

@ -4,6 +4,7 @@
1. Настройка и Запуск сервера - [тута](./setup)
2. Плагины и Ивент система - [тута](./plugins)
3. Мультиязычность - [тута](./multilanguage)
4. KuiToi WebAPI - [тута](./web)
5. Тут будет что-то ново....
3. Ньансы работы Lua - [тута](./plugins/lua)
4. Мультиязычность - [тута](./multilanguage)
5. KuiToi WebAPI - [тута](./web)
6. Тут будет что-то новое....

View File

@ -36,19 +36,24 @@ Game:
map: gridmap_v2
max_cars: 1
players: 8
Server:
Options:
debug: false
description: Welcome to KuiToi Server!
encoding: utf-8
language: en
log_chat: true
speed_limit: 0
use_lua: true
use_queue: false
Server:
description: Welcome to KuiToi Server!
name: KuiToi-Server
server_ip: 0.0.0.0
server_port: 30813
server_port: 30814
WebAPI:
enabled: false
secret_key: <random_key>
secret_key: 3838ccb03c86cdb386b67fbfdcba62d0
server_ip: 127.0.0.1
server_port: 8433
```
### Auth
@ -62,11 +67,19 @@ WebAPI:
* `max_cars` - Максимальное количество машин на игрока
* `players` - Максимально количество игроков
### Server
### Options
* `debug` - Нужно ли выводить debug сообщения (только для опытных пользователей, немного теряется в производительности)
* `description` - Описания сервера для лаунчера BeamMP
* `encoding` - С какой кодировкой открывать файлы
* `language` - С каким языком запустится сервер (Доступные на данный момент: en, ru)
* `log_chat` - Нужно-ли выводить чат в консоль
* `speed_limit` - Ограничение скорости на скачивание модов (В Мб/с)
* `use_lua` - Включить ли поддержку lua
* `use_queue` - Скачивать по очереди, т.е. в один момент может скачивать только 1 клиент
### Server
* `description` - Описания сервера для лаунчера BeamMP
* `name` - Названия сервер для лаунчера BeamMP
* `server_ip` - Какой IP адрес занять серверу (только для опытных пользователей, по умолчанию 0.0.0.0)
* `server_port` - На каком порту будет работать сервер

View File

@ -54,12 +54,16 @@ class Client:
@property
def addr(self):
return self._addr
return self._addr[0]
@property
def cid(self):
return self._cid
@property
def pid(self):
return self._cid
@property
def key(self):
return self._key
@ -78,7 +82,7 @@ class Client:
@property
def cars(self):
return self._cars
return {i: v for i, v in enumerate(self._cars) if v is not None}
@property
def last_position(self):
@ -99,7 +103,11 @@ class Client:
self.__alive = True
return False
async def kick(self, reason):
async def kick(self, reason=None):
if not reason:
reason = "Kicked!"
else:
reason = f"{reason!r}"
if not self.__alive:
self.log.debug(f"{self.nick}.kick('{reason}') skipped: Not alive;")
return
@ -109,9 +117,19 @@ class Client:
self.__alive = False
async def send_message(self, message, to_all=True):
await self._send(f"C:{message}", to_all=to_all)
if not message:
message = "no message"
to_all = False
await self._send(f"C:{message!r}", to_all=to_all)
async def send_event(self, event_name, event_data, to_all=True):
if isinstance(event_data, (list, tuple, dict)):
event_data = json.dumps(event_data, separators=(',', ':'))
else:
event_data = f"{event_data!r}"
if len(event_data) > 104857599:
self.log.error("Client data too big! >=104857599")
return
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):
@ -351,8 +369,8 @@ class Client:
async def _spawn_car(self, data):
car_data = data[2:]
car_id = next((i for i, car in enumerate(self.cars) if car is None), len(self.cars))
cars_count = len(self.cars) - self.cars.count(None)
car_id = next((i for i, car in enumerate(self._cars) if car is None), len(self._cars))
cars_count = len(self._cars) - self._cars.count(None)
if self._snowman['id'] != -1:
cars_count -= 1 # -1 for unicycle
self.log.debug(f"car_id={car_id}, cars_count={cars_count}")
@ -400,6 +418,9 @@ class Client:
des = f"Od:{self.cid}-{car_id}"
await self._send(des)
async def delete_car(self, car_id):
await self._delete_car(car_id=car_id)
async def _delete_car(self, raw_data=None, car_id=None):
if not car_id and raw_data:
@ -408,13 +429,13 @@ class Client:
cid = self.cid
raw_data = f"Od:{self.cid}-{car_id}"
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...
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)
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)
ev_data_list.extend(d2)
for ev_data in ev_data_list:
# TODO: handle event onCarDelete
@ -422,7 +443,7 @@ class Client:
if cid == self.cid or admin_allow:
await self._send(raw_data, to_all=True, to_self=True)
car = self.cars[car_id]
car = self._cars[car_id]
if car['snowman']:
self.log.debug(f"Snowman found")
unicycle_id = self._snowman['id']
@ -437,10 +458,10 @@ class Client:
async def _edit_car(self, raw_data, data):
cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and self.cars[car_id]:
if car_id != -1 and self._cars[car_id]:
client = self.__Core.get_client(cid=cid)
if client:
car = client.cars[car_id]
car = client._cars[car_id]
new_car_json = {}
try:
new_car_json = json.loads(data[data.find("{"):])
@ -478,7 +499,7 @@ class Client:
async def _reset_car(self, 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)
ev.call_lua_event("onVehicleReset", self.cid, car_id, raw_data[raw_data.find("{"):])
car_json = {}
@ -531,16 +552,17 @@ class Client:
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
self._ready = True
for client in self.__Core.clients:
if not client:
continue
for car in client.cars:
for car in client._cars:
if not car:
continue
await self._send(car['packet'])
self._ready = True
async def _chat_handler(self, data):
sup = data.find(":", 2)
if sup == -1:
@ -643,7 +665,7 @@ class Client:
self.__alive = False
if (self.cid > 0 or self.nick is not None) and \
self.__Core.clients_by_nick.get(self.nick):
for i, car in enumerate(self.cars):
for i, car in enumerate(self._cars):
if not car:
continue
self.log.debug(f"Removing car: car_id={i}")

View File

@ -7,7 +7,7 @@
import asyncio
from asyncio import StreamReader, StreamWriter, DatagramTransport
from logging import Logger
from typing import Tuple, List, Dict, Optional, Union
from typing import Tuple, List, Dict, Optional, Union, Any
from core import Core, utils
@ -46,6 +46,7 @@ class Client:
def addr(self) -> Tuple[str, int]: ...
@property
def cid(self) -> int: ...
def pid(self) -> int: ...
@property
def key(self) -> str: ...
@property
@ -55,20 +56,21 @@ class Client:
@property
def identifiers(self) -> list: ...
@property
def cars(self) -> List[dict | None]: ...
def cars(self) -> dict: ...
@property
def last_position(self): ...
def last_position(self) -> dict: ...
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:...
async def send_event(self, event_name: str, event_data: str) -> None: ...
async def send_event(self, event_name: str, event_data: Any, to_all: bool = True) -> None: ...
async def _send(self, data: bytes | str, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ...
async def _sync_resources(self) -> None: ...
async def _recv(self, one=False) -> bytes | None: ...
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, car_id : int =None) -> None: ...
async def delete_car(self, car_id: int) -> 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

@ -50,6 +50,8 @@ class Core:
ev.register_event("_get_player", lambda x: self.get_client(**x['kwargs']))
def get_client(self, cid=None, nick=None):
if cid is None and nick is None:
return None
if cid is not None:
if cid == -1:
return [i for i in self.clients if i is not None]

View File

@ -6,7 +6,6 @@ import random
import shutil
import threading
import time
from threading import Thread
import toml
from lupa.lua53 import LuaRuntime
@ -186,7 +185,7 @@ class MP:
return self._lua.table(), "Bad client"
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
car = client.cars[car_id]
car = client._cars[car_id]
if car:
return self._lua.table_from(car['pos'])
return self._lua.table(), "Vehicle not found"
@ -231,7 +230,7 @@ class MP:
return self._lua.table()
client = ev.call_event("_get_player", cid=player_id)[0]
if client:
return self._lua.table_from([f'{v["json"]}' for d in [i for i in client.cars if i is not None]
return self._lua.table_from([f'{v["json"]}' for d in [i for i in client._cars if i is not None]
for k, v in d.items() if k == "json"])
def GetPlayers(self):

View File

@ -16,19 +16,22 @@ from threading import Thread
from core import get_logger
# TODO: call_client_event, get_player, get_players, GetPlayerCount
class KuiToi:
_plugins_dir = ""
def __init__(self, name=None):
def __init__(self, name):
if name is None:
raise AttributeError("KuiToi: Name is required")
self.log = get_logger(f"Plugin | {name}")
self.__log = get_logger(f"Plugin | {name}")
self.__name = name
self.__dir = os.path.join(self._plugins_dir, self.__name)
if not os.path.exists(self.__dir):
os.mkdir(self.__dir)
@property
def log(self):
return self.__log
@property
def name(self):
return self.__name
@ -41,6 +44,7 @@ class KuiToi:
def open(self, file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None):
path = os.path.join(self.__dir, file)
self.log.debug(f'Trying to open "{path}" with mode "{mode}"')
# Really need?
# if not os.path.exists(path):
# with open(path, 'x'): ...
f = None
@ -57,9 +61,35 @@ class KuiToi:
self.log.debug(f"Registering event {event_name}")
ev.register_event(event_name, event_func)
def call_event(self, event_name, *data, **kwargs):
def call_event(self, event_name, *args, **kwargs):
self.log.debug(f"Called event {event_name}")
ev.call_event(event_name, *data, **kwargs)
return ev.call_event(event_name, *args, **kwargs)
async def call_async_event(self, event_name, *args, **kwargs):
self.log.debug(f"Called async event {event_name}")
return await ev.call_async_event(event_name, *args, **kwargs)
def call_lua_event(self, event_name, *args):
self.log.debug(f"Called lua event {event_name}")
return ev.call_lua_event(event_name, *args)
def get_player(self, pid=None, nick=None, cid=None):
self.log.debug("Requests get_player")
return ev.call_event("_get_player", cid=cid or pid, nick=nick)[0]
def get_players(self):
self.log.debug("Requests get_players")
return self.get_player(-1)
def players_counter(self):
self.log.debug("Requests players_counter")
return len(self.get_players())
def is_player_connected(self, pid=None, nick=None):
self.log.debug("Requests is_player_connected")
if pid < 0:
return False
return bool(self.get_player(cid=pid, nick=nick))
class PluginsLoader:

View File

@ -0,0 +1,48 @@
{
"": "基础阶段",
"hello": "来自KuiToi服务器的问候",
"config_path": "使用{}进行配置。",
"init_ok": "初始化完成。",
"start": "服务器已启动!",
"stop": "服务器已停止!",
"": "服务器认证",
"auth_need_key": "需要BeamMP密钥才能启动",
"auth_empty_key": "BeamMP密钥为空",
"auth_cannot_open_browser": "无法打开浏览器:{}",
"auth_use_link": "使用此链接:{}",
"": "GUI阶段",
"GUI_yes": "是",
"GUI_no": "否",
"GUI_ok": "确定",
"GUI_cancel": "取消",
"GUI_need_key_message": "需要BeamMP密钥才能启动\n是否在浏览器中打开链接以获取密钥",
"GUI_enter_key_message": "请输入密钥:",
"GUI_cannot_open_browser": "无法打开浏览器。\n请使用此链接{}",
"": "Web阶段",
"web_start": "WebAPI已启动{}CTRL+C停止",
"": "命令man",
"man_message_man": "man - 显示COMMAND的帮助页面。\n用法man COMMAND",
"help_message_man": "显示COMMAND的帮助页面。",
"man_for": "帮助页面",
"man_message_not_found": "man找不到帮助页面。",
"man_command_not_found": "man找不到\"{}\"命令!",
"": "命令help",
"man_message_help": "help - 显示命令的名称和简短描述。\n用法help [--raw]\n命令`help`列出所有可用的命令,并为每个命令提供简短描述。",
"help_message_help": "显示命令的名称和简短描述。",
"help_command": "命令",
"help_message": "文本",
"help_message_not_found": "无文本",
"": "命令stop",
"man_message_stop": "stop - 关闭服务器。\n用法stop",
"help_message_stop": "关闭服务器。",
"": "命令exit",
"man_message_exit": "exit - 关闭服务器。\n用法exit",
"help_message_exit": "关闭服务器。"
}