Софт [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 · Просмотры: 2,856
  • MoonBot 1.12.rar
    256.4 KB · Просмотры: 8,518
  • MoonBot 2.01.rar
    161.1 KB · Просмотры: 688
Последнее редактирование:

Ya Zaregalsya

Известный
370
127
Каких только ботов не пишу, их всех крашит при вызове disconnect(), при этом если соединение обрывается самим сервером, то всё нормально. Подозреваю, что как-то связано с выходом какой-то переменной из области видимости. Вот пример кода, который крашит после
ArpBasicBotPool[1]:disconnect():

Lua:
local ArpBasicBotPool = { }
local ArpBasicBot = { }
ArpBasicBot.__index = ArpBasicBot

function ArpBasicBot:new(botSlot)
    local obj =
    {
        botType = BotType.ARP_BASIC_BOT,
        mbId = -1, -- MoonBot ID
    }
    setmetatable(obj, self)
    local bot = mb.add()
    obj.mbId = bot.index
    return obj
end
function ArpBasicBot:getHandle()
    return mb.getBotHandleByIndex(self.mbId)
end
function ArpBasicBot:changeNickname(newName)
    self:getHandle():changeName(newName)
end
function ArpBasicBot:getNickname()
    return self:getHandle().name
end
function ArpBasicBot:connect(ip, port)
    self:getHandle():connect(ip, port)
end
function ArpBasicBot:disconnect()
    self:getHandle():disconnect()
end
function ArpBasicBot:isConnected()
    return self:getHandle().connected
end

function main()
    if not isSampLoaded() or not isSampfuncsLoaded() then return end
    sampRegisterChatCommand("testBasicBot", function()
        lua_thread.create(function()
            ArpBasicBotPool[1] = ArpBasicBot:new()
            ArpBasicBotPool[1]:changeNickname("bot3232")
            local ip, port = sampGetCurrentServerAddress()
            ArpBasicBotPool[1]:connect(ip, port)
            wait(10000)
            ArpBasicBotPool[1]:disconnect() --Тут игра вылетает.
        end)
    end)
    while true do
        wait(0)
        mb.updateCallbacks()
    end
end

Обрати внимание, что самого хэндла бота в таблице нет, как ты указывал в туторе, там только его индекс, и уже по этому индексу при необходимости запрашивается хэндл из mb.

Когда пытаюсь сделать bot:disconnect(), вылетает игра, срабатывает как-то 4/1
Решил проблему?
 
Последнее редактирование:

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,127
4,714
Каких только ботов не пишу, их всех крашит при вызове disconnect(), при этом если соединение обрывается самим сервером, то всё нормально. Подозреваю, что как-то связано с выходом какой-то переменной из области видимости. Вот пример кода, который крашит после
ArpBasicBotPool[1]:disconnect():

Lua:
local ArpBasicBotPool = { }
local ArpBasicBot = { }
ArpBasicBot.__index = ArpBasicBot

function ArpBasicBot:new(botSlot)
    local obj =
    {
        botType = BotType.ARP_BASIC_BOT,
        mbId = -1, -- MoonBot ID
    }
    setmetatable(obj, self)
    local bot = mb.add()
    obj.mbId = bot.index
    return obj
end
function ArpBasicBot:getHandle()
    return mb.getBotHandleByIndex(self.mbId)
end
function ArpBasicBot:changeNickname(newName)
    self:getHandle():changeName(newName)
end
function ArpBasicBot:getNickname()
    return self:getHandle().name
end
function ArpBasicBot:connect(ip, port)
    self:getHandle():connect(ip, port)
end
function ArpBasicBot:disconnect()
    self:getHandle():disconnect()
end
function ArpBasicBot:isConnected()
    return self:getHandle().connected
end

function main()
    if not isSampLoaded() or not isSampfuncsLoaded() then return end
    sampRegisterChatCommand("testBasicBot", function()
        lua_thread.create(function()
            ArpBasicBotPool[1] = ArpBasicBot:new()
            ArpBasicBotPool[1]:changeNickname("bot3232")
            local ip, port = sampGetCurrentServerAddress()
            ArpBasicBotPool[1]:connect(ip, port)
            wait(10000)
            ArpBasicBotPool[1]:disconnect() --Тут игра вылетает.
        end)
    end)
    while true do
        wait(0)
        mb.updateCallbacks()
    end
end

Обрати внимание, что самого хэндла бота в таблице нет, как ты указывал в туторе, там только его индекс, и уже по этому индексу при необходимости запрашивается хэндл из mb.


Решил проблему?
Краш может быть вызван тем, что бот не успел зайти в игру. Остальной код работает корректно? Попробуй спустя твои 10 секунд вызвать не дисконнект, а чет другое. Также проверь состояние переменной connected. Можешь еще проверить на предыдущей версии мунбота. Если проблема не решится, то нужно будет код посмотреть, мб где-то профукался
 

Ya Zaregalsya

Известный
370
127
Краш может быть вызван тем, что бот не успел зайти в игру. Остальной код работает корректно? Попробуй спустя твои 10 секунд вызвать не дисконнект, а чет другое. Также проверь состояние переменной connected. Можешь еще проверить на предыдущей версии мунбота. Если проблема не решится, то нужно будет код посмотреть, мб где-то профукался
Остального кода там немного, пока что только несвязанный с ботом SNet (в другом проекте без СНета дисконнект тоже крашил) и пара неиспользуемых методов ArpBasicBot. В остальном кроме дисконнекта бот работает нормально:

Lua:
sampRegisterChatCommand("testBasicBot", function()
        lua_thread.create(function()
            ArpBasicBotPool[1] = ArpBasicBot:new()
            ArpBasicBotPool[1]:changeNickname("bot3232")
            local ip, port = sampGetCurrentServerAddress()
            ArpBasicBotPool[1]:connect(ip, port)
            wait(5000)
            ArpBasicBotPool[1]:getHandle():sendRequestClass(1)
            ArpBasicBotPool[1]:getHandle():sendRequestSpawn()
            ArpBasicBotPool[1]:getHandle():sendSpawn() --Бот спавнится.
            if ArpBasicBotPool[1]:getHandle().connected then
                sampAddChatMessage("connected", -1) --Выводит.
            end
            wait(10000)
            ArpBasicBotPool[1]:disconnect() --Крашит с микрозадержкой.
            sampAddChatMessage("disconnect", -1)
        end)
    end)

Хотя вот пока тестил он пару раз нормально дисконнектился. Я подумал может это из-за того, что скрипт перед этим пару раз падал и перезагружался, поэтому попробовал перезайти, чтобы проверить на заново запущенном Сампе, ну и он опять начал крашить после дисконнекта (тот же самый код). Пытался как-то воспроизвести свои действия перед этим, перезегрузил несколько раз скрипт посреди работы, но добиться того же результата не вышло, он каждый раз крашится.
 
  • Нравится
Реакции: MrCreepTon

Yuriy Code

Известный
754
928
@MrCreepTon, в дальнейшем мог бы сделать гайд, как сам раксамп подключить в проект? Допустим, для тех же sf плагинов, чтобы так же ботов использовать можно было.
 

Neil_

Активный
197
28
Ебаный в нос, какую синхру слать, каким хуем я нихуя не понимаю, где про эту ебаную синхру читать, максимум что я смог, это заспавнить этого пидора и написать дубликат сообщение своего сообщения
 
  • Ха-ха
Реакции: MrCreepTon

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,127
4,714
Остального кода там немного, пока что только несвязанный с ботом SNet (в другом проекте без СНета дисконнект тоже крашил) и пара неиспользуемых методов ArpBasicBot. В остальном кроме дисконнекта бот работает нормально:

Lua:
sampRegisterChatCommand("testBasicBot", function()
        lua_thread.create(function()
            ArpBasicBotPool[1] = ArpBasicBot:new()
            ArpBasicBotPool[1]:changeNickname("bot3232")
            local ip, port = sampGetCurrentServerAddress()
            ArpBasicBotPool[1]:connect(ip, port)
            wait(5000)
            ArpBasicBotPool[1]:getHandle():sendRequestClass(1)
            ArpBasicBotPool[1]:getHandle():sendRequestSpawn()
            ArpBasicBotPool[1]:getHandle():sendSpawn() --Бот спавнится.
            if ArpBasicBotPool[1]:getHandle().connected then
                sampAddChatMessage("connected", -1) --Выводит.
            end
            wait(10000)
            ArpBasicBotPool[1]:disconnect() --Крашит с микрозадержкой.
            sampAddChatMessage("disconnect", -1)
        end)
    end)

Хотя вот пока тестил он пару раз нормально дисконнектился. Я подумал может это из-за того, что скрипт перед этим пару раз падал и перезагружался, поэтому попробовал перезайти, чтобы проверить на заново запущенном Сампе, ну и он опять начал крашить после дисконнекта (тот же самый код). Пытался как-то воспроизвести свои действия перед этим, перезегрузил несколько раз скрипт посреди работы, но добиться того же результата не вышло, он каждый раз крашится.
Только щас появилось время добраться до Мунбота. Проблему вроде как нашел, была связана с порядком правил. Простыми словами бот сначала дисконнектился на уровне РакНета, а потом только говорил, что он отключился.
Заодно подредачу немного синхру, возможно поможет в некоторых моментах
 

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,127
4,714
1.12
  1. Убрано мое "гениальное" решение с синхрой. Теперь не должно отключать от сервера спустя время
  2. Теперь bot:disconnect() не приводит к вылету игры
 

Вложения

  • MoonBot.dll
    974.5 KB · Просмотры: 35

Neil_

Активный
197
28
1.12
  1. Убрано мое "гениальное" решение с синхрой. Теперь не должно отключать от сервера спустя время
  2. Теперь bot:disconnect() не приводит к вылету игры
Блять я только настроил эту синхру, этот мудак вылетал через 15 секунд я думал у меня хуйня нахуй удалил весь код, а это оказывается в этом
 
  • Ха-ха
Реакции: RAYDON

Ya Zaregalsya

Известный
370
127
Сделай лог отдельно для своей либы, чтобы можно было хотя бы немного заглянуть под капот во время разработки. Скрипты себя ведут крайне непредсказуемо.

Остального кода там немного, пока что только несвязанный с ботом SNet (в другом проекте без СНета дисконнект тоже крашил) и пара неиспользуемых методов ArpBasicBot. В остальном кроме дисконнекта бот работает нормально:

Lua:
sampRegisterChatCommand("testBasicBot", function()
        lua_thread.create(function()
            ArpBasicBotPool[1] = ArpBasicBot:new()
            ArpBasicBotPool[1]:changeNickname("bot3232")
            local ip, port = sampGetCurrentServerAddress()
            ArpBasicBotPool[1]:connect(ip, port)
            wait(5000)
            ArpBasicBotPool[1]:getHandle():sendRequestClass(1)
            ArpBasicBotPool[1]:getHandle():sendRequestSpawn()
            ArpBasicBotPool[1]:getHandle():sendSpawn() --Бот спавнится.
            if ArpBasicBotPool[1]:getHandle().connected then
                sampAddChatMessage("connected", -1) --Выводит.
            end
            wait(10000)
            ArpBasicBotPool[1]:disconnect() --Крашит с микрозадержкой.
            sampAddChatMessage("disconnect", -1)
        end)
    end)

Хотя вот пока тестил он пару раз нормально дисконнектился. Я подумал может это из-за того, что скрипт перед этим пару раз падал и перезагружался, поэтому попробовал перезайти, чтобы проверить на заново запущенном Сампе, ну и он опять начал крашить после дисконнекта (тот же самый код). Пытался как-то воспроизвести свои действия перед этим, перезегрузил несколько раз скрипт посреди работы, но добиться того же результата не вышло, он каждый раз крашится.
Ещё один баг. Если в этом коде сделать паузу между changeNickname() и connect() хотя бы на 1 сек, то бот не подключится к серверу. Судя по всему баг связан с автоперезаходом после changeName(), видимо если их вызвать одновременно, то они выполняются параллельно и дают вот такой необычный результат, но я могу гадать потому что никаких логов у Мунтбота нет. Ты предусматривал такую возможность, чтобы создание бота, установка его имени и подключение к серверу выполнялось в три отдельных этапа, а не сразу друг за другом?
 
Последнее редактирование:

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,127
4,714
Сделай лог отдельно для своей либы, чтобы можно было хотя бы немного заглянуть под капот во время разработки. Скрипты себя ведут крайне непредсказуемо.


Ещё один баг. Если в этом коде сделать паузу между changeNickname() и connect() хотя бы на 1 сек, то бот не подключится к серверу. Судя по всему баг связан с автоперезаходом после changeName(), видимо если их вызвать одновременно, то они выполняются параллельно и дают вот такой необычный результат, но я могу гадать потому что никаких логов у Мунтбота нет. Ты предусматривал такую возможность, чтобы создание бота, установка его имени и подключение к серверу выполнялось в три отдельных этапа, а не сразу друг за другом?
changeNickname уже имеет переподключение. Попробуй после changeNickname вызвать disconnect и потом уже connect
 
  • Нравится
Реакции: Ya Zaregalsya

g3r4ld

Участник
57
7
Как подгрузить боту мои скрипты из папки мунлоадера? Типо чтобы бот зашел с этими скриптами