MOTR logo
    • Категории
    • Последние
    • Популярные
    • Метки
    • Пользователи
    • Группы
    • Зарегистрироваться
    • Войти

    Гайд по написанию скриптов для гомункулусов

    Запланировано Прикреплена Закрыта Перенесена Жизнь в пробирке
    528 Сообщения 60 Posters 51.3k Просмотры
    Загружаем больше сообщений
    • Сначала старые
    • Сначала новые
    • По количеству голосов
    Ответить
    • Ответить, создав новую тему
    Авторизуйтесь, чтобы ответить
    Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
    • Aiko StarA Не в сети
      Aiko Star
      отредактировано

      Гайда по АИ гомункулусов.

      Основано на гайде DracoRPG.

      Апдейт 0
      В связи с умиранием рагнаинфы, откуда я переводила в свое время это все, да и из-за растолстения сего гайда моими добавлениями, как-то оно уже не просто перевод... Не знаю я, что делать с авторскими правами, в общем х_х Буду потихоньку переделывать гайдик под текущую механику, ибо кое-что существенно изменилось. Ну и со своей нынешней высоты кой-чего добавлять. Например, вступление нормальное 🙂

      Перевод не дословный, кое-что я могу добавить свое, но большая часть - это оно.

      Вступление.

      Как многие знают, долгожданные (мною... два с половиной года - это серьезный срок, да) покемончики являются, на мой взгляд, одним из уникальнейших новведений в РО вообще и в ММОРПГ в частности. Идея петов/саммонов, которые бегают за хозяином, прокачиваются и в какой-то степени управляются, существует во многих играх. Однако ж, Гравити поступила оригинально. То ли из-за лени, то ли с глубоким умыслом (поди пойми этих корейцев), но она разрешила нам самим настраивать поведение хомячка как угодно. Ибо скрипт, который этим самым зверьком рулит, лежит в папочке \AI клиента и свободно редактируется. Что, собственно, меня и привлекает больше всего... Однако ж, перейду от общих слов к конкретике.
      Итак. Во-первых, для того, чтобы написать более-менее стоящий скрипт, необходимо знать программирование как таковое. Во-вторых, рекомендуется знать синтаксис и структуру Lua (скриптовый язык, на котором собственно пишутся скрипты для покемонов), однако в случае выполнения первого пункта разобраться с этим проблем не составит - язык очень прост. Полное описание можно взть на родном сайте http://lua.org (на сайте <!-- w --><a class="postlink" href="http://www.lua.ru">www.lua.ru</a><!-- w --> лежит переведенный мануал, рекомендую).

      Урок первый: AI() функция (AI.lua)

      [code:3bu2cgzi]function AI(myid)

      MyID = myid
      local msg = GetMsg (myid)
      local rmsg = GetResMsg (myid)

      if msg[1] == NONE_CMD then
      if rmsg[1] ~= NONE_CMD then
      if List.size(ResCmdList) < 10 then
      List.pushright (ResCmdList,rmsg)
      end
      end
      else
      List.clear (ResCmdList)
      ProcessCommand (msg)
      end

      if (MyState == IDLE_ST) then 
        OnIDLE_ST () 
      

      elseif (MyState == CHASE_ST) then
      OnCHASE_ST ()
      elseif (MyState == ATTACK_ST) then
      OnATTACK_ST ()
      elseif (MyState == FOLLOW_ST) then
      OnFOLLOW_ST ()
      elseif (MyState == MOVE_CMD_ST) then
      OnMOVE_CMD_ST ()
      elseif (MyState == STOP_CMD_ST) then
      OnSTOP_CMD_ST ()
      elseif (MyState == ATTACK_OBJECT_CMD_ST) then
      OnATTACK_OBJECT_CMD_ST ()
      elseif (MyState == ATTACK_AREA_CMD_ST) then
      OnATTACK_AREA_CMD_ST ()
      elseif (MyState == PATROL_CMD_ST) then
      OnPATROL_CMD_ST ()
      elseif (MyState == HOLD_CMD_ST) then
      OnHOLD_CMD_ST ()
      elseif (MyState == SKILL_OBJECT_CMD_ST) then
      OnSKILL_OBJECT_CMD_ST ()
      elseif (MyState == SKILL_AREA_CMD_ST) then
      OnSKILL_AREA_CMD_ST ()
      elseif (MyState == FOLLOW_CMD_ST) then
      OnFOLLOW_CMD_ST ()
      end

      end[/code:3bu2cgzi]

      Чтобы можно было как-то начать, необходимо понимать, что делает эта функция. Как видно из названия, это основная функция AI, единственная необходимая для работы скрипта. Без нее клиент РО будет грязно ругаться на корейском и вообще вести себя неприлично.
      Она вызывается клиентом каждый раз, когда покемон "думает", что ему делать, - точно так же мобы каждые N милисекунд определяют свое поведение (атаковать, сменить цель, применить скил и т.п.).
      Основное назначение этой функции - обрабатывать отданные покемончику команды (в основном через Alt+клик) и на их основе задавать определенное поведение. Всю функцию можно разделить на 3 части.

      Самая первая:

      [code:3bu2cgzi]function AI(myid)

      MyID = myid
      local msg = GetMsg (myid)
      local rmsg = GetResMsg (myid) [/code:3bu2cgzi]

      Как мы видим, клиент вызывает эту функцию, используя в качестве аргумента некий ID - в данном случае это ID покемона.
      В РО у каждого объекта (игрок, моб, ловушка, непись и так далее) существует свой ID (для справки: ID игроков есть ID их аккаунта. Это означает, что для всех чаров на одном аккаунте используется один ID. Как это можно использовать, мы узнаем в следующей серии. То есть в других уроках).
      Этот ID, который передается в функцию в виде переменной myid, сразу же заносится в другую переменную MyID (зачем - сложно сказать... может быть, myid может быть перезаписан в любой момент, или у Гравити возникли какие-то проблемы в ходе отладки, которые они не смогли убрать... кто знает). Затем клиент запрашивает сообщение, которое игрок передает покемону (хранится в переменной msg), и зарезервированные сообщения (список ранее отданных, но пока не исполненных команд. Подробнее будет ясно в следующих уроках).

      Апдейт 1.
      Изучив на досуге (ну не работать же мне на работе, в самом-то деле О.о) мануал по lua, хочу отметить следующую особенность сего языка.
      Он регистрозависим. Сие видно на упомянутом только что примере - переменные myid и MyID являются двумя разными (кхм. по отношению к ним системы, не по содержанию х_х) вещами. Сие стоит запомнить, поскольку в том же мануале по lua есть хороший пример:
      оператор and является зарезервированным словом и не может быть использован в качестве наименования переменной. Однако, мы вполне можем создать переменную And или AND, которые будут адекватно восприниматься компилятором. Посему - не путайтесь, когда будете свои переменные обзывать.

      Далее следующая часть кода:
      [code:3bu2cgzi]
      if msg[1] == NONE_CMD then
      if rmsg[1] ~= NONE_CMD then
      if List.size(ResCmdList) < 10 then
      List.pushright (ResCmdList,rmsg)
      end
      end
      else
      List.clear (ResCmdList)
      ProcessCommand (msg)
      end [/code:3bu2cgzi]

      Она начинается с проверки, была ли игроком отдана команда покемону.

      Апдейт 2
      Ранее у меня неправильно была расписана структура этого блока. Понадобилось двое суток, чтобы осознать сакральный смысл сего кода. Теперь пишу правильно: если команды от игрока не поступало, идет проверка, хранится ли у нас зарезирвированная команда. Если хранится (условие rmsg[1] не равно пустой команде), то мы эту самую команду пихаем в конец списка зарезервированных команд ResCmdList. Если в этом списке уже больше 10 команд, то ничего не пихаем. И, вероятно, теряем текущую команду (поскольку переменной rmsg[1] может быть присвоено новое значение).

      Но нас больше интересует другое. В частности, когда игрок ОТДАВАЛ команду покемону: ResCmdList очищается и - самое главное - вызывается функция ProcessCommand(). Собственно с этого момента и начинается создание своего скрипта (ранее были общие вещи, которые не имели никакого отношения к собственно поведению покемона). Эту функцию мы разберем позже подробнее, а сейчас нужно понять суть: на основе того, какая команда была отдана игроком, вызывается определенная функция, которая и определяет, что будет делать покемончик.

      Наконец, последняя, третья часть:
      [code:3bu2cgzi]
      if (MyState == IDLE_ST) then
      OnIDLE_ST ()
      elseif (MyState == CHASE_ST) then
      OnCHASE_ST ()
      elseif (MyState == ATTACK_ST) then
      OnATTACK_ST ()
      elseif (MyState == FOLLOW_ST) then
      OnFOLLOW_ST ()
      elseif (MyState == MOVE_CMD_ST) then
      OnMOVE_CMD_ST ()
      elseif (MyState == STOP_CMD_ST) then
      OnSTOP_CMD_ST ()
      elseif (MyState == ATTACK_OBJECT_CMD_ST) then
      OnATTACK_OBJECT_CMD_ST ()
      elseif (MyState == ATTACK_AREA_CMD_ST) then
      OnATTACK_AREA_CMD_ST ()
      elseif (MyState == PATROL_CMD_ST) then
      OnPATROL_CMD_ST ()
      elseif (MyState == HOLD_CMD_ST) then
      OnHOLD_CMD_ST ()
      elseif (MyState == SKILL_OBJECT_CMD_ST) then
      OnSKILL_OBJECT_CMD_ST ()
      elseif (MyState == SKILL_AREA_CMD_ST) then
      OnSKILL_AREA_CMD_ST ()
      elseif (MyState == FOLLOW_CMD_ST) then
      OnFOLLOW_CMD_ST ()
      end

      end [/code:3bu2cgzi]

      Тут все довольно просто: на основе того, какое состояние сейчас у покемончика (оно заносится в переменную MyState в других частях кода), вызывается функция, которая определяет, что собственно ему (покемону) следует делать, будучи в таком состоянии.

      Следующий урок: Прежде чем копать глубже, рассмотрим функции и константы (что внутри файлов Const.lua и Utils.lua).

      1 ответ Последний ответ Ответить Цитировать 0
      • StorrS Не в сети
        Storr
        отредактировано

        Когда будут введены покемошки, сделаем прикрепленную тему с сылками на гайды и обсуждения по ним. 😎

        1 ответ Последний ответ Ответить Цитировать 0
        • A Не в сети
          AbDuL
          отредактировано

          еще не читал, но большооое спасибо :lol:
          очень рад такой плюшке, да 😄 наверное и остальные, которые не асы в бусурманском.

          1 ответ Последний ответ Ответить Цитировать 0
          • BUTUZB Не в сети
            BUTUZ
            отредактировано

            Aiko Star, спасиб что начала переводить, а то я когда тот же гайд для себя со своим корявым инглишом переводил не чяго не понял))) Кстати есть что то похожее на бейсик...

            1 ответ Последний ответ Ответить Цитировать 0
            • Aiko StarA Не в сети
              Aiko Star
              отредактировано

              Второй урок: Прежде чем копать глубже, рассмотрим функции и константы (что внутри файлов Const.lua и Utils.lua)

              Итак, мы немного разобрались в принципах работы скрипта. Теперь займемся тем, что Гравити предоставила нам для создания AI. В большинстве случаев это функции (часть написана на Lua, часть выполняется клиентом - то есть их код недоступен в открытых файлах, и вообще они написаны на С).
              Некоторые функции используют числа для определения свойств какого-либо объекта (например, состояние покемона или же тип объекта). Поэтому вместо того, чтобы использовать наборы чисел, созданы константы (пишутся ЗАГЛАВНЫМИ БУКВАМИ - согласно какому-то там соглашению по именованию переменных и констант, вероятно. Меня никогда этому не учили, но пусть будет х_х).
              Все это (функции и константы) могут быть найдены соответственно в файлах Utils.lua и Const.lua.

              Итак, константы. Открываем Const.lua и видим следующее...

              [code:1b074yds]-------------------------------------------------
              -- constants


              V_OWNER = 0
              V_POSITION = 1
              V_TYPE = 2
              V_MOTION = 3
              V_ATTACKRANGE = 4
              V_TARGET = 5
              V_SKILLATTACKRANGE = 6
              V_HOMUNTYPE = 7
              V_HP = 8
              V_SP = 9
              V_MAXHP = 10
              V_MAXSP = 11


              LIF = 1
              AMISTR = 2
              FILIR = 3
              VANILMIRTH = 4
              LIF2 = 5
              AMISTR2 = 6
              FILIR2 = 7
              VANILMIRTH2 = 8
              LIF_H = 9
              AMISTR_H = 10
              FILIR_H = 11
              VANILMIRTH_H = 12
              LIF_H2 = 13
              AMISTR_H2 = 14
              FILIR_H2 = 15
              VANILMIRTH_H2 = 16


              MOTION_STAND = 0
              MOTION_MOVE = 1
              MOTION_ATTACK = 2
              MOTION_DEAD = 3
              MOTION_ATTACK2 = 9


              -- command

              NONE_CMD = 0
              MOVE_CMD = 1
              STOP_CMD = 2
              ATTACK_OBJECT_CMD = 3
              ATTACK_AREA_CM = 4
              PATROL_CMD = 5
              HOLD_CMD = 6
              SKILL_OBJECT_CMD = 7
              SKILL_AREA_CMD = 8
              FOLLOW_CMD = 9
              -------------------------- [/code:1b074yds]

              Как видно, кода тут не так уж и много. Первая группа консант (начинаются с V_) используются для определения свойств объектов. Из названий понятно, какая константа что обозначает (если сложно сообразить, вот пример: V_HP символизирует текущие HP объекта). Подробнее мы их разберем, рассматривая функцию getV().
              Затем идет список типов покемончиков (эволюционные формы имеют приписку H, альтернативные спрайты здесь тоже есть). Эта группа в основном используется для определения типа нашего (вашего? О.о) покемона (хотя можно определить тип и чужого покемона - почему бы и нет? - только вот зачем это надо...).
              Затем идет список констант состояния объекта (MOTION
              ) - они определяют, что сейчас делает объект (стоит, атакует, идет, кастует и т.д.).
              ВНИМАНИЕ!В скрипт по умолчанию не входит константа MOTION_SIT, она соответствует номеру 6. Кому надо - дописываем ручками, ага.
              Наконец, _CMD-константы определяют команды, которые мы (вы?) отдаем нашей зверюшке через клиент. Да-да, те самые, которые храним в msg (вспоминаем первый урок).

              Теперь посмотрим, что нам подарила Гравити, а именно функции. Начнем с С-функций, выполняемых клиентом. Повторюсь: их код не содержится в файлах .lua, но их список (закомментированный, естественно) можно найти в начале Const.lua.

              [code:1b074yds]function TraceAI (string) end [/code:1b074yds]

              Эта функция выводит указанный в качестве параметра текст в файл traceAI.txt, который находится в корневой папке игры (НЕ в папке AI). Используется в основном для отладки.
              Замечу, что эта функция не очень удобна - помимо нужного текста она пишет какую-то фигню на корейском, которую не любит Блокнот. Поэтому не удивляйтесь неизвестным символам.

              [code:1b074yds]function MoveToOwner (id) end [/code:1b074yds]

              Эта функция использует ID покемончика в качестве аргумента и отдает ему приказ немедленно двигаться в ту же точку, где стоит его хозяин (так слишком долго, поэтому вдальнейшем я буду просто говорить "туда, где стоит Мастер" и т.д. ).

              [code:1b074yds]function Move (id,x,y) end [/code:1b074yds]

              Аналогична предыдущей, но точка назначения определяется параметрами x и y. Одна из наиболее часто используемых функций.

              [code:1b074yds]function Attack (id,id) end [/code:1b074yds]

              Принцип все тот же. Эта функция отдает приказ покемону (первый id в списке параметров) атаковать объект (второй id). Очевидно, что если цель является неправильным противником (например, игрок вне арены/дуэли/гв), то скорее всего ничего не произойдет.

              [code:1b074yds]function GetV (V_,id) end [/code:1b074yds]

              Эта одна из двух функций, которые являются всеми органами чувств нашего покемончика. Проще говоря, эта функция позволяет получить тонны информации, если ее использовать правильно. Первый параметр определяет, что собственно мы хотим получить (вспоминаем список V_-констант), второй - цель, с которой будет получена информация. Пара примеров для наглядности:
              GetV(V_OWNER, MyID) возвращает ID Мастера, если в MyID хранится ID покемона.
              MyX, MyY = GetV(V_POSITION,MyID) занесет в MyX и MyY текущие координаты покемона (требование к MyID аналогично предыдущему примеру - там должен лежать ID покемона).
              GetV(V_TYPE, ID) - сейчас не вспомню, чего эта штука делает... Возвращает тип объекта. Что за тип?.. надо смотреть будет.
              GetV(V_MOTION, MyID) вернет текущее состояние покемона (список состояний был выше). Все довольно очевидно, исключая различие между MOTION_ATTACK и MOTION_ATTACK2. Предполагаю, что это просто различные анимации обычной физической атаки (у ванильки, например, их две).
              GetV(V_ATTACKRANGE, MyID) вернет "дальнобойность" покемона.
              GetV(V_SKILLATTACKRANGE, MyID, MySkill) вернет дальность действия указанного скилла. Список ID скиллов я приведу чуть попозже.
              GetV(V_HOMUNTYPE, MyID) возвращает тип гомункулуса (список, опять же, был выше). Используется для того, чтобы не заставлять ванилек спамить мунлайтами, к примеру. Ну или базу данных вести "у какого хима какой покемон", тоже дело 🙂
              GetV(V_HP, MyID) вернет текущие HP покемона. Удивительное дело - если использовать V_SP, то мы получим... текущие SP покемона. Неожиданно, правда?
              GetV(V_MAXHP, MyID) угадайте, чего возвращает... угу, правильно, максимальные HP. А V_MAXSP... ну понятно, думаю.

              [code:1b074yds]function GetActors () end [/code:1b074yds]

              Это вторая функция, которая позволяет получать информацию о внешнем мире. Возвращает список, состоящий из ID каждого моба, игрока или гомункулуса на экране. Без нее не обойтись в процедурах определения целей.

              [code:1b074yds]function GetTick () end [/code:1b074yds]

              Нигде не используется в скрипте по умолчанию, однако очень и очень функциональна. Возвращает текущий тик (количество милисекунд, прошедших с определенного момента. По умолчанию используется для синхронизации между клиентом и сервером). С помощью нее можно считать, сколько прошло времени с какого-то момента (например, после применения скилла - отсчитываем задержку).
              Для этого после того, как был применен скилл, кладем в переменную значение GetTick(), затем в нужном месте снова вызываем GetTick() и из него вычитаем старое значение. Получаем количество милисекунд, прошедших с того момента. Чего делать дальше - решать вам :Р

              [code:1b074yds]function GetMsg (id) end [/code:1b074yds]

              Уже должны знать сами, что делает эта функция. Используя в качестве параметра ID покемона, возвращает список с командой в виде ID (смотрим список _CMD-констант) на первом месте и прочей информацией далее (ID цели, координаты и т.д.)

              [code:1b074yds]function GetResMsg (id) end [/code:1b074yds]

              Возвращает зарезервированную команду клиента (как я понимаю, из списка ResCmdList, надо проверять). Позволяет создавать очередь команд на выполнение. Требует ID покемона в качестве параметра.

              [code:1b074yds]function SkillObject (id,level,skill,target) end [/code:1b074yds]

              Отдает покемону (определяется через ID) приказ использовать скилл (skill - сюда кладется ID скилла) заданного уровня (level) на указанную цель (target - определяется через ID).
              Обещанное ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ.
              Как стало известно в ходе экспериментов...

              Апдейт не помню какой уже.
              ... Гравити пофиксили ботоводство с помощью покемончиков. Нельзя теперь через скрипты запускать скиллы хима. Вот и ладушки, хотя рассадку огородика жаль (был такой плагин для биосов, через покемона позволял чуть ли не леса на локациях устраивать).

              Теперь список ID скиллов (взято из исходников сервера за версией 2219 - надо бы обновить, может и поменялись):

              [code:1b074yds]скиллы гомункулусов:
              Лиф:
              8001 HLIF_HEAL
              8002 HLIF_AVOID
              8003 HLIF_BRAIN
              8004 HLIF_CHANGE

              Амистр:
              8005 HAMI_CASTLE
              8006 HAMI_DEFENCE
              8007 HAMI_SKIN
              8008 HAMI_BLOODLUST

              Филир:
              8009 HFLI_MOON
              8010 HFLI_FLEET
              8011 HFLI_SPEED
              8012 HFLI_SBR44

              Ванильки:
              8013 HVAN_CAPRICE
              8014 HVAN_CHAOTIC
              8015 HVAN_INSTRUCT
              8016 HVAN_EXPLOSION[/code:1b074yds]

              Замечу, что четвертые скиллы у гомункулусов доступны только эволюционным формам.

              [code:1b074yds]function SkillGround (id,level,skill,x,y) end [/code:1b074yds]

              Аналогично предыдущей функции, но применяется на местность. Поэтому вместо ID цели указываются координаты точки применения. Через эту штуку огородик и сажался, да... Для скиллов хомяков применения не вижу, у них все скиллы на цель кастуются.

              [code:1b074yds]function IsMonster (id) end [/code:1b074yds]

              Эта функция возвращает 1, если цель является монстром, и 0, если не является.

              Апдейт еще один.
              Lua - весьма гибкий язык. Переменные тут не имеют фиксированного типа данных, поэтому в одну и ту же переменную можно класть и текст, и числа, и логические значения и еще кучу всего. Мало того, в зависимости от операторов, в которых эта переменная используется, хранящиеся в ней данные перегоняются в нужный формат. Пример:
              ** x = "123" -- занесли в переменную текстовую стрчоку**
              ** y = 321 -- занесли в переменную числовое значение**
              ** z = x + y -- складываем обе наших переменных и результат пишем в z**
              Если после этого кода посмотреть содержимое z, то оно будет равно 444. Текстовое значение автоматически конвертировалось в числовое. Аналогично будет происходить и обратное - если использовать в операторах работы со строками числовые переменные.
              К чему я это все веду. Я честно не помню, какое именно значение возвращает значение функция IsMonster - числовое или логическое. Но на самом деле это и не важно. В Lua в логических операторах значение "false" (ложь) получается, если использовать переменную со значением 0 или nil (пустое значение, по сути - в переменной ничего не хранится). Все остальные возможные значения переменной (числа, текст, списки и прочее) являются истиной ("true"), поэтому-то разницы и нет, что возвращает данная функция.
              Для понимания - пример.


              [code:1b074yds]--бла-бла-бла, тут какой-то код
              ** local FearfulEnemy = GetOwnerEnemy (MyID)**
              ** if IsMonster (FearfulEnemy) then**
              ** Attack (MyID, FearfulEnemy)**
              ** return**
              ** end[/code:1b074yds]**


              Простенький код. С помощью функции GetOwnerEnemy (принцип ее работы мы рассмотрим позже) получаем id некого врага, который покусился на нашего хима, и кладем его (id, не хима) в переменную "штрашный враг". Затем проводим проверку, является ли этот ужасный противник монстров. Если да, то хомячок верно бросается на защиту своего хозяина, если же нет (сей враг - другой игрок, к примеру), то... то все, ничего не делаем.
              Закончу на этом свое флудерское отступление и продолжу гайд.

              Функции, написанные на LUA, их код находится в файле Utils.lua.

              [code:1b074yds]require "./AI/Const.lua" [/code:1b074yds]

              Подключаем файл Const.lua. Хороший пример, как подключать дополнительные файлы к скрипту.

              [code:1b074yds]

              -- List utility

              List = {}
              [/code:1b074yds]
              Довольно большой кусок, названный List Utility, позволяет работать с двусторонними списками. В общем-то, совсем необязательно понимать, как тут что работает, т.к. нам нужны будут от этих функций только результаты. Но раз Драко разобрал, то и мы займемся...

              [code:1b074yds]
              function List.new ()
              return { first = 0, last = -1}
              end [/code:1b074yds]

              Эта функция создает и инициализирует список. Присваиваем значение этой функции какой-нибудь переменной, чтобы создать список. Например, так:
              MyStack = List.new ()

              [code:1b074yds]
              function List.pushleft (list, value)
              local first = list.first-1
              list.first = first
              list[first] = value;
              end

              function List.pushright (list, value)
              local last = list.last + 1
              list.last = last
              list[last] = value
              end [/code:1b074yds]

              Эти функции заносят значения в список. Первая добавляет элемент слева от уже существующих, вторая - справа.

              [code:1b074yds]function List.popleft (list)
              local first = list.first
              if first > list.last then
              return nil
              end
              local value = list[first]
              list[first] = nil -- to allow garbage collection
              list.first = first+1
              return value
              end

              function List.popright (list)
              local last = list.last
              if list.first > last then
              return nil
              end
              local value = list[last]
              list[last] = nil
              list.last = last-1
              return value
              end
              [/code:1b074yds]
              Эти функции противоположны предыдущим: они убират самый левый (и соответственно самый правый) элемент из списка и возвращают его. Запомните - они не только возвращают значение, но и УДАЛЯЮТ. Пример:
              MyValue = List.popleft(MyList)

              [code:1b074yds]
              function List.clear (list)
              for i,v in ipairs(list) do
              list** = nil **
              ** end **
              --[[ **
              ** if List.size(list) == 0 then **
              ** return **
              ** end **
              ** local first = list.first **
              ** local last = list.last **
              ** for i=first, last do **
              ** list
              = nil **
              ** end **
              **--]] **
              ** list.first = 0 **
              ** list.last = -1 **
              end [/code:1b074yds]


              Эта функция очищает список и переинициализирует (ну и слово х_х) его заново. После этой функции список полностью аналогичен тому, что получится после использования List.new() - пустой то бишь.
              Кстати, очень порадовал закомментированный кусок кода. Молодцы Гравити, так держать...


              [code:1b074yds]
              **function List.size (list) **
              ** local size = list.last - list.first + 1 **
              ** return size **
              **end **


              ------------------------------------------------- [/code:1b074yds]


              Возвращает количество элементов в списке. Без комментариев.


              Затем идут "маленькие, но гордые" функции - они используются очень часто.
              [code:1b074yds]
              **function GetDistance (x1,y1,x2,y2) **
              ** return math.floor(math.sqrt((x1-x2)^2+(y1-y2)^2)) **
              **end **


              **function GetDistance2 (id1, id2) **
              ** local x1, y1 = GetV (V_POSITION,id1) **
              ** local x2, y2 = GetV (V_POSITION,id2) **
              ** if (x1 == -1 or x2 == -1) then **
              ** return -1 **
              ** end **
              ** return GetDistance (x1,y1,x2,y2) **
              end [/code:1b074yds]


              Getdistance() возвращает расстояние между двумя точками, заданными координатами, в то время как GetDistance2 возвращает расстояние между двумя объектами.
              [code:1b074yds]
              **function GetOwnerPosition (id) **
              ** return GetV (V_POSITION,GetV(V_OWNER,id)) **
              **end **


              **function GetDistanceFromOwner (id) **
              ** local x1, y1 = GetOwnerPosition (id) **
              ** local x2, y2 = GetV (V_POSITION,id) **
              ** if (x1 == -1 or x2 == -1) then **
              ** return -1 **
              ** end **
              ** return GetDistance (x1,y1,x2,y2) **
              end [/code:1b074yds]


              GetOwnerPosition() возвращает координаты Мастера.В основном используется в функции GetDistanceFromOwner(), которая возвращает расстояние до Мастера. Непонятно, правда, почему не основана на функции GetDistance2... лишний раз одно и то же пишут, в общем. Тем не менее, полезно, чтобы покемончик не терялся и не шлялся где-то без нас 😉
              [code:1b074yds]
              **function IsOutOfSight (id1,id2) **
              ** local x1,y1 = GetV (V_POSITION,id1) **
              ** local x2,y2 = GetV (V_POSITION,id2) **
              ** if (x1 == -1 or x2 == -1) then **
              ** return true **
              ** end **
              ** local d = GetDistance (x1,y1,x2,y2) **
              ** if d > 20 then **
              ** return true **
              ** else **
              ** return false **
              ** end **
              end [/code:1b074yds]


              Возвращает истину, если объекты id1 и id2 НЕ видят друг друга (как видно, область видимости - 20 клеток. Хотя в окне клиента видно примерно 15, что оставляет небольшой запас), и возвращает ложь, если объекты ВИДЯТ друг друга. Однако это не значит, что если моб нас видит, то будет атаковать: их область агрессивности гораздо меньше 20 клеток). И опять же, почему не GetDistance2? Если уж сами функцию написали, могли бы и использовать...
              [code:1b074yds]
              **function IsInAttackSight (id1,id2) **
              ** local x1,y1 = GetV (V_POSITION,id1) **
              ** local x2,y2 = GetV (V_POSITION,id2) **
              ** if (x1 == -1 or x2 == -1) then **
              ** return false **
              ** end **
              ** local d = GetDistance (x1,y1,x2,y2) **
              ** local a = 0 **
              ** if (MySkill == 0) then **
              ** a = GetV (V_ATTACKRANGE,id1) **
              ** else **
              ** a = GetV (V_SKILLATTACKRANGE,id1,MySkill) **
              ** end **


              ** if a >= d then **
              ** return true; **
              ** else **
              ** return false; **
              ** end **
              end [/code:1b074yds]


              Наконец, последняя функция. Вполне соответствует своему названию. Однако есть одно "но": в качестве id1 всегда нужно использовать ID покемона, так как проверяется область атаки по дальности действия скиллов покемона. Но никто не мешает исправить и, к примеру, имея id скиллов мобов, высчитывать дальность их действия (если клиент на запрос GetV(V_SKILLATTACKRANGE) с параметрами моба отреагирует). Бубнить в очередной раз про GetDistance2 не буду.


              Урок следующий: а теперь, собственно, настоящий AI!

              1 ответ Последний ответ Ответить Цитировать 0
              • Aiko StarA Не в сети
                Aiko Star
                отредактировано

                Урок третий: а теперь, собственно, настоящий AI!

                Отлично. Теперь мы знаем все необходимое, чтобы понять, как работает дефолтный скрипт. Так что давайте его разберем, однако уже не так подробно, как это было раньше (все-таки кое-что вы теперь уже понимаете).

                Из первого урока мы помним, что функция ProcessCommand() вызывает всякие функции в зависимости от того, какие команды мы отдали нашему покемону. Вот ее код (еще раз):

                [code:2vgpuz0l]function ProcessCommand (msg)

                if (msg[1] == MOVE_CMD) then
                OnMOVE_CMD (msg[2],msg[3])
                TraceAI ("MOVE_CMD")
                elseif (msg[1] == STOP_CMD) then
                OnSTOP_CMD ()
                TraceAI ("STOP_CMD")
                elseif (msg[1] == ATTACK_OBJECT_CMD) then
                OnATTACK_OBJECT_CMD (msg[2])
                TraceAI ("ATTACK_OBJECT_CMD")
                elseif (msg[1] == ATTACK_AREA_CMD) then
                OnATTACK_AREA_CMD (msg[2],msg[3])
                TraceAI ("ATTACK_AREA_CMD")
                elseif (msg[1] == PATROL_CMD) then
                OnPATROL_CMD (msg[2],msg[3])
                TraceAI ("PATROL_CMD")
                elseif (msg[1] == HOLD_CMD) then
                OnHOLD_CMD ()
                TraceAI ("HOLD_CMD")
                elseif (msg[1] == SKILL_OBJECT_CMD) then
                OnSKILL_OBJECT_CMD (msg[2],msg[3],msg[4],msg[5])
                TraceAI ("SKILL_OBJECT_CMD")
                elseif (msg[1] == SKILL_AREA_CMD) then
                OnSKILL_AREA_CMD (msg[2],msg[3],msg[4],msg[5])
                TraceAI ("SKILL_AREA_CMD")
                elseif (msg[1] == FOLLOW_CMD) then
                OnFOLLOW_CMD ()
                TraceAI ("FOLLOW_CMD")
                end
                end [/code:2vgpuz0l]

                Все просто. Функция проверяет msg[1], который содержит ID отданной команды, печатает отладочное сообщение в TraceAI.txt о том, какая команда была получена, и вызывает соответствующую OnXXX_CMD() функцию, передавая дополнительные параметры, если это необходимо. Настоящий код по сути - это как раз эти функции, которые находятся выше ProcessCommand() в файле AI.lua. Вот их и рассмотрим.

                [code:2vgpuz0l]function OnMOVE_CMD (x,y)

                TraceAI ("OnMOVE_CMD")

                if ( x == MyDestX and y == MyDestY and MOTION_MOVE == GetV(V_MOTION,MyID)) then
                return
                end

                local curX, curY = GetV (V_POSITION,MyID)
                if (math.abs(x-curX)+math.abs(y-curY) > 15) then
                List.pushleft (ResCmdList,{MOVE_CMD,x,y})
                x = math.floor((x+curX)/2)
                y = math.floor((y+curY)/2)
                end

                Move (MyID,x,y)

                MyState = MOVE_CMD_ST
                MyDestX = x
                MyDestY = y
                MyEnemy = 0
                MySkill = 0

                end [/code:2vgpuz0l]
                Первым делом печатается отладочное сообщение о том, что функция была вызвана. Затем функция немедленно завершается, если покемончик уже в точке назначения И находится в состоянии движения, дабы избежать лишних прогонов кода. Более заковыристая часть кода ниже есть всего лишь проверка, находится ли целевая точка в радиусе 15 клеток (дальность обзора клиента. Логично, что посылать покемона на большое расстояние будет проблематично). Если да, то целевая клетка меняется на клетку в том же направлении, но вдвое ближе, а команда двигаться в изначальную клетку кладется в список зарезервированных команд ResCmdList (очевидно, она будет выполнена позже, когда гомункуль будет в пределах 15 клеток от цели, если до этого момента не отдать другой команды). Наконец, просто отдается приказ передвинуться в целевую клетку, одновременно с этим меняются некоторые глобальные переменные, обозначающие состояние покемона. Их названия говорят сами за себя, поэтому особо разбирать их не будем. Более подробно их использование мы разберем, изучая третью часть AI().

                [code:2vgpuz0l]
                function OnSTOP_CMD ()

                TraceAI ("OnSTOP_CMD")

                if (GetV(V_MOTION,MyID) ~= MOTION_STAND) then
                Move (MyID,GetV(V_POSITION,MyID))
                end
                MyState = IDLE_ST
                MyDestX = 0
                MyDestY = 0
                MyEnemy = 0
                MySkill = 0

                end [/code:2vgpuz0l]
                Аналогично предыдущей, но еще проще. Единственное на первый взгляд непонятное - часть кода, которая передвигает покемона в ту точку, где он уже находится. Кажется странным, однако это позволяет "поймать" зверюшку там, где она есть, и прекратить ее движение.

                [code:2vgpuz0l]
                function OnATTACK_OBJECT_CMD (id)

                TraceAI ("OnATTACK_OBJECT_CMD")

                MySkill = 0
                MyEnemy = id
                MyState = CHASE_ST

                end [/code:2vgpuz0l]
                Нечего объяснять х_х

                [code:2vgpuz0l]
                function OnATTACK_AREA_CMD (x,y)

                TraceAI ("OnATTACK_AREA_CMD")

                if (x ~= MyDestX or y ~= MyDestY or MOTION_MOVE ~= GetV(V_MOTION,MyID)) then
                Move (MyID,x,y)
                end
                MyDestX = x
                MyDestY = y
                MyEnemy = 0
                MyState = ATTACK_AREA_CMD_ST

                end [/code:2vgpuz0l]
                Вызывается, когда мы приказываем атаковать местность (это как, интересно? х_х). Двигает покемона в указанную клетку, если он уже не там или уже не в состоянии движения. Так и не поняла, нафига эта функция нужна...

                [code:2vgpuz0l]
                function OnPATROL_CMD (x,y)

                TraceAI ("OnPATROL_CMD")

                MyPatrolX , MyPatrolY = GetV (V_POSITION,MyID)
                MyDestX = x
                MyDestY = y
                Move (MyID,x,y)
                MyState = PATROL_CMD_ST

                end [/code:2vgpuz0l]
                Она вызывается, когда мы говорим покемону идти куда-то, а затем вернуться. Запоминается текущая позиция (чтобы было куда возвращаться) в переменных MyPatrolX, MyPatrolY, затем покемон передвигается в указанную клетку.

                [code:2vgpuz0l]
                function OnHOLD_CMD ()

                TraceAI ("OnHOLD_CMD")

                MyDestX = 0
                MyDestY = 0
                MyEnemy = 0
                MyState = HOLD_CMD_ST

                end [/code:2vgpuz0l]
                Простейшая команда "оставаться на месте".

                [code:2vgpuz0l]
                function OnSKILL_OBJECT_CMD (level,skill,id)

                TraceAI ("OnSKILL_OBJECT_CMD")

                MySkillLevel = level
                MySkill = skill
                MyEnemy = id
                MyState = CHASE_ST

                end [/code:2vgpuz0l]
                Аналогично атаке, только используется скилл. Заметим, что состояние становится аналогичным (CHASE_ST).

                [code:2vgpuz0l]
                function OnSKILL_AREA_CMD (level,skill,x,y)

                TraceAI ("OnSKILL_AREA_CMD")

                Move (MyID,x,y)
                MyDestX = x
                MyDestY = y
                MySkillLevel = level
                MySkill = skill
                MyState = SKILL_AREA_CMD_ST

                end [/code:2vgpuz0l]
                Точно так же, только на местность.

                [code:2vgpuz0l]
                function OnFOLLOW_CMD ()

                if (MyState ~= FOLLOW_CMD_ST) then
                MoveToOwner (MyID)
                MyState = FOLLOW_CMD_ST
                MyDestX, MyDestY = GetV (V_POSITION,GetV(V_OWNER,MyID))
                MyEnemy = 0
                MySkill = 0
                TraceAI ("OnFOLLOW_CMD")
                else
                MyState = IDLE_ST
                MyEnemy = 0
                MySkill = 0
                TraceAI ("FOLLOW_CMD_ST - IDLE_ST")
                end

                end [/code:2vgpuz0l]
                Апдейт фиг знает какой + 1
                Однако ж... Какой у мя был корявый гайд, и никто ни слова не сказал О.О
                Если покемон не идет за хозяином (MyState не равно FOLLOW_CMD_ST), то отдается команда топать к Мастеру. Если уже топает, то мы это прекращаем, выставляя ему в состояние "стоять" (IDLE_ST).

                Угу. Это все функции, которые вызываются при отдаче приказа. Теперь последняя часть - функции, которые собственно отвечают за поведение покемона в зависимости от его состояния и других переменных, которые мы определили.

                [code:2vgpuz0l]
                function OnIDLE_ST ()

                TraceAI ("OnIDLE_ST")

                local cmd = List.popleft(ResCmdList)
                if (cmd ~= nil) then
                ProcessCommand (cmd)
                return
                end

                local object = GetOwnerEnemy (MyID)
                if (object ~= 0) then
                MyState = CHASE_ST
                MyEnemy = object
                TraceAI ("IDLE_ST CHASE_ST : MYOWNER_ATTACKED_IN")
                return
                end

                object = GetMyEnemy (MyID)
                if (object ~= 0) then
                MyState = CHASE_ST
                MyEnemy = object
                TraceAI ("IDLE_ST CHASE_ST : ATTACKED_IN")
                return
                end

                local distance = GetDistanceFromOwner(MyID)
                if ( distance > 3 or distance == -1) then
                MyState = FOLLOW_ST
                TraceAI ("IDLE_ST FOLLOW_ST")
                return;
                end

                end [/code:2vgpuz0l]

                Становится сложно, да?
                Это - то, что делает наш покемон, когда ничего не делает. Первым делом выполняем первую команду из зарезервированного списка команд (типа движения к удаленной клетке и т.п.). Затем проверяется, бьет ли кого Мастер, функцией GetOwnerEnemy(); если нашли кого-то, то переключаем состояние на преследование, запоминаем цель как противника, после чего функция немедленно завершается (следующий пробег по функции AI() обнаружит, что покемончик уже в состонии преследования, и выполнит соответсвующую функцию). Если противников таким образом не найдено, то ищутся противники на экране, используя функцию GetMyEnemy(): она вернет или уже атакуемую цель, если она есть (поведение по умолчанию для Амистра и Лифа), или просто ближайшую цель (поведение по умолчанию для Филира и Ванильки). Код этой функции мы разберем позже. Наконец, если противников не обнаружено, покемончик переключается в режим следования за Мастером. Мы ведь не хотим уйти, оставив нашу зверюшку одну?

                [code:2vgpuz0l]
                function OnFOLLOW_ST ()

                TraceAI ("OnFOLLOW_ST")

                if (GetDistanceFromOwner(MyID) <= 3) then
                MyState = IDLE_ST
                TraceAI ("FOLLOW_ST IDLW_ST")
                return;
                elseif (GetV(V_MOTION,MyID) == MOTION_STAND) then
                MoveToOwner (MyID)
                TraceAI ("FOLLOW_ST FOLLOW_ST")
                return;
                end

                end [/code:2vgpuz0l]
                Этот код попроще: если покемончик рядом с Мастером, переключает его состояние (покемона, не Мастера) на стояние (idle), иначе отдает приказ зверюшке бежать к вам. Замечу, что если наш страшный жверь уже бежит за химом и расстояние больше 3 клеток, то ничего не меняется.

                [code:2vgpuz0l]
                function OnCHASE_ST ()

                TraceAI ("OnCHASE_ST")

                if (true == IsOutOfSight(MyID,MyEnemy)) then
                MyState = IDLE_ST
                MyEnemy = 0
                MyDestX, MyDestY = 0,0
                TraceAI ("CHASE_ST IDLE_ST : ENEMY_OUTSIGHT_IN")
                return
                end
                if (true == IsInAttackSight(MyID,MyEnemy)) then
                MyState = ATTACK_ST
                TraceAI ("CHASE_ST ATTACK_ST : ENEMY_INATTACKSIGHT_IN")
                return
                end

                local x, y = GetV (V_POSITION,MyEnemy)
                if (MyDestX ~= x or MyDestY ~= y) then
                MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
                Move (MyID,MyDestX,MyDestY)
                TraceAI ("CHASE_ST CHASE_ST : DESTCHANGED_IN")
                return
                end

                end [/code:2vgpuz0l]
                Во-первых, возвращает покемона в состояние "ничегонеделанья" (как idle правильно перевести? х_х) и завершается, если противник вышел из поля зрения. Затем переключает в состояние атаки и завершается, если противник в пределах досягаемости. Наконец, уточняются координаты цели и отдается приказ двигаться к ней, если до противника надо топать (а это часто происходит).

                [code:2vgpuz0l]
                function OnATTACK_ST ()

                TraceAI ("OnATTACK_ST")

                if (true == IsOutOfSight(MyID,MyEnemy)) then
                MyState = IDLE_ST
                TraceAI ("ATTACK_ST IDLE_ST")
                return
                end

                if (MOTION_DEAD == GetV(V_MOTION,MyEnemy)) then
                MyState = IDLE_ST
                TraceAI ("ATTACK_ST IDLE_ST")
                return
                end

                if (false == IsInAttackSight(MyID,MyEnemy)) then
                MyState = CHASE_ST
                MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
                Move (MyID,MyDestX,MyDestY)
                TraceAI ("ATTACK_ST CHASE_ST : ENEMY_OUTATTACKSIGHT_IN")
                return
                end

                if (MySkill == 0) then
                Attack (MyID,MyEnemy)
                else
                SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)
                MySkill = 0
                end
                TraceAI ("ATTACK_ST ATTACK_ST : ENERGY_RECHARGED_IN")
                return

                end [/code:2vgpuz0l]
                Если противник вне поля зрения, возвращает в состояние стояния и убивается (завершается, в смысле). Аналогично, если противник умер (довольно необычное движение - MOTION_DEAD...) Если противник оказался вне дальности атаки, переключаемся на преследование. Наконец, цель просто атакуется, если не нужно использовать скилл, или использует скилл, если это было приказано.

                [code:2vgpuz0l]
                function OnMOVE_CMD_ST ()

                TraceAI ("OnMOVE_CMD_ST")

                local x, y = GetV (V_POSITION,MyID)
                if (x == MyDestX and y == MyDestY) then
                MyState = IDLE_ST
                end
                end [/code:2vgpuz0l]
                Если точка назначения достигнута, включается мое любимое состояние ничегонеделанья.

                [code:2vgpuz0l]
                function OnSTOP_CMD_ST ()
                end

                function OnATTACK_OBJECT_CMD_ST ()
                end [/code:2vgpuz0l]
                Пусто О.о

                [code:2vgpuz0l]
                function OnATTACK_AREA_CMD_ST ()

                TraceAI ("OnATTACK_AREA_CMD_ST")

                local object = GetOwnerEnemy (MyID)
                if (object == 0) then
                object = GetMyEnemy (MyID)
                end

                if (object ~= 0) then
                MyState = CHASE_ST
                MyEnemy = object
                return
                end

                local x , y = GetV (V_POSITION,MyID)
                if (x == MyDestX and y == MyDestY) then
                MyState = IDLE_ST
                end

                end [/code:2vgpuz0l]
                Классическое состояние "атаковать местность": пытается найти сначала противника Мастера, затем своего противника (и начинает его преследовать, если нашло). В противном же случае, если достигнута точка назначения... да-да, "idle" состояние.

                [code:2vgpuz0l]
                function OnPATROL_CMD_ST ()

                TraceAI ("OnPATROL_CMD_ST")

                local object = GetOwnerEnemy (MyID)
                if (object == 0) then
                object = GetMyEnemy (MyID)
                end

                if (object ~= 0) then
                MyState = CHASE_ST
                MyEnemy = object
                TraceAI ("PATROL_CMD_ST CHASE_ST : ATTACKED_IN")
                return
                end

                local x , y = GetV (V_POSITION,MyID)
                if (x == MyDestX and y == MyDestY) then
                MyDestX = MyPatrolX
                MyDestY = MyPatrolY
                MyPatrolX = x
                MyPatrolY = y
                Move (MyID,MyDestX,MyDestY)
                end

                end [/code:2vgpuz0l]
                Простейшее состояние патрулирования: сначала пытается найти противника вышеуказанным способом, если не нашла, проверяет, достигнута ли точка назначения. Если да, отдает приказ идти в изначальную клетку (предварительно запомнив текущие координаты еще раз - для бесконечного патрулирования между двумя клетками, конечно же).

                [code:2vgpuz0l]
                function OnHOLD_CMD_ST ()

                TraceAI ("OnHOLD_CMD_ST")

                if (MyEnemy ~= 0) then
                local d = GetDistance(MyEnemy,MyID)
                if (d ~= -1 and d <= GetV(V_ATTACKRANGE,MyID)) then
                Attack (MyID,MyEnemy)
                else
                MyEnemy = 0;
                end
                return
                end

                local object = GetOwnerEnemy (MyID)
                if (object == 0) then
                object = GetMyEnemy (MyID)
                if (object == 0) then
                return
                end
                end

                MyEnemy = object

                end [/code:2vgpuz0l]
                Апдейт еще раз.
                Или у меня косяк в скорпированной части когда, или эта штука не будет работать. Ибо там должо быть GetDistance2. Ну и вообще надо было использовать IsInAttackSight, раз уж косячим... Ппц в Гравити программисты.

                Режим удержания местности: атаковать противника, если он близко (они могли и должны были использовать функцию IsInAttackSight(), но в Гравити, похоже, сами не пользуются своими функциями х___х), иначе забить и искать другого через GetOwnerEnemy() и GetMyEnemy(), однако не преследовать их. Следующий проход этой функции определит, следует ли атаковать найденного противника или нет.

                [code:2vgpuz0l]
                function OnSKILL_OBJECT_CMD_ST ()
                end [/code:2vgpuz0l]
                Угу, здесь тоже ничего нет.

                [code:2vgpuz0l]
                function OnSKILL_AREA_CMD_ST ()

                TraceAI ("OnSKILL_AREA_CMD_ST")

                local x , y = GetV (V_POSITION,MyID)
                if (GetDistance(x,y,MyDestX,MyDestY) <= GetV(V_SKILLATTACKRANGE,MyID,MySkill)) then
                SkillGround (MyID,MySkillLevel,MySkill,MyDestX,MyDestY)
                MyState = IDLE_ST
                MySkill = 0
                end

                end [/code:2vgpuz0l]
                М... применяет скилл туда, куда указали (если цель в пределах досягаемости) и возвращает покемона в ленивое состояние (лингво меня спасет - там куча значений слова idle).

                [code:2vgpuz0l]
                function OnFOLLOW_CMD_ST ()

                TraceAI ("OnFOLLOW_CMD_ST")

                local ownerX, ownerY, myX, myY
                ownerX, ownerY = GetV (V_POSITION,GetV(V_OWNER,MyID))
                myX, myY = GetV (V_POSITION,MyID)

                local d = GetDistance (ownerX,ownerY,myX,myY)

                if ( d <= 3) then
                return
                end

                local motion = GetV (V_MOTION,MyID)
                if (motion == MOTION_MOVE) then
                d = GetDistance (ownerX, ownerY, MyDestX, MyDestY)
                if ( d > 3) then
                MoveToOwner (MyID)
                MyDestX = ownerX
                MyDestY = ownerY
                return
                end
                else
                MoveToOwner (MyID)
                MyDestX = ownerX
                MyDestY = ownerY
                return
                end

                end [/code:2vgpuz0l]
                Во-первых... ДА, ОНИ ДОЛЖНЫ БЫЛИ ИСПОЛЬЗОВАТЬ GetDistanceFromOwner()!!! Но это Гравити, чего еще ждать х_х
                Вы уже должны сами понимать, что здесь происходит, однако ж: если недалеко от Мастера, завершается, иначе идти к Мастеру, если покемон не двигается или двигается в клетку, которая очень далеко от Мастера.

                Уря, мы закончили все функции, обрабатывающие состояния покемонов! Теперь остались только функции выбора противника. Потерпите, остался последний рывок х_х

                [code:2vgpuz0l]
                function GetOwnerEnemy (myid)
                local result = 0
                local owner = GetV (V_OWNER,myid)
                local actors = GetActors ()
                local enemys = {}
                local index = 1
                local target
                for i,v in ipairs(actors) do
                if (v ~= owner and v ~= myid) then
                target = GetV (V_TARGET,v)
                if (target == owner) then
                if (IsMonster(v) == 1) then
                enemys[index] = v
                index = index+1
                else
                local motion = GetV(V_MOTION,i)
                if (motion == MOTION_ATTACK or motion == MOTION_ATTACK2) then
                enemys[index] = v
                index = index+1
                end
                end
                end
                end
                end

                local min_dis = 100
                local dis
                for i,v in ipairs(enemys) do
                dis = GetDistance2 (myid,v)
                if (dis < min_dis) then
                result = v
                min_dis = dis
                end
                end

                return result
                end [/code:2vgpuz0l]
                Ух. Не позволяйте себя испугать, тут все проще, чем кажется на первый взгляд.
                "for i,v in ipairs(actors) do" просто перебирает вектор actors, где i - текущий индекс элемента, а v - значение этого элемента. По сути, эта функция работает так: получает список активных целей (actors - всё, что может обладать ID - мобы, игроки, трапы, etc). Если это не покемон и не его Мастер, проверяется его цель. Если оно выцеливает Мастера и атакует (да, это разные вещи), запоминаем его в списке enemys. Наконец, перебираем список отобранных целей (enemys), выбирая ближайшего.

                [code:2vgpuz0l]
                function GetMyEnemy (myid)
                local result = 0

                local type = GetV (V_HOMUNTYPE,myid)
                if (type == LIF or type == LIF_H or type == AMISTR or type == AMISTR_H or type == LIF2 or type == LIF_H2 or type == AMISTR2 or type == AMISTR_H2) then
                result = GetMyEnemyA (myid)
                elseif (type == FILIR or type == FILIR_H or type == VANILMIRTH or type == VANILMIRTH_H or type == FILIR2 or type == FILIR_H2 or type == VANILMIRTH2 or type == VANILMIRTH_H2) then
                result = GetMyEnemyB (myid)
                end
                return result
                end [/code:2vgpuz0l]
                Таак, знаменитая функция GetMyEnemy()... Как мы видим, она всего лишь вызывает другие функции в зависимости от типа покемона

                [code:2vgpuz0l]
                function GetMyEnemyA (myid)
                local result = 0
                local owner = GetV (V_OWNER,myid)
                local actors = GetActors ()
                local enemys = {}
                local index = 1
                local target
                for i,v in ipairs(actors) do
                if (v ~= owner and v ~= myid) then
                target = GetV (V_TARGET,v)
                if (target == myid) then
                enemys[index] = v
                index = index+1
                end
                end
                end

                local min_dis = 100
                local dis
                for i,v in ipairs(enemys) do
                dis = GetDistance2 (myid,v)
                if (dis < min_dis) then
                result = v
                min_dis = dis
                end
                end

                return result
                end [/code:2vgpuz0l]
                Защитная версия: учитывает только противников, выцеливающих самого покемона, и выбирает ближайшего

                [code:2vgpuz0l]
                function GetMyEnemyB (myid)
                local result = 0
                local owner = GetV (V_OWNER,myid)
                local actors = GetActors ()
                local enemys = {}
                local index = 1
                local type
                for i,v in ipairs(actors) do
                if (v ~= owner and v ~= myid) then
                if (1 == IsMonster(v)) then
                enemys[index] = v
                index = index+1
                end
                end
                end

                local min_dis = 100
                local dis
                for i,v in ipairs(enemys) do
                dis = GetDistance2 (myid,v)
                if (dis < min_dis) then
                result = v
                min_dis = dis
                end
                end

                return result
                end [/code:2vgpuz0l]
                Агрессивная версия: учитывает каждого моба вокруг (тут Драко говорит о том, что в ПвП эта функция игнорирует игроков. Уж не знаю, на арене моя Ванилька с удовольствием гоняла сагов, будучи запущена с дефолтным скриптом) и выбирает ближайшего.

                Уря, ВСЕ! Теперь вы знаете, как думают ваши покемоны. В следующем уроке мы разберем примеры модифицирования, начиная с простейших добавлений и заканчивая супер-пупер-мега-ультра-гипно-модификациями

                Урок следующий: первые модификации...

                1 ответ Последний ответ Ответить Цитировать 0
                • LisandreLL Не в сети
                  LisandreL
                  отредактировано

                  в состояние "ничегонеделанья" (как idle правильно перевести? х_х)

                  Может "в состояние покоя".
                  Кстати когда закончишь, попроси модераторов лишние посты потереть - что б гайд в постах 1 за другим шёл... ну или в новую тему перенеси.

                  1 ответ Последний ответ Ответить Цитировать 0
                  • Aiko StarA Не в сети
                    Aiko Star
                    отредактировано

                    LisandreL, лингво гениально перевел - "состояние лени" :lol:
                    А переносить лень... заново пробивать тэги - я умру. И так все в блокноте ручками пишу...

                    1 ответ Последний ответ Ответить Цитировать 0
                    • LisandreLL Не в сети
                      LisandreL
                      отредактировано

                      Переносить просто: жмёшь правку поста и он уже весь с тегами.

                      1 ответ Последний ответ Ответить Цитировать 0
                      • StorrS Не в сети
                        Storr
                        отредактировано

                        Когда гайд будет закончен, лишние посты будут потерты. 🙂

                        1 ответ Последний ответ Ответить Цитировать 0
                        • Aiko StarA Не в сети
                          Aiko Star
                          отредактировано

                          Урок четвертый: первые модификации

                          Ну что, устали от теории? Если честно, мя тоже (еще бы, за два с половиной года безделья-то...). К счастью, она на этом заканчивается. Теперь будем разбирать настоящие примеры. Начнем с самых простеньких.
                          Прежде чем выполнять все, что здесь написано, скопируйте все три файла (AI.lua, Const.lua и Utils.lua) в папку USER_AI и вдальнейшем работайте с этими копиями.
                          Это нужно на случай, если что-то пойдет не так, а вы не сможете отловить и исправить ошибку. Всегда, повторяюсь, ВСЕГДА храните где-нибудь копию оригинальных скриптов. Иначе может быть очень плохо.
                          Теперь хватит пугать, перейдем к делу.
                          PS Да, еще одно. Чтобы клиент использовал скрипты из папки \AI\USER_AI (а не из \AI, как он это делает по умолчанию), в игре нужно использовать команду /hoai (ее достаточно ввести один раз, клиент запоминает даже на время выхода, какие файлы нужно читать).

                          ВНИМАНИЕ!!! Все приведенные ниже примеры были написаны Драко (да-да, я здесь непричем) еще до того, как он разобрался с работой AI. Например, использование GetV() функции, чтобы получить уровень HP моба: на самом деле это работать не будет, так как клиент всегда будет выдавать -1. Однако в качестве примера (и просто хорошей идеи) использовать это можно.

                          *Отображение HP противника. *
                          Довольно простое, но очень полезное (если бы работало х_х) дополнение: попробуем использовать нашу гипно-функцию GetV() для получения той информации, которую просто так не узнаешь.
                          Первая проблема - это куда добавлять код. В данном случае логично, что это должно работать, когда покемон атакует моба, поэтому OnATTACK_ST() - это то, что нам нужно. Например, после блоков Attack() и SkillObject(). Теперь вторая проблема - что же туда добавлять? Вы уже должны сами ее решить, однако это все-таки гайд (офигеть, какая я была добрая в те времена О.о)

                          [code:1tt97pnp]-- Кладем это все в конец функции OnATTACK_ST()
                          do
                          local HP = GetV(V_HP, MyEnemy)
                          local MaxHP = GetV(V_MAXHP, MyEnemy)
                          TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
                          end [/code:1tt97pnp]
                          Заметим, что весь код лежит между операторов do ... end, так что эту часть можно легко перемещать, а локальные переменные действуют только внутри нее. Сам по себе код кристалльно чист (это дословный перевод х_х), за исключением, пожалуй, большого количества .. (двух точек - стандартный оператор соединения строк в Lua).

                          Улучшаем наше дополнение.
                          Сам по себе указанный выше код должен работать, однако с ним возможны несколько проблем. Первое - мы не знаем, как часто клиент обращается к функции AI(). Весьма может быть, что в результате файл TraceAI.txt будет зафлужен одинаковыми сообщениями. Есть два выхода: или прописывать сообщение, если HP изменилось, или же просто каждые сколько-то секунд.
                          Первый способ довольно просто внедрить: просто объявим (и зададим значение по умолчанию 0) глобальную переменную MyEnemyHP и будем ее использовать для хранения и проверки нового значения.

                          [code:1tt97pnp]-- Добавляем это в самый верх файла
                          MyEnemyHP = 0 [/code:1tt97pnp]
                          [code:1tt97pnp]
                          -- А это - как и раньше
                          do
                          local HP = GetV(V_HP, MyEnemy)
                          if MyEnemyHP ~= HP then
                          local MaxHP = GetV(V_MAXHP, MyEnemy)
                          MyEnemyHP = HP
                          TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
                          end
                          end [/code:1tt97pnp]
                          Заметим, что MaxHP создается и получает значение только если необходимо (маленькая и по сути бесполезная оптимизация).

                          Второй метод - хороший пример использования функции GetTick(). Нам просто нужно хранить следующее время вывода вражеского HP в глобальной переменной (назовем ее EnemyHPDisplayTick), и проверять, нужно ли сейчас его отображать.

                          [code:1tt97pnp]-- Желательно добавлять в самый верх файла
                          EnemyHPDisplayTick = 0 [/code:1tt97pnp]

                          [code:1tt97pnp]-- А это кладем все туда же х_х
                          if GetTick() >= EnemyHPDisplayTick then
                          local HP = GetV(V_HP, MyEnemy)
                          local MaxHP = GetV(V_MAXHP, MyEnemy)
                          TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
                          EnemyHPDisplayTick = GetTick() + 3000 -- Определяем, как часто будет выводиться HP (в милисекундах)
                          end [/code:1tt97pnp]
                          Тут все должно быть ясно.
                          А теперь что можно сделать, опираясь на эту идею: покемон будет жаловаться, когда у него низкий уровень HP/SP; отображать HP цели Мастера. Оба дополнения очень просты, их нужно добавить сразу в AI(), чтобы они выполнялись каждый раз, когда покемон получает команду или меняет состояние.

                          Как получить ID другого игрока (или немного об ID аккаунтов).
                          Помните, я говорила про это? Да-да. ID аккаунтов можно использовать для... создания френд-листа. А уж возможностей его применения - море. Да, ID - единственное, что мы можем получить через GetV() с других объектов (исключая нашу зверюшку и самого Мастера). GetActors() выдает нам ID игроков, но в общей куче с другими, поэтому нужно придумать какой-то способ обозначить нужного нам игрока (например, как-то подвигать покемона вокруг нужного игрока), а затем через скрипт получить не очень хорошую, но рабочую функцию, которая будет заносить ID игроков, которых мы обозначили, в файл.

                          Запуск функции только один раз (инициализация).
                          Очень просто, на самом деле. Можно сделать через глобальную логическую переменную, можно чуть сложнее. Например, мы хотим, чтобы в traceai.txt клалось некое приветствие при первой обработке AI() (перевызов покемона, левел-ап, переход на другую локацию).

                          [code:1tt97pnp]
                          -- Лучше всего положить к другим глобальным переменным
                          FirstLaunch = true [/code:1tt97pnp]

                          [code:1tt97pnp]-- В самое начало AI()
                          if FirstLaunch then
                          FirstLaunch = false
                          TraceAI("Превед, Мастерчег!")
                          end [/code:1tt97pnp]
                          Проще некуда. Естественно, содержимое TraceAI() можно заменить чем угодно.
                          Второй способ чуть хитрее и основан на гибкости языка Lua, однако позволяет обходиться без глобальных переменных. Основан он на том, что в LUA всему, даже самим функциям, можно присваивать новые значения. Когда мы объявляем функцию стандартными средствами:

                          [code:1tt97pnp]
                          function FunctionName() TraceAI("Blank Function") end [/code:1tt97pnp]
                          то это действие, по сути, аналогично следующему:

                          [code:1tt97pnp]
                          FunctionName = function() TraceAI("Blank Function") end [/code:1tt97pnp]
                          Можно опустить часть кода, проверяющего нашу логическую переменную и переназначить функцию.

                          [code:1tt97pnp]
                          function AI(id)
                          --[[ Пока покемон на карте, его ID не изменяется, поэтому эту часть нужно вызывать только один раз--]]
                          MyID = id

                          -- Превед, мирчег!
                          TraceAI("Превед, мирчег! Вызываем это только один раз!")

                          -- Переназначаем функцию
                          AI = AI_part2

                          -- Сразу же и вызываем новую функцию, чтобы не пропускать первый проход.
                          AI_part2()
                          end

                          function AI_part2()
                          local cmd = GetCmd(MyID)

                          -- и т.д.
                          end [/code:1tt97pnp]

                          Заметим, что при переназначении функции не надо использовать скобки.

                          Атаковать цель Мастера.
                          По умолчанию покемон атакует ближайшего моба, который бьет Мастера. Или же ищет просто ближайшего, если Мастера никто не бьет. Если же мы хотим, чтобы зверюшка помогала убивать нашу цель, надо немного модифицировать GetOwnerEnemy() (конечно, есть и другие пути, но пусть будет этот).

                          [code:1tt97pnp]-- Добавляем после объявления локальных переменных но до основного кода
                          target = GetV(V_TARGET,owner)
                          if(target ~= 0) then
                          return target
                          end [/code:1tt97pnp]
                          Если GetV() может возвращать цель Мастера (а тут я не помню, надо проверить, кстати), то это заставит покемона атаковать такого моба в первую очередь. Если же Мастер никого не бьет, то он будет выбирать ближайшего моба, атакующего Мастера и т.д.

                          За сим уроки Драко заканчиваются. От себя чуть позже я добавлю еще кое-какой материал (работа с текстовыми файлами из скрипта - формирование френд-листа, пример функции проверки на килстил, автоиспользование скиллов, может, еще чего нарою в инете).

                          Урок следующий - Текстовые файлы, автоскиллы, киллстил и прочие вкусности.

                          1 ответ Последний ответ Ответить Цитировать 0
                          • СонечкоС Не в сети
                            Сонечко
                            отредактировано

                            Так, а че делать тем, кто ничего не понял? И врядли у него это получится? Я че буду без покемона? :shock:

                            1 ответ Последний ответ Ответить Цитировать 0
                            • Aiko StarA Не в сети
                              Aiko Star
                              отредактировано

                              Урок пятый: Текстовые файлы, автоскиллы, киллстил и прочие вкусности.

                              Прежде чем всю эту фигню продолжить, выскажусь.
                              Произучав два самых, на мой взгляд, популярных и мощных из вышедших скриптов для покемонов, я уж и не знаю, имеет ли смысл МНЕ чего-либо пытаться сделать на этом поприще.
                              МирАИ - универсальная "попсовая" машинка для ленивых со внешним графическим конфигом, обладающая довольно большой гибкостью.
                              РэмпейджАИ - это... это гениально. Полмегабайта (552 килобайта) чистого кода с комментариями - в последней релизной версии с дополнениями Накаямы. Умеет все, от проверки пвп- или гв-зон до отдачи ЛЮБЫХ команд "цифровым" передвижением покемона и эмулированием клиента РО для отладки собственного скрипта. Считает трафик, имеет специальные противолаговые функции работы скрипта, определяет варпы на карте (как - хз, мой мосск эту функцию не осилил), определяет время неуязвимости на гв 1.0 (нынче, уже, правда неактуально, но все же)... Честно - сняла бы шляпу, если бы она у меня была. Не знаю, сколько лет это писалось, но... в общем, вот.

                              Немного отойдя от общих впечатлений, вернемся все-таки к обещанному.

                              Интересные и важные замечания/плюшки.

                              Заметки к скиллам покемонов, согласно readменя замечательного и всеми любимого MirAI (проверять эти утверждения надо ВСЕ, ибо последняя версия MirAI датируется концом 2006 года):

                              • Castling барашка не работает (вроде бы работало, когда мя с ним бегала недавно).
                              • Chaotic Blessings ванильки не работает через скрипты (только запуск вручную через панель).
                              • Accelerated Flight филира не работает вообще (возможно, актуально до сих пор, т.к. в RampageAI этот скилл тоже отнесен к неработающим).

                              Баг обработки скрипта:
                              Кроме скиллов, отталкивающих покемона назад (Charge Arrow и т.п.), наблюдается баг Гравити: даже с пустым AI скриптом покемончик подползает к стреляющим мобам, которые его ударили.
                              Надо проверять, опять же.

                              Плюшки из RampageAI
                              Дополнения к константам:


                              -- возвращаемые значения для GetV(V_MOTION,id)

                              MOTION_STAND = 0 -- Стояние на месте
                              MOTION_MOVE = 1 -- Движение
                              MOTION_ATTACK = 2 -- Атака
                              MOTION_DEAD = 3 -- Смерть (трупиком лежит О.о)
                              MOTION_DAMAGE = 4 -- Получение повреждений
                              MOTION_BENDDOWN = 5 -- Э... наклоны х_х (при взятии лута, установки трапов, etc.)
                              MOTION_SIT = 6 -- Сидит
                              MOTION_SKILL = 7 -- Использует скилл (анимация окончания каста)
                              MOTION_CASTING = 8 -- Кастует скилл
                              MOTION_ATTACK2 = 9 -- Атака 2 (в оригинале в скобочках стоит "double dagger?", вероятно, Роберт сам так и не разобрался)
                              MOTION_TOSS = 12 -- Бросок чего-нибудь (spear boomerang / aid potion)
                              MOTION_COUNTER = 13 -- Counter-attack
                              MOTION_PERFORM = 17 -- Performance (скиллы бардов/данс?)
                              MOTION_JUMP_UP = 19 -- Тыквопрыг - взлетает
                              MOTION_JUMP_FALL= 20 -- Тыквопрыг - приземляется
                              MOTION_SOULLINK = 23 -- Soul linker использует линк
                              MOTION_TUMBLE = 25 -- Tumbling / тыквопрыг приземление (а разницы от того, что раньше было?)
                              MOTION_BIGTOSS = 28 -- "серьезный" бросок (slim поты / acid demonstration)

                              Выдержки из комментариев к коду RampageAI.

                              1. Покемоновский GetV(V_MOTION) никогда не возвращает MOTION_ATTACK, если применять на самого покемона. Вообще никогда.
                                Проверить бы...
                              2. Слух: ПВП-игроки показываются как противники, если они не в нашей пати.
                                Замечание: НЕПРАВДА!! ПВП-игроки всегда показываются как противники.
                                Знать бы еще, что нам это дает и что он подразумевает под противниками... функцию IsMonster()?

                              -- Check when the last received command was
                              function AMC_Check()
                              -- Check if we should be checking... XD
                              Без комментариев. Мя посмеялось 🙂

                              Анти-киллстил (на примере фукнции MirAI)

                              [code:8bhd8pjq]function isKS()
                              if 0 == IsMonster(MyEnemy) then return false; end

                              local mob_target = GetV(V_TARGET, MyEnemy)
                              if mob_target > 0 then
                              	if not IsMonster2(mob_target) then
                              		if (mob_target == MyID) or (mob_target == OwnerID) or (Friends[mob_target] ~= nil) then
                              			return false
                              		else
                              			Log("MyEnemy was already in battle, action aborted (anti-KS)")
                              			return true
                              		end
                              	end
                              end
                              
                              local actors = GetActors()
                              for i,v in ipairs(actors) do
                              	if (v ~= OwnerID) and (v ~= MyID) then
                              		if 0 == IsMonster(v) then
                              			if isNotFriend(v) then
                              				if GetV(V_TARGET, v) == MyEnemy then
                              					Log("Another player is aiming at MyEnemy, action aborted (anti-KS)")
                              					return true
                              				end
                              			end
                              		end
                              	end
                              end
                              
                              return false
                              

                              end[/code:8bhd8pjq]

                              Многа букф. Но будем разбирать, что поделать...
                              Перво-наперво, если наша цель - не моб (игрок, вестимо), то функция завершается. Как сказано в комментариях Rampage AI: "Нельзя скиллстилить игрока О.о" 🙂
                              Затем идет пачка проверок. Во-первых, берется цель нашей цели. Если эта цель есть, проверяем, выцеливают ли нас (хима, покемона или друга). Если выцеливают, то украсть мы ничего не можем, стало быть, можно бить (пока что). Если выцеливают не нас, то бить нельзя, о чем и пишем.
                              Вторая проверка - выцеливает ли кто-то еще нашу выбранную цель. Перебираем всех действующих лиц на экране. Если они не мы (хим или покемон), смотрим, моб ли это. Если не моб, проверяем дальше - друг это или нет. Если не друг и если он выцеливает нашу цель, то мы эту цель бить не будем.
                              Вот. RampageAI использует другой алгоритм, который в том числе высчитывает расстояние от моба до ближайшего игрока и не трогает мобов, стоящих близко к другим персонажам. Рассматривать его мы не будем, поскольку там используется огромнейшее количество собственных функций, которые я задолбаюсь объяснять. Кому интересно - сами посмотрите 🙂

                              Работа с внешними файлами на примере френд-листа (утащено из Rampage AI).
                              Часть первая, запись файлов:

                              [code:8bhd8pjq] local filename = ScriptLocation .. "Conf/Friends.lua"
                              local savefile = io.open(filename, "w")
                              savefile:write(StringBuffer.Get(out))
                              savefile:close()[/code:8bhd8pjq]

                              Что же тут творится? Рассмотрим построчно. Сразу замечание - это конкретно кусок кода открытия-записи файла, часть используемых тут переменных определяется раньше, но я их объясню.
                              Первым делом в локальную переменную filename кладется путь к френдлисту (переменная ScriptLocation задается при первом запуске AI. В скрипте используется замороченная функция проверки существования файлов, но если очень условно, то оно выглядит так:
                              [code:8bhd8pjq]ScriptLocation = "./AI/USER_AI/"[/code:8bhd8pjq]
                              Следующая строчка открывает файл Friends.lua и заносит в переменную savefile внутренний код для работы с открытым файлом (file handler или хэндлер, простите мой французский). У функции io.open в качестве первого параметра указывается путь к открываемому файлу, в качестве второго - режим работы с файлом. В примере мы открываем файл для записи. Вообще же эти режимы могут быть следующими:
                              "r": режим чтения (по умолчанию);
                              "w": режим записи;
                              "a": режим добавления;
                              "r+": режим обновления, все предыдущие записи защищены;
                              "w+": режим обновления, все предыдущие записи стираются;
                              "a+": режим добавления-обновления (append update x_x), все предыдущие записи защищены, запись в файл разрешена только в конец файла.
                              У этой функции есть дополнительный параметр для открытия файлов в бинарном режиме, но нам это нафиг не надо. Кому интересно - читайте мануал lua 🙂
                              Следующая строчка - запись в файл savefile отформатированной определенным образом переменной out, которая в скрипте задается ранее. Записывать в файлы можно текстовые и числовые переменные, для остальных надо использовать tostring или string.format ДО записи в файл.
                              Последняя строчка закрывает файл savefile, сохраняя все изменения. В принципе, можно их и не закрывать, но это может плохо кончиться и вообще считается дурным тоном в программировании :Р

                              Касательно косяков - мне кажется, что оно Rampage AI неправильно пишет этот самый френд-лист (переменная out в указанном примере неправильно формируется), но это надо на практике проверять, а не на работе 🙂

                              Часть вторая, чтение файлов:

                              [code:8bhd8pjq] local filename = ScriptLocation .. "Conf/Friends.lua"
                              local func = loadfile(filename)
                              if func ~= nil then
                              func()
                              end[/code:8bhd8pjq]

                              В принципе, все просто. Единственная заморочка с локальной функцией func. Первым делом мы эту функцию приравниваем к результату стандартной функции Lua loadfile(), которая читает указанный в качестве параметра файл и... и читает.
                              Проверяем, пустой ли у нас этот файл (func ~= nil), если не пустой, то читаем все-таки его. Поскольку файл отформатирован в соответствии с синтаксисом Lua и представляет собой просто толстую переменную friends вида

                              [code:8bhd8pjq]Friends = {
                              [ID] = true
                              }[/code:8bhd8pjq]

                              где ID - тот самый ID аккаунта нашего друга (то есть переменная Friends в данном случае - список, в котором на ID-ом месте лежит true, если это наш друх. Сложно, но работает х_х), то, прочитав файл, мы сразу получаем эту переменную в свое распоряжение и можем к ней обращаться.
                              Вопрос - почему не используем require?
                              Ответ - потому что require подключает файл один раз. А список друзей у нас может меняться в течение функционирования скрипта, поэтому надо иметь возможно его загружать по мере необходимости. Что тут и делается.

                              Альтернативная функция IsMonster2() [MirAI]

                              [code:8bhd8pjq]function IsMonster2(ID)
                              local mob_type = GetV(V_HOMUNTYPE, ID)
                              if (mob_type >=0) and ((mob_type < 1000) or (mob_type > 4000)) then
                              return false
                              end
                              return true
                              end[/code:8bhd8pjq]

                              Забавная функция 🙂 Использует GetV с аргументом V_HOMUNTYPE для определения мобов. По идее, работает, проверить бы. Но идея хороша, да 🙂

                              Утилиты для работы с .lua-файлами:
                              "MirAI рекомендует":

                              ConTEXT - хороший редактор с подсветкой синтаксиса:
                              http://www.context.cx/
                              Плагин для подсветки:
                              http://context.cx/component/option,com_doc...,100/Itemid,48/

                              1 ответ Последний ответ Ответить Цитировать 0
                              • СонечкоС Не в сети
                                Сонечко
                                отредактировано

                                И куда его потом просто в папку с РО ложить? ОМГ :shock: :shock: фпанике

                                1 ответ Последний ответ Ответить Цитировать 0
                                • Aiko StarA Не в сети
                                  Aiko Star
                                  отредактировано

                                  Сонечко, в папке с клиентом есть папка AI, в ней - USER_AI, вот в нее и кидаешь. В игре прописываешь один раз (вообще один, не при каждом заходе) /hoai и... любуешься.

                                  1 ответ Последний ответ Ответить Цитировать 0
                                  • СонечкоС Не в сети
                                    Сонечко
                                    отредактировано

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

                                    1 ответ Последний ответ Ответить Цитировать 0
                                    • СонечкоС Не в сети
                                      Сонечко
                                      отредактировано

                                      Я вот тут подумала. Я вот ниче не понимаю, накидаю себе в папку каких-нить скриптов, а меня хоп и в бан, мол низя.
                                      Предлагаю, для таких как я, а я думаю, я не одна, создать какой-нить "социальный пакет" - скрипты, самые необходимые, и самое важное, согласованные с администрацией.

                                      1. Как будет реализовано правило, типа не создай себе игрового преимущества. Вот я не могу себе ниче придумать понятно, а вот другие (это им плюс большой, могут), получиццо, что мой покемон и покемон товарища - это совершенно разные по полезности зверушки. Ну я допустим еще на форум захожу, могу себе тут чего-нить накачать, а есть товарищи и этого не делают, тогда их покемон, будет еще хуже моего. Че делать?

                                      Что-то я не могу себе полностью уяснить, или если скрипты не вводить, то покемон тоже самоебудет делать, только ему надо это будет указывать, или как?
                                      Что дают эти скрипты такого кординальноо, или можно без них обойтись вообще. И тут какбы и правила не нарушишь, и в тоже время зверь не совсем безполезный. Объясните для тугодумов. Спасибо.

                                      1 ответ Последний ответ Ответить Цитировать 0
                                      • LisandreLL Не в сети
                                        LisandreL
                                        отредактировано

                                        Сонечко, пока что администрация (в лице Zeno) ограничилась коментариями, что в скрипте не должно быть автокача и что за киллстил будет отвечать сам владелец.
                                        В общем со всеми готовыми скриптами идёт описание. По нему и следует ориентироваться. В общем советую избегать скриптов, где не написанно, что есть защита от киллстилла, где написанно, что есть возможность атокача и где есть автоматизация использования поушен пинчера и болтов с мечей. (на счёт последних 2 пунктов официально запрета не было, но я б поостерёгся - а то будет ка с редумптио у пристов: вначале забанят, а потом объявят, что нельзя было использовать).
                                        Скрипты не вводить нельзя - есть скрипт по умолчаению, но он далёк от идеала: в частности киллстиллит.
                                        Тему со скриптами можно сделать хоть сейчас (я могу даже этим заняться), но вот как провести согласование с администрацией - я без понятия.

                                        1 ответ Последний ответ Ответить Цитировать 0
                                        • Aiko StarA Не в сети
                                          Aiko Star
                                          отредактировано

                                          Поясню еще кое-что. Без скриптов нельзя. Если удалить скрипты вообще, то вызванный покемончик будет просто стоять на месте, не реагируя вообще ни на что.
                                          Далее, так и не уяснила для себя, что есть автокач. Автоматическое нападение покемона на мобов? Так это делает и дефолтный скрипт. И это не автокач (в связи с введением 6-минутного ограничения на стояние на месте).
                                          С киллстилом все вроде понятно, за исключением пары забавных фактов. Я пользуюсь скриптом с двойной проверкой на киллстил (атакуют ли другие игроки моба и атакует ли он сам других игроков). И моя ванилька пару раз утаскивала из чужих паровозов хая :lol:

                                          1 ответ Последний ответ Ответить Цитировать 0
                                          • пОнт-121П Не в сети
                                            пОнт-121
                                            отредактировано

                                            Скрипт в студию,если можно >_<
                                            Заранее спасибо.

                                            1 ответ Последний ответ Ответить Цитировать 0
                                            • Первое сообщение
                                              Последнее сообщение