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

horacy

Известный
102
92
Посмотреть вложение 102222
Actual verison: 1.11


MoonBot - модуль для Moonloader, позволяющая создавать внутриигровых ботов, вроде тех, что вы могли лицезреть в RakSAMP, RakBot, Overlight. Вы ограничиваетесь исключительно сервером.
У каждого бота имеется свой индекс, по которому к нему можно обращаться. По разумным причинам, индексы не объединены у всех скриптов. То есть у каждого скрипта может быть бот с 1-ым индексом, но доступа к чужому боту с таким же индексом у них нет.
Давайте сразу договоримся, что переменная mb будет значить это:
Lua:
local mb = require('MoonBot') -- Имя аргумента может быть любое, в дальнейшем для удобства будет использоваться это.
Название методаОписание
local bot = mb.add({Имя})Регистрирует бота. В ответ вы получаете его хендл
mb.remove([Индекс бота])Удаляет бота по его индексу
mb.registerIncomingRPC([ID RPC])Регистрирует отлавливание указанного RPC. Для приема используйте функцию onBotRPC(botHandle, rpcId, bitStream)
mb.disconnectAfterUnload([bool status])Устанавливает то, будут ли боты авто-удалятся при oтpaбaтывaнии функции ниже
mb.unload()Выгружает все хуки/ботов (касаемо последних, если не указан аргумент выше на true)
mb.updateCallbacks()Обновляет хуки. Я пытался сделать без этого костыля, но особо не вышло :/
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 бота. Доступно только для чтения
local hp = bot.hp
bot.hp = 100
Получает/устанавливает здоровье бота
local armor = bot.armor
bot.armor = 100
Получает/устанавливает броню бота
local x, y, z = bot.position.x, bot.position.y, bot.position.z
bot.position.x, bot.position.y, bot.position.z = 0, 0, 0
Получает/устанавливает позицию бота
local password = bot.password
bot.password = '123123'
Получает/устанавливает пароль для сервера с паролем. Пустая строка - пароля нет.
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:setDelay([time in ms])Устанавливает задержку между синхрой в мс. От этого зависит пинг. Будьте аккуратны с этой опцией, ибо прокси пока нет! Изначальная формула расчета: 5 * (Индекс бота)
bot:giveDamage([damage])Наносит урон боту. Эта функция используется при выстреле в бота кем-то.
Название методаОписание
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:writeInt8([int])Записывает значение типа short (2 байта) в BitStream
bs:writeInt8([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 и записывает её в буфер
  • 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') -- Имя аргумента может быть любое, в дальнейшем для удобства будет использоваться это.
В конец кода или куда угодно добавляем следующее (или если у вас уже есть onScriptTerminate, то дописываете):
Lua:
function onScriptTerminate(script, quitGame)
    if script == thisScript() then
        mb.unload()
    end
end
Если мы хотим отлавливать пакеты, RPC, то:
  1. Добавляем в бесконечный цикл метод mb.updateCallbacks()
    Lua:
    function main()-- code
    while true do -- Важно! Если у вас уже есть беск. цикл, то не нужно делать второй! Просто добавьте туда вызов этого метода!
        wait(0) -- Задержка может быть любой, но лучше до 100 мс
        mb.updateCallbacks()
    end
  2. Объявляем функции: если хотите перелавливать входящие RPC, то:
    Lua:
    function onBotIncomingRPC(bot, rpcId, bs) -- bot - хендл бота, rpcId - id полученного rpc, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
    -- code
    end
    Важно! В случае с RPC в function main еще нужно указать id RPC, которые вы намерены ловить! Делается это так:
    Lua:
    function main()    -- code
        mb.registerIncomingRPC(12) -- Регистрируем RPC SetPlayerPos для примера.
        while true do
            wait(0)
            mb.updateCallbacks()
        end
    end
    Если хотите перелавливать входящие пакеты, то:
    Lua:
    function onBotIncomingPacket(bot, packetId, bs) -- bot - хендл бота, packetId -- ид пакета, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
    -- code
    end
    Важно! При работе с исходящей синхрой, не забывайте пользоваться сбрасыванием указателя.
    Если хотите перелавливать исходящие RPC, то:
    Lua:
    function onBotOutcomingRPC(bot, rpcId, bs) -- bot - хендл бота, rpcId - id полученного rpc, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
    -- code
    end
    Если хотите перелавливать исходящие пакеты, то:
    Lua:
    function onBotOutcomingPacket(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()
    mb.registerIncomingRPC(12)
    sampRegisterChatCommand('bot.add', addCommand)
    sampRegisterChatCommand('bot.remove', removeCommand)
    while true do
        wait(0)
        mb.updateCallbacks()
    end
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

function onScriptTerminate(script, quitGame)
    if script == thisScript() then
        mb.unload()
    end
end
Заходим в игру проверять команды! При добавлении и удалении (кроме строчки от сервера о подключении) должно получиться так:
Посмотреть вложение 102223
Всё остальное вы можете пощупать уже самостоятельно, либо посмотреть в примерах.
Lua:
local mb = require('MoonBot')

function main()
    repeat wait(0) until isSampAvailable()
    sampRegisterChatCommand('cv.add', function(param)
        local bot = mb.add(param)
        bot:connect()
    end)
    sampRegisterChatCommand('cv.remove', function(param)
        if param:match('%d+') then
            mb.remove(tonumber(param))
        end
    end)
    while true do
        wait(0)
        mb.updateCallbacks()
    end
end

function onBotOutcomingRPC(bot, rpcId, bs)
    if rpcId == 25 then
        bs:resetReadPointer()
        local ver = bs:readInt32()
        local mod = bs:readInt8()
        local nick = bs:readString8()
        local challengeResponse = bs:readInt32()
        local authkey = bs:readString8()
        local clientVer = bs:readString8()
        local unk = bs:readInt32()
        bs:resetWritePointer()
        bs:writeInt32(ver)
        bs:writeInt8(mod)
        bs:writeString8(nick)
        bs:writeInt32(challengeResponse)
        bs:writeString8(authkey)
        bs:writeString8('MOONBOT')
        bs:writeInt32(unk)
        printStringNow('client version changed!', 1000)
    end
end

function onScriptTerminate(script, quitGame)
    if script == thisScript() then
        mb.unload()
    end
end

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
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
  • Присутствует ли поддержка прокси?
    • На текущий момент нет. Основная причина: отсутствие у меня подходящих прокси с нормальным пингом.
  • На чем создана библиотека?
    • 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:
  • Пофикшены просады ФПС, если бот не мог подключиться к серверу
  • Возращена предыдущая система задержки.
  • Пофиксить зависание игры с включенным mb.disconnectAfterUnload(false)
  • Добавить прокси (???)
  • Перевести векторы (x, y, z (w)) на массивы
Благодарности:
  • @damag за небольшой патч для серверов с React
  • @Lolendor за тесты
Установка: dll из архива закинуть в moonloader/lib.
О багах можете сообщать в эту тему, только пишите подробно, пожалуйста, что и как вы делали, при каких обстоятельствах.
Надеюсь на вашу поддержку! Для меня это серьезный шаг ☺️
what to do if the bot is spamming the message and does not connect to the server?
I'm using your sample script, it used to connect before, after one connection it stopped connecting and finally it stopped connecting to the server even though I can connect when use client to it normally.
 
Последнее редактирование:

Ya Zaregalsya

Известный
370
131
Такая проблема. Беру онфут синхру через mb.getPlayerData(), дальше туда указываю позицию, кватернион, скорость, анимацию и флаги, и ещё здоровье с бронёй, чтобы не кикнуло, отправляю через sendPlayerData(). Это всё данные предварительно перехваченные с игры и записанные в файл, то есть они совершенно точно правильные. По идее бот должен бежать и прыгать, он движется по правильным координатам, с правильным вращением и скоростью, но делает это без анимации, то есть просто скользит. При этом если со стороны игрока включить лог входящих пакетов, то видно, что бот в своей синхре отправляет эти анимации и флаги, но они почему-то не применяются. Есть у кого-то код с отправкой онфут синхронизации, где анимация успешно применилась?

Криптон, как сделать так чтобы было как на этом видео?
Перехватываешь свою реальную инкар синхру и отправляешь её через бот с задержкой.
 
Последнее редактирование:

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,117
4,671
Такая проблема. Беру онфут синхру через mb.getPlayerData(), дальше туда указываю позицию, кватернион, скорость, анимацию и флаги, и ещё здоровье с бронёй, чтобы не кикнуло, отправляю через sendPlayerData(). Это всё данные предварительно перехваченные с игры и записанные в файл, то есть они совершенно точно правильные. По идее бот должен бежать и прыгать, он движется по правильным координатам, с правильным вращением и скоростью, но делает это без анимации, то есть просто скользит. При этом если со стороны игрока включить лог входящих пакетов, то видно, что бот в своей синхре отправляет эти анимации и флаги, но они почему-то не применяются. Есть у кого-то код с отправкой онфут синхронизации, где анимация успешно применилась?


Перехватываешь свою реальную инкар синхру и отправляешь её через бот с задержкой.
Попробуй отправлять еще keysData, updownKeys и leftRightKeys
 
  • Нравится
Реакции: Ya Zaregalsya

bottom_text

Известный
675
317
Почему бот не отправляет RPC_Spawn, хотя onBotOutcomingRPC(bot, rpcId, bs) сообщает об отправке?
Я делал авторегистрацию и авторизацию для одного сервера и бот втыкал на моменте, где надо отправить RPC_Spawn для дальнейшего взаимодействия с сервером, решил чекнуть через свой сервер что происходит. Если спавнится клиент, то сервер видит спавн, если бот - нет
 

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,117
4,671
Почему бот не отправляет RPC_Spawn, хотя onBotOutcomingRPC(bot, rpcId, bs) сообщает об отправке?
Я делал авторегистрацию и авторизацию для одного сервера и бот втыкал на моменте, где надо отправить RPC_Spawn для дальнейшего взаимодействия с сервером, решил чекнуть через свой сервер что происходит. Если спавнится клиент, то сервер видит спавн, если бот - нет
а код можно?
 

bottom_text

Известный
675
317
Lua:
local mb = require('MoonBot')

function onBotOutcomingRPC(bot, rpcId, bs) -- bot - хендл бота, rpcId - id полученного rpc, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
    sampAddChatMessage(string.format('Bot %s send RPC: %d', bot.name, rpcId), -1)
end
function main()
    mb.registerIncomingRPC(12)
    sampRegisterChatCommand('bot-add', addCommand)
    sampRegisterChatCommand('bot-remove', removeCommand)
    sampRegisterChatCommand('bot-spawn', function(arg)
        local bot = mb.getBotHandleByIndex(tonumber(arg))
        bot:sendSpawn()
        sampAddChatMessage('BOT SPAWNED', -1)
    end) 
    while true do
        wait(0)
        mb.updateCallbacks()
    end
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
function onScriptTerminate(script, quitGame)
    if script == thisScript() then
        mb.unload()
    end
end
весь код который использовался в том видео
 

MrCreepTon

Неизвестный
Автор темы
Всефорумный модератор
2,117
4,671
Lua:
local mb = require('MoonBot')

function onBotOutcomingRPC(bot, rpcId, bs) -- bot - хендл бота, rpcId - id полученного rpc, bs - битстрим (с ним нельзя работать как в мунлоадеровским! Как им пользоваться описывалось ранее)
    sampAddChatMessage(string.format('Bot %s send RPC: %d', bot.name, rpcId), -1)
end
function main()
    mb.registerIncomingRPC(12)
    sampRegisterChatCommand('bot-add', addCommand)
    sampRegisterChatCommand('bot-remove', removeCommand)
    sampRegisterChatCommand('bot-spawn', function(arg)
        local bot = mb.getBotHandleByIndex(tonumber(arg))
        bot:sendSpawn()
        sampAddChatMessage('BOT SPAWNED', -1)
    end)
    while true do
        wait(0)
        mb.updateCallbacks()
    end
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
function onScriptTerminate(script, quitGame)
    if script == thisScript() then
        mb.unload()
    end
end
весь код который использовался в том видео
Попробуй слать еще requestClass и requestSpawn