Софт [2.01] MoonBot - внутриигровые боты для Moonloader

moonbot 2.jpg

Actual verison: 2.01

MoonBot - это библиотека для Moonloader, позволяющая создавать внутриигровых ботов, вроде тех, что вы могли видеть в RakSAMP, RakBot, Overlight и т.д.
У каждого бота имеется свой индекс, по которому к нему можно обращаться (индексы не объединены у всех скриптов, т.е. у каждого скрипта может быть бот с 1-ым индексом, но доступа к чужому боту с таким же индексом у них нет).
Давайте сразу договоримся, что переменная mb будет значить это:
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
Первое, что мы делаем, подключаем библиотеку:
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
Заходим в игру проверять команды! При добавлении и удалении (кроме строчки от сервера о подключении) должно получиться так:
1624440829296.png

Всё остальное вы можете пощупать уже самостоятельно, либо посмотреть в примерах.
Примечание
Примеры ниже рассчитаны на 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.01:
  • Теперь индексы ботов начинаются с 1, а не с 0
  • Фикс Vehicle (Incar) синхры, теперь она работает корректно
1.1:
  • Добавлена возможность ставить пароль
  • Пофикшены просады ФПС
  • Добавлена возможность собственноручно указывать задержку ботам
  • Теперь позиция, здоровье, броня хранится ботом. С этими значениями можете работать вы, а также сервер
  • Добавлена возможность редактировать исходящую синхронизацию. Будьте осторожны с ней! Пример добавлен в тему.
  • В связи с добавлением исходящей синхры, onBotPacket теперь следует называть onBotIncomingPacket, а onBotRPC следует называть onBotIncomingRPC. На текущий момент скрипты не сломаются, будет уведомление от библиотеки. Однако в дальнейших версиях поддержка старого написания хуков может быть удалено.
  • Бот может получать урон от пуль. Вы также можете сами наносить урон боту.
1.11:
  • Пофикшены просады ФПС, если бот не мог подключиться к серверу
  • Возращена предыдущая система задержки.
1.12
  • Убрано мое "гениальное" решение с синхрой. Теперь не должно отключать от сервера спустя время
  • Теперь bot:disconnect() не приводит к вылету игры
2.0
  • Код проекта почти полностью переписан
  • Добавлена система прокси
  • Убрана регистрация входящих RPC (mb.registerIncomingRPC), теперь приходят информация о всех RPC автоматически
  • mb.unload и mb.updateCallbacks более необязательны, можно писать код без них
  • У бота убран автореконнект, здоровье, броня, позиция, система урона. Полный контроль над ботом передаётся скриптеру
  • Убран хук исходящей синхронизации. Впоследствии возможно будет возвращено
  • Функции onBotIncomingRPC и onBotIncomingPacket переименованы в onBotRPC и onBotPacket соответственно
2.01
  • Добавлена поддержка аутентификации в прокси
Благодарности:
  • @damag за небольшой патч для серверов с React
  • @Lolendor за тесты
  • @AdCKuY_DpO4uLa за вдохновение на прокси
Установка: содержимое архива закинуть в moonloader/lib.
О багах можете сообщать в эту тему, только пишите подробно, пожалуйста, что и как вы делали, при каких обстоятельствах.
 

Вложения

  • MoonBot v1.1.rar
    307.2 KB · Просмотры: 3,037
  • MoonBot 1.12.rar
    256.4 KB · Просмотры: 8,987
  • MoonBot 2.01.rar
    161.1 KB · Просмотры: 1,804
Последнее редактирование:

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,202
4,981
*** Скрытый текст не может быть процитирован. ***
Должно быть bot:setProxy (двоеточие, а не точка). Однако спешу заранее предупредить о другой ошибке в коде, а именно у тебя прокси установлены, но не запущены. После setProxy следует сделать useProxy. И bot:connect нужно будет делать уже внутри тела функции. Однако реализация в выложенной версии чуть кривая, у меня есть чуть подправленная, но недостаточно протестированная.
 
  • Клоун
  • Нравится
Реакции: Fott и congic

bidgemachine

Новичок
12
5
Добрый вечер, мистер Криптон. Имеется ли в открытом доступе так называемый slave bot?
 

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,202
4,981
  • Клоун
  • Нравится
Реакции: Fott и bidgemachine

congic

Известный
331
106
как прикрутить то,чтобы при server closed the connection бот переподключался?
 

Вложения

  • rakbot.lua
    1.5 KB · Просмотры: 10

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,202
4,981
как прикрутить то,чтобы при server closed the connection бот переподключался?
Вот способы детектить отключение:
  • bot.connected - отображает статус бота: подключен он или нет
  • Хук входящего пакета PACKET_DISCONNECTION_NOTIFICATION, ид 32
 
  • Грустно
  • Клоун
Реакции: Fott и congic

TRIPLDH

Новичок
1
0
друзья вот я скачал что мне дальше делать вообще не шарю?
 

congic

Известный
331
106
почему не работает авто реконнект после отключения?
 

Вложения

  • rakbot.lua
    1.5 KB · Просмотры: 8

P%R

Участник
51
12
Как записать что-то от имени бота, в инпут диалога ?
 

oixroishere

Новичок
3
0
Добрый день, может кто сможет помочь разобраться?
При использованиии мунбота краш
С редистами всё окей, все стоят с 5 до 22 год.

Скрытое содержимое для пользователя(ей):
 

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,202
4,981
Добрый день, может кто сможет помочь разобраться?
При использованиии мунбота краш
С редистами всё окей, все стоят с 5 до 22 год.

*** Скрытый текст не может быть процитирован. ***
Кинь код, который приводит к ошибке