Actual verison: 2.01
MoonBot - это библиотека для Moonloader, позволяющая создавать внутриигровых ботов, вроде тех, что вы могли видеть в RakSAMP, RakBot, Overlight и т.д.
У каждого бота имеется свой индекс, по которому к нему можно обращаться (индексы не объединены у всех скриптов, т.е. у каждого скрипта может быть бот с 1-ым индексом, но доступа к чужому боту с таким же индексом у них нет).
Lua:
local mb = require('MoonBot') -- Имя аргумента может быть любое, в дальнейшем для удобства будет использоваться это.
Примечание
То что в угловых скобках <> - опциональный аргумент, то что в квадратных скобках [] - обязательный аргумент.
Название метода | Описание |
local bot = mb.add(<Имя>) | Регистрирует бота. В ответ вы получаете его хендл |
mb.remove([Индекс бота]) | Удаляет бота по его индексу |
mb.disconnectAfterUnload([bool status]) | Устанавливает то, будут ли боты авто-удалятся при oтpaбaтывaнии функции ниже |
local bots = mb.getBots() | Возвращает таблицу с хендлами ботов |
local bot = mb.getBotHandleByIndex([Индекс бота]) | Возвращает хендл бота по индексу |
local bitStream = mb.getBitStream() | Возвращает объект BitStream. Необходимо удалять! |
local data = mb.getPlayerData() | Возвращает объект PlayerData. Удаляется сам. |
local data = mb.getIncarData() | Возвращает объект IncarData. Удаляется сам. |
local data = mb.getPassengerData() | Возвращает объект PassengerData. Удаляется сам. |
local data = mb.getUnoccupiedData() | Возвращает объект UnoccupiedData. Удаляется сам. |
local data = mb.getTrailerData() | Возвращает объект TrailerData. Удаляется сам. |
local data = mb.getBulletData() | Возвращает объект BulletData. Удаляется сам. |
local data = mb.getSpectatorData() | Возвращает объект SpectatorData. Удаляется сам. |
local data = mb.getAimData() | Возвращает объект AimData. Удаляется сам. |
Примечание
То что в фигурных скобках {} - опциональный аргумент, то что в квадратных скобках [] - обязательный аргумент.
Название метода / аргумента | Описание |
local index = bot.index | Получает индекс бота. Доступно только для чтения |
local name = bot.name | Получает имя бота. Доступно только для чтения |
local connected = bot.connected | Получает статус подключенности бота (bool). Доступно только для чтения |
local playerId = bot.playerID | Получает серверный ID бота. Доступно только для чтения |
bot:connect(<IP>, <Port>) | Подключает бота к серверу |
bot:changeName([Name]) | Меняет игровой ник боту. Учтите, что происходит переподключение к серверу |
bot:connectAsNPC([bool state]) | Устанавливает то, будет ли бот подключаться как NPC. Полезно для обхода античита |
bot:disconnect() | Отключает бота от сервера |
bot:sendRPC([rpcId], [BitStream]) | Отправляет указанный RPC от лица бота |
bot:sendBitStream([BitStream]) | Отправляет указанный bitStream от лица бота |
bot:sendPlayerData([data]) | Отправляет пакет ID_ONFOOT_SYNC от лица бота |
bot:sendVehicleData([data]) | Отправляет пакет ID_DRIVER_SYNC от лица бота |
bot:sendPassengerData([data)] | Отправляет пакет ID_PASSENGER_SYNC от лица бота |
bot:sendUnoccupiedData([data]) | Отправляет пакет ID_UNOCCUPIED_SYNC от лица бота |
bot:sendTrailerData([data]) | Отправляет пакет ID_TRAILER_SYNC от лица бота |
bot:sendSpectatorData([data]) | Отправляет пакет ID_SPECTATOR_SYNC от лица бота |
bot:sendBulletData([data]) | Отправляет пакет ID_BULLET_SYNC от лица бота |
bot:sendAimData([data]) | Отправляет пакет ID_AIM_SYNC от лица бота |
bot:sendRequestClass([classId]) | Отправляет RPC RequestClass от лица бота |
bot:sendEnterVehicle([vehicleId], [isPassenger]) | Отправляет RPC EnterVehicle от лица бота |
bot:sendClickPlayer([playerID], [source]) | Отправляет RPC ClickPlayer от лица бота |
bot:sendCommand([команда с /]) | Отправляет команду от лица бота |
bot:sendChat([message]) | Отправляет сообщение от лица бота |
bot:sendSpawn() | Отправляет RPC Spawn от лица бота |
bot:sendDeathNotification([reason], [killerId]) | Отправляет RPC смерти от лица бота |
bot:sendDialogResponse([dialogId], [buttonId], [listboxId], [input]) | Отправляет ответ на диалог от лица бота |
bot:sendClickTextdraw([textdrawId]) | Отправляет клик на textDraw от лица бота |
bot:sendInteriorChange([interiorId]) | Отправляет смену интерьера от лица бота |
bot:sendRequestSpawn() | Отправляет RPC RequestSpawn от лица бота |
bot:sendPickedUpPickup([pickupId]) | Поднимает пикап от лица бота |
bot:sendExitVehicle([vehicleId]) | Отправляет RPC ExitVehicle от лица бота |
bot:setReconnectTime([time in ms]) | Устанавливает время автореконнекта в мс |
bot:setProxy([ip], [port], <login>, <password>) | Устанавливает параметры прокси (SOCKS 5 UDP) |
bot:useProxy([true/false], function(state: boolean) end) | Активирует/деактивирует прокси бота. Результат подключения/отключения возвращается в функцию |
Примечание
То что в фигурных скобках {} - опциональный аргумент, то что в квадратных скобках [] - обязательный аргумент.
Большинство определений позаимствованы из вики мунлоадера, чтобы дать более грамотное описание
Название метода | Описание |
bs:remove() | Удаляет битстрим. Необходимо вызывать после работы с ним, если вы получили его с помощью mb.getBitStream() |
bs:setReadOffset([offset]) | Устанавливает смещение для последующего чтения битстрима (в битах) |
bs:setWriteOffset([offset]) | Устанавливает смещение для последующей записи в битстрим (в битах) |
bs:resetReadPointer() | Сбрасывает указатель чтения битстрима |
bs:resetWritePointer() | Сбрасывает указатель записи битстрима |
bs:ignoreBits([bits]) | Осуществляет пропуск битов в указателе чтения/записи битстрима |
local bits = bs:getNumberOfBitsUsed() | Возвращает количество записанных битов в битстриме |
local bits = bs:getNumberOfUnreadBits() | Возвращает количество непрочитанных битов в битстриме |
local bool = bs:readBool() | Читает значение типа boolean из BitStream |
local int = bs:readInt8() | Читает значение типа byte (1 байт) из BitStream |
local int = bs:readInt16() | Читает значение типа short (2 байта) из BitStream |
local int = bs:readInt32() | Читает значение типа integer (4 байта) из BitStream |
local float = bs:readFloat() | Читает значение типа float из BitStream |
local string = bs:readString8() | Читает строку, размером в 1 байт из BitStream |
local string = bs:readString16() | Читает строку, размером в 2 байта из BitStream |
local string = bs:readString32() | Читает строку, размером в 4 байта из BitStream |
bs:writeBool([bool]) | Записывает значение типа boolean в BitStream |
bs:writeInt8([int]) | Записывает значение типа byte (1 байт) в BitStream |
bs:writeInt16([int]) | Записывает значение типа short (2 байта) в BitStream |
bs:writeInt32([int]) | Записывает значение типа integer (4 байта) в BitStream |
bs:writeFloat([float]) | Записывает значение типа float в BitStream |
bs:writeString8([string]) | Записывает строку, размером в 1 байт из BitStream |
bs:writeString16([string]) | Записывает строку, размером в 2 байта из BitStream |
bs:writeString32([string]) | Записывает строку, размером в 4 байта из BitStream |
local string = bs:decodeString([maxCharsToWrite]) | Декриптует строку из BitStream`a и записывает её в буфер |
Примечания
1. Векторы (x, y, z, w) пока нельзя передавать в качестве массива. Возможно позже исправлю, пока имеет то, что имеем.
2. Структуры готовых пакетов идентичны тем, что описаны в библиотеке Samp.Lua
- data.leftRightKeys
- data.upDownKeys
- data.keysData
- data.position.x, data.position.y, data.position.z
- data.quaternion.x, data.quaternion.y, data.quaternion.z, data.quaternion.w
- data.health
- data.armor
- data.weapon
- data.specialAction
- data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z
- data.surfingOffsets.x, data.surfingOffsets.y, data.surfingOffsets.z
- data.surfingVehicleId
- data.animationId
- data.animationFlags
- data.vehicleId
- data.leftRightKeys
- data.upDownKeys
- data.keysData
- data.position.x, data.position.y, data.position.z
- data.quaternion.x, data.quaternion.y, data.quaternion.z, data.quaternion.w
- data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z
- data.vehicleHealth
- data.playerHealth
- data.armor
- data.currentWeapon
- data.siren
- data.landingGearState
- data.trailerId
- data.trainSpeed
- data.vehicleId
- data.seatId
- data.currentWeapon
- data.health
- data.armor
- data.leftRightKeys
- data.upDownKeys
- data.keysData
- data.position.x, data.position.y, data.position.z
- data.vehicleId
- data.seatId
- data.position.x, data.position.y, data.position.z
- data.roll.x, data.roll.y, data.roll.z
- data.direction.x, data.direction.y, data.direction.z
- data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z
- data.turnSpeed.x, data.turnSpeed.y, data.turnSpeed.z
- data.vehicleHealth
- data.trailerId
- data.position.x, data.position.y, data.position.z
- data.roll.x, data.roll.y, data.roll.z
- data.direction.x, data.direction.y, data.direction.z
- data.speed.x, data.speed.y, data.speed.z
- data.unk
- data.leftRightKeys
- data.upDownKeys
- data.keysData
- data.position.x, data.position.y, data.position.z
- data.targetType
- data.targetId
- data.origin.x, data.origin.y, data.origin.z
- data.target.x, data.target.y, data.target.z
- data.center.x, data.center.y, data.center.z
- data.weaponId
- data.camMode
- data.camFront.x, data.camFront.y, data.camFront.z
- data.camPos.x, data.camPos.y, data.camPos.z
- data.aimZ
- data.camExtZoom
- data.weaponState
- data.unknown
Первое, что мы делаем, подключаем библиотеку:
Если мы хотим отлавливать пакеты, RPC, то:
Объявляем функции: если хотите перелавливать входящие RPC, то:
Если хотите перелавливать входящие пакеты, то:
Подготовительные работы успешно выполнены! Давайте создадим команду, которая будет подключать нам бота. Также сразу добавим команду на его удаление
По итогу должно получиться так:
Заходим в игру проверять команды! При добавлении и удалении (кроме строчки от сервера о подключении) должно получиться так:
Всё остальное вы можете пощупать уже самостоятельно, либо посмотреть в примерах.
Lua:
local mb = require('MoonBot') -- Имя аргумента может быть любое, в дальнейшем для удобства будет использоваться это.
Если мы хотим отлавливать пакеты, RPC, то:
Объявляем функции: если хотите перелавливать входящие RPC, то:
Lua:
function onBotRPC(bot, rpcId, bs) -- bot - хендл бота, rpcId - id полученного rpc, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
-- code
end
Если хотите перелавливать входящие пакеты, то:
Lua:
function onBotPacket(bot, packetId, bs) -- bot - хендл бота, packetId -- ид пакета, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
-- code
end
Подготовительные работы успешно выполнены! Давайте создадим команду, которая будет подключать нам бота. Также сразу добавим команду на его удаление
Lua:
function main()
-- code
sampRegisterChatCommand('bot.add', addCommand) -- Можно не создавать отдельно функцию, сделал так для наглядности
sampRegisterChatCommand('bot.remove', removeCommand)
-- code
end
function addCommand(arg) -- Функция, вызываемая при команде /bot.add
local bot = mb.add(arg) -- Создаём бота. Если бы ник был пустой, то модуль сам бы назвал бота в формате UnnamedИНДЕКС
bot:connect() -- Подключаем бота. Как видите, айпи и порт не указан: бот возьмёт их сам. Мы бы могли их указать сами, но щас это не нужно
sampAddChatMessage(string.format('Connecting %s', bot.name), -1) -- Выводим сообщение с ником бота
end
function removeCommand(arg) -- Функция, вызываемая при команде /bot.remove
if arg:match('%d+') then -- Проверяем, что переданный аргумент - число
local bot = mb.getBotHandleByIndex(tonumber(arg)) -- Получаем хендл бота по индексу (не рекомендуется хранить хендлы в таблицах)
if bot ~= nil then -- Проверяем, что бот с таким индексом действительно есть
sampAddChatMessage(string.format('Deleting %s', bot.name), -1) -- Выводим сообщение об удалении заранее (ибо после бот будет удалён и если мы попытаемся что-то прочитать, то нас крашнет)
mb.remove(tonumber(arg)) -- Удаляем бота
end
end
end
Lua:
local mb = require('MoonBot')
function onBotRPC(bot, rpcId, bs)
sampAddChatMessage(string.format('Bot %s got RPC: %d', bot.name, rpcId), -1)
end
function main()
sampRegisterChatCommand('bot.add', addCommand)
sampRegisterChatCommand('bot.remove', removeCommand)
wait(-1)
end
function addCommand(arg)
local bot = mb.add(arg)
bot:connect()
sampAddChatMessage(string.format('Connecting %s', bot.name), -1)
end
function removeCommand(arg)
if arg:match('%d+') then
local bot = mb.getBotHandleByIndex(tonumber(arg))
if bot ~= nil then
sampAddChatMessage(string.format('Deleting %s', bot.name), -1)
mb.remove(tonumber(arg))
end
end
end
end
Всё остальное вы можете пощупать уже самостоятельно, либо посмотреть в примерах.
Примечание
Примеры ниже рассчитаны на MoonBot v1.0! В них используются устаревшие механики. Рассматривайте их как пример, а не готовый код!
Примечание
С коробки у вас может не заработать. На вашей нубо-аризоне могут либо быть выключены боты, либо стоять другие текстдравы.
Также не считайте этот код образцовым, это просто пример, что можно сделать.
Lua:
local mb = require('MoonBot')
local ev = require('lib.samp.events')
local password = '123123'
local raznos = false
local botData = {}
function main()
repeat wait(0) until isSampAvailable()
--mb.disconnectAfterUnload(false)
mb.registerIncomingRPC(61) -- dialog
mb.registerIncomingRPC(68) -- spawnInfo
mb.registerIncomingRPC(134) -- textdraw
sampRegisterChatCommand('abot.add', function(param)
if param:len() >= 3 then
local bot = mb.add(param)
table.insert(botData, {
index = bot.index,
logined = false,
rvanka = false
})
bot:connectAsNPC(true)
bot:connect()
sampAddChatMessage(string.format('Connecting %s (Index: %d)', bot.name, bot.index), -1)
end
end)
sampRegisterChatCommand('abot.remove', function(param)
if param:match('%d+') then
local index = tonumber(param)
for k, bot in pairs(mb.getBots()) do
if bot.index == index then
sampAddChatMessage(string.format('Removed %s (Index: %d)', bot.name, bot.index), -1)
for j, lBot in pairs(botData) do
if lBot.index == bot.index then
table.remove(botData, j)
break
end
end
mb.remove(index)
break
end
end
end
end)
sampRegisterChatCommand('abot.raznos', function()
raznos = not raznos
for k, lBot in pairs(botData) do
local bot = mb.getBotHandleByIndex(lBot.index)
if bot ~= nil then
if not bot.connected and lBot.logined then
lBot.rvanka = false
end
end
end
sampAddChatMessage('raznos mode ' .. (raznos and 'ON' or 'OFF'), -1)
end)
while true do
wait(50)
mb.updateCallbacks()
local ped, playerId = nil, -1
if raznos then
local x, y, z = getCharCoordinates(PLAYER_PED)
_, ped = findAllRandomCharsInSphere(x, y, z, 100, true, true)
_, playerId = sampGetPlayerIdByCharHandle(ped)
if _ and not sampIsPlayerPaused(playerId) and not sampIsPlayerNpc(playerId) then
-- fine :3
else
ped = nil
end
end
for k, lBot in pairs(botData) do
local bot = mb.getBotHandleByIndex(lBot.index)
if bot ~= nil then
if not bot.connected and lBot.logined then
lBot.logined = false
end
if raznos then
if lBot.logined and ped ~= nil then
local angle = getCharHeading(ped)
local data = mb.getPlayerData()
local multiplier = 10
local x, y, z = getCharCoordinates(ped)
data.health = getCharHealth(PLAYER_PED)
data.armor = 0
data.weapon = 0
data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z = math.sin(-math.rad(angle)) * multiplier, math.cos(-math.rad(angle)) * multiplier, multiplier
data.position.x, data.position.y, data.position.z = x - math.sin(-math.rad(angle)), y - math.cos(-math.rad(angle)), z + (isCharOnFoot(ped) and 0.5 or -1)
bot:sendPlayerData(data)
lBot.rvanka = true
else
lBot.rvanka = false
end
else
lBot.rvanka = false
end
end
end
if ped ~= nil and raznos then
printStringNow(string.format('Fisting for ~r~%s[%d]', sampGetPlayerNickname(playerId), playerId), 500)
end
end
end
function onBotIncomingPacket(bot, packetId, bs)
--sampfuncsLog(string.format('%s [%d] got packet: %d', bot.name, bot.index, packetId))
if packetId == 41 then
sampAddChatMessage(string.format('%s [%d] successfully connected (PlayerID: %d)', bot.name, bot.index, bot.playerID), -1)
end
end
function onBotIncomingRPC(bot, rpcId, bs)
--sampAddChatMessage('got rpc: ' .. rpcId, -1)
if rpcId == 61 then
local dialogId = bs:readInt16()
local style = bs:readInt8()
local title = bs:readString8()
local btn1 = bs:readString8()
local btn2 = bs:readString8()
if title:find('Авторизация') then
sampAddChatMessage(string.format('%s [%d] logining...', bot.name, bot.index), -1)
bot:sendDialogResponse(dialogId, 1, 0, password)
end
if title:find('%(1/4%)') then
sampAddChatMessage(string.format('%s [%d] registering...', bot.name, bot.index), -1)
bot:sendDialogResponse(dialogId, 1, 0, password)
end
if title:find('%[2/5%]') or title:find('%[3/5%]') or title:find('%[3/4%]') then
bot:sendDialogResponse(dialogId, 1, 0, '')
sampAddChatMessage(string.format('%s [%d] skipped dialog (title: %s)', bot.name, bot.index, title), -1)
end
end
if rpcId == 68 then
for k, lBot in pairs(botData) do
if lBot.index == bot.index and not lBot.logined then
lBot.logined = true
bot:sendRequestSpawn()
bot:sendSpawn()
sampAddChatMessage(string.format('%s [%d] sent request spawn', bot.name, bot.index), -1)
end
end
end
if rpcId == 134 then
local tdId = bs:readInt16()
if tdId == 254 then
bot:sendClickTextdraw(242)
sampAddChatMessage(string.format('%s [%d] selected skin', bot.name, bot.index), -1)
for k, lBot in pairs(botData) do
if lBot.index == bot.index then
lBot.logined = true
end
end
end
end
end
function onScriptTerminate(script, quitGame)
if script == thisScript() then
mb.unload()
end
end
function ev.onSendPlayerSync(data)
local offset = 0
for k, lBot in pairs(botData) do
if lBot.logined and lBot ~= nil and not lBot.rvanka then
offset = offset + 1
local angle = getCharHeading(PLAYER_PED) - 90
local sync = mb.getPlayerData()
sync.position.x, sync.position.y, sync.position.z = data.position.x + math.sin(-math.rad(angle)) * offset, data.position.y + math.cos(-math.rad(angle)) * offset, data.position.z
sync.health = data.health
sync.armor = 0
sync.quaternion.w, sync.quaternion.x, sync.quaternion.y, sync.quaternion.z = data.quaternion[0], data.quaternion[1], data.quaternion[2], data.quaternion[3]
sync.moveSpeed.x, sync.moveSpeed.y, sync.moveSpeed.z = data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z
sync.weapon = 0
local bot = mb.getBotHandleByIndex(lBot.index)
bot:sendPlayerData(sync)
end
end
end
Примечание
Не считайте этот код образцовым, это просто пример, что можно сделать.
Также он может быть чучуть устаревшим, 27 числа я наверное выложу более крутую версию.
Lua:
local mb = require('MoonBot')
local ev = require('lib.samp.events')
local bots = {}
local time = 5
local password = '123123'
local targetId = -1
local dist = 10
local hi = false
function onScriptTerminate(script, quitGame)
if script == thisScript() then
mb.unload()
end
end
function onBotIncomingPacket(bot, packetId, bs)
printStringNow('got packet: ' .. packetId, 500)
if packetId == 41 then
sampAddChatMessage('connected: ' .. bot.playerID, -1)
end
end
function onBotRPC(bot, rpcId, bs)
if rpcId == 134 then
local tdId = bs:readInt16()
sampfuncsLog('td: ' .. tdId)
if tdId == 637 then
bot:sendClickTextdraw(637)
sampAddChatMessage('skin selected', -1)
bot:sendRequestSpawn()
bot:sendSpawn()
end
end
if rpcId == 93 then
local color = bs:readInt32()
local msg = bs:readString32()
sampfuncsLog('message: ' .. msg)
if msg == 'Поздравляем вас с успешной регистрацией!' or msg == '{FFCC00}Авторизация прошла успешно.' or msg == '{DFCFCF}[Подсказка] {DC4747}Вы можете задать вопрос в нашу техническую поддержку /report.' or msg == '[Подсказка] {FFFFFF}Благодарим вас за регистрацию на нашем сервере' then
bot:sendRequestSpawn()
bot:sendSpawn()
sampAddChatMessage(string.format('[Bot #%d]: Spawned', bot.index), -1)
if msg == '{DFCFCF}[Подсказка] {DC4747}Вы можете задать вопрос в нашу техническую поддержку /report.' then
for k, botData in pairs(bots) do
if botData.index == bot.index then
botData.logined = true
sampAddChatMessage(string.format('[Bot #%d]: Logined', bot.index), -1)
break
end
end
end
end
end
if rpcId == 61 then
local dialogId = bs:readInt16()
local style = bs:readInt8()
local title = bs:readString8()
local btn1 = bs:readString8()
local btn2 = bs:readString8()
sampAddChatMessage('dialogID: ' .. dialogId, -1)
if (dialogId == 1 and title:find('Пароль')) or dialogId == 2 then
bot:sendDialogResponse(dialogId, 1, 0, password)
sampAddChatMessage(string.format('[Bot #%d]: Logining / Registering...', bot.index), -1)
end
if dialogId == 1 and not title:find('Пароль') then
bot:sendDialogResponse(dialogId, 1, 0, '')
sampAddChatMessage(string.format('[Bot #%d]: Skipping dialogs...', bot.index), -1)
end
end
if rpcId == 12 then
local x, y, z = bs:readFloat(), bs:readFloat(), bs:readFloat()
for k, botData in pairs(bots) do
if botData.index == bot.index then
botData.position = {x = x, y = y, z = z}
pcall(sampAddChatMessage, string.format('Position: %f, %f, %f', botData.position.x, botData.position.y, botData.position.z), -1)
end
end
end
if rpcId == 68 then
local team = bs:readInt8()
local skin = bs:readInt32()
local unk = bs:readInt8()
local x, y, z = bs:readFloat(), bs:readFloat(), bs:readFloat()
for k, botData in pairs(bots) do
if botData.index == bot.index then
botData.position = {x = x, y = y, z = z}
end
end
end
end
function getDistanceToPlayer(playerId)
local _, ped = sampGetCharHandleBySampPlayerId(playerId)
if _ then
local mx, my, mz = getCharCoordinates(PLAYER_PED)
local x, y, z = getCharCoordinates(ped)
return getDistanceBetweenCoords3d(x, y, z, mx, my, mz)
else
return 9999
end
end
function main()
repeat wait(0) until isSampAvailable()
mb.registerIncomingRPC(134)
mb.registerIncomingRPC(93)
mb.registerIncomingRPC(61)
mb.registerIncomingRPC(12)
mb.registerIncomingRPC(68)
sampRegisterChatCommand('arizona.add', function(param)
if param ~= nil then
local ip, port = sampGetCurrentServerAddress()
local bot = mb.add(param, ip, port)
bot:setReconnectTime(10000)
table.insert(bots, {
name = bot.name,
index = bot.index,
logined = false,
position = {
x = 0,
y = 0,
z = 0
},
timer = 0,
incar = false,
seatId = -1,
hiTimer = 0,
})
sampAddChatMessage(string.format('Connecting %s... (index: %d)', param, bot.index), -1)
end
end)
sampRegisterChatCommand('arizona.remove', function(param)
if param:match('%d+') then
local i = 0
for k, botData in pairs(bots) do
i = i + 1
if botData.index == tonumber(param) then
table.remove(bots, i)
mb.remove(tonumber(param))
sampAddChatMessage('deleted ' .. param, -1)
break
end
end
end
end)
sampRegisterChatCommand('arizona.hi', function(param)
if hi then hi = false return sampAddChatMessage('off', -1) end
if param:match('%d+') then
targetId = tonumber(param)
local _, ped = sampGetCharHandleBySampPlayerId(targetId)
if _ then
if isCharOnFoot(ped) and not sampIsPlayerNpc(targetId) then
if getDistanceToPlayer(targetId) <= dist then
hi = true
sampAddChatMessage('on', -1)
else
sampAddChatMessage('too far', -1)
end
else
sampAddChatMessage('player onfoot/npc', -1)
end
else
sampAddChatMessage('incorrect player', -1)
end
end
end)
while true do
wait(0)
mb.updateCallbacks()
local offset = 0
for k, botData in pairs(bots) do
wait(10)
if botData.logined then
offset = offset + 1
if botData.timer > 0 then
botData.timer = botData.timer - 1
end
if hi and botData.timer <= 0 then
local _, ped = sampGetCharHandleBySampPlayerId(targetId)
if _ then
if isCharOnFoot(ped) and not sampIsPlayerNpc(targetId) then
if getDistanceToPlayer(targetId) <= dist then
botData.hiTimer = botData.hiTimer + 1
local x, y, z = getCharCoordinates(ped)
local angle = getCharHeading(ped)
x = x + (math.sin(-math.rad(angle)) / 1)
y = y + (math.cos(-math.rad(angle)) / 1)
angle = angle + 180
local b = 0 * math.pi / 360.0
local h = 0 * math.pi / 360.0
local a = angle * math.pi / 360.0
local c1, c2, c3 = math.cos(h), math.cos(a), math.cos(b)
local s1, s2, s3 = math.sin(h), math.sin(a), math.sin(b)
local sync = mb.getPlayerData()
sync.health = getCharHealth(PLAYER_PED)
sync.armor = 0
sync.quaternion.w = c1 * c2 * c3 - s1 * s2 * s3
sync.quaternion.z = -( c1 * s2 * c3 - s1 * c2 * s3 )
sync.position.x, sync.position.y, sync.position.z = x, y, z
sync.moveSpeed.x, sync.moveSpeed.y = 0.01, 0.01
local bot = mb.getBotHandleByIndex(botData.index)
bot:sendPlayerData(sync)
botData.position = {x = x, y = y, z = z}
--sync:remove()
--printStringNow('sent', 500)
printStringNow(botData.hiTimer, 500)
if botData.hiTimer >= 50 then
botData.hiTimer = 0
bot:sendCommand('/hi ' .. targetId)
end
else
hi = false
sampAddChatMessage('too far', -1)
end
else
hi = false
sampAddChatMessage('player incar/npc', -1)
end
else
hi = false
sampAddChatMessage('player disappeared', -1)
end
end
end
end
end
end
function ev.onSendPlayerSync(data)
local offset = 0
for k, botData in pairs(bots) do
if botData.logined and not hi and getDistanceBetweenCoords3d(botData.position.x, botData.position.y, botData.position.z, data.position.x, data.position.y, data.position.z) <= dist then
local angle = getCharHeading(PLAYER_PED) + 90
if botData.timer <= 0 and not stop then
local bot = mb.getBotHandleByIndex(botData.index)
offset = offset + 1
botData.timer = time + offset
local sync = mb.getPlayerData()
sync.leftRightKeys = data.leftRightKeys
sync.upDownKeys = data.upDownKeys
sync.keysData = data.keysData
sync.position.x, sync.position.y, sync.position.z = data.position.x + (math.sin(-math.rad(angle)) * offset), data.position.y + (math.cos(-math.rad(angle)) * offset), data.position.z
sync.moveSpeed.x, sync.moveSpeed.y, sync.moveSpeed.z = data.moveSpeed.x, data.moveSpeed.y, data.moveSpeed.z
sync.quaternion.w, sync.quaternion.x, sync.quaternion.y, sync.quaternion.z = data.quaternion[0], data.quaternion[1], data.quaternion[2], data.quaternion[3]
sync.health = data.health
sync.armor = data.armor
sync.weapon = data.weapon
sync.specialAction = data.specialAction
sync.surfingOffsets.x, sync.surfingOffsets.y, sync.surfingOffsets.z = data.surfingOffsets.x, data.surfingOffsets.y, data.surfingOffsets.z
sync.surfingVehicleId = data.surfingVehicleId
sync.animationId = data.animationId
sync.animationFlags = data.animationFlags
bot:sendPlayerData(sync)
botData.position = {x = data.position.x, y = data.position.y, z = data.position.z}
botData.incar = false
botData.seatId = -1
--sync:remove()
end
end
end
end
- Присутствует ли поддержка прокси?
- Да! В версии 2.0 есть
- На чем создана библиотека?
- C++ с применением sol2, а также RakNet (позаимствовано из RakSAMP).
- Будет ли работать на Arizona, Evolve, Diamond?
- Боты подключаться будут, а всё остальное зависит от вас.
- Сколько велась разработка первой версии?
- Чуть больше месяца. Большинство сил было потрачено на то, чтобы не использовать updateCallbacks(). Но, как видите, он все же используется.
- На каких версиях проверялось?
- SA:MP 0.3.7 R1, касаемо мобильных серверов пока ничего не смогу сказать
- Будет ли поддержка модуля?
- Смотря, будет ли на нее спрос. Лично я горю этим, так что буду стараться поддерживать.
- Что на нем вообще можно сделать?
- Начиная от Join флудера, заканчивая вашей фантазией. Ну условно еще можно сделать ботов-разносчиков, ботов-фармилок
- При перезагрузке скрипта боты отключаются и их нужно заново подключать. Что с этим можно сделать?
- Используйте mb.disconnectAfterUnload(false). Однако с ним временно есть проблемы при выходе из игры
1.0:
- Релиз
- Теперь индексы ботов начинаются с 1, а не с 0
- Фикс Vehicle (Incar) синхры, теперь она работает корректно
- Добавлена возможность ставить пароль
- Пофикшены просады ФПС
- Добавлена возможность собственноручно указывать задержку ботам
- Теперь позиция, здоровье, броня хранится ботом. С этими значениями можете работать вы, а также сервер
- Добавлена возможность редактировать исходящую синхронизацию. Будьте осторожны с ней! Пример добавлен в тему.
- В связи с добавлением исходящей синхры, onBotPacket теперь следует называть onBotIncomingPacket, а onBotRPC следует называть onBotIncomingRPC. На текущий момент скрипты не сломаются, будет уведомление от библиотеки. Однако в дальнейших версиях поддержка старого написания хуков может быть удалено.
- Бот может получать урон от пуль. Вы также можете сами наносить урон боту.
- Пофикшены просады ФПС, если бот не мог подключиться к серверу
- Возращена предыдущая система задержки.
- Убрано мое "гениальное" решение с синхрой. Теперь не должно отключать от сервера спустя время
- Теперь bot:disconnect() не приводит к вылету игры
- Код проекта почти полностью переписан
- Добавлена система прокси
- Убрана регистрация входящих RPC (mb.registerIncomingRPC), теперь приходят информация о всех RPC автоматически
- mb.unload и mb.updateCallbacks более необязательны, можно писать код без них
- У бота убран автореконнект, здоровье, броня, позиция, система урона. Полный контроль над ботом передаётся скриптеру
- Убран хук исходящей синхронизации. Впоследствии возможно будет возвращено
- Функции onBotIncomingRPC и onBotIncomingPacket переименованы в onBotRPC и onBotPacket соответственно
- Добавлена поддержка аутентификации в прокси
- @damag за небольшой патч для серверов с React
- @Lolendor за тесты
- @AdCKuY_DpO4uLa за вдохновение на прокси
О багах можете сообщать в эту тему, только пишите подробно, пожалуйста, что и как вы делали, при каких обстоятельствах.
Вложения
Последнее редактирование: