1. Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, сыграть в увлекательные ФРПГ, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
    Скрыть объявление
  2. Форум аддона "Возвращение" 2.0:
    — Обсудить игру, почитать о прохождениях и/или разрешить свои вопросы по игре вы можете в одной из тем одноименного форума. Посетить...
    — Прочитать историю изменения и/или скачать последнюю версию аддона "Возвращение", вы можете на страницах наших ресурсов. Скачать...
    Скрыть объявление

AST/AST SDK - Уроки и примеры использования

Тема в разделе "Уpоки AST", создана пользователем Gratt, 15 сен 2016.

Статус темы:
Закрыта.
  1. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    Тема 1: Заклинание телепортации на короткие дистанции.

    1.1. Таргетная телепортация в указанную точку. (SDK)


    С чего начинаем: Возьмем чистый SDK проект и средства разработки AST (скачиваем архив с библиотекой, копируем содержимое Tools в папку с исходниками плагина). На этом настройка проекта закончена :)
    ------
    Далее непосредственно к коду. Цель задачи - написать алгоритм, перемещающий главного персонажа в точку прицела.



    Переходим в PluginLoop и объявляем новую функцию Teleport. Вызываем ее из цикла SafeLoop.
    1.png



    По условию персонаж перемещается при помощи прицела. Отрисовка будет происходить по удерживанию ПКМ.
    2.png



    Прицел готов, значит можно переходить непосредственно к телепортации. Нацелившись, необходимо создать условие, по которому персонаж будет перемещен в указанную точку. Например, вызов блока будет происходить по ЛКМ. Но есть проблема - проверка состояния кнопки происходит в цикле, а значит необходимо ограничить интервал ее выполнения (В противном случае, Npc будет летать по карте как истребитель.). Воспользуемся AST таймером. Предлагаю программно установить лимит автоматического повторения необходимого условного блока не чаще чем 500мс (полсекунды). Для этого объявляем контейнер таймеров (1), регистрируем его в нашем цикле (2) и создаем условие на ограничения выполнения в режиме T_PRIMARY (3). Выходя из автоматического режима избавляемся от таймера и его задержек (4).
    Открыть спойлер
    Как это работает: 1. Когда мы зажимаем ЛКМ, происходит вызов таймера с id 10 и задержкой 500мс. Режим T_PRIMARY говорит о том, что условие выполнится сразу при нажатии на кнопку, но каждое следующее выполнение будет не чаще чем полсекунды.
    2. В конце, когда мы удаляем таймер отпустив ЛКМ, также затирается и информация о задержках, а значит при каждом новом клике условие сработает не ожидая 500мс после предыдущего выполнения.

    3.png



    Из условия понятно, что точкой отсчета будет центр нашей камеры. На данном этапе важно получить интересующий нас вектор, относительно которого мы будем искать точку, на которую смотрит прицел. Обращаемся к данным игры, запрашиваем активную камеру (не забываем подключить соответствующий заголовочный файл для работы с классом zCCamera) и обращаемся к указателю ее воба. Исходя из этого объекта, строим 2 координаты. Первая получает начальную точку, Вторая указывает смещение вдоль камеры.
    4.png



    Чтобы трассировать локацию, необходимо ее указать. Один из способов - запросить у Npc мир, в котором он находится на данный момент. И далее подставляем наши вектора в функцию поиска пересечений.
    (входными данными являются: начальный вектор, вектор смещения, список игнорируемых объектов и что именно обрабатывать).
    5.png



    Завершающим шагом проверяем, было ли хотя бы одно пересечение и если да, то запрашиваем эту точку. Применяем ее к Npc с небольшим смещением над землей.
    6.png



    Компилируем проект (в VS 2012 F7), подключаем библиотеку в AST, запускаем игру.

    Результат:
    G2AST_15_9_2016_15_41_29_103.png G2AST_15_9_2016_15_41_33_96.png

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


    Исходники кода прилагаю ниже.
     

    Вложения:

    Последнее редактирование: 16 сен 2016
  2. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    1.2 Простая привязка к магической системе. (SDK)

    В предыдущем примере рассмотрен оптимизированный вариант вызова трассировки лучей по конкретному событию.
    Для следующего урока немного видоизменим нашу функцию и приведем комментарии к сокращенному виду.

    Изменения:
    1. Прицел поменяем с плюсика на крестик. Это не обязательно, но в будущем пригодится.
    2. Трассировка теперь вызывается непрерывно по удерживанию ПКМ.
    3. Таймер перемещен непосредственно к применению позиции персонажа.
    (Задержка -1 замораживает таймер и он не позволяет выполнить блок. T_PRIMARY, вопреки замороженному таймеру, вызывает операцию один раз при первом обращении к объекту)
    1.png


    Добавим зависимость функции от маны персонажа. Пропишем переменную для хранения промежуточного вычитаемого. Значение находим относительно расстояния персонажа до искомой точки. Подгоняем результат под нужны и сохраняем.
    2.png



    Разрешаем телепорт в случае валидного количества маны. При отрицательных результатах трассирования или маны, выведем соответствующие сообщения на экран.
    3.png



    На время перемещения отключаем коллизии. По завершении операции кушаем указанное количество маны.
    4.png




    Завершая урок, напишем трехслойный статусбар стедствами AST.
    1. Основной слой. Относительно него будут отображаться остальные элементы. задаем ему текстуру, позицию на экране и размеры в пикселях.
    2. Добавляем слой, отслеживающий состояние маны по атрибутам персонажа.
    3. Добавляем слой, который будет показывать промежуточное значение маны.
    Его значения будем передавать из функции телепорта.
    5.png



    Результат:
    G2AST_16_9_2016_2_55_16_314.png G2AST_16_9_2016_2_55_20_900.png G2AST_16_9_2016_2_55_27_297.png G2AST_16_9_2016_2_55_30_242.png G2AST_16_9_2016_2_55_38_223.png G2AST_16_9_2016_2_55_43_480.png

    Светло-синяя полоска - динамический индикатор, показывающий как могут измениться показания магической энергии.

    Исходники ниже
     

    Вложения:

    Последнее редактирование: 16 сен 2016
  3. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    Тема 2: Простой менеджер регенерации здоровья

    2.1 Введение и подготовка

    Я на самом деле думал довести до ума заклинание телепортации, навешать туда эффектов, вывести половину функционала в скрипты и тд, но как-то мне не до этого..
    Значит первое: обновить аст и сдк тулзы до 002.
    Второе: опишем план работ.
    Сообразим функцию регенерации, вызываемую при использовании зелий лечения.
    Сама же функция будет выполняться по типу бафа, работающего независимо от основного кода программы.
    Это значит, что целесообразно применить новые методы триггер-функций.
    Также потребуется записывать в сейв данные о степени выполнения регена на случай сейвлода.

    Теперь к коду. Напишем болванку непосредственно процесса регенерации и вызовем его по кейбинду (временно и только для игрока).

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



    Добавим код восстановления, а также набросаем условие, которое будет обязывать триггер самоуничтожиться.
    2.png



    Добавим какой-нибудь лимит восстановления нашему зелью.
    4.png



    Ну и проверим работает ли код.. Для этого идем в цикл и втыкаем в него вызов метода по какой-нибудь удобной кнопке.. базовый костяк у нас есть. остальное будет на следующих этапах...
    3.png
     

    Вложения:

  4. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    2.2 Создание менеджера регистрации функций и получения к ним доступа

    Собственно задержался, да.. сейчас сами поймете почему :)

    Сразу определим новые исходные данные, которые будем использовать в этой теме.
    1.jpg

    Укажем заголовки в stdafx.h
    2.jpg

    Во всех файлах исходного кода добавим включения
    3.jpg

    // -------------------------------------------- ;

    Итак. прежде чем начнем писать баф менеджер, проведем несколько подготовительных мероприятий, нацеленных на избавление нас от внезапных конфузов в процессе кодинга.
    Важная составляющая нашего будущего триггера - связанная с ним функция, НО! Выделю 2 возможные проблемы доступа: 1. у обычной функции может измениться адрес. После загрузки игры моментально словим краш, если до этого ваш плагин был несколько изменен.. 2. сложно обращаться через парсер (из скриптов). Было бы неплохо разобраться и с этим моментом.

    Откроем CDynamicFunc.h и добавляем новый класс. Именно через него и будем оперировать функциями.
    4.jpg

    Укажем свойства: статический лист всех зарегистрированных функций; имя, по которому будет производиться обращение; адрес функции.
    статический список сразу определяем в CDynamicFunc.cpp.

    5.jpg

    Добавляем конструктор класса и 2 метода.
    6.1.jpg

    В конструктор передаем название и адрес.
    метод void* Search возвращает указатель на функцию по ее названию.
    метод zSTRING Search возвращает название функции по ее адресу.
    6.2.jpg

    Идем в CTrigger.h и создаем прототип класса. В файле CTriggerFuncs.h объявляем прототипы будущих функций лечения.
    7.jpg 8.jpg

    Открываем CTriggerFuncs.cpp и определяем функции лечения, но пока оставляем их пустыми.
    За каждой из них закрепляем объекты класса CDynamicFunc, в которые передаем адреса и указываем для каждой свое мнимое имя.
    9.jpg

    С этим закончили. Далее мы можем быть спокойны за наши указатели и доступность вызова из парсера.
     
  5. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    2.3 Менеджер динамических эффектов

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

    1.
    Сообщаем классу, что неписи для нас френдовые, необходимо им предоставлять публичный доступ к данным.
    Создаем массив, в котором будет храниться полный список всех созданных триггеров.
    2.
    структура CTimer будет содержать в себе временны́е интервалы очередного вызова функции, время предыдущей отработки и значение автоматического удаления...
    3.
    сюда пишем адрес вызываемой функции, интервал выполнения цикла и указатель на нпс, для которого мы это все и затеяли.
    1.jpg

    Далее опишем статической метод создания нового триггера. Передаем в него нпс-цель, функцию, задержку (по дэфолту 100), флаг (по дэфолту первичный. аналогично аст таймерам).
    флаг будет определять первое обращение к функции. если это T_PRIMARY, первый вызов произойдет на этапе конструирования объекта.
    2.jpg

    Определим объявленные данные в CTrigger.cpp.
    Обращаю внимание на то, что в конструкторе и деструкторе происходит автоматическое добавление/удаление себя из списка.
    В методе CreareTrigger создаем новый экземпляр триггера. нпс, функцию и задержку просто присваиваем.
    Для m_nEndTime ставим условие, когда начинать вызывать функцию. если ключ T_PRIMARY, обращение произойдет сразу, при первом же обращении к условию. Иначе первый вызов произойдет в соответствии с указанной задержкой.
    3.jpg

    Напишем еще 2 очень важный метода. Именно они будут определять время самоуничтожения функции.
    Напоминаю, что в структуре таймера мы имеем параметр m_nDestroy. Сделаем так: если значение равно -1, этот пункт мы игнорируем и функция будет условно бесконечна. иначе, задаем количество тиков, разрешающих функции выполниться.
    В первом случае мы задаем безусловное количество проходов триггера, после чего он уничтожится.
    Во втором случае - задаем количество проходов по временному отрезку, где зависимость равна отношению задержки ко времени выполнения.
    4.1.jpg 4.2.jpg

    Создадим вспомогательные методы, свойство текущего триггера и новый синоним на указатель на функцию.
    Метод CallFunc будет обращаться к привязанной функции.
    End будет завершать работу текущего триггера и удалять его.
    ClearData избавляется от всех триггеров.
    Определим m_mCurrentTrigger вверху документа.
    5.1.jpg 5.2.jpg 8.jpg

    Теперь пишем два из самых основных метода.
    Это Attach - привязка всех элементов к общему циклу, поочередно вызывая их из списка.
    и Do - обрабатывает триггер и вызывается из цикла. тут мы работаем со временем.
    Определяем, пора ли вызвать функцию триггера.. нужно ли корректировать время в момент паузы..
    Следим за автоудалением.
    6.1.jpg 6.2.jpg

    И последний штрих - метод, принимающий ссылку на массив и заносящий в него все триггеры, принадлежащие для данного нпс;
    а также метод, возвращающий нпс для данного триггера.
    7.1.jpg 7.2.jpg



    // ----------------------------------------------------------------------;

    Переходим к последнему этапу настройки класса.
    Идем в цикл и привязываем триггеры к функции SafeLoop
    9.jpg

    Определим одну из наших триггер функций для теста исправности работы.
    10.1.jpg 10.2.jpg

    // ----------------------------------------------------------------------;

    Этот этап закончен.
     
    MaGoth, gggg12345678!, Gor и ещё 1 пользователь поблагодарили.
  6. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    2.4 Хуки и сохранение триггеров

    Тут мы будем работать с классом CCallBack. именно он позволит нам не только перехватить методы, но и подключиться к уже существующим внутри самого AST. и попутно сообщит о наших ошибках, если таковые будут.
    Для начала зайдем в PluginCallBack.cpp и заведем события начала и завершения определения хуков.
    1.jpg

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

    для справки
    Открыть спойлер
    0.jpg


    Имеем функцию LoadGame, обрати внимание на соглашение __fastcall, сообщающее о том, что аргументы должны приниматься в регистрах, в противном случае при попытке переадресовать функцию __thiscall, которая в свою очередь метод класса, просто напорется на краш. внутри происходит обращение к указателю pLoadGame - это позволяет нам из любого места в коде вызывать оригинальный метод и возвращать его значения.
    1.jpg

    Теперь хукаем. Заводим функцию DetourTrigger и добавляем в нее обращение к CCallBack::Attach - это метод, которых привязывает функцию к объекту.
    в нее передаем ссылку на указатель pLoadGame и функцию переадресации LoadGame.
    Затем идем опять в файл PluginCallBack и посредством слова extern вызываем DetourTrigger.
    2.jpg 3.jpg

    теперь у нас есть функция загрузок. в нее я намерен вставить очистку триггеров. сначала удаляем все данные, потом вызываем оригинальную г2 функцию.
    4.jpg

    далее для работы нам необходимо открыть файл ocnpc.h в директории G2API и в класс oCNpc в самый-самый конец добавить 2 новых метода:
    5.jpg

    создаем конструкцию, аналогичную загрузке мира, но для архиваторов. но помимо этого определяем новые функции npc.
    6.jpg

    добавляем вызов наших аст архиваторов сразу после оригинальных.
    7.jpg

    Перехватываем аналогично загрузке..
    8.jpg

    // ---------------------------------------------------------;

    Теперь необходимо заполнить наши архиваторы данными..
    Archive:

    Чтобы сохранить триггеры в память нпс, нужно как минимум получить их список. на радость мы уже написали такую функцию в CTrigger
    9.jpg

    запишем общее количество триггеров в память. это очень важно! затем начнем попорядку писать их параметры. Но перед работой с архивами, нужно подключить соответствующий заголовок. а для более удобной записи строк зададим соответствие _s.
    10.jpg

    Вычисление времени для сохранения необходимо преобразовать в остатки.. иначе ничего не получится :) а благодаря CDynamicFunc мы можем записать функцию-триггер как текст.
    11.jpg

    ну а дальше просто пишем инфу *right*
    12.jpg

    На этом архивация данных завершена.

    // ---------------------------------------------------------;
    Unarchive:

    Самым первым делом читаем количество элементов..
    13.jpg

    читаем исходные значения
    14.jpg

    далее методом CDynamicFunc ищем функцию и создаем на ее основе триггер.
    14.jpg

    используя обратные архиватору формулы получаем значения таймера.
    15.jpg

    --------------------------------------------------------------------------------------

    все. теперь все триггеры будут записываться и загружаться.

    P.S. вместо Archive_AST в примере написано Archive. потом поправлю.
     
    Siemekk, Gor, MaGoth и 2 других пользователей поблагодарили.
  7. Gratt

    Gratt
    Модостроитель

    Регистрация:
    14 ноя 2014
    Сообщения:
    834
    Благодарности:
    874
    Баллы:
    135
    Пол:
    Мужской
    2.5 Внешние функции

    Под внешними функциями подразумеваются ключи в парсере, при обращении к которым вызываются методы движка, целиком определенные на C++. В функции DefineExternals происходит процесс их регистрации, следовательно нам необходимо ее перехватить.
    При попытке хукнуть методом Attach, AST сообщит об ошибке переопределения, поскольку в головном модуле эта функция уже перехвачена. Открываем Externals.cpp, нам необходимо задействовать метод AddCallback. Он подключит нас к уже существующей переадресации..
    1.jpg 2.jpg

    Создадим внешнюю функцию регистрации триггеров. для этого определим ее в файле Externals.cpp
    3.jpg

    Сразу попробуем сообразить, какие аргументы должна иметь наша функция.
    1. нпс-цель
    2. название триггер-функции
    3. задержка
    4. лимит выполнения
    5. тип выполнения (по тикам, по времени)
    Объявляем переменные для нового триггера.
    4.jpg

    Далее запрашиваем у парсера параметры в обратном (!) порядке.
    5.jpg

    Получаем указатель на функцию по имени и ставим проверку валидности самых важный данных.
    6.jpg

    Создаем триггер и задаем ему лимиты.
    7.jpg

    Регистрируем функцию в парсер: название функции, сама функция, тип, аргументы (instarce нашего нпс, string нашей CDynamicFunc, int задержка, int лимит, int тип лимита) и на конце 0 (обязательно. так мы понимаем конечную точку указателя)
    8.jpg

    // --------------------------------------------------------------;

    объявим наши триггер-функции френдовыми для класса CTrigger
    12.JPG

    Идем в CTriggerFuncs и начинаем описывать функции.
    1. функция 01 будет обычный циклом регена здоровья, в котором лечение происходит исключительно при условии жизнеспособности персонажа. иначе произойдет принудительное удаление..
    9.JPG

    2. функция 02 - яд. отрава, жрущая здоровье до тех пор, пока персонаж не помрет..
    10.JPG

    3. функция 03 - противоядие. не позволяет отравлять персонажа, удаляя указанные в нем триггеры..
    11.JPG

    // --------------------------------------------------------------;

    Идем в директорию GothicSourcer 3.15+ в директорию system и открываем ExternalFuncs.d. В конец добавляем нашу функцию..
    13.JPG

    В решении для готики создаем тестовые инстанции зелий и присваиваем каждому эффект..
    14.1.JPG 14.2.JPG 14.3.JPG 14.4.JPG 14.5.JPG

    для удобства добавляем инстанции в инвентарь гг
    15.JPG

    И тестируем..


    // --------------------------------------------------------------;

    Заметки:

    адреса функций
    0x00746470 protected: virtual void __thiscall oCNpc::Archive(class zCArchiver &)
    0x00747230 protected: virtual void __thiscall oCNpc::Unarchive(class zCArchiver &)
    0x006C67D0 public: void __thiscall oCGame::LoadSavegame(int,int)
    0x006D4780 private: void __thiscall oCGame::defineExternals_Ulfi(class zCParser *)

    Исходники:
     

    Вложения:

    • Сорсы.zip
      Размер файла:
      7,9 КБ
      Просмотров:
      40
  8. Saturas

    Saturas
    Модостроитель

    Регистрация:
    11 фев 2009
    Сообщения:
    1.880
    Благодарности:
    573
    Баллы:
    275
    Пол:
    Женский
    Очь круто!
     
Статус темы:
Закрыта.

Поделиться этой страницей