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

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Внимание!
    — Требуется примерно по 3-5 человек на каждую из версий ОС:: - Windows® XP SP3, Windows® Vista SP2, Windows® 7 SP1, Windows® 8, Windows® 8.1, Windows® 10(build 10 1607) и Windows® 10(build 10 1703). Для стационарных ПК и ноутбуков. Заявку на участие можно оставить здесь...

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

Статус
В этой теме нельзя размещать новые ответы.

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
Тема 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

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


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

Вложения

Последнее редактирование:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
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

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

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

Вложения

Последнее редактирование:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
Тема 2: Простой менеджер регенерации здоровья

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

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

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

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



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



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



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

Вложения

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
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

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

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
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

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

Этот этап закончен.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
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. потом поправлю.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
1.350
Благодарности
1.515
Баллы
255
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 *)

Исходники:
 

Вложения

Saturas


Модостроитель
Регистрация
11 Фев 2009
Сообщения
1.936
Благодарности
624
Баллы
275
Очь круто!
 
Статус
В этой теме нельзя размещать новые ответы.
Сверху Снизу