Скриптинг серверов SADEMP
Руководство разработчика по написанию игровых режимов (RP, Дрифт, DM, Freeroam) и фильтрскриптов для сервера SADEMP на Python. Серверу нужен только Python 3.11+ — дополнительные библиотеки не требуются.
1. Быстрый старт
Скрипт в SADEMP — это обычный файл .py. Гейммод лежит в server/gamemodes/, фильтрскрипты — в server/filterscripts/. Какие из них загружать, указывается в server.toml. Фильтрскрипты получают события раньше гейммода (как в SA-MP).
from sademp import * # всё API SADEMP
from sademp.compat import * # + функции в стиле SA-MP (по желанию)
@on("game_mode_init")
def init():
print("Сервер SADEMP успешно запущен!")
set_world_time(12, 0)
set_weather(1)
Скрипты можно раскладывать по подпапкам: файл filterscripts/mymod/x.py указывается в server.toml как "mymod/x". Запуск сервера: start_server.bat на Windows или sh start_server.sh на Linux.
2. Структура каталогов
| Путь | Назначение |
|---|---|
server/server.toml | Настройки сервера: название, порт, пароли, список скриптов. |
server/gamemodes/ | Игровой режим (пример — freeroam.py). |
server/filterscripts/ | Фильтрскрипты (примеры — admin.py, accounts.py), можно в подпапках. |
server/data/ | Базы и файлы данных скриптов: sqlite3.connect(data_path("my.db")). |
server/logs/ | Журнал сервера server_log.txt. |
server/sademp/ | Движок сервера. Не изменяйте — он обновляется вместе с SADEMP. |
docs/SCRIPTING.md | Краткий справочник по API. |
tools/bot.py | Боты для проверки сервера без запуска игры. |
start_server.bat / start_server.sh | Запуск сервера на Windows / Linux. |
3. Конфигурация server.toml
[server]
name = "My SADEMP Server"
host = "0.0.0.0"
port = 7777 # UDP-порт; на хостинге его нужно открыть
max_players = 50
password = "" # пусто = вход без пароля
rcon_password = "changeme" # ОБЯЗАТЕЛЬНО поменяйте
tick_rate = 30 # тиков сервера в секунду
stream_distance = 300.0 # дальность видимости игроков
debug = false
[master]
host = "127.0.0.1:7780" # мастер-сервер SADEMP; пусто = не показывать во вкладке «Интернет»
[scripts]
gamemode = "freeroam" # файл gamemodes/freeroam.py
filterscripts = ["admin", "accounts"] # файлы filterscripts/*.py (подпапки: "mymod/script")
Пока rcon_password равен "changeme", вход администратора через /rcon login не сработает — пароль нужно сменить.
4. Консоль и горячая перезагрузка
Перезапускать сервер после правок в скриптах не нужно. В консоли сервера доступны команды:
| Команда | Что делает |
|---|---|
reload <имя> | Перезагрузить загруженный скрипт (гейммод или фильтрскрипт). |
load <имя> | Загрузить фильтрскрипт из filterscripts/ (можно mymod/x). |
unload <имя> | Выгрузить скрипт. |
scripts | Список загруженных скриптов (GM — гейммод, FS — фильтрскрипт). |
players | Список игроков онлайн: id, ник, IP, пинг. |
kick <id или ник> | Кикнуть игрока. |
say <текст> | Сообщение всем игрокам от имени администратора. |
exit / quit / stop | Остановить сервер. |
Свои команды консоли добавляются через событие rcon_command. При выгрузке скрипта автоматически удаляются его события, команды, таймеры, а также созданные им пикапы, 3D-тексты и текстдравы. Машины и объекты не удаляются — уничтожайте их сами в script_exit / game_mode_exit.
5. События — @on("имя")
Если обработчик вернул False, цепочка останавливается и событие считается отменённым (например, сообщение не уйдёт в общий чат, спавн будет запрещён).
| Событие | Аргументы | Значение False |
|---|---|---|
game_mode_init / game_mode_exit | — | — |
script_init / script_exit | — (для фильтрскриптов) | — |
player_connect | player | Кикнуть игрока при входе |
player_disconnect | player, reason (0 вылет, 1 выход, 2 кик) | — |
player_request_spawn | player | Запретить спавн |
player_spawn | player | — |
player_death | player, killer, reason (killer — Player или None, reason — id оружия) | — |
player_text | player, text | Не рассылать сообщение в чат |
player_command | player, "/cmd args" — только для команд без @command | Команда обработана |
player_update | player — каждый пакет синхронизации | Не пересылать другим игрокам |
player_state_change | player, new_state, old_state | — |
player_enter_vehicle | player, vehicle, is_passenger | Выкинуть из транспорта |
player_exit_vehicle | player, vehicle | — |
player_take_damage | player, issuer, amount, weapon, body_part | — |
player_enter_checkpoint / player_leave_checkpoint | player | — |
player_pickup | player, pickup | — |
dialog_response | player, dialog_id, response, list_item, input_text | Ответ обработан |
rcon_command | cmd, args — неизвестная команда консоли | Команда обработана |
6. Команды — @command
Сигнатура: @command("имя", "алиас", ..., help="", admin=False). Аргументы команды разбираются автоматически по аннотациям типов.
@command("pm", help="Отправить личное сообщение")
def pm(player: Player, target: Player, text: str):
target.send_message(COLOR_YELLOW, f"ЛС от {player.name}: {text}")
@command("veh", "car", help="Создать машину")
def veh(player: Player, model: int, color1: int = -1, color2: int = -1):
x, y, z = player.pos
car = create_vehicle(model, x + 2.0, y, z, player.heading, color1, color2)
player.put_in_vehicle(car)
- Типы аргументов:
int,float,str,Player(id или начало ника). - Последний аргумент типа
strполучает весь остаток строки. - Аргументы со значением по умолчанию необязательны; при ошибке игрок увидит подсказку «Использование: …».
admin=True— только для игроков, вошедших через/rcon login <пароль>. Эту команду даёт фильтрскриптadmin.py— не убирайте его изserver.toml.
7. Класс Player
Поля: id, name, ip, state, pos, heading, health, armor, weapon, skin, money, score, interior, virtual_world, vehicle, seat, color, team, is_admin, ping_ms, is_spawned и data — словарь для ваших данных.
player.pos — это вектор с полями .x, .y, .z. Его можно распаковать (x, y, z = player.pos), но нельзя индексировать: player.pos[0] вызовет ошибку.
| Метод | Описание |
|---|---|
send_message(color, text) | Сообщение в чат игрока |
game_text(text, time_ms=3000, style=3) | Крупный текст на экране |
show_dialog(id, style, caption, info, button1="OK", button2="") | Диалог; style — DialogStyle.MSGBOX / INPUT / LIST / PASSWORD / TABLIST |
set_pos(x, y, z), set_heading(a), set_interior(i) | Позиция, поворот, интерьер |
set_virtual_world(w) | Виртуальный мир: игроки в разных мирах не видят друг друга |
set_health(hp), set_armor(arm) | Здоровье / броня |
give_weapon(id, ammo), reset_weapons() | Оружие |
set_money(v), give_money(v) | Деньги |
set_skin(id), set_color(rgba), set_wanted_level(n) | Внешний вид и розыск |
set_spawn_info(skin, x, y, z, heading=0, team=255), spawn() | Точка спавна / заспавнить |
put_in_vehicle(vehicle, seat=0), remove_from_vehicle() | Посадить в транспорт / высадить |
set_checkpoint(x, y, z, size=3.0), disable_checkpoint() | Чекпоинт |
set_map_icon(id, x, y, z, marker, color=0), remove_map_icon(id) | Иконки на карте |
set_camera(pos, look_at), camera_behind() | Камера |
set_time(h, m), set_weather(w) | Время и погода только для этого игрока |
toggle_controllable(bool) | Заморозить / разморозить |
distance_to(x, y, z) | Расстояние до точки |
kick(reason) | Кикнуть игрока |
8. Транспорт и объекты
car = create_vehicle(model, x, y, z, heading=0, color1=-1, color2=-1, respawn_delay=300)
car.set_pos(x, y, z); car.set_health(1000); car.repair(); car.set_color(1, 1)
car.respawn(); car.destroy()
car.driver # Player или None
car.occupants # список: водитель и пассажиры
car.data["owner"] = player.name # ваши данные
obj = create_object(model, x, y, z, rx=0, ry=0, rz=0, draw_distance=300)
obj.move(x, y, z, speed)
obj.destroy()
Найти транспорт: get_vehicle(id), get_vehicles(). Машины и объекты не удаляются при выгрузке скрипта автоматически.
9. Пикапы, 3D-текст и текстдравы
# Пикап (аналог CreatePickup); подбор — событие player_pickup
pk = create_pickup(1240, 2, x, y, z, world=-1) # world=-1 — во всех мирах
@on("player_pickup")
def on_pickup(player, pickup):
if pickup is pk:
player.set_health(100)
# 3D-текст в мире; "\n" — новая строка, {RRGGBB} — цвет
label = create_3d_text_label("Автосалон\n{33CCFF}/buycar", COLOR_WHITE, x, y, z, draw_distance=20)
label.update(text="Закрыто")
# Текстдрав (координаты экрана 640x448)
td = create_textdraw(320.0, 420.0, "SADEMP")
td.alignment = 2 # 1 влево, 2 по центру, 3 вправо
td.show_for(player); td.show_for_all()
td.set_text("Новый текст"); td.hide_for(player); td.destroy()
Свойства текстдрава (letter_size, color, font, alignment, box, box_color и др.) задаются атрибутами до показа; для уже показанного вызовите show_for() повторно. Эти три вида объектов удаляются автоматически при выгрузке скрипта.
10. Таймеры и утилиты
t = set_timer(func, 1000, repeat=True, *args) # вызвать func через 1000 мс (повторять)
t.kill() # или kill_timer(t)
get_players(); get_player(id); find_player("Ник или id")
send_message_to_all(color, text); game_text_for_all(text, time_ms, style)
set_world_time(hour, minute=0); set_weather(id)
server_name(); max_players(); rcon_password()
data_path("my.db") # путь к файлу в server/data
Константы: COLOR_* (формат RGBA), WEAPON_*, DEATH_REASON_*, PlayerState, DialogStyle. В тексте сообщений цвет меняется вставками {RRGGBB}, как в SA-MP.
11. Перенос логики с Pawn (SA-MP)
| Классический Pawn (SA-MP) | Python (SADEMP) |
|---|---|
public OnPlayerConnect(playerid) | @on("player_connect") def f(player): |
SendClientMessage(playerid, -1, "...") | player.send_message(COLOR_WHITE, "...") или SendClientMessage(player.id, -1, "...") из compat |
CMD:veh(playerid, params[]) + sscanf | @command("veh") def veh(player, model: int): |
SetTimerEx("F", 1000, true, "i", id) | set_timer(f, 1000, True, player) |
PlayerData[playerid][pCash] | player.data["cash"] |
mysql_query(...) (плагин) | sqlite3 или любая библиотека из PyPI |
Модуль sademp.compat содержит привычные функции с playerid: GetPlayerName, SetPlayerPos, GivePlayerWeapon, CreateVehicle, PutPlayerInVehicle, ShowPlayerDialog, SetTimer и другие. Коды цвета Pawn (-1 = белый) поддерживаются.