Итак, прежде чем начать разговаривать непосредственно о партийце, рассмотрим какие знания нам понадобятся.
Если вы абсолютный новичок в моддинге, то первым делом вам нужно ознакомиться со следующими кладезями мудрости.
Какие файлы нам придётся модифицировать в процессе создания нового партийца?
Итак, что такое партиец?
Партиец - это особый NPC в команде Чузена (От англ.Choosen - Избранный,
главный герой), обладающий бОльшими по сравнению с другими NPC
возможностями, управление которым частично осуществляется игроком.
С того момента как партиец присоединяется к команде чузена , он может выполнять некоторые команды, которые даёт ему чузен.
Что нужно сделать, для того чтобы партиец мог выполнять команды? Нужно создать скрипт для этого криттера, и в нём прописать команды, которые требуются.
Если вы не знаете, что такое скрипт, советую вам обратиться к документации "FScript-HOWTO" от Равена. Вы можете найти её на сайте http://www.teamx.ru в разделе "Документация".
Итак, вы прочитали документацию от Равена, вы уже знаете, что такое скрипт, и с чем его едят.
Что делать в первую очередь?
Естественно, прочитав всю документацию от Равена, и зная немало о скриптах, вы сразу же кинетесь писать скрипт.
Неправильно!
Первым делом вы должны составить план:
Кто такой, ваш партиец, что он может о себе рассказать, какой квест
(если таковой имеется) нужно выполнить, чтобы он к вам присоединился.
Так же вы должны составить план действий, которые партиец будет делать, уже состоя в команде чузена.
Прошу учесть, что это только план, т.е. теоретическая часть вопроса. Техническая часть будет потом.
Первым делом мы составляем биографию партийца, дабы он мог рассказать о себе чузену.
Возьмём простой пример. Имя у партийца будет Тэд. Наш партиец родился в деревушке неподалёку от Арройо. Когда на его племя напало враждующее племя, ему едва исполнилось два года, поэтому он не помнит ни матери, ни отца, ни даже названия деревни. Он был воспитан и выращен во вражеском племени, и когда ему исполнилось двадцать лет, наш партиец отправился искать счастья в большом мире.
Повторяю, это только пример. Биография может быть любой.
Теперь, когда мы составили биографию для партийца, и ему есть что ответить на просьбу "Расскажи мне о себе", можно придумать квест для присоединения.
Начнём с маленького. Допустим, наш квест состоит в том, чтобы
принести партийцу пистолет (неважно какой марки, главное пистолет).
Как мы узнаем о том, что мы должны сделать? Правильно! Партиец сам нам
об этом скажет. Следовательно, нужно составить диалог вне команды (в то
время как партиец нам ещё не знаком).
Итак, диалог будет выглядеть примерно так:
Реплика партийца: "Привет."
Здесь чуть-чуть остановимся.
Важно, чтобы на каждую реплику криттера у нас было бы несколько ответов, так как Fallout отличается нелинейностью сюжета.
Наши варианты ответов:
Следующая реплика партийца: "Я Тэд, а ты кто?"
Наши ответы:
Теперь обратим внимание на реалистичность диалога. Если партиец уже рассказал нам кто он такой, нелогично будет, что мы спрашиваем его ещё раз "Привет, кто ты такой"... Следовательно, нужно продумать и этот случай. Итак, если партиец уже рассказывал нам о себе, и мы отказались с ним разговаривать, а потом возобновили диалог, то партиец будет нас спрашивать "Так кто ты?".
Вот тут то и пригодилась нам биография партийца!
Следующая реплика партийца: "Биография."
Наши ответы:
Вариант возобновления диалога:
Партиец - "Что ты хочешь?"
Чузен - "Не хочешь отправиться со мной? Вдвоём будет легче в этом полном опасностей мире."
Следующая реплика партийца:
Вариант возобновления диалога:
Партиец - "Принеси мне пистолет, и я с тобой пойду."
Наши ответы:
Как вы уже поняли, партиец дал нам задание, которое состоит в том, чтобы принести ему пистолет.Отлично! Получив задание, мы идём за пистолетом, приносим его партийцу, и опять нас ждёт диалог.
Реплика партийца: "Ну, как? Принёс мне пистолет?"
Наши ответы:
Здесь могут быть два варианта реплики партийца.
Реплика на ответ "Да, вот он.": "Спасибо, теперь я могу к тебе присоединиться."
Наши ответы:
Реплика на ответ "Нет, пока нет": "Ну, тогда удачи в поисках."
Наш ответ:
Вроде бы действия партийца вне команды определены. Перейдём к действиям в команде.
Действия в команде:
Итак, партиец в команде, и нам надо придумать какие команды он будет выполнять. Существует несколько обязательных, на мой взгляд, команд. Такие как:Ну, и от себя я добавлю ещё неколько фич. Такие как: выбор оружия(из всего, что есть у партийца) посредством диалога, смена вида партийца(в зависимости от надетой брони) и ещё кое-что.
Для того чтобы сообщить партийцу, что мы от него хотим, нам естественно потребуется диалог.
Что ж, диалог, так диалог. Составляем диалог.
Реплика партийца, когда мы обращаемся к нему (на тот момент, когда он уже в команде): "Что ты хочешь?"
Наши ответы. Ого! Тут ответов много выйдет:
Реплика партийца: Что ты хочешь? Наши ответы если партиец вооружён:
Реплика партийца: "Хорошо, я сделаю это"
Реплика партийца: "Да, я слушаю" Наши ответы:
Когда партиец ждёт :
Реплика партийца: "Что ты хочешь?" Наш ответ :
Реплика партийца: "Пойдём" Наш ответ :
Теоретическая часть закончена, переходим к практической части.
Практическую часть разделим на две части - построение диалогов, и создание процедур.
Для полноценной работы вооружимся утилитой F-geck от Tehnokrat и двумя файлами из Sample Kit для партийца (partyman.ssl - файл скрипта, и partyman.msg - файл диалога)Итак, сами диалоги у нас есть, осталось только прикрепить их к процедурам. Я обычно называю диалоговые процедуры dialog (dialogX), хотя процедура может называться как вы захотите. Главное, когда называете процедуру, называйте её так, чтобы было понятно(хотя бы приблизительно, что в ней происходит.
Для того чтобы прикрепить диалоговые строчки к процедурам нам понадобится msg - файл.
Открываем "FScript-HOWTO" и смотрим, как делается msg - файл.
Наш msg - файл это partyman.msg
Некоторые строчки не встречались в вышеуказанных диалогах, но не беспокойтесь, об их назначении вы узнаете позже.
Далее нам нужно засунуть диалоговые строчки в процедуры, то есть осуществить диалоги программно.
У БИСовцев существуют некоторые стандартные define - замены, как, например:
start_dialog_at_node(x), где х - номер процедуры. Смотрим COMMAND.H строчка 434:
Что делает эта замена? Начинает диалог с криттером, в скрипте которого прописана, с процедуры х.Reply(x), где х - номер строчки в msg - файле, соответствующем нашему скрипту. Смотрим COMMAND.H строчка 493:
Смотрим документацию по скриптам от Wasteland Ghost: void gsay_reply(int msg_list, int msg_num) - выводит реплику НПС Аргументы: msg_list - номер файла msg msg_num - номер строки в файле msg Что делает замена Reply(x)? Выводит в окошко диалога реплику из заданного msg - файла.NOption(x,y,z), где x - номер строки в файле msg, соответствующем нашему скрипту,
y - процедура, к которой осуществляется переход при выборе соответствующего ответа
z - ограничитель интеллекта (если отрицательный, то условие означает
"интеллект меньше или равен", если положительный - "больше или равен")
Нужно так же добавлять и свои дефайны, например:
#define NAME (номер вашего скрипта)
Эта замена не обязательная, но желательная, так как без неё ни одна из вышеуказанных замен работать не будет. Почему? Смотрим выше: gSay_Reply(NAME,x), start_gdialog(NAME,self_obj,4,-1,-1).
Опять же, вы не обязаны пользоваться именно этими заменами. Вы можете сделать свои, типа:
#define Rep(x) gsay_reply(NAME,mstr(x)) #define Opt(x,y) gsay_option(NAME,x,y,-1) #define Dialogue(x,y) start_gdialog(NAME, x, 4, -1, -1);\ gsay_start;\ call y;\ gsay_end;\ end_dialogue
Но тут и далее мы будем пользоваться общепринятым стандартом (для удобства чтения). Вы же не хотите начав читать свои скрипты через год после того как вы их написали, ничего не понимать и по три часа искать какие вы там сделали дефайны ;)
Кстати, думаю вы должны знать, что define - замену необязательно пихать в одну строчку. Вот такая "\" черточка означает перенос. Т.е. когда движок считывает скрипт, он читает это как одну строчку.
Теперь, для того чтобы обозначить реплику у нас есть , Reply(x) где x - номер строки в msg - файле.
Для того чтобы обозначить опцию для ответа у нас есть NOption(x,y,z) , где x - номер строки в msg - файле, а y - имя процедуры, которая начнётся, как только мы выберем этот вариант и z - ограничитель интеллекта.
Ну и start_dialog_at_node(x), где х - номер процедуры, которая используется для начала диалога.
Теперь будем заполнять процедуры.
Нужно поставить проверку на имение\неимение пистолета,и знаешь\не знаешь о квесте. Откуда мы узнаем имеется или нет пистолет в инвентаре? Очень просто.Делаем обычную define - замену (можно и без неё обойтись, но опять же не хочется писать километровые строчки каждый раз)
#define Have_pistol ((obj_is_carrying_obj_pid(dude_obj,PID_DESERT_EAGLE)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_10MM_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_LASER_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_14MM_PISTOL)) \ OR(obj_carrying_pid_obj(dude_obj,PID_PLASMA_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_ALIEN_LASER_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_223_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_NEEDLER_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_PK12_GAUSS_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_YK32_PULSE_PISTOL)) \ OR(obj_is_carrying_obj_pid(dude_obj,PID_MAGNETO_LASER_PISTOL)))
Вы можете прописать всё это в одной строчке, а можете поставить перенос.
Вне партии:
Procedure talk_p_proc begin if((Have_pistol)and(global_var(GVAR_partyman)==4))then set_global_var(GVAR_partyman,5); start_dialog_at_node(Choose_dialog); end
Для квеста нам понадобится глобальная переменная - Гвара. Нашу Гвару мы назовём GVAR_PARTYMAN.
Значит просто придадим два значения к нашей Гваре. Разговаривали насчёт квеста, и согласились выполнить - 4, Выполнили квест - 5.А почему же нельзя придать нашей Гваре другое значение спросите вы? Можно, просто я решил значения 1,2,3 оставить на партию(вне партии\в партии\ждет).
Что происходит в вышеуказанной процедуре? Мы проверяем, если чузен нашёл пистолет, разговаривал насчёт квеста, и уже выполнил его, то придаём нашей гваре значение 5(выполнил квест).
Разбираем процедуру Choose_dialog:
Первые две строчки - проверка(согласился чузен выполнять квест, или
нет). Если согласился, то идёт диалог(выполнил, или нет), но об этом в
своё время.
Далее - если партиец в партии, идёт , соответственно диалог в партии(снять оружие, поменять дистанцию и т.д.)
Далее - если партиец ждёт идёт соответствующий диалог
Далее:
Для того чтобы диалог вне партии не начинался всё время заново
(Представляете, вы уже познакомились с партийцем, а он каждый раз
заговаривает с вами заново "привет, а ты кто". Глупо, не так ли?) нам
нужна проверяющая переменная. Гвару новую регистрировать неохота,
поэтому мы воспользуемся Лварой - локальной переменной. Почему,
спросите вы, мы не пользуемся для проверки обычной переменной -
вариаблой. Всё просто - вариабла обнуляется, когда скрипт перестаёт
действовать. Т.е. когда вы выйдете с локации, и задёте обратно, то
вариабла будет равна нулю.
Итак, если Лвара равна 1, то начинается первая стадия диалога вне
партии(чуть-чуть изменённая), если 2, вторая стадия, если 3, третья.
Значение Лваре мы будем придавать дальше, в соответствующих процедурах.
И наконец, если ни одно из этих действий не состоится, начинается первая стадия диалога вне партии.
Разбираем процедуру Dialog1:
Проходит первая стадия диалога, и Лваре придаётся значение 1. Т.е. если
вы сейчас закончите диалог и заговорите с партийцем заново, начнётся
сразу первая изменённая стадия диалога
Dialog1_a - это та же самая процедура, только после того, как мы уже разговаривали с партийцем (т.е. чуть-чуть изменённая)
Дальше Procedure Dialog2, Dialog2_a, Dialog3, Dialog3_a, Dialog4, Dialog4_a, Dialog4_b) объяснять не буду, сами поймёте.
Вот тут, я думаю, стоит приостановиться, и объяснить, что
происходит. Итак, мы дошли до той части диалога, где партиец к нам
присоединяется. Разбираем процедуру Add_to_party:
Party_add(self_obj); - делает криттера партийцем.
critter_add_trait (self_obj, 1, 6, 0); - добавляет партийца в команду
чузена (чтобы если чузена атакуют, партиец не стоял как столб, а тоже
сражался на стороне чузена)
set_global_var(GVAR_partyman, 3); - придаём нашей Гваре значение 3 (в партии)
После того, как партиец присоединился, при попытке заговорить с ним, будет начинатся диалог в партии.
В партии: Смотрим процедуру Dialog_in_party:
Что происходит в этой процедуре? Просто при выборе определённого
варианта ответа, начинается соответствующая процедура. Как видите,
процедура снятия брони доступна лишь тогда, когда броня есть.
Диалог в общих чертах мы набросали, косметические исправления уже в
ваших руках. Прежде чем перейти к созданию процедур, хочу уточнить
несколько детелей. Вы наверное заметили, что во всех NOption
ограничение по интеллекту = 3. Так вот, это НЕОБЯЗАТЕЛЬНО должно
равняться трём. Это по вашему усмотрению. В этих диалогах, например,
персонажу с интеллектом меньше трёх делать нечего :) Но ваши диалоги -
это ваши, а не мои, так что какое ограничение по интеллекту ставить -
это тоже ваше дело :)
Далее...Все диалоговые процедуры тут обозваны оригинальными именами.
Это потому что их здесь мало. Если вы собираетесь делать огромный
диалог с большим количеством реплик и опций для ответов, то делайте как
БИСовцы. Берём процедуру, обзываем её как хотим (в нашем случае это
Node), и нумеруем имена процедур как нам удобно, для того чтобы
построить дерево диалогов(Типа Node1000, Node1001, Node000, Node022 и
т.д. как вам удобно).
И конечно же не забываем о процедурах description_p_proc и
look_at_p_proc. Разумеется, только если вы хотите, чтобы при
рассмотрении вашего партийца, выдавалось описание, написанное вами, а
не PRO-файлом. Для этого нужно всего лишь добавить в наш msg-файл
строчки для описания, и прописать их вывод на экранчик сообщений в
процедурах description_p_proc и look_at_p_proc таким образом:
procedure description_p_proc begin script_overrides; display_mstr(номер вашей строчки в msg - файле); end procedure look_at_p_proc begin script_overrides; display_mstr(номер вашей строчки в msg - файле); end
Теперь перейдём к созданию процедур.
Если вам нужно будет узнавать из другого скрипта состояние партийца(ждёт/в партии/не в партии), то используйте Гвару. При вызове процедуры "Ждать" придавайте Гваре значение, которое вы назначили для состояния "Партиец ждёт", а при заходе в talk_p_proc ставьте проверку на Гвару. Если она равна этому значению, вызывайте соответствующий диалог.
Если вам не нужно узнавать из другого скрипта ждёт ли партиец, можно воспользоваться БИСовскими макросами. Для этого нам нужно:
И всё :)
Итак, как вы уже заметили, в диалоге в партии есть опция с переходом к процедуре Weapon:
Всё просто. Если партиец вооружён, у нас есть варианты ответа "Убрать оружие" и "Взять другое оружие". Иначе, есть только один вариант ответа "Взять оружие".В процедуре Remove_weapon:
inven_unwield(self_obj); - приказ криттеру убрать всё оружие/предметы из рук.Процедура Choose_Weapon:
С этой процедурой вы сейчас разобраться не сможете, мы вернёмся к ней позже. А пока объясню саму идею.Представьте себе как будет выглядеть выбор оружия из инвентаря...Допустим партиец будет говорить нам "Какое оружие мне взять?", а в окошке ответов будут названия оружия, которое есть у партийца.
Как это сделать? Если у него будет, допустим, 20 различных пушек, все названия не вместятся в одно окошко ответов. Соответственно нужно несколько окошек. Осуществлять переход между ними будем с помощью вариантов ответа "Ещё" и "Назад". В каждом окне будет 5 названий оружия. Для того, чтобы знать сколько окон нужно будет всего, мы сделаем специальную процедуру. И назовём её get_self_weapons_count. Но перед тем, как смотреть какое оружие есть у партийца, мы должны знать какое оружие есть вообще в игре. Для этого мы также создадим процедуру. Итак процедура weapons_pid_array:
Эта процедура немножко отличается от тех процедур, которые мы делали раньше. Она получает номер, и зависимости от него, с помощью команды return возвращает PID оружия. Всего оружия в игре 99 штук. Но это включая "Коготь Смертокогта", пушку робота, то есть те предметы, которые обычный человек взять не может. Поэтому в этой процедуре указано всего 93 оружия. Вы не обязаны указывать в этой процедуре все 93 оружия. Если вы не хотите, чтобы какое-то оружие высвечивалось в опциях выбора оружия, просто е указывайте его тут :) Для чего нам понадобилась именно процедура с параметрами, вы увидите дальше.
Итак, следующая процедура - get_self_weapons_count:
В этой процедуре у нас есть две локальных скриптовых переменных i и total_cnt, и одна глобальная скриптовая переменная pages. Почему глобальная? Потому что она нам понадобится не только в этой процедуре, но в других местах скрипта.
Переменные:
i - это своего рода счётчик
total_cnt - это общее количество оружия
pages - это число страниц(в диалоге), нужных для того, чтобы показать весь ассортимент оружия.
Что происходит в процедуре:
Начинается цикл while со значениями i=1 и total_cnt=0.
Потом идёт проверка - если у объекта есть оружие с ПИДом, возращаемым функцией weapons_pid_array получающей i = 1,
добавляем к total_cnt 1.
Затем добавляем к i 1, и проверяем для этого i.
Цикл продолжается до тех-пор пока i меньше 94, то есть проверяет все 93
оружия, а когда значение i доходит до 94, цикл прекращается.
Затем расчитывается кол-во страниц, нужных для того, чтобы показать всё это оружие:
pages:=total_cnt/5;
Общее кол-во оружия разделить на 5 (5 опций ответа).
А что если будет 67 оружий? Ведь если делить без остатка(а именно так движок делит числа), то 67/5 = 13. И ещё два оружия мы не увидим. Для того чтобы исправить это положение, пишем следующее:
if(total_cnt%5) > 0 then pages:=pages+1;
Что это значит? Знак % означает остаток, то есть алгоритм этой формулы будет таков:
Если (остаток от total_cnt/5) > 0, добавляем ещё одну страницу.
Для перехода между страницами, как я уже говорил, мы будем использовать опции "Ещё" и "Назад".
При выборе опции "Ещё" будет вызываться процедура inc_page:
При выборе опции назад будет вызываться функция dec_page:
cur_page - страница, на которой мы сейчас находимся. Каждый раз при начале диалога, её значение будет выставляться cur_page:=1.
Для возврата в диалог партии без выбора какого-либо оружия, будет опция "Ничего, забудь", доступная только с первой страницы.
Перед тем как показать людям, которые будут играть в ваш мод, весь спектр оружия, который имеется у партийца, давайте чуть-чуть поразмыслим...Допустим партиец будет мутант. Разве мутант может взять в руки small gun? Нет, не может. Не логично тогда будет показвать в диалоге опцию выбора этого оружия. То есть конечно можно показать, но зачем? Ведь даже если мы прикажем ему взять в руки small gun (выберем именно эту опцию ), выйдя из диалога, мы обнаружим, что ничего не произошло. Так как сделать так, чтобы именно то оружие, которое он не может взять, не показывалось в диалоге, и чтобы мы могли сами решать, каким оружием он не может пользоваться? Очень просто :) Мы просто не укажем это оружие в weapons_pid_array.
А теперь представьте себе другую ситуацию. Допустим вы не хотите чтобы партиец умел пользоваться гаусской. Соответственно вы не укажете её в weapons_pid_array. Но если вы выберете оружие через Приказ>Взять лучшее оружие, то партиец возьмёт гаусску. Как же запретить ему брать гаусску? На первый взгляд никак - двиг мешает. На второй взгляд, как всегда, всё очень просто :) Если не дано убрать действие, убираем последствия. Сделаем ещё одну процедуру по примеру weapons_pid_array, только она будет включать в себя запрещённое оружие (оружие, которое партиец не сможет взять в руки). Назовём её forbidden_weapon_pid_array:
В этой процедуре вы можете вставлять любое оружие, какое вам заблагорассудится :) Я вставлял наобум. Теперь нужно только осуществить проверку. Итак, в конце процедуры talk_p_proc вызываем процедуру ээ...назовем её check_forbidden_weapon:
Что делает процедура? Проверяет есть ли в списке запрещённого оружия то оружие которое партиец держит в руке, и если да, то убирает его в инвентарь и выводит float "Я не умею пользоваться этим оружием".
Ну а теперь, самая сложная, но одновременно самая важная процедура - вывод опций ответов на экран.
Как вы помните, в процедура Choose_Weapon есть такая строка: call add_weapon_options(x);
Что же это за процедура add_weapon_options? Рассмотрим её поподробней:
Начинается цикл. Что он делает?if (counter==(num+((cur_page-1)*5)))then begin
Рассмотрим пример. Допустим num=1, т.е. это будет первая по счёту опция выбора оружия.
Обе стороны будут равны, только когда:
Что общего между всеми этими значениями переменной counter?
А то, что все они будут высвечиваться первой опцией в диалоге выбора
оружия. То есть на первой странице будет высвечиваться оружие, при
котором counter=1, на второй странице, оружие, при котором counter=6,
но все они будут первой опцией на странице.
И это мы увидим в следующих строчках:
if num==1 then gsay_option(-1, obj_name(self_item(weapons_pid_array(i))),WeaponOption1Selected, -1); else if num==2 then gsay_option(-1, obj_name(self_item(weapons_pid_array(i))),WeaponOption2Selected, -1); else if num==3 then gsay_option(-1, obj_name(self_item(weapons_pid_array(i))),WeaponOption3Selected, -1); else if num==4 then gsay_option(-1, obj_name(self_item(weapons_pid_array(i))),WeaponOption4Selected, -1); else if num==5 then gsay_option(-1, obj_name(self_item(weapons_pid_array(i))),WeaponOption5Selected, -1);
Как вы видите, при разных значениях num, вызываются разные процедуры.
Теперь, если взглянуть в предыдущую процедуру, а точнее
Choose_Weapon, то мы увидим, что num=1 это первая опция, num=2 это
вторая опция, и т.д.
А уж какое именно оружие будет высвечиваться в этой опции, напрямую зависит от страницы, на которой мы находимся.
Далее...смотрим доку от Wasteland Ghost:
void gSay_Option(int msg_list, int msg_num, procedure target, int reaction) - вывести вариант ответа в диалоге без проверки интеллекта Аргументы: msg_list - номер файла msg msg_num - номер строки в файле msg target - процедура, к которой осуществляется переход при выборе соответствующего ответа reaction - эмоциональная реакция: GOOD_REACTION (49) NEUTRAL_REACTION (50) BAD_REACTION (51) Возвращаемое значение: нет
Значит у нас это будет выглядеть так:
gsay_option(Без msg файла, название оружия ,WeaponOptionXSelected , Без реакции);
Всего, как вы уже видели есть 5 таких процедур: WeaponOption1Selected, WeaponOption2Selected, WeaponOption3Selected, WeaponOption4Selected, WeaponOption5Selected.
И отличаются они только в одном месте.
Перейдём к рассмотрению WeaponOptionXSelected(где Х - число от 1 до 5 включительно)
В этой процедуре мы повторяем то действие которое произвели в
add_weapon_options, но тут мы уже присваиваем переменной Pid_weapon ПИД
выбранного оружия.
Что происходит в целом? В процедуре Choose_Weapon мы выбираем одну из
любезно предоставленных на процедурой add_weapon_options опций, и, в
зависимости какую опцию мы выбрали(1,2,3,4 или 5) переходим к процедуре
WeaponOption(1,2,3,4 или 5)Selected, и получаем глобальную скриптовую
переменную Pid_weapon, которая равна ПИДу того оружия, которое мы
выбрали.
Теперь осталось вооружить партийца оружием с этим ПИДом в talk_p_proc.
Procedure talk_p_proc begin Pid_weapon:=0; cur_page:=1; call get_self_weapons_count; start_dialog_at_node(Choose_dialog); if(Pid_weapon!=0)then begin inven_unwield(self_obj); wield_obj(self_item(Pid_weapon)); end call Check_forbidden_weapon; end
Для чего обнуляем Pid_weapon, спросите вы? Объясняю.
Представьте себе такую ситуацию. Вы сказали партийцу взять в руки
дробовик, закончили диалог, всё прекрасно - партиец взял дробовик. И
пошли вы с ним мочить рейдеров. Посередине боя, у него, как назло,
закончились патроны. Партиец, естественно(не будь дурак), берёт в руку
нож, и начинает кромсать рейдеров. Замочили вы их, случайно нажали на
партийца. Естественно завязался диалог. Но при выходе с диалога партиец
должен взять в руки дробовик, так как Pid_weapon - всё ещё ПИД
дробовика. Ну разве это логично? Начинаете говорить с партийцем, у него
в руках нож, а заканчиваете(не приказав при этом ничего) - дробовик.
Дабы избежать этого мы обнуляем Pid_weapon.
Да, если вы не хотите ставить запретное оружие (ака
Check_forbidden_weapon), то имеем проблему. Небольшую и решаемую, как
всегда :) Представьте себе, что ваш партиец, допустим, мертвяк. Мертвяк
не может взять в руки пулемёт, так как не имеет подходящей анимации,
поэтому, когда вы прикажете взять ему пулемёт (ну укажете по ошибке в
weapons_pid_array) то ничего не произойдёт. А игрок, будет в
недоразумении смотреть на экран, а потом постить на форуме сообщения о
"БАГАХ" :) Дабы избежать этого, ставим такую простую проверку в конце
talk_p_proc:
if((critter_inven_obj(self_obj,INVEN_TYPE_RIGHT_HAND)==0)and(Pid_weapon!=0)) then floater(0);
Поняли как действует? Нет? Тогда объясняю. Если криттер не вооружён, но тем не менее Pid_weapon не равен нулю( т.е. мы приказали партийцу взять кое-какое оружие в руки), выводим "плавающее" сообщение, в котором говорим несведущим игрокам, что мол извините, но анимации для данного криттера с данным оружием не нарисовали. Вот и всё :) Ниже, после того как вы разберётесь с сменой внешнего вида в зависимости от надетой брони, мы с вами рассмотрим ещё один случай такого типа, но чуть-чуть посложнее.
Для того чтобы снять броню нужно тоже сначала поставить проверку, надета ли вообще броня.
Проверка:
if (critter_inven_obj(self_obj, 0)!=0) then begin
Далее, чтобы не писать километровые строки, советую создать вспомогательную переменную.
Variable restock_obj;
Далее следует сама процедура
restock_obj := critter_inven_obj(self_obj, 0); // возвращает указатель на объект в инвентаре. Если его там нет, возвращает 0.
Здесь стоит остановиться для объяснения. Дело в том, что в Фалауте
нету функции, которая помещала бы объект с указанного слота в
инвентарь. Так что же приходится делать? Приходится сначала удалять
объект из слота и затем создавать его в инвентаре.
Вот зачем нам понадобилась вспомогательная переменная. Чтобы запоминать
объект который был в слоте, дабы потом поместить ТОТ-ЖЕ САМЫЙ объект в
инвентарь.
А то представляете, снимаете вы с партийца Power Armor, а в инвентаре оказывается кожаная куртка :-) Устраивает такой вариант?
rm_obj_from_inven(self_obj, restock_obj); // удаляет объект из инвентаря add_obj_to_inven(self_obj, restock_obj); // добавляет объект в инвентарь end
Осталось только добавить это в процедуру, к которой ведёт опция ответа "Сними броню" и со снятием брони мы закончили :)
Мы все прекрасно знаем, что БИСовцы не сделали в Фалауте смену внешнего вида(анимации) в зависимости от надетой брони. Если вы вдруг захотите добавить к вашему партийцу и такую функцию, то флаг вам в руки. Как это делается, я сейчас вам объясню.
Начнём с малого. Что есть смена анимации и как менять анимацию? Смена анимации это ни что иное, как смена базового ФИДа (идентификатор фрейма).Список ФИДов, хоть и неполный вы можете найти в заголовке ARTFID.H. Неприсутствующие там ФИДы (например ФИД Вика, или Ленни) вы можете вытащить из прошника криттера, используя HEX-эдитор или официальный маппер от БИСовцев. Поподробнее можно прочитать об этом на сайте TeamX. Для смены анимации существует макрос от metarule3. metarule имеет ещё множество функций, но в данном случае сменяет анимацию криттера. Сам макрос выглядит так:
art_change_fid_num(ObjectPtr who, int fid) - изменить базовый номер FID (идентификатора фрейма) Аргументы: who - указатель на криттера идентификатор фрейма (см. ARTFID.H)
Вопрос номер два. Где ставить смену анимации. Давайте порассуждаем. Когда сменяется анимация и партийца? Правильно! Когда мы говорим ему одеть другую броню. Когда мы ему говорим одеть другую броню? Когда находимся в режиме диалога. Значит смену анимации логичнее всего ставить по выходу из диалога - в конце процедуры talk_p_proc.
Теперь о главном. Как всё это обрисовать на деле:
Смотрим весь блок begin .... end идущий после строки if(critter_inven_obj(self_obj,INVEN_TYPE_WORN)!=0)then (см. talk_p_proc)Я уверен, что вы уже разобрались во всём сами, но всё таки объясню, что тут происходит. Если на партийце надета броня, то мы начинаем проверки. Если pid_armor(ПИД надетой брони) равен ПИДу кожаной или улучшеной кожаной брони, Art_fidM получет значение ФИДа персонажа в кожанной броне. Зачем у на две переменных, Art_fidM и Art_fidF? Вам это необязательно, всё зависит от того кем будет ваш партиец, мужчиной или женщиной. Я же показываю тут универсальное решение, и для мужчины, и для женщины. Итак, Art_fidM получает значение мужского ФИДа, а Art_fidF получает значение женского ФИДа. Дальше по аналогии смотрите сами: те же проверки стоят для робы, кожанной куртки, металлической брони, боевой брони, брони БС и брони АНКЛАВа. MyFID - это исконный ФИД криттера.
Далее мы используем наш макрос:
art_change_fid_num(self_obj,Art_fidF) если женщина и art_change_fid_num(self_obj,Art_fidM) если мужчина.
Всё, смена брони сделана, осталось лишь рассмотреть один момент.
Представьте себе опять, что ваш партиец мертвяк. Мы уже разбирали
тот случай, когда мы пытаемся дать ему оружие на которое не нарисована
анимация. Теперь рассмотрим случай посложнее. Допустим вы одели
партийца в броню БС и дали ему в руки гранатомёт. Но вдруг, совершенно
внезапно, вам эта броня понадобилась, и вы приказываете партийцу её
снять. Что происходит? Правильно, меняется анимация. Но ведь нет
анимации мертвяка с гранатомётом в руках. Что же происходит в этом
случае? Партиец просто пропадает. То есть совсем, навсегда и
бесповоротно. Как это предотвратить? Нужно поставить проверку. Если
анимация у партийца мертвяческая, а оружие в руках не мертвяческое,
тогда вывести всё то же "плавающее" сообщение.
Проверку на всё оружие, неподходящее под эту анимацию, делать долго,
нудно и непрактично. Объясняю способ покороче. Вы знаете почему файлы
анимации (FRM) названы так и не иначе? Не знаете, тогда советую вам
научиться пользоваться программой FRMid от Wasteland Ghost и прочитать
документацию к ней. Вкратце, я вам скажу, что каждая цифра в ФИДе что
то значит. Так, если первые 6 цифр ФИДа равны:
167772 - в руках у криттера пусто
167813 - оружие с анимацией ножа
167854 - оружие с анимацией дубинки (дубинка, гаечный ключ и т.д)
167895 - оружие с анимацией молота
167936 - оружие с анимацией копья
167977 - оружие с анимацией пистолета
168018 - оружие с анимацией пистолет-автомата
168059 - оружие с анимацией ружья(автоматы, ружья и т.д)
168100 - оружие с анимацией лазерного ружья (лазерное ружьё, плазменноё ружьё, огнемёт и т.д.)
168141 - оружие с анимацией пулемёта (все пулемёты)
168182 - оружие с анимацией гранатомёта
Значит, зная какая анимация есть у криттера (а узнать мы это можем используя FRMid и папку critter.dat/art) мы просто ставим проверку:
if(Art_fidM==MyFID)then if (obj_art_fid(self_obj)/100==168182)OR(obj_art_fid(self_obj)/100==168100)then begin inven_unwield(self_obj); art_change_fid_num(self_obj,Art_fidM); floater(0); end
Где MyFID - это ФИД мертвяка в нашем случае. Что мы видим? В этом примере, если ФИД равен ФИДу мертвяка, идёт следующая проверка. Если мертвяк-партиец держит в руках оружие с анимацией лазерного ружья или гранатомёта (чего быть не может) то убираем оружие в инвентарь и выводим всё тот же float.
Всё, все остались довольны, партиец не исчез :) В проверку вы можете добавлять любые из 11 чисел представленных выше.
Итак. Для смены дистанции мы определили три положения.
Чтобы осуществить движение партийца вслед за чузом и смену
дистанции, у нас имеется два способа. Первый способ соответствует
принципу программиста "Если работает, то внутрь лучше не лезть":
Следуем по стопам БИСовцев. Вообще, несколько отступая от темы, скажу
вам, используйте БИСовские заголовки(файлы с расширением .h) на полную
катушку! Значительно сокращает работу. Итак, идём по стопам БИСовцев, а
конкретнее, создаём две Лвары (локальных переменных):
#define LVAR_FOLLOW_DISTANCE (4) #define LVAR_WAITING (5)
Для чего? Для того :) Хотите разобраться как они работают, велкам ту party.h. А следуя принципу программиста, мы просто делаем следующие вещи:
Всё. Всё должно работать. Только не забудьте эти Лвары объявить, т.е. в scripts.lst добавить 2 к значению local_vars.
Способ второй специально для мазохистов. Если мы хотим во всём разобраться и всё сделать сами, то создаём две обычных переменных the_range:=5; и the_dist:=5; и прописываем в critter_p_proc несколько строчек кода (см. закомментированный код в critter_p_proc)
Объясняю что там происходит. the_range - на каком расстояни от чуза держаться, the_dist - на каком расстоянии от чуза останавливаться.
Разбираемся с кодом:
Current_Distance_From_Dude - макрос. Смотрим Command.h:
#define Current_Distance_From_Dude tile_distance_objs(self_obj,dude_obj)
anim_busy(ObjectPtr who) - проверка состояния анимации объекта Аргументы: who - указатель на объект Возвращаемое значение: TRUE - если объект в данный момент анимируется, иначе - FALSE
tile_num_in_direction(int start_tile, int dir, int distance) - номер гекса, расположенного на заданном растоянии, в заданном направлении от указанного гекса Аргументы: start_tile - номер гекса (связь с координатами x, y: hex = 200 * y + x) dir - направление (0...5) distance - расстояние (в гексах) Возвращаемое значение: если аргумент distance отличен от нуля, то возвращает номер гекса (связь с координатами x, y: hex = 200 * y + x), иначе возвращает -1
dude_tile - макрос. Смотрим Command.h:
#define dude_tile (tile_num(dude_obj))
Run_Away_From_Dude_Dir - макрос. Смотрим Command.h:
#define Run_Away_From_Dude_Dir Get_Rotation_Away_From_Dude(self_obj) #define Get_Rotation_Away_From_Dude(x) rotation_to_tile(tile_num(dude_obj),tile_num(x)) rotation_to_tile(int srcTile, int destTile) - направление к гексу destTile от гекса srcTile Аргументы: srcTile - номер гекса "от" (связь с координатами x, y: hex = 200 * y + x) destTile - номер гекса "к" (связь с координатами x, y: hex = 200 * y + x) Возвращаемое значение: направление (0...5)
animate_run_to_tile(int tile) - анимация бега на заданный гекс Аргументы: tile - номер гекса (связь с координатами x, y: hex = 200 * y + x) Возвращаемое значение: нет Примечание: макрос от animate_move_obj_to_tile(self_obj, tile, ANIMATE_RUN)
animate_move_to_tile(int tile) - анимация "перейти на заданный гекс" Аргументы: tile - номер гекса (связь с координатами x, y: hex = 200 * y + x) Возвращаемое значение: нет Примечание: макрос от animate_move_obj_to_tile(self_obj, tile, ANIMATE_WALK)
self_distance_from_dude - макрос. Смотрим Command.h:
#define self_distance_from_dude tile_distance(self_tile, dude_tile) #define self_tile (tile_num(self_obj)) #define dude_tile (tile_num(dude_obj))
Итак, если партиец в партии, то:
Если расстояние от чуза больше чем должно быть, то:
Если партиец не анимируется то:
dest_tile(вспомогательная переменная, объявлена в Command.h) равна:
Тут надо приостановиться и разобрать эту строчку поподробнее.
tile_num_in_direction(dude_tile, Run_Away_From_Dude_Dir, the_dist)
Что нам вернёт сиё действие? Номер гекса. Какого? Расположенного на расстоянии the_dist от чуза, по направлению от чуза к партийцу. Т.е. даст нам номер гекса, на котором партиец должен остановиться в случае остановки чуза.
Переходим к следующему этапу строчки dest_tile :
tile_num_in_direction(tile_num_in_direction(dude_tile, Run_Away_From_Dude_Dir, the_dist), random(0, 5), random(0, 2));
Что возвращает действие? Номер гекса, расположенного на расстоянии
от 0 до 2 по рандомному направлению(одному из шести) от того гекса, на
котором партиец должен остановиться в случае остановки чуза.
Далее, если расстояние между партиец находится на расстоянии в два раза
большем чем the_range или большем, то воспроизводим анимацию бега к
вышеуказанному гексу.
Иначе, воспроизводим анимацию ходьбы к тому же гексу.
Если партиец не анимируется то:
Если расстояние между гексами чуза и партийца меньше расстояния между
гексом партийца и гексом dest_tile, то отчищаем список анимаций.
Теперь остаётся в процедурах изменения дистанции придать нужные
значения переменным the_range и the_dist. Это уже на ваше усмотрение
Для того чтобы подлечиться существует такая простая функция(точнее define - замена), как obj_heal.
То есть если вы хотите, чтобы партиец подлечился, просто пишете
obj_heal(self_obj);
Как действует данная процедура?
Находим в party.h такую строку :
#define obj_heal(who)
Смотрим весь дефайн. Что же тут происходит?
Смотрим алгоритм:
Если \ У объекта есть суперстимпак, значит начать \ Погасить экран \ Восстановить экран \ PartyHealingItem получает значение указателя на объект(суперстимпак)\ Использовать объект (PartyHealingItem) на объекте (Наш объект)\ Сообщение на отладочном экране (наш объект использовал суперстимпак) \ Закончить
Далее идёт else if , и так по всем предметам, которыми можно восстановить жизнь.
Теперь, когда вы знаете как делать все главные( и дополнительные) процедуры для создания партица, открою вам маленький секрет. Если вы хотите сделать несколько партийцев с одной и той же структурой процедур, вам вовсе необязательно писать для каждого в отдельности все процедуры. Как это сделать легче? А так:
Пишем коротенькие скрипты для партийцев, и компилируем с помощью исправленного party.h
Для того, чтобы создать полноценного партийца, нам, кроме скрипта и истории, нужно ещё как минимум две вещи:
[Fallout2 root]
| - dev
| | - proto
| | - critters
| | - items
| | - misc
| | - scenery
| | - tiles
| | - walls
| - data
| - proto
| | - critters
| | - items
| | - misc
| | - scenery
| | - tiles
| | - walls
| - text
| - english
| - game
Замечание: Часть папок может быть создана программой установки Fallout2.
2. Распаковать из master.dat файлы
proto\CRITTERS\critters.lst
proto\ITEMS\items.lst
proto\MISC\MISC.LST
proto\SCENERY\scenery.lst
proto\TILES\TILES.LST
proto\WALLS\walls.lst
text\english\game\pro_crit.msg
text\english\game\pro_item.msg
text\english\game\pro_misc.msg
text\english\game\pro_scen.msg
text\english\game\pro_tile.msg
text\english\game\pro_wall.msg
и поместить их в соответствующие подпапки [Fallout2 root]\data
Замечание: Было обнаружено, что если используется патченная
версия Фоллаута и в папке с fallout2.exe находится patch000.dat, то для
устранения несоответствий в ресурсах требуется наличие такого же файла
в папке с mapper2.exe.
3. Заменить значение параметра в mapper2.cfg
librarian=1
4. Запустить mapper2.exe
5. Выбрать в какой-нибудь объект в палитре объектов.
6. Нажать клавишу
7. Выбрать самый последний, пустой квадратик и нажать кнопку
8. Настроить параметры объекта и нажать кнопку
9. В папке data\proto\... появится НОВЫЙ PRO-файл. Кроме этого он будет зарегистрирован в LST-файле и описан в MSG-файле.
10. В папке dev\proto\... будет создан текстовый файл с информацией из нового PRO-файла.
Теперь, когда мы настроили маппер для работы с прошниками, создаём четыре новых прошника.
При создании прошника, нас интересуют слежующие параметры:
Name - имя объекта
Advanced - тут мы выставляем хар-ки криттеру
Gender - тут пол и перки
Flags - специальные свойства объекта
Head FID - говорящая голова (анимированный фон во время разговора)
Body Type - тип существа (двуногое, четвероногое и робот)
Action Flags - свойства действий, которые можно произвести над объектом
Critter Flags - особые свойства криттера
AI Packet - искутвенный интеллект, о нём мы поговорим позже.
Team num - номер комнды, в которой состоит объект (в нашем случае это 1 - команда чуза)
Exp.Value - опыт, который чуз получит, когда убьёт криттера
Barter - возможность торговать (YES/NO)
Kill type - тип смерти (используется для сводной таблицы убитых игроком существ)
Crit.Bonus - бонус критических повреждений.
Dmg type - тип повреждения (Это уже зависит от вашего партийца, кто он - робот, человек, турель и т.д. :)
HP Bonus - бонус к здоровью
AP Bonus - бонус к очкам действий
Melee dam - урон в рукопашке
DM res - устойчивость к повреждениям
DM thr - порог повреждений
Если хотите узнать об этом поподробнее, читайте доку "Формат PRO-файлов Fallout2". Найти её можно на сайте TeamX.
Создав четыре прошки, нам нужно как то осуществить переход по
уровням (чтобы прошки подменялись одна на другую, при переходе партийца
на более высший уровень). Как это осуществить? С помощью party.txt .
Найти этот файлик вы можете в папке Fallout2\Master.dat\data.
Итак, что из себя представляет party.txt? А представляет он следующее:
[Party Member NUM] ; pMPartyMan_PID
NUM - порядковый номер партийца
(при добавлении нового партийца порядковый номер должен быть равен последнему номеру в party.txt + 1)
pMPartyMan_PID - строчка(обычный комментарий), которая помогает нам
найти нашего партийца в MISC.MSG (этот файл находится в папке
text/english/game). Какое отношение имеет MISC.MSG к партийцам? Начиная
со строки 9000 там прописаны реплики для повышения уровня. Прописано
это таким образом(показываю на примере Кэса) :
{9030}{}{Дааа, хорошо вспомнить старые времена...} # pMMacRae_PID
{9031}{}{Xхе! Говорил же я, старый конь борозды не испортит.}
{9032}{}{Похоже, все эти путешествия начинают окупаться.}
{9033}{}{Я выучил парочку новых трюков.}
Заметьте, у Кэса четыре раза повышается уровень, каждому разу
соответствует своя реплика. Реплики идут по возрастанию
последовательно. Для первого повышения реплика 9030, для второго 9031,
для третьего 9032 и т.д. Как движок знает что это реплики Кэса?
Обратите внимание на строчку в party.txt - [Party Member 3]
и на номера реплик в MISC.MSG - {9030},{9031},{9032},{9033}
Что мы видим? Правильно, Party Member 3 и {903Х}. Какая между ними
связь? В MISC.MSG строчки 9000 и далее - это реплики партийцев, а
последующие цифры в числе обозначают пакет партийца в Party.h. Так, у
Кэса номер пакета 3, а номер реплик 903(0-3).9 - обозначение того, что
это реплики партийцев, 03 - это обозначение пакета Кэса, и цифры 0-3 -
это номера реплик. Именно так движок находит нужные реплики.
Итак, если мы хотим, чтобы наш партиец говорил что то умное при
повышении уровня, мы идём в конец MISC.MSG, и прописываем там следующие
строчки (например) :
{9260}{}{Я чувствую прилив сил!} # pMPartyMan_PID
{9261}{}{Ооо! Кажется я становлюсь круче!}
{9262}{}{Я на вершине своих возможностей!}
Номера релик должны быть последовательными. Разумеется "9ХХ0" в MISC.MSG должно быть идентичным с "Party Member ХХ" в party.txt.
Т.е. если мы добавляем партийца с Party Member = 67, то реплики в
MISC.MSG должны нумероваться от 9670 и дальше(9671, 9672, 9673 и т.д.)
party_member_pid= Пид нашего партийца (т.е. первого прошника, без повышения уровней)
Все нижеуказанные значения действительны для поведения "По выбору":
area_attack_mode= возможные для этого партийца варианты атаки (как атаковать).
Существующие значения: always, sometimes, be_sure, be_careful, be_absolutely_sure
attack_who= возможные для этого партийца варианты атаки (кого атаковать)
Существующие значения: whomever_attacking_me, strongest, weakest, whomever, closest
best_weapon= возможные для этого партийца варианты предпочтения оружия
Существующие значения: no_pref, melee, melee_over_ranged, ranged_over_melee, ranged, unarmed, unarmed_over_thrown, random
chem_use= возможные для этого партийца варианты использования наркотиков и медикаментов
Существующие значения: clean, stims_when_hurt_little, stims_when_hurt_lots, sometimes, anytime, always
distance= возможные для этого партийца варианты дистанции
Существующие значения: stay_close, charge, snipe, on_your_own, stay
run_away_mode= возможные для этого партийца варианты побега
Существующие значения: none, coward, finger_hurts, bleeding, not_feeling_good, tourniquet, never
disposition= возможные для этого партийца варианты поведения
Существующие значения: none, custom, coward, defensive, aggressive, berserk
level_minimum= минимальный уровень чуза, при котором партиец начинает получать новые уровни каждые level_up_every уровней чуза.
level_up_every= через какое кол-во уровней чуза у партийца будет
повышаться уровень (прошка будет заменяться на следующую в списке).
Если нету, значение = 0.
level_pids= список Пидов прошек, из которых будут браться данные для
следующих уровней. Прошки должны быть в порядке возрастания по уровню.
Например, если у вас кроме оригинальной прошки есть ещё две прошки для
уровней, допустим, прошка с пидом 16777576 это прошка для первого
уровня, а прошка с пидом 16777267 это прошка для второго уровня, то в
списке Пидов они должны быть написаны в следующем порядке:
level_pids= 16777576,16777267
Заметьте, при поднятии уровня прошки НЕ ЗАМЕНЯЮТСЯ, как думают
некоторые. Прошка остаётся та же, что и в начале, просто данные берутся
из других прошек. Т.е. ПИД, имя и род партийца не меняются, но все
технические данные (скиллы, перки, хар-ки) берутся из прошки с
повышенным уровнем. То есть если вы укажете в party.txt в прошках для
поднятия уровня номер прошки Патрульного Анклава, ваш партиец при
достижении этого уровня будет иметь хар-ки анклавовца :)
Warning: Adding a party member here means you must also add it to party.h!
Это значит, что добавив партийца в party.txt вам нужно прописать
его в party.h во ВСЕХ!!! местах, где перечисляются партийцы. И
перекомпилировать все скрипты, использующие party.h :) Но, об этом НИЖЕ. Набор фалов, с помощью которого вы можете скомпилировать все необходимые скрипты вы найдете тут
Теперь, когда мы добавили нашего партийца в party.txt, нужно сделать
последний штрих. А именно - дать ему мозги :) Тобеж искуственный
интеллект.
Вроде бы всё. Теперь вам остаётся только объединить то, что вы сделали. Заходим в маппер, выставляем у наших новых прошек значение параметра AI packet = PARTY NAME_NPC CUSTOM (где NAME_NPC - это имя нашего партийца, в нашем случае AI packet = PARTY TED CUSTOM), ставим партийца на карту, прикрепляем к нему сделанный нами скрипт и ВПЕРЁД!
Теперь, когда белых пятен в создании нового партийца для вас нету, я
должен вам сказать, что всё, что мы здесь делали (по скриптовой части)
можно сделать гораздо короче. Для этого нужно разобраться с party.h.
Итак,разберём стандартные макросы в party.h. Для того чтобы не
путаться, будем идти по порядку (сверху вниз) и попутно объяснять
действие макросов. Начнём:
#define FOLLOW_DISTANCE_CLOSE (3)
#define FOLLOW_DISTANCE_MEDIUM (6)
#define FOLLOW_DISTANCE_FAR (9)
Это стандартные значения для дистанции (в гексах). То есть когда
дистанция маленькая, то расстояние между партийцем и чузом равно 3,
когда средняя - 6 и когда большая - 9. Если вы измените эти значения,
то вам нужно будет перекомпилировать все скрипты, использующие эти
define'ы. Это действительно и для остальных макросов и define'ов.
Идём дальше...
#define obj_in_party(x)
Стандартный макрос. Проверяет находится ли криттер с ПИДом х в партии.
Возвращаемые значения:
TRUE - если криттер в партии, FALSE если не в партии.
#define Vic_Ptr
#define Myron_Ptr
#define Marcus_Ptr
#define MacRae_Ptr
#define Sulik_Ptr
....и т.д.
Указатели на партийцев. Если вы хотите добавить своего партийца в игру, советую вам прописать его здесь. Например:
#define
TED_Ptr
party_member_obj(PID_TED)
где PID_TED - ПИД вашего партийца.
С помощью этого define'а мы можем сделать множество операций. Ведь если
мы знаем указаьель на объект, мы можем получить и всю остальную
информацию об объекте. НО. TED_Ptr будет равно указателю ТОЛЬКО в том
случае если Тэд в партии. В противном случае TED_Ptr будет равно NULL.
#define Vic_In_Party
#define Myron_In_Partyr>
#define Marcus_In_Party
....и т.д.
Проверки на принадлежность партийцев к партии (простите за каламбур :).
Сюда тоже рекомендуется добавить заново созданного партийца. Например:
#define
TED_In_Party
(TED_Ptr !=0)
Возвращаемые значения:
TRUE - если Тэд в партии, FALSE если не в партии.
Примечание: этот define будет работать только в том случае, если вы
добавили в party.h указатель на своего партийца. См.выше - TED_Ptr.
#define party_size
Возвращает количество партийцев в команде игрока, не учитывая скрытых
членов партии, но учитывая машину (машина тоже считается партийцем).
#define true_party_size
Количество партийцев криттеров, состоящих в команде игрока (не учитывая машину).
#define party_max_formula
Максимальное количество партийцев, которых чуз может таскать за собой.
#define dude_at_max_party_size
Проверка набрал ли чуз максимальное количество партийцев к себе в партию. Если да, то возвращает TRUE, если нет - FALSE.
#define party_size_humans
Подсчитывает кол-во человек в партии. Если ваш партиец человек, то рекомендуется добавить его сюда. Например
#define
party_size_humans
(Myron_In_Party+MacRae_In_Party+.....+TED_In_Party)
Примечание: этот define будет работать лишь в том случае, если вы
добавили в party.h проверку на принадлежность партийца к партии.
См.выше - TED_In_Party.
#define party_size_male
Аналогично вышеуказанному define'у, подсчитывает кол-во мужчин в
партии. Если ваш партиец мужчина, рекомендуется добавить его сюда.
#define party_size_female
Аналогично вышеуказанному define'у, подсчитывает кол-во женщин в
партии. Если ваш партиец женщина, рекомендуется добавить его сюда.
#define party_size_strange
Аналогично вышеуказанному define'у, подсчитывает кол-во нечеловек в
партии. Если ваш партиец нечеловек, рекомендуется добавить его сюда.
#define party_size_biped
Подсчитывает кол-во мужчин+женщин в партии. Сюда ничего добавлять не надо :)
#define Is_Injured(Who)
Проверяет ранен ли криттер Who(Who - указатель на криттера). Если
текущее кол-во жизней криттера меньше чем максимальное, возвращает
TRUE, если нет - FALSE.
#define Injured_How_Much(Who)
Возвращает кол-во очков жизни(HP) на которое ранен объект Who. То есть
кол-во HP, которого на данный момент недостаёт до максимального кол-ва
HP криттера.
#define Is_Crippled(Who)
Проверяет покалечен ли криттер Who(сломана рука, нога). Если покалечен, возвращает TRUE, если нет - FALSE.
#define Is_Blind(Who)
Проверяет ослеплён ли криттер Who. Если ослеплён, возвращает TRUE, если нет - FALSE.
#define Is_Armed(Who)
Проверка вооружён ли криттер. Если вооружён, возвращает TRUE, если нет
- FALSE. Внимание! Не может быть применено по отношению к чузу. Потому
что если криттер невооружён, это значит в его правой руке нету оружия,
а если чуз невооружён, это значит в его обеих руках нет оружия. В
данном define стоит проверка только на правую руку.
#define If_Party_Has_Injured
Проверка повреждён ли кто-нибудь из партийцев. Действует по аналогии с
Is_Injured(Who). Рекомендуется прописать тут и своего партийца.
Например:
#define
If_Party_Has_Injured
How_Many_Party_Members_Are_Injured := 0; \
if (Vic_In_Party) then \
if (Is_Injured(Vic_Ptr)) then \
How_Many_Party_Members_Are_Injured+=1; \
..........
if (TED_In_Party) then \
if (Is_Injured(TED_Ptr)) then \
How_Many_Party_Members_Are_Injured+=1; \
Примечание: обратите внимание, если вы хотите реализовать эту проверку
в скрипте, ни в коем случае не пишите if(If_Party_Has_Injured) then,
так как это выражение само по себе является проверкой. Записывать это
надо в таком виде:
If_Party_Has_Injured then
#define If_Party_Is_Armed
По аналогии с предыдущим выражением. Проверка вооружён ли кто-нибудь из
команды. Рекомендуется добавить сюда своего партийца (по аналогии с
предыдущим выражением).
#define Party_Childkiller_Mask
Проверка имеет ли кто-нибудь из партийцев репутацию детоубийцы. Если имеет, то возвращает TRUE, если нет - FALSE.
#define attempt_place_party_member(the_obj, the_tile, the_elev)
Распологает криттера на заданом уровне карты на заданном гексе, в случае если криттер состоит в партии и вИдим.
#define attempt_place_party(the_tile, the_elev)
Вышеуказанное выражение (attempt_place_party_member(the_obj, the_tile,
the_elev)), но применённое для всех партийцев. Рекомендуется добавить
сюда своего партийца. Например:
#define attempt_place_party(the_tile, the_elev) \
attempt_place_party_member(Vic_Ptr, the_tile, the_elev) \
attempt_place_party_member(Myron_Ptr, the_tile, the_elev) \
attempt_place_party_member(Marcus_Ptr, the_tile, the_elev)
......
attempt_place_party_member(TED_Ptr, the_tile, the_elev)
Примечание: этот define будет работать только в том случае, если вы
добавили в party.h указатель на своего партийца. См.выше - TED_Ptr.
#define Childkiller_Vic
#define Childkiller_Myron
......и т.д.
Проверка, является ли указанный партиец детоубийцей, или нет. Если является, возвращает TRUE, если нет - FALSE.
Рекомендуется добавить сюда своего партийца. Например:
#define
Childkiller_Vic
((global_var(GVAR_PARTY_CHILDKILLER) BWAND bit_2) and (Vic_Ptr != 0))
#define
Childkiller_Myron
((global_var(GVAR_PARTY_CHILDKILLER) BWAND bit_4) and (Myron_Ptr != 0))
......
#define
Childkiller_TED
((global_var(GVAR_PARTY_CHILDKILLER) BWAND bit_30) and (TED_Ptr != 0))
Примечание: этот define будет работать только в том случае, если вы
добавили в party.h указатель на своего партийца. См.выше - TED_Ptr.
#define Party_Childkiller
Вышеуказанная проверка, но для всех партийцев. Рекомендуется добавить сюда своего партийца. Например:
#define
Party_Childkiller
(Childkiller_Vic or Childkiller_Myron or....or Childkiller_TED)
Примечание: этот define будет работать только в том случае, если вы
добавили в список детоубийц своего партийца. См.выше - Childkiller_TED.
Следующая часть, начиная variable PartyHealingItem; и кончая //Hiding
party members, by Chris Holland, относится к процедуре "Подлечи себя",
которую мы разбирали, и возвращаться к этому мы не будем.
#define get_p_hidden_flag_obj(x, the_ptr, the_bit, the_var)
Проверяет состояние бита the_bit гвары GVAR_PARTY_MEMBERS_HIDDEN, если
х (указатель на объект равен указателю the_ptr), и присваивает значение
бита (1 или 0) переменной the_var.
#define get_p_hidden_flag(x, hidden)
Призводит вышеуказанную проверку для объекта х, но со всеми партийцами. Рекомендуется добавить сюда своего партийца. Например
#define get_p_hidden_flag(x, hidden) \
get_p_hidden_flag_obj(x, Vic_Ptr, hidden_vic_bit, hidden) \
else get_p_hidden_flag_obj(x, Myron_Ptr, hidden_myron_bit, hidden)
.....
else get_p_hidden_flag_obj(x, TED_Ptr, hidden_TED_bit, hidden)
Примечание: этот define будет работать только в том случае, если вы
добавили в party.h указатель на своего партийца (См.выше - TED_Ptr) и
бит партийца для Гвары GVAR_PARTY_MEMBERS_HIDDEN (См. ниже).
#define hidden_vic_bit
#define hidden_myron_bit
#define hidden_marcus_bit
#define hidden_macrae_bit
#define hidden_sulik_bit
....... и т.д.
#define party_member_hidden(x, result)
Производит проверку get_p_hidden_flag(x, hidden) для объекта х, если он вИдим и в партии.
#define party_bit_hidden(x)
Возвращает состояние(1 или 0) бита х Гвары GVAR_PARTY_MEMBERS_HIDDEN.
#define all_party_hidden
Возвращает состояние (1 или 0) бита all_hidden_bit(см. выше #define all_hidden_bit bit_19) Гвары GVAR_PARTY_MEMBERS_HIDDEN.
#define set_party_bit_hidden(x, member)
Устанавливает бит х Гвары GVAR_PARTY_MEMBERS_HIDDEN в 1, и делает объект member невидимым.
#define party_member_hide(the_obj, the_bit)
Если объект the_obj в партии, и он вИдим, Устанавливает бит the_bit
Гвары GVAR_PARTY_MEMBERS_HIDDEN в 1 и делает объект the_obj невидимым.
#define party_member_hide_all
Производит вышеуказанное действие со всеми членами команды. Рекомендуется добавить сюда своего партийца. Например:
#define party_member_hide_all \
set_gvar_bit_on(GVAR_PARTY_MEMBERS_HIDDEN, all_hidden_bit); \
party_member_hide(Vic_Ptr, hidden_vic_bit) \
party_member_hide(Myron_Ptr, hidden_myron_bit) \
........
party_member_hide(TED_Ptr, hidden_TED_bit)
else get_p_hidden_flag_obj(x, TED_Ptr, hidden_TED_bit, hidden)
Примечание: этот define будет работать только в том случае, если вы
добавили в party.h указатель на своего партийца. См.выше - TED_Ptr, и
бит партийца для Гвары GVAR_PARTY_MEMBERS_HIDDEN.
#define all_party_unhidden
Проверка все ли члены партии вИдимы. Если да, то возвращает TRUE, если нет - FALSE.
#define set_party_bit_unhidden(x, member)
Устанавливает бит невидимости х в 0 и делает объект member видимым.
#define party_member_unhide(the_obj, the_bit)
Если объект the_obj в партии и он невидим, устанавливает бит невидимости х в 0 и делает объект the_obj видимым.
Короче говоря, макросы set_party_bit_hidden(x, member) и
set_party_bit_unhidden(x, member), а так же макросы
party_member_hide(the_obj, the_bit) и party_member_unhide(the_obj,
the_bit) - как знаки + и -, противопоожны по воздействию на объект
the_obj.
#define party_member_unhide_all
Макрос противоположный по действию макросу party_member_hide_all. Т.е.
делает всех членов партии видимыми. Рекомендуется добавить сюда своего
партийца, по аналогии с party_member_hide_all (см. выше).
#define party_healed_max
#define party_healed_good
#define party_healed_hurt
#define party_healed_bad
Проверяют уровень жизни криттера, в скрипте которого
вызваны.Возвращаемые значения TRUE - если условие подтверждается, FALSE
- если нет.
#define def_heal_msg
#define def_wait_msg
#define def_unarm_msg
#define def_close_msg
...... и т.д.
Замены стандартных реплик партийцев. Стандартные реплики прописаны в
файле GENERIC.MSG, который находится в папке text/english/dialog.
#define DEF_PARTY_HEAL_NODE
#define DEF_PARTY_WAIT_NODE
#define DEF_UNARM_NODE
#define DEF_FOLLOW_CLOSE_NODE
...... и т.д.
Замены стандартных процедур партийцев. Например, Node1001 - процедура для лечения, Node1003 - процедура для разоружения и т.д.
#define party_member_options(heal_msg, follow_msg, gear_msg, wait_msg, nowait_msg, end_msg, stupid_msg)
Внимание! Это очень важный момент в party.h. Если вы хотите добавить
всем партийцам по умолчанию ещё одну опцию диалога в партии, то вам
сюда. Этот define вызывается из всех оригинальных скриптов партийцев.
Точнее говоря, это define опций диалога в партии.
Итак, рассмотрим его повнимательней:
if (party_is_waiting == false) then begin ....
Если партиец не ждёт, и дистанция не задана, то задаём среднюю дистанцию.
if (heal_msg != 0) then begin ....
Если номер heal_msg указан в party_member_options(см.выше) не равен 0,
тогда, если партиец ранен, предоставить опцию, ведущую к процедуре
лечения. Это значит, что если вы не хотите, чтобы у вашего партийца
была такая опция, вы просто указываете на месте heal_msg в
party_member_options 0. Пример:
party_member_options(0, follow_msg, gear_msg, wait_msg, nowait_msg, end_msg, stupid_msg)
То же самое справедливо и для остальных процедур, указанных тут.
#define party_follow_options(close_msg, med_msg, far_msg, follow_ignore_msg)
Действует аналогично party_member_options, только для дистанции. То есть:
Если номер close_msg указан в party_follow_options и не равен 0, и
текущая дистанция не равна FOLLOW_DISTANCE_CLOSE, то предоставляем
опцию ответа, ведущую к процедуре, которая устанавливает короткую
дистанцию.
#define party_gear_options(weapon_use_msg, unarm_msg, armor_msg, gear_ignore_msg)
Действует аналогично party_follow_options, только для оружия. Объяснять
не буду, думаю двух примеров, данных выше, вам с лишкОм хватило, чтобы
разобраться ;)
#define party_member_default_options)
#define party_member_def_follow_options
#define party_member_def_gear_options
party_member_options ,party_follow_options и party_gear_options соответственно со стандартным набором реплик.
#define party_no_follow_on
Придаёт Гваре GVAR_PARTY_NO_FOLLOW значение 1.
#define party_no_follow_off
Придаёт Гваре GVAR_PARTY_NO_FOLLOW значение 0.
#define party_no_follow
Проверяет значение Гвары GVAR_PARTY_NO_FOLLOW. Если отлично от 0 - возвращает TRUE, иначе - FALSE.
#define set_follow_close
#define set_follow_medium
#define set_follow_far
Выставляют значение Лвары LVAR_FOLLOW_DISTANCE в разные значения. С этим мы уже сталкивались.
#define party_member_follow_dude
Заставляет партийца, в скрипте которого прописан, бегать за чузом. Прописывается в critter_p_proc.
#ifndef set_default_party_follow
#define
set_default_party_follow
set_follow_medium
Выставляет дистанцию на среднюю величину.(в нашем случае 6).
#define store_party_team
Если партиец не состоит в команде чуза, запоминает номер команды в Лвару LVAR_TEAM.
Примечание: если будете пользоваться этим методом, не забудьте объявить Лвару в script.lst
#define party_add_self
Добавляет партийца в команду (только в том случае если он жив ;),
выставляет дистанцию на среднюю величину, и запоминает номер команды, в
которой он был, в Лвару LVAR_TEAM.
#define party_remove_self
Убирает партийца, в скрипте которого прописан, из партии.
#define set_self_abandon_party
Ещё один define для удаления из партии. Но он отличается от
party_remove_self. Помимо того, что он удаляет партийца из партии чуза,
он ещё и добавляет его в прежнюю команду - LVAR_TEAM (если таковая
существует)
#define party_is_waiting
#define set_party_waiting
#define end_party_waiting
Макросы "ожидания".
party_is_waiting - проверяет ждёт ли партиец. Если ждёт - возвращает TRUE, иначе - FALSE.
set_party_waiting - придаёт Лваре "ожидания" - LVAR_WAITING значение игрового времени и удаляет партийца из партии.
end_party_waiting - противоположное действие. Придаёт LVAR_WAITING значение = 0 и добавляет партийца в партию.
#define
PARTY_CLOSE_DIST
(5)
#define CHECKMEMBERNEARDOOR(inparty, obj)
Этот макрос проверяет расстояние между объектом obj и объектом в
скрипте которого прописывается. В основном используется в скритах
дверей.
Проверяет, если inparty (см. ниже) то проверяет - если расстояние между
объектом, в скрипте которого прописан макрос, и объектом obj <=
PARTY_CLOSE_DIST, возвращает 1.
procedure checkPartyMembersNearDoor
Процедура производит проверку CHECKMEMBERNEARDOOR для всех членов
партии. Рекомендуется прописать здесь своего партийца. Пример:
procedure checkPartyMembersNearDoor begin
CHECKMEMBERNEARDOOR(Vic_In_Party, Vic_Ptr)
CHECKMEMBERNEARDOOR(Myron_In_Party, Myron_Ptr)
CHECKMEMBERNEARDOOR(Marcus_In_Party, Marcus_Ptr)
......
CHECKMEMBERNEARDOOR(TED_In_Party, TED_Ptr)
return 0;
end
Примечание: процедура будет работать только если вы добавили в party.h
указатель на своего партийца, См.выше - TED_Ptr, и проверку на
принадлежность к партии, См. выше TED_In_Party.
Набор файлов, с помощью которого вы можете скомпилировать все скрипты использующие party.h, вы найдете тут.
Теперь, уважаемые читатели, вы можете клепать партийцев как на
заводе, в чём желаю вам преуспеть. :) Со всеми вопросами и пожеланиями
вы можете обращаться ко мне.
Copyright by binyan 2006