Сопровождение судна
Начинается всё действо в диалоге с тавернщиком:
// ..\PROGRAM\DIALOGS\Common_Tavern.c
case "convoy":
// ...
dialog.text = "Ты вовремя ко мне "+ GetSexPhrase("обратился парень","обратилась") +", тут один купец как раз искал подходящую компанию, а вот, как раз и он, видишь, вошел в таверну? Поговори с ним.";
link.l1 = "Я так и сделаю, не сомневайся.";
link.l1.go = "exit";
pchar.quest.destination = GetIslandByCityName(NPChar.city); // остров, где взяли квест
AddDialogExitQuest("prepare_for_convoy_quest"); // активация квеста при выходе из диалога
// ...
break;
По сюжету квеста мы спрашиваем работу у трактирщика и получаем наводку на торговца, который просит сопроводить нас до какого-то острова.
Что мы здесь видим?
Во-первых, нас перебрасывают на ноду диалога "exit". И ничего странного в этом нет, потому что задание мы берём (или не берём) непосредственно у торговца, а не у трактирщика.
А далее, собственно, происходит квестовая магия. В атрибут pchar.quest.destination сохраняется остров, на котором мы получили задание, чтобы исключить его из списка потенциальных точек назначения в будущем. И это первый пример плохого кода. Данное действие - исключительно квестовое, которое не имеет абсолютно никакого отношения к диалогу, и находиться оно должно в файле quests_reaction.c.
Более того, разбрасывание частей кода не там, где ему положено находиться, приводит к затруднениям в работе с этим самым кодом. Когда вы (или кто-то другой), спустя некоторе время будете пытаться изменить что-то в этом квесте - все его экшены вы будете искать в квест-реакшн. И не найдёте там нужного куска кода, потому что он блт в диалоге! Или может вы не будете ничего искать, а просто что-то добавите/измените, а оно будет работать не так, как ожидается, потому что где-то существует код, который вы не видели и не учли.
Во-первых, нас перебрасывают на ноду диалога "exit". И ничего странного в этом нет, потому что задание мы берём (или не берём) непосредственно у торговца, а не у трактирщика.
А далее, собственно, происходит квестовая магия. В атрибут pchar.quest.destination сохраняется остров, на котором мы получили задание, чтобы исключить его из списка потенциальных точек назначения в будущем. И это первый пример плохого кода. Данное действие - исключительно квестовое, которое не имеет абсолютно никакого отношения к диалогу, и находиться оно должно в файле quests_reaction.c.
Более того, разбрасывание частей кода не там, где ему положено находиться, приводит к затруднениям в работе с этим самым кодом. Когда вы (или кто-то другой), спустя некоторе время будете пытаться изменить что-то в этом квесте - все его экшены вы будете искать в квест-реакшн. И не найдёте там нужного куска кода, потому что он блт в диалоге! Или может вы не будете ничего искать, а просто что-то добавите/измените, а оно будет работать не так, как ожидается, потому что где-то существует код, который вы не видели и не учли.
Метод AddDialogExitQuest("prepare_for_convoy_quest"); запускает обработку квеста с переданным названием после завершения диалога.
В этом уроке мы не будем разбирать техническое устройство квестовой системы, а сразу перейдём к точке назначения - это файл quests_reaction.c. А именно - кейс с названием, которое мы передали в метод AddDialogExitQuest.
В этом уроке мы не будем разбирать техническое устройство квестовой системы, а сразу перейдём к точке назначения - это файл quests_reaction.c. А именно - кейс с названием, которое мы передали в метод AddDialogExitQuest.
// ..\PROGRAM\QUESTS\quests_reaction.c
case "prepare_for_convoy_quest":
sld = characterFromID("Quest trader"); // получаем алиас персонажа по ID
SetFantomParam(sld); // генерируем ему уровень и навыки
iPassenger = rand(4); // генерируем нацию
sld.nation = iPassenger;
sld.BakNation = sld.nation;
sld.location = "none";
SetCaptanModelByEncType(sld, "trade"); // генерируем модель персонажа
SetRandomNameToCharacter(sld); // генерируем имя
ChangeCharacterAddressGroup(sld, pchar.location, "reload", "reload1"); // телепортируем торговца в локацию ГГ
pchar.quest.generate_convoy_quest_progress = "begin"; // прогресс задания
LAi_SetActorType(sld); // передаём управление персонажем под контроль компьютера
LAi_SetActorType(pchar);
LAi_ActorFollow(pchar, sld, "pchar_back_to_player", 6.0); // приказ идти к другому персонажу
LAi_ActorFollow(sld, pchar, "prepare_for_convoy_quest_2", 5.0);
break;
У вас, наверное, возникает вопрос, как мы перескочили от диалога с тавернщиком до получения ссылки на персонажа? Где же он был создан? В тех глубинах устройства квестовой системы, которые я не стал расписывать?
Нет. И это уже пример хорошего кода. Данный квест - повторяющийся. И чтобы не создавать и не описывать каждый раз нашего торговца, он создаётся один раз в начале игры, а при взятии квеста ему меняется внешность и он телепортируется к нам в таверну.
Расположен он в файле ..\PROGRAM\Characters\init\TempQuestCharacters.c, найти его можно по идентификатору "Quest trader".
Нет. И это уже пример хорошего кода. Данный квест - повторяющийся. И чтобы не создавать и не описывать каждый раз нашего торговца, он создаётся один раз в начале игры, а при взятии квеста ему меняется внешность и он телепортируется к нам в таверну.
Расположен он в файле ..\PROGRAM\Characters\init\TempQuestCharacters.c, найти его можно по идентификатору "Quest trader".
Далее при помощи ф-ции ChangeCharacterAddressGroup() его помещают в локацию к нашему персонажу.
В атрибут pchar.quest.generate_convoy_quest_progress записывается прогресс задания. На данном этапе это "begin".
Следом идут методы AI-управления персонажами: LAi_SetActorType() передает управление персонажем под контроль AI, а метод LAi_ActorFollow() заставляет идти к другому персонажу и запускает задание (если передано).
Главному герою мы передали "pchar_back_to_player" - это заглушка, в которую зашита ф-ция, что возвращает игроку контроль над ГГ. Вот её код:
Следом идут методы AI-управления персонажами: LAi_SetActorType() передает управление персонажем под контроль AI, а метод LAi_ActorFollow() заставляет идти к другому персонажу и запускает задание (если передано).
Главному герою мы передали "pchar_back_to_player" - это заглушка, в которую зашита ф-ция, что возвращает игроку контроль над ГГ. Вот её код:
case "pchar_back_to_player":
Lai_SetPlayerType(pchar);
break;
А торговцу было передано "prepare_for_convoy_quest_2" - это название следующего кейса по нашему заданию. Смотрим из чего он состоит:
// ..\PROGRAM\QUESTS\quests_reaction.c
case "prepare_for_convoy_quest_2":
LAi_ActorDialog(characterFromID("Quest trader"), pchar, "", 10.0, 1.0); // приказ начать диалог
characters[GetCharacterIndex("quest trader")].dialog.currentnode = "prepare_convoy_quest"; // указание ноды диалога
break;
Ничего неожиданного. Персонажи сблизились и далее мы инициируем между ними диалог.
Если вы уже смотрели инициацию нашего торговца в TempQuestCharacters.c, то знаете в каком файле этот самый диалог искать. Если нет - самое время это сделать. Напомню, что купец скрывается под id "Quest trader", а файл его диалога записан в атрибут ch.Dialog.Filename.
Если вы уже смотрели инициацию нашего торговца в TempQuestCharacters.c, то знаете в каком файле этот самый диалог искать. Если нет - самое время это сделать. Напомню, что купец скрывается под id "Quest trader", а файл его диалога записан в атрибут ch.Dialog.Filename.
// ..\PROGRAM\DIALOGS\convoy_trader.c
case "prepare_convoy_quest":
dialog.text = TimeGreeting() + ", "+GetAddress_Form(NPChar) + "! Я "+ GetFullName(NPChar) + ", торговец. Я слышал, что вы ищете работу?";
link.l1 = "Что-то вроде того. А вы, как я слышал"+ GetSexPhrase("","а") +", ищете капитана, который бы сопроводил вас и ваше судно к месту назначения?";
link.l1.go = "prepare_convoy_quest_2";
break;
case "prepare_convoy_quest_2":
dialog.text = "Совершенно верно. Более того, думаю, что вы мне подходите в качестве сопровождающе"+ GetSexPhrase("го","й") +". Что скажете?";
link.l1 = "Я скажу - назови мне сумму, и, возможно, мы договоримся.";
link.l1.go = "prepare_convoy_quest_3";
break;
case "prepare_convoy_quest_3":
GenerateConvoyQuestSwp();
dialog.text = "Мне нужно, что бы меня сопроводили до города " + GetCityName(pchar.quest.destination) +
" за "+ pchar.ConvoyQuest.iDay +" дней, и за это я заплачу вам " + pchar.ConvoyQuest.convoymoney + " золотом. Что скажете?";
link.l1 = "Я "+ GetSexPhrase("согласен","согласна") +".";
link.l1.go = "convoy_agreeded";
link.l2 = "Не думаю, что мне это интересно.";
link.l2.go = "convoy_refused";
break;
case "convoy_refused":
Diag.CurrentNode = Diag.TempNode;
DialogExit();
AddDialogExitQuest("convoy_refused"); // отмена задания
break;
case "convoy_agreeded":
pchar.convoy_quest = pchar.quest.destination;
Diag.CurrentNode = Diag.TempNode;
DialogExit();
AddDialogExitQuest("convoy_agreeded"); // принятие задания
break;
case "complete_convoy_quest":
dialog.text = "О! Спасибо вам. Под вашей защитой я чувствовал себя как никогда спокойно. Вот ваша награда.";
Link.l1 = "Благодарю вас.";
link.l1.go = "exit";
AddDialogExitQuest("convoy_refused");
OfficersReaction("good");
AddCharacterExpToSkill(pchar, "Sailing", 40);
AddCharacterExpToSkill(pchar, "Leadership", 20);
AddQuestTemplate("Gen_convoy_quest", "t4");
CloseQuestHeader("Gen_convoy_quest");
break;
По порядку.
Из квест-реакшн нас направляют на самую первую ноду диалога - "prepare_convoy_quest". Далее по разговору мы переходим на вторую, третью и уже в ней получаем выбор - принять или отклонить задание.
Но ещё перед тем, как мы сделаем выбор, запускается некая ф-ция GenerateConvoyQuest(). Чтобы ответить на вопрос, почему она выполняется еще ДО самого тела ноды диалога, давайте сначала посмотрим на саму функцию:
Из квест-реакшн нас направляют на самую первую ноду диалога - "prepare_convoy_quest". Далее по разговору мы переходим на вторую, третью и уже в ней получаем выбор - принять или отклонить задание.
Но ещё перед тем, как мы сделаем выбор, запускается некая ф-ция GenerateConvoyQuest(). Чтобы ответить на вопрос, почему она выполняется еще ДО самого тела ноды диалога, давайте сначала посмотрим на саму функцию:
// ..\PROGRAM\QUESTS\quests_functions.c
void GenerateConvoyQuest()
{
ref PChar;
ref NPChar;
PChar = GetMainCharacter();
int iShipType, iCargoType, iTradeGoods, iTradeMoney, iNation, irank;
string sdestination;
irank = PChar.rank;
NPChar = characterFromID("Quest trader");
DeleteAttribute(NPChar, "Ship"); // удаляем корабль от предыдущего квеста
SetShipMerchant(NPChar, true); // генерируем новый корабль
iTradeMoney = (7-GetCharacterShipClass(npchar))*1000 + sti(NPChar.rank)*200 + rand(10)*50;
pchar.ConvoyQuest.convoymoney = iTradeMoney; // сумма награды
pchar.ConvoyQuest.iDay = 20 + rand(10); // сроки выполнения
SetTimerCondition("generate_convoy_quest_timer", 0, 0, sti(pchar.ConvoyQuest.iDay), false); // устанавливаем таймер на задание
pchar.quest.generate_convoy_quest_progress = "begin"; // прогресс задания
pchar.quest.generate_convoy_quest_failed.win_condition.l1 = "NPC_Death"; // условия провала задания - смерть персонажа
pchar.quest.generate_convoy_quest_failed.win_condition.l1.character = "Quest trader"; // указываем какого именно персонажа отслеживать
pchar.quest.generate_convoy_quest_failed.win_condition = "generate_convoy_quest_failed"; // кейс задания, который запустится в случае выполнения этого условия
sdestination = GenerateDestination(NPChar, sti(NPChar.nation));
if (sdestination == "")
{
sDestination = "Nevis";
}
pchar.quest.destination = sdestination; // пункт назначения
}
Как видим, эта функция создаёт все условия задания: вычисляет сумму награды, сроки выполнения, пункт назначения. А также описывает условия провала.
Теперь вернёмся к диалогу и посмотрим на него внимательно:
Теперь вернёмся к диалогу и посмотрим на него внимательно:
// ..\PROGRAM\DIALOGS\convoy_trader.c
case "prepare_convoy_quest_3":
GenerateConvoyQuest();
dialog.text = "Мне нужно, что бы меня сопроводили до города " + GetCityName(pchar.quest.destination) +
" за "+ pchar.ConvoyQuest.iDay +" дней, и за это я заплачу вам " + pchar.ConvoyQuest.convoymoney + " золотом. Что скажете?";
link.l1 = "Я "+ GetSexPhrase("согласен","согласна") +".";
link.l1.go = "convoy_agreeded";
link.l2 = "Не думаю, что мне это интересно.";
link.l2.go = "convoy_refused";
break;
Когда торговец предлагает нам работу, он уже указывает куда его нужно сопроводить, как быстро он должен оказаться в пункте назначения и сколько он за это платит.
Именно поэтому задание сгенерировалось ещё до нашего решения за него взяться.
Именно поэтому задание сгенерировалось ещё до нашего решения за него взяться.
С этим разобрались, едем дальше. Соглашаемся взяться за работу:
// ..\PROGRAM\DIALOGS\convoy_trader.c
case "convoy_agreeded":
pchar.convoy_quest = pchar.quest.destination;
Diag.CurrentNode = Diag.TempNode;
DialogExit();
AddDialogExitQuest("convoy_agreeded"); // кейс принятия задания
break;
Здесь нас встречает уже знакомый метод активации задания по выходу из диалога, который отправляет нас к кейсу "convoy_agreeded" в квест-реакшене:
// ..\PROGRAM\QUESTS\quests_reaction.c
case "convoy_agreeded":
SetCompanionIndex(Pchar, -1, GetCharacterIndex("quest trader")); // назначаем торговца компаньоном
SetCharacterRemovable(characterFromID("quest trader"), false); // меняем ему флаг removable
characters[GetCharacterIndex("quest trader")].CompanionEnemyEnable = true; // активируем возможность стать враждебным при попытке его захвата
GetCharacterPos(GetMainCharacter(), &locx, &locy, &locz);
homelocator = LAi_FindNearestFreeLocator("reload", locx, locy, locz); // находим ближайший свободный локатор в локации ГГ
LAi_SetActorType(characterFromID("quest trader"));
LAi_ActorGoToLocation(characterFromID("quest trader"), "reload", homelocator, "none", "", "", "", 10.0); // отправляем торговца на выход из локации
// журнал заданий:
ReOpenQuestHeader("Gen_convoy_quest"); // копируем раздел с квестом (т.к. он повторяемый)
sTemp = AddQuestTemplate("Gen_convoy_quest", "t1"); // подгружаем шаблон описания
Pchar.QuestInfo.Gen_convoy_quest.t1.(sTemp).City = GetCityName(pchar.quest.destination); // указываем пункт назначения
Pchar.QuestInfo.Gen_convoy_quest.t1.(sTemp).Day = pchar.ConvoyQuest.iDay; // указываем сроки
sDest = GetPortByCityName(pchar.quest.destination);
pchar.quest.generate_convoy_quest_completed.win_condition.l1 = "Location"; // условие выполнения - вход в локацию
pchar.quest.generate_convoy_quest_completed.win_condition.l1.location = sDest; // в качестве локации указываем порт нужной колонии
pchar.quest.generate_convoy_quest_completed.win_condition = "generate_convoy_quest_completed"; // кейс задания, который запустится в случае выполнения этого условия
break;
В этом кейсе происходят все остальные действия по запуску задания, которые небыли совершены в предварительной генерации:
Присоединяем торговца к эскадре игрока. Обратите внимание, что идентификатор ему присваивается не первый свободный, а -1.
Меняем ему значение removable на false, чтобы игрок не мог его уволить, как обычного офицера.
Разблокируем возможность стать враждебным. Это нужно для того, чтобы он вступал с нами в бой, если мы попытаемся атаковать или захватить его корабль.
Далее идут визуализационные активности: находим ближайший свободный локатор (а это будет дверь таверны, в которую он вошёл) и отправляем его выйти в эту дверь и перемещаем на несуществующую локацию, чтобы он исчез.
Следом идут записи в журнал заданий и добавление условий выполнения нашему квесту.
Присоединяем торговца к эскадре игрока. Обратите внимание, что идентификатор ему присваивается не первый свободный, а -1.
Меняем ему значение removable на false, чтобы игрок не мог его уволить, как обычного офицера.
Разблокируем возможность стать враждебным. Это нужно для того, чтобы он вступал с нами в бой, если мы попытаемся атаковать или захватить его корабль.
Далее идут визуализационные активности: находим ближайший свободный локатор (а это будет дверь таверны, в которую он вошёл) и отправляем его выйти в эту дверь и перемещаем на несуществующую локацию, чтобы он исчез.
Следом идут записи в журнал заданий и добавление условий выполнения нашему квесту.
Журнал заданий (квест бук) лежит по пути ..\RESOURCE\INI\TEXTS\RUSSIAN\QuestBook.txt. Инетересующие нас строки находятся по запросу Gen_convoy_quest. Выглядит это всё следующим образом:
// ..\RESOURCE\INI\TEXTS\RUSSIAN\QuestBook.txt
Gen_convoy_quest_title {Сопровождение купца}
Gen_convoy_quest_t1
{
Я взялся сопроводить торговца до города #sCity# за #sDay# дней.
}
Gen_convoy_quest_t2
{
Торговец устал плавать со мной и ждать, когда я соизволю его сопроводить. Он вышел из моей эскадры.
}
Gen_convoy_quest_t3
{
Торговец мертв. Задание полностью провалено.
}
Gen_convoy_quest_t4
{
Задание выполнено.
}
Детали работы с квест-буком я буду разбирать в одном из следующих уроков. Здесь нам нужно лишь понимание, откуда берутся все эти записи. В данный момент мы достаем из этого файла запись с пометкой t1.
Далее предполагается, что мы выходим в море и сопровождаем торговца до пункта назначения.
Судя по коду, который мы видели, по прибытии в указанную локацию должен запуститься кейс "generate_convoy_quest_completed":
Судя по коду, который мы видели, по прибытии в указанную локацию должен запуститься кейс "generate_convoy_quest_completed":
// ..\PROGRAM\QUESTS\quests_reaction.c
case "generate_convoy_quest_completed":
homelocation = pchar.location;
PlaceCharacter(characterFromID("quest trader"), "goto", homelocation); // помещаем торговца в локацию ГГ
LAi_SetActorType(characterFromID("quest trader")); // передаём управление торговцем
LAi_SetActorType(pchar); // и ГГ под контроль компьютера
Pchar.GenQuest.Hunter2Pause = true; // ставим на паузу охотников за головами (ОЗГ)
DoQuestCheckDelay("pchar_back_to_player", 25.0); // возврат контроля игроку через 25сек
LAi_ActorFollow(pchar, characterFromID("quest trader"), "", 2.0); // направляем ГГ к торговцу
LAi_ActorFollow(characterFromID("quest trader"), pchar, "generate_convoy_quest_completed_2", 2.0); // а его к ГГ
break;
Когда мы оказываемся на причале в нужном порту, к нам должен подойти сопровождаемый торговец, поблагодарить и отдать награду.
Это то мы и наблюдаем в приведённом кейсе - торговец помещается к нам в локацию и отправляется идти к ГГ с последующим запуском кейса "generate_convoy_quest_completed_2".
Но перед этим есть пара интересных строк. Во-первых это ОЗГ. Их нужно поставить на паузу, чтобы (если они есть) они не порубили нас (и торговца) пока мы ведём диалог.
Вторая - возврат контроля спустя 25 секунд. Особенности движка таковы, что вполне реальной может быть ситуация, когда между вами и торговцем окажется несколько стражников (или ваших офицеров) и вы никогда не сможете до него дойти по узкому причалу.
Обе эти строки - это меры безопасности, которые появились здесь намного позже создания самого квеста.
Это то мы и наблюдаем в приведённом кейсе - торговец помещается к нам в локацию и отправляется идти к ГГ с последующим запуском кейса "generate_convoy_quest_completed_2".
Но перед этим есть пара интересных строк. Во-первых это ОЗГ. Их нужно поставить на паузу, чтобы (если они есть) они не порубили нас (и торговца) пока мы ведём диалог.
Вторая - возврат контроля спустя 25 секунд. Особенности движка таковы, что вполне реальной может быть ситуация, когда между вами и торговцем окажется несколько стражников (или ваших офицеров) и вы никогда не сможете до него дойти по узкому причалу.
Обе эти строки - это меры безопасности, которые появились здесь намного позже создания самого квеста.
// ..\PROGRAM\QUESTS\quests_reaction.c
case "generate_convoy_quest_completed_2":
LAi_type_actor_Reset(pchar); // возврат контроля игроку
LAi_ActorWaitDialog(pchar, characterFromID("quest trader")); // приказ стоять и ждать диалога
pchar.GenQuest.CantSpeakNPCId_2 = "quest trader";
LAi_ActorDialog(characterFromID("quest trader"), pchar, "speak_completed_None_2", 2.0, 1.0); // инициируем диалог
characters[GetCharacterIndex("quest trader")].dialog.currentnode = "complete_convoy_quest"; // указываем ноду диалога
pchar.quest.generate_convoy_quest_progress = "completed"; // меняем прогресс выполнения задания
break;
Здесь мы просто инициируем диалог "complete_convoy_quest" с торговцем:
// ..\PROGRAM\DIALOGS\convoy_trader.c
case "complete_convoy_quest":
dialog.text = "О! Спасибо вам. Под вашей защитой я чувствовал себя как никогда спокойно. Вот ваша награда.";
Link.l1 = "Благодарю вас.";
link.l1.go = "exit";
AddDialogExitQuest("convoy_refused"); // запуск экшена квеста
OfficersReaction("good"); // вызываем положительную реакцию офицеров на наши действия
AddCharacterExpToSkill(pchar, "Sailing", 40); // добавляем очки в прокачку скиллов
AddCharacterExpToSkill(pchar, "Leadership", 20);
AddQuestTemplate("Gen_convoy_quest", "t4"); // добавляем запись в квест бук
CloseQuestHeader("Gen_convoy_quest"); // отправляем раздел в архив
break;
Здесь тоже всё уже знакомое, выделить можно только строки с реакцией офицеров и прокачкой навыков. Это всё из аддонов на базе ВМЛ. Если у вас в руках одна из оригинальных игр серии - таких записей вы не увидите.
И это, кстати, второй пример плохого кода. Данные строки добавлены разработчиками аддона. Помимо того, что им здесь в принципе не место, они еще и дублируют некоторые действия из кейса "convoy_refused", который вызывается отсюда же.
И это, кстати, второй пример плохого кода. Данные строки добавлены разработчиками аддона. Помимо того, что им здесь в принципе не место, они еще и дублируют некоторые действия из кейса "convoy_refused", который вызывается отсюда же.
// ..\PROGRAM\QUESTS\quests_reaction.c
case "convoy_refused":
pchar.quest.generate_convoy_quest_failed.over = "yes"; // отмечаем все условия как оконченные
pchar.quest.generate_convoy_quest_completed.over = "yes";
pchar.quest.generate_convoy_quest_timer.over = "yes";
GetCharacterPos(GetMainCharacter(), &locx, &locy, &locz);
homelocator = LAi_FindNearestFreeLocator("reload", locx, locy, locz);
LAi_SetActorType(characterFromID("quest trader"));
LAi_ActorGoToLocation(characterFromID("quest trader"), "reload", homelocator, "none", "", "", "", 5.0); // торговца отправляем на выход из локации
if (checkquestattribute("generate_convoy_quest_progress", "completed")) // если задание выполнено
{
iPassenger = makeint(pchar.ConvoyQuest.convoymoney);
AddMoneyToCharacter(pchar, iPassenger); // выплачиваем деньги ГГ
ChangeCharacterReputation(pchar, 1); // повышаем репутацию
OfficersReaction("good"); // положительная реакция офицеров
RemoveCharacterCompanion(Pchar, characterFromID("quest trader")); // удаляем торговца из компаньонов
pchar.quest.generate_convoy_quest_progress = ""; // обнуляем прогресс задания
}
if (!checkquestattribute("generate_convoy_quest_progress", "begin")) // если прогресс не равен "begin"
{
CloseQuestHeader("Gen_convoy_quest"); // отправляем раздел в архив
}
pchar.quest.generate_convoy_quest_progress = ""; // обнуляем прогресс задания
break;
А вот здесь уже интересно. Данный кейс вызывается также при отказе от него в самом первом диалоге с торговцем. Отсюда и проверка на статус "begin" перед отправкой квеста в архив - ведь на тот момент запись в журнале ещё не создана.
А проверка на статус "completed", соответственно, не даёт нам получить награду за выполнение в момент отказа.
А проверка на статус "completed", соответственно, не даёт нам получить награду за выполнение в момент отказа.
Это достаточно простое задание и если разделить кейсы отказа и успешного завершения - то кейс отказа будет полностью дублировать код кейса завершения, только без наград. Поэтому их и объединили. В большинстве же случаев, это реализуется отдельными кейсами.
Нам осталось рассмотреть только условия провала задания.
Если вы помните, их было всего два: таймер и смерть торговца. Есть ещё такой игровой момент как нападение на него, но разработчики подразумевают смерть торговца в итоге такого развития событий.
Если вы помните, их было всего два: таймер и смерть торговца. Есть ещё такой игровой момент как нападение на него, но разработчики подразумевают смерть торговца в итоге такого развития событий.
Начнём с таймера. Освежим в памяти ф-цию его объявления:
SetTimerCondition("generate_convoy_quest_timer", 0, 0, sti(pchar.ConvoyQuest.iDay), false);
Устройство данной ф-ции мы разберём в соответствующем уроке, а здесь нас интересует только первый её аргумент - это кейс в квест-реакшн, который будет вызван по истечению таймера.
// ..\PROGRAM\QUESTS\quests_reaction.c
case "generate_convoy_quest_timer":
AddQuestTemplate("Gen_convoy_quest", "t2"); // запись в судовой журнал
CloseQuestHeader("Gen_convoy_quest"); // отправляем раздел журнала с нашим квестом в архив
sld = characterFromID("Quest trader");
ChangeCharacterHunterScore(GetMainCharacter(), NationShortName(sti(sld.BakNation)) + "hunter", 10+rand(10)); // добавляем чуть монеток в награду за голову ГГ
RemoveCharacterCompanion(Pchar, sld); // убираем торговца из компаньонов
OfficersReaction("bad"); // отрицательная реакция офицеров
ChangeCharacterReputation(pchar, -10); // режем репутацию
pchar.quest.generate_convoy_quest_progress = ""; // обнуляем прогресс задания
pchar.quest.generate_convoy_quest_failed.over = "yes"; // отмечаем все условия как оконченные
pchar.quest.generate_convoy_quest_completed.over = "yes";
break;
По наполнению очень похоже на предыдущий кейс, только наоборот: запись в квестбук о провале, а не завершении, репутация и отношение офицеров - в минус. Ещё и с нацией торговца отношения портятся.
В случае гибели торговца вызываемый кейс был указан следующий:
pchar.quest.generate_convoy_quest_failed.win_condition = "generate_convoy_quest_failed";
Смотрим, что внутри:
// ..\PROGRAM\QUESTS\quests_reaction.c
case "generate_convoy_quest_failed":
ChangeCharacterReputation(pchar, -5); // понижение репутации
OfficersReaction("bad"); // отрицательная реакция офицеров
RemoveCharacterCompanion(Pchar, characterFromID("quest trader")); // убираем торговца из компаньонов
pchar.quest.generate_convoy_quest_progress = ""; // обнуляем прогресс задания
pchar.quest.generate_convoy_quest_failed.over = "yes"; // отмечаем все условия как оконченные
pchar.quest.generate_convoy_quest_completed.over = "yes";
pchar.quest.generate_convoy_quest_timer.over = "yes";
AddQuestTemplate("Gen_convoy_quest", "t3"); // запись в судовой журнал
CloseQuestHeader("Gen_convoy_quest"); // в архив
break;
Собственно, здесь всё то же самое, только штрафа к отношению с нацией нет, так как это не прямая наша вина, что торговец погиб.
Вы скажете, что ведь игрок сам мог его потопить - но при нападении на "своего" игрок и так получает соответствующий штраф.
Вы скажете, что ведь игрок сам мог его потопить - но при нападении на "своего" игрок и так получает соответствующий штраф.
На этом технический разбор окончен.
В следующем уроке я проведу анализ этого задания с точки зрения квестописателя, а не программиста.
Мы попытаемся реконструировать процесс создания этого квеста, чтобы на живом примере понять во-первых, как такой сюжетно простой квест стал таким большим, а во-вторых, как превратить идею квеста в техническое задание для его реализации в коде.
В следующем уроке я проведу анализ этого задания с точки зрения квестописателя, а не программиста.
Мы попытаемся реконструировать процесс создания этого квеста, чтобы на живом примере понять во-первых, как такой сюжетно простой квест стал таким большим, а во-вторых, как превратить идею квеста в техническое задание для его реализации в коде.