Реализация НПС в Instead

Эта заметка предназначена начинающим авторам на Instead.

Во всех квестах конечно есть НПС с которыми можно общаться, получать задания и т.д. Исторически разработчики не заморачивались разнообразием реплик НПС-болванчиков. Один и тот же диалог запускался при каждом обращении. Я лично считаю это вполне нормальным поведением и данью классическим квестам и рпг, но некоторые считают иначе и ниже я приведу пару способов разнообразить поведение НПС.

Обычно НПС выглядит как-то так:
obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
		walk 'Разговор с трактирщиком';
	end;
};
Итак вариант №1: завести специальную квестовую переменную отвечающую за реплики трактирщика. Пишу:

global { -- определение глобальных переменных
	q1 = 0;
};
Переписываю трактирщика:

obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
                if q1 == 0 then
        	    walk 'Разговор с трактирщиком';
                    q1 = 1;
                else
                    walk 'Другой разговор с трактирщиком';
                end;
	end;
};
Ладно, но может есть способ попроще? Вариант №2: использование функции visits(). visits() возвращает количество посещений комнаты или диалога. Код тогда будет такой:

obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
                if visits('Разговор с трактирщиком') == 0 then
        	    walk 'Разговор с трактирщиком';
                else
                    walk 'Другой разговор с трактирщиком';
                end;
	end;
};
Хорошо, а если трактирщик выдает какой-то предмет для выполнения квеста? Как например в Fallout 2 Мамаша в Дыре даёт обед для Смитти?

obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
                if not have 'Обед для Смитти' then
        	    walk 'Разговор с трактирщиком';
                else
                    p 'Ты ещё не отнёс обед для Смитти? Поторопись, остынет же!';
                end;
	end;
};
Кстати реакцию по else я заменил просто кратким напоминанием о задании. Но вот герой отнёс обед, вернулся и опять получает диалог про обед? Нелогично, хотя во многих ММОРПГ обычное дело — дейлики всякие. Можно использовать переменную или опять visits().

obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
                if not have 'Обед для Смитти' and visits('Разговор с трактирщиком') == 0 then
        	    walk 'Разговор с трактирщиком';
                elseif have 'Обед для Смитти' then
                    p 'Ты ещё не отнёс обед для Смитти? Поторопись, остынет же!';
                else
                    walk 'Другой разговор с трактирщиком';
                end;
	end;
};
На самом деле тут получается небольшая нелогичность, если в диалоге например предусмотрена возможность отказаться от квеста с намерением вернуться к нему позже. Тогда остаётся только переменная:

obj {
	nam = 'Трактирщик';
	dsc = 'За барной стойкой {трактирщик} протирает стаканы ветхим полотенцем.';
	act = function()
                if q1 == 0 then
        	    walk 'Разговор с трактирщиком';
                elseif have 'Обед для Смитти' then
                    p 'Ты ещё не отнёс обед для Смитти? Поторопись, остынет же!';
                else
                    walk 'Другой разговор с трактирщиком';
                end;
	end;
};
И уже в диалоге если герой согласился сделать задание, присвойте q1 = 1;

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

Ответьте на опрос ниже и конечно лайк, шер и ретвит! Вопросы пишите в комментариях.

Интересен такой формат капитанства для начинающих?

7 комментариев

Wol4ik
Все-таки есть в тебе совесть))) жду остальное про диалоги.
techniX
Вместо
if visits('Разговор с трактирщиком') == 0 then
можно сказать и так
if not visited('Разговор с трактирщиком') then

И насчет глобальных квестовых переменных — тоже спорный вопрос. Я предпочитаю объявлять эти переменные внутри объекта, раз уж у нас есть ООП :) В приведенном выше примере можно было бы сделать переменную quest1 частью объекта 'Трактирщик'. За пределами объекта проверку на то, дал ли нам трактирщик квест, можно делать как-то так:
if _"Трактирщик".quest1 then
К тому же сразу понятно, за чей квест отвечает переменная quest1.

В общем, полезная статья. Пиши ещё! :)
Комментарий отредактирован: 13 апреля 2018, 23:16
Irremann
Ну это стиль уже кодинга, я visits() предпочитаю потому, что можно и второе и третье посещение ловить. А в глобале все переменные храню, потому что проще мне их искать в одном месте. Никто же не мешает дописать комментарий типа
q1 = 0; --разговор с трактирщиком.
Wol4ik
Если переменные глобальные, то разговор с одним НПС может повлиять на что-то вне этого разговора (переходы, об! екты других сцен) и даже на другого НПС.
Если q1 == 1, то другой НПС может начать разговор например «Трактирщик мне уже все рассказал, герой...»
alastochkin
Полезная статья! Автор, пиши в следующий раз про следование npc за игроком и моделировании их перемещений.
Irremann
Там лошадка же в документации описана. Я могу конечно расписать как сделать например армию и расставлять её по локациям, но это тоже все очень просто.
fireton
Дам тут ссылку на свой старый перевод: ifhub.club/2015/03/06/za-faade-model-dialogov-v-igrah.html
Если б кто-то реализовал этот подход на Instead, мог бы выйти толк, я считаю.